From cc93024488cfb7fc3e6f4b028d70dd285a52b89f Mon Sep 17 00:00:00 2001 From: Steven Tibbs Date: Wed, 28 Oct 2026 12:19:40 -0400 Subject: [PATCH 01/42] Porches, decks, railings, stairs, foundations and framing tools - Walls: push and overlap fixes, square T and corner joins, pull to continue from a wall end, line-up snap, arrow nudge, Tab through stacked selections - Rooms: invisible walls; Room, Porch, Deck and Patio room types; outdoor rooms step down from the house and take their defaults - Porches: automatic posts and beam (sizes, spacing, Automatic or Manual), optional on decks and patios - Decks: 3D boards (size, gap, direction) on a framed deck drawn in the model; one framing layout shared with the framing plugin - Railings: railing wall type with styles, posts and openings; rooms close on railings - Beams and posts: beam and post tools, panels and plan symbols - Stairs: wood stringers, concrete or steel, with code checks - Foundations: designed slab, raised floor and piles; stepped and drawn footings; ground graded round slabs; porch and patio slabs run to grade - Roofs: roof plan, wall catch, gable walls; snapping to porch, deck and invisible-wall edges - Assemblies: wall stud spacing, roof and ceiling layers, floor build-up - Display: framing plan hooks, foundation view, ceilings and walls below - Box selection leaves rooms' automatic floors and ceilings out Co-Authored-By: Claude Opus 5.5 --- apps/editor/components/build-tab.tsx | 18 +- apps/editor/components/viewer-toolbar.tsx | 15 + apps/editor/lib/build-palette.test.ts | 31 + apps/editor/lib/build-palette.ts | 19 +- apps/editor/lib/build-panel-model.ts | 9 +- apps/editor/public/icons/beam.svg | 1 + apps/editor/public/icons/post.svg | 1 + .../agent-operations/__fixtures__/cases.ts | 18 +- .../src/agent-operations/create-roof.test.ts | 23 +- .../src/agent-operations/wall-skirting.ts | 2 + .../core/src/agent-tools/room-structure.ts | 22 +- .../core/src/agent-tools/wall-openings.ts | 4 +- .../core/src/building/corner-window.test.ts | 18 + packages/core/src/building/corner-window.ts | 12 +- packages/core/src/building/door-types.test.ts | 6 + packages/core/src/building/door-types.ts | 8 +- packages/core/src/building/foundation.test.ts | 298 ++++ packages/core/src/building/foundation.ts | 258 +++ packages/core/src/building/ground-level.ts | 15 +- packages/core/src/building/index.ts | 3 + .../core/src/building/level-insertion.test.ts | 159 +- packages/core/src/building/level-insertion.ts | 86 +- packages/core/src/building/level-removal.ts | 127 ++ .../core/src/building/level-wall-copy.test.ts | 187 +++ packages/core/src/building/level-wall-copy.ts | 99 ++ packages/core/src/building/merge-windows.ts | 1 + .../core/src/building/wall-openings.test.ts | 46 +- packages/core/src/building/wall-openings.ts | 26 +- .../commands/structure/attached-walls.test.ts | 95 ++ .../src/commands/structure/attached-walls.ts | 80 + .../structure/floor-foundation-lift.test.ts | 87 +- packages/core/src/commands/structure/index.ts | 18 + .../structure/move-separators.test.ts | 388 +++++ .../src/commands/structure/move-separators.ts | 440 +++++ .../commands/structure/set-wall-geometry.ts | 68 +- .../src/commands/structure/set-zone-intent.ts | 171 +- .../commands/structure/wall-overlap.test.ts | 281 ++++ .../src/commands/structure/wall-overlap.ts | 334 ++++ .../core/src/contracts/reference-inventory.ts | 58 +- .../hooks/spatial-grid/spatial-grid-sync.ts | 6 + packages/core/src/index.ts | 321 +++- packages/core/src/lib/beam.test.ts | 271 +++ packages/core/src/lib/beam.ts | 408 +++++ packages/core/src/lib/build-up.test.ts | 181 ++ packages/core/src/lib/build-up.ts | 388 +++++ packages/core/src/lib/building-foundation.ts | 330 ++++ packages/core/src/lib/deck-boards.test.ts | 249 +++ packages/core/src/lib/deck-boards.ts | 269 +++ packages/core/src/lib/deck-framing.test.ts | 303 ++++ packages/core/src/lib/deck-framing.ts | 696 ++++++++ .../core/src/lib/designed-basement.test.ts | 307 ++++ .../core/src/lib/designed-foundation.test.ts | 566 +++++++ packages/core/src/lib/designed-foundation.ts | 1426 ++++++++++++++++ packages/core/src/lib/exterior-wall-loops.ts | 10 +- packages/core/src/lib/floor-build-up.test.ts | 71 + .../core/src/lib/floor-foundation-datum.ts | 78 +- packages/core/src/lib/floor-plates.ts | 55 +- packages/core/src/lib/footing.test.ts | 210 +++ packages/core/src/lib/footing.ts | 329 ++++ .../src/lib/foundation-construction.test.ts | 28 +- .../core/src/lib/foundation-grading.test.ts | 192 +++ packages/core/src/lib/foundation-grading.ts | 170 ++ packages/core/src/lib/foundation-view.test.ts | 295 ++++ packages/core/src/lib/foundation-view.ts | 259 +++ packages/core/src/lib/framing-post.test.ts | 152 ++ packages/core/src/lib/framing-post.ts | 250 +++ .../core/src/lib/generic-foundation.test.ts | 67 +- packages/core/src/lib/generic-foundation.ts | 454 ++++- packages/core/src/lib/ground-seat.ts | 85 +- packages/core/src/lib/level-footprints.ts | 10 + packages/core/src/lib/opening-casing.test.ts | 164 ++ packages/core/src/lib/opening-casing.ts | 149 ++ packages/core/src/lib/outdoor-room.test.ts | 554 ++++++ packages/core/src/lib/outdoor-room.ts | 90 + packages/core/src/lib/plate-surface.ts | 8 +- packages/core/src/lib/porch-framing-sync.ts | 272 +++ packages/core/src/lib/porch-framing.test.ts | 82 + packages/core/src/lib/porch-framing.ts | 200 +++ packages/core/src/lib/railing-rooms.test.ts | 128 ++ packages/core/src/lib/railing-rooms.ts | 47 + packages/core/src/lib/roof-eave.test.ts | 186 +++ packages/core/src/lib/roof-eave.ts | 301 ++++ .../core/src/lib/slab-room-plates.test.ts | 212 +++ packages/core/src/lib/slab-room.test.ts | 326 ++++ packages/core/src/lib/slab-room.ts | 193 +++ packages/core/src/lib/space-detection.test.ts | 158 +- packages/core/src/lib/space-detection.ts | 141 +- packages/core/src/lib/structure-kernel.ts | 197 ++- packages/core/src/lib/structure-reconcile.ts | 10 +- packages/core/src/lib/surface-intent.ts | 26 + packages/core/src/lib/wall-face-script.ts | 7 +- packages/core/src/material-kinds.ts | 1 + packages/core/src/material-library.test.ts | 44 +- packages/core/src/material-library.ts | 237 ++- packages/core/src/node-slots/wall.ts | 24 +- packages/core/src/registry/index.ts | 2 + packages/core/src/registry/types.ts | 41 + .../src/schema/__fixtures__/node-fixtures.ts | 1 + packages/core/src/schema/assembly.ts | 13 + packages/core/src/schema/build-up.ts | 152 ++ .../core/src/schema/host-children.test.ts | 14 +- packages/core/src/schema/index.ts | 82 +- packages/core/src/schema/nodes/beam.ts | 175 ++ packages/core/src/schema/nodes/ceiling.ts | 5 + packages/core/src/schema/nodes/column.ts | 14 +- packages/core/src/schema/nodes/door.ts | 19 +- packages/core/src/schema/nodes/footing.ts | 42 + packages/core/src/schema/nodes/foundation.ts | 29 + packages/core/src/schema/nodes/level.ts | 6 + .../core/src/schema/nodes/opening-types.ts | 8 + .../src/schema/nodes/railing-wall.test.ts | 110 ++ .../core/src/schema/nodes/railing-wall.ts | 189 +++ .../core/src/schema/nodes/roof-segment.ts | 86 +- packages/core/src/schema/nodes/roof-types.ts | 9 + packages/core/src/schema/nodes/roof.ts | 4 + packages/core/src/schema/nodes/site.ts | 7 + packages/core/src/schema/nodes/slab.ts | 9 + .../core/src/schema/nodes/stair-structure.ts | 50 + packages/core/src/schema/nodes/stair.ts | 3 + packages/core/src/schema/nodes/wall.ts | 54 +- packages/core/src/schema/nodes/window.ts | 15 +- packages/core/src/schema/nodes/zone.ts | 36 + packages/core/src/schema/types.ts | 6 + packages/core/src/schema/wall-type.ts | 37 + .../core/src/services/roof-bearing.test.ts | 40 + packages/core/src/services/roof-bearing.ts | 8 +- .../core/src/store/actions/node-actions.ts | 13 +- packages/core/src/store/derived-node-guard.ts | 12 +- .../core/src/store/floor-opening-live.test.ts | 256 ++- .../core/src/systems/owned-floor-openings.ts | 63 + .../src/systems/roof/roof-elevation.test.ts | 11 + .../core/src/systems/roof/roof-elevation.ts | 8 +- .../src/systems/roof/roof-footprint.test.ts | 230 +++ .../core/src/systems/roof/roof-footprint.ts | 191 ++- .../src/systems/stair/stair-clearance.test.ts | 33 + .../core/src/systems/stair/stair-clearance.ts | 32 +- .../src/systems/stair/stair-construction.ts | 84 +- .../core/src/systems/stair/stair-sizing.ts | 7 + .../src/systems/stair/stair-structure.test.ts | 343 ++++ .../core/src/systems/stair/stair-structure.ts | 763 +++++++++ .../src/systems/wall/wall-assembly.test.ts | 17 +- .../core/src/systems/wall/wall-assembly.ts | 303 +++- .../core/src/systems/wall/wall-finish.test.ts | 62 +- packages/core/src/systems/wall/wall-finish.ts | 10 +- .../core/src/systems/wall/wall-footprint.ts | 4 +- .../core/src/systems/wall/wall-merge.test.ts | 21 +- packages/core/src/systems/wall/wall-merge.ts | 36 +- .../src/systems/wall/wall-mitering.test.ts | 95 ++ .../core/src/systems/wall/wall-mitering.ts | 252 ++- packages/core/src/systems/wall/wall-move.ts | 85 +- .../src/systems/wall/wall-operations.test.ts | 44 + .../core/src/systems/wall/wall-operations.ts | 2 + .../core/src/systems/wall/wall-top.test.ts | 5 + packages/core/src/systems/wall/wall-top.ts | 10 +- .../src/systems/wall/wall-topology.test.ts | 88 + .../core/src/systems/wall/wall-topology.ts | 223 ++- .../core/src/systems/wall/wall-types.test.ts | 243 +++ packages/core/src/utils/structure-id.ts | 4 + .../src/utils/wall-face-migration.test.ts | 13 +- .../floorplan-adjacent-walls-layer.test.ts | 76 + .../floorplan-adjacent-walls-layer.tsx | 186 +++ .../floorplan-alignment-guide-layer.tsx | 66 +- .../editor-2d/floorplan-foundation-layer.tsx | 243 +++ .../editor-2d/floorplan-group-move.tsx | 2 +- .../floorplan-open-wall-ends-layer.tsx | 18 +- .../floorplan-registry-move-overlay.tsx | 8 +- .../editor-2d/floorplan-snap-beacon-layer.tsx | 100 +- .../floorplan-dimension-edit-overlay.tsx | 19 +- .../floorplan-registry-layer.test.ts | 12 + .../renderers/floorplan-registry-layer.tsx | 216 ++- .../editor/alignment-3d-guide-layer.tsx | 111 +- .../src/components/editor/bake-thumbnail.tsx | 5 +- .../editor/custom-camera-controls.tsx | 28 +- .../first-person/build-collider-world.ts | 5 +- .../src/components/editor/floorplan-panel.tsx | 302 +++- .../editor/footprint-height-handle.tsx | 11 +- .../editor/src/components/editor/grid.tsx | 39 +- .../src/components/editor/group-move-3d.ts | 22 +- .../editor/group-selection-box-3d.tsx | 3 +- .../editor/src/components/editor/index.tsx | 33 +- .../src/components/editor/room-handles-2d.tsx | 107 +- .../src/components/editor/room-handles.tsx | 183 +- .../src/components/editor/room-highlight.tsx | 2 + .../src/components/editor/room-labels.test.ts | 3 +- .../src/components/editor/room-labels.tsx | 8 +- .../components/editor/selection-manager.tsx | 142 +- .../components/editor/separator-end-grip.tsx | 74 + .../use-floorplan-background-placement.ts | 102 +- .../editor/wall-move-side-handles.tsx | 4 + .../src/components/editor/wall-pull-grip.tsx | 193 +++ .../editor/wall-snap-beacon-layer.tsx | 82 +- .../systems/ceiling/ceiling-system.test.ts | 141 ++ .../systems/ceiling/ceiling-system.tsx | 71 +- .../tools/item/placement-strategies.test.ts | 65 +- .../tools/item/placement-strategies.ts | 14 +- .../tools/item/use-placement-coordinator.tsx | 6 +- .../tools/select/select-candidates.test.ts | 45 + .../tools/select/select-candidates.ts | 15 +- .../src/components/tools/tool-manager.tsx | 22 + .../tools/wall/wall-drafting.test.ts | 336 +++- .../components/tools/wall/wall-drafting.ts | 160 +- .../tools/wall/wall-pull-snap.test.ts | 274 +++ .../components/tools/wall/wall-pull-snap.ts | 563 +++++++ .../tools/wall/wall-snap-geometry.test.ts | 114 +- .../tools/wall/wall-snap-geometry.ts | 137 ++ .../src/components/tools/zone/zone-tool.tsx | 6 +- .../ui/action-menu/structure-tools.tsx | 3 + .../ui/action-menu/view-toggles.test.ts | 18 + .../ui/action-menu/view-toggles.tsx | 166 +- .../components/ui/build-panel/build-panel.tsx | 362 +++- .../ui/build-panel/wall-type-controls.tsx | 245 +++ .../build-panel/wall-type-editor-dialog.tsx | 543 ++++++ .../ui/command-palette/editor-commands.tsx | 2 + .../components/ui/controls/finish-swatch.tsx | 31 +- .../ui/controls/material-picker.tsx | 41 +- .../components/ui/floating-level-selector.tsx | 175 +- .../ui/helpers/helper-manager.test.tsx | 38 +- .../components/ui/helpers/helper-manager.tsx | 26 +- .../components/ui/level-wall-copy-prompt.tsx | 128 ++ .../ui/panels/building-foundation-section.tsx | 1485 +++++++++++++++++ .../ui/panels/deck-boards-fields.tsx | 107 ++ .../ui/panels/floor-build-up.test.tsx | 444 +++++ .../ui/panels/floor-foundation-panel.tsx | 350 +++- .../ui/panels/floor-opening-panel.tsx | 16 +- .../ui/panels/foundation-view-panel.tsx | 275 +++ .../ui/panels/multi-field-value.test.ts | 27 + .../components/ui/panels/multi-field-value.ts | 21 +- .../ui/panels/multi-parametric-inspector.tsx | 144 +- .../components/ui/panels/panel-manager.tsx | 23 + .../ui/panels/parametric-field-control.tsx | 2 +- .../ui/panels/parametric-inspector.tsx | 9 +- .../ui/panels/room-construction-rows.tsx | 392 ++++- .../components/ui/panels/wall-region-list.tsx | 5 +- .../sidebar/panels/settings-panel/index.tsx | 3 + .../keyboard-shortcuts-dialog.tsx | 72 +- .../ui/sidebar/panels/site-panel/index.tsx | 2 + .../panels/site-panel/inline-rename-input.tsx | 25 +- .../editor/src/hooks/use-keyboard.test.ts | 112 ++ packages/editor/src/hooks/use-keyboard.ts | 60 +- .../editor/src/hooks/use-selected-room.ts | 22 +- packages/editor/src/index.tsx | 125 +- packages/editor/src/lib/add-storey.test.ts | 104 +- .../src/lib/building-foundation.test.ts | 434 +++++ .../editor/src/lib/building-foundation.ts | 268 +++ packages/editor/src/lib/continuation.ts | 5 +- .../editor/src/lib/floor-footprints.test.ts | 83 + packages/editor/src/lib/floor-footprints.ts | 112 ++ .../src/lib/floorplan/dimension-drive.test.ts | 33 +- .../src/lib/floorplan/dimension-drive.ts | 66 +- .../src/lib/floorplan/floorplan-extension.ts | 34 + .../src/lib/floorplan/floorplan-readonly.ts | 5 + .../floorplan/foundation-dimensions.test.tsx | 96 ++ .../lib/floorplan/foundation-dimensions.ts | 143 ++ .../editor/src/lib/foundation-view.test.tsx | 229 +++ packages/editor/src/lib/foundation-view.ts | 114 ++ packages/editor/src/lib/grid-frame.test.ts | 59 +- packages/editor/src/lib/grid-frame.ts | 29 +- packages/editor/src/lib/ground-surface.ts | 9 +- packages/editor/src/lib/keyboard-pan.test.ts | 7 + packages/editor/src/lib/keyboard-pan.ts | 11 +- .../editor/src/lib/layer-thickness.test.ts | 22 + packages/editor/src/lib/layer-thickness.ts | 79 + packages/editor/src/lib/level-selection.ts | 14 +- .../editor/src/lib/material-swatch-style.ts | 24 + .../editor/src/lib/orbit-near-plane.test.ts | 12 + packages/editor/src/lib/orbit-near-plane.ts | 13 + packages/editor/src/lib/paint-scope.test.ts | 52 + packages/editor/src/lib/paint-scope.ts | 15 +- packages/editor/src/lib/panel-rows.ts | 25 +- .../src/lib/room-controls-phase6.test.ts | 41 + packages/editor/src/lib/room-handle-drag.ts | 377 ++++- .../src/lib/room-push-dimensions.test.ts | 37 + .../editor/src/lib/room-push-dimensions.ts | 198 ++- .../editor/src/lib/room-selection.test.ts | 50 +- packages/editor/src/lib/selection-cycle.ts | 63 + .../editor/src/lib/separator-edit.test.ts | 431 +++++ packages/editor/src/lib/separator-edit.ts | 179 ++ packages/editor/src/lib/separator-lines.ts | 38 + packages/editor/src/lib/tool-shortcuts.ts | 240 +++ .../editor/src/lib/wall-draw-variant.test.ts | 170 +- packages/editor/src/lib/wall-draw-variant.ts | 211 ++- packages/editor/src/lib/wall-nudge.test.ts | 44 + packages/editor/src/lib/wall-nudge.ts | 131 ++ .../editor/src/lib/wall-parallel-snap.test.ts | 83 + packages/editor/src/lib/wall-parallel-snap.ts | 176 ++ .../src/lib/wall-placement-pointer.test.ts | 23 + .../editor/src/lib/wall-placement-pointer.ts | 18 + packages/editor/src/lib/wall-polygon-draft.ts | 3 + packages/editor/src/lib/wall-pull.test.ts | 228 +++ packages/editor/src/lib/wall-pull.ts | 199 +++ .../editor/src/lib/wall-push-line-up.test.ts | 132 ++ packages/editor/src/lib/wall-region-list.ts | 19 +- packages/editor/src/lib/wall-run-snap.test.ts | 188 +++ packages/editor/src/lib/wall-run-snap.ts | 83 + packages/editor/src/lib/wall-type.test.ts | 232 +++ packages/editor/src/lib/wall-type.ts | 380 +++++ .../editor/src/lib/world-grid-snap.test.ts | 141 +- packages/editor/src/lib/world-grid-snap.ts | 207 ++- .../editor/src/store/use-alignment-guides.ts | 28 +- packages/editor/src/store/use-editor.tsx | 92 + .../use-floorplan-annotation-visibility.ts | 13 + .../src/store/use-wall-snap-indicator.ts | 17 +- packages/mcp/src/tools/measure-world.test.ts | 1 + .../src/beam/actions/move-endpoint.test.ts | 71 + .../nodes/src/beam/actions/move-endpoint.ts | 73 + packages/nodes/src/beam/definition.test.ts | 117 ++ packages/nodes/src/beam/definition.ts | 204 +++ packages/nodes/src/beam/draft-settings.ts | 119 ++ packages/nodes/src/beam/drafting.test.ts | 173 ++ packages/nodes/src/beam/drafting.ts | 154 ++ .../nodes/src/beam/floorplan-affordances.ts | 47 + packages/nodes/src/beam/floorplan-move.ts | 65 + packages/nodes/src/beam/floorplan.test.ts | 135 ++ packages/nodes/src/beam/floorplan.ts | 112 ++ packages/nodes/src/beam/geometry.ts | 80 + packages/nodes/src/beam/index.ts | 2 + .../nodes/src/beam/move-endpoint-tool.tsx | 93 ++ packages/nodes/src/beam/move-tool.tsx | 140 ++ packages/nodes/src/beam/panel-model.test.ts | 109 ++ packages/nodes/src/beam/panel-model.ts | 157 ++ packages/nodes/src/beam/panel.tsx | 231 +++ packages/nodes/src/beam/parametrics.ts | 6 + packages/nodes/src/beam/renderer.tsx | 77 + packages/nodes/src/beam/schema.ts | 1 + packages/nodes/src/beam/tool.test.ts | 126 ++ packages/nodes/src/beam/tool.tsx | 220 +++ .../nodes/src/ceiling/build-up-section.tsx | 122 ++ packages/nodes/src/ceiling/definition.ts | 3 + packages/nodes/src/ceiling/materials.ts | 21 + .../nodes/src/ceiling/multi-layers.test.ts | 50 + packages/nodes/src/ceiling/panel-model.ts | 63 + packages/nodes/src/ceiling/panel.tsx | 19 +- packages/nodes/src/ceiling/renderer.tsx | 25 +- packages/nodes/src/ceiling/tool.test.ts | 162 ++ packages/nodes/src/ceiling/tool.tsx | 27 +- packages/nodes/src/column/definition.ts | 3 + packages/nodes/src/column/floorplan.ts | 22 + packages/nodes/src/column/panel.tsx | 6 +- packages/nodes/src/column/post-options.ts | 84 + packages/nodes/src/column/post-placement.ts | 236 +++ packages/nodes/src/column/post-section.tsx | 163 ++ packages/nodes/src/column/post-tool.tsx | 135 ++ packages/nodes/src/column/post.test.ts | 201 +++ packages/nodes/src/column/tool.tsx | 17 +- packages/nodes/src/door/actions.tsx | 52 + packages/nodes/src/door/definition.ts | 2 + packages/nodes/src/door/floorplan-move.ts | 16 +- packages/nodes/src/door/floorplan.test.ts | 42 + packages/nodes/src/door/floorplan.ts | 17 +- packages/nodes/src/door/move-tool.tsx | 15 +- packages/nodes/src/door/panel.tsx | 6 +- packages/nodes/src/door/swing.ts | 47 + packages/nodes/src/door/tool.tsx | 51 +- packages/nodes/src/fence/drafting.ts | 10 +- .../nodes/src/floor-opening/floorplan.test.ts | 113 +- packages/nodes/src/floor-opening/floorplan.ts | 245 ++- packages/nodes/src/footing/definition.ts | 88 + packages/nodes/src/footing/drafting.ts | 68 + .../src/footing/floorplan-affordances.ts | 145 ++ packages/nodes/src/footing/floorplan-move.ts | 71 + packages/nodes/src/footing/floorplan.test.ts | 86 + packages/nodes/src/footing/floorplan.ts | 159 ++ packages/nodes/src/footing/index.ts | 1 + packages/nodes/src/footing/panel.test.ts | 107 ++ packages/nodes/src/footing/panel.tsx | 244 +++ packages/nodes/src/footing/parametrics.ts | 6 + packages/nodes/src/footing/renderer.tsx | 72 + packages/nodes/src/footing/schema.ts | 1 + packages/nodes/src/footing/tool.test.ts | 132 ++ packages/nodes/src/footing/tool.tsx | 185 ++ .../nodes/src/foundation/boundary-editor.tsx | 164 ++ packages/nodes/src/foundation/definition.ts | 89 + packages/nodes/src/foundation/family.ts | 80 + .../foundation/floorplan-affordances.test.ts | 153 ++ .../src/foundation/floorplan-affordances.ts | 107 ++ packages/nodes/src/foundation/floorplan.ts | 137 ++ .../nodes/src/foundation/foundation.test.ts | 228 +++ packages/nodes/src/foundation/index.ts | 1 + packages/nodes/src/foundation/panel.tsx | 10 + packages/nodes/src/foundation/renderer.tsx | 61 + packages/nodes/src/foundation/schema.ts | 1 + packages/nodes/src/foundation/tool.tsx | 246 +++ .../src/foundation/wall-face-snap.test.ts | 175 ++ .../nodes/src/foundation/wall-face-snap.ts | 107 ++ packages/nodes/src/index.ts | 17 + .../src/roof-segment/build-up-section.tsx | 290 ++++ .../nodes/src/roof-segment/definition.test.ts | 131 +- packages/nodes/src/roof-segment/definition.ts | 76 +- .../floorplan-affordances.test.ts | 153 +- .../src/roof-segment/floorplan-affordances.ts | 108 +- .../nodes/src/roof-segment/floorplan.test.ts | 35 + packages/nodes/src/roof-segment/floorplan.ts | 30 +- .../src/roof-segment/multi-layers.test.ts | 110 ++ packages/nodes/src/roof-segment/paint.ts | 54 +- .../nodes/src/roof-segment/panel-model.ts | 83 + packages/nodes/src/roof-segment/panel.tsx | 80 +- .../nodes/src/roof-segment/parametrics.ts | 4 + packages/nodes/src/roof-segment/renderer.tsx | 32 +- packages/nodes/src/roof/definition.test.ts | 117 +- packages/nodes/src/roof/definition.ts | 39 +- .../src/roof/floorplan-overrides.test.ts | 96 ++ .../nodes/src/roof/floorplan-overrides.ts | 57 + .../nodes/src/roof/floorplan-tool.test.ts | 516 ++++++ packages/nodes/src/roof/floorplan-tool.tsx | 226 +++ packages/nodes/src/roof/floorplan.test.ts | 216 +++ packages/nodes/src/roof/floorplan.ts | 191 ++- packages/nodes/src/roof/paint.ts | 196 +++ packages/nodes/src/roof/panel.tsx | 59 + packages/nodes/src/roof/renderer.tsx | 15 +- .../nodes/src/roof/roof-footprint-source.ts | 26 + .../nodes/src/roof/roof-footprint.test.ts | 129 ++ packages/nodes/src/roof/roof-footprint.ts | 168 +- packages/nodes/src/roof/roof-materials.ts | 11 +- .../src/roof/roof-placement-mode.test.ts | 9 +- .../nodes/src/roof/roof-placement-mode.ts | 18 +- packages/nodes/src/roof/roof-plan-creases.ts | 228 +++ packages/nodes/src/roof/roof-ridge-mode.ts | 36 + packages/nodes/src/roof/tool.test.ts | 158 +- packages/nodes/src/roof/tool.tsx | 424 +++-- packages/nodes/src/roof/wall-catch.test.ts | 345 ++++ packages/nodes/src/roof/wall-catch.ts | 372 +++++ .../nodes/src/separator/definition.test.ts | 28 + packages/nodes/src/separator/definition.ts | 22 +- .../src/separator/floorplan-affordances.ts | 32 + packages/nodes/src/separator/floorplan.ts | 59 + packages/nodes/src/separator/renderer.tsx | 36 +- packages/nodes/src/separator/selection.tsx | 59 + .../nodes/src/shared/build-up-controls.tsx | 297 ++++ .../contextual-floorplan-dimensions.test.ts | 44 +- packages/nodes/src/shared/eyedropper.test.ts | 2 +- packages/nodes/src/shared/layer-thickness.ts | 3 + packages/nodes/src/shared/move-roof-tool.tsx | 124 +- .../nodes/src/shared/node-batch/candidates.ts | 5 +- .../src/shared/opening-casing-move.test.tsx | 292 ++++ .../nodes/src/shared/opening-casing-move.ts | 50 + .../src/shared/opening-casing-section.tsx | 81 + .../nodes/src/shared/opening-casing.test.ts | 136 ++ packages/nodes/src/shared/opening-casing.ts | 102 ++ .../src/shared/polygon-vertex-affordance.ts | 91 +- .../src/site/foundation-view-shell.test.tsx | 90 + .../nodes/src/site/foundation-view-shell.tsx | 64 + .../site/generic-foundation-geometry.test.ts | 82 +- .../src/site/generic-foundation-geometry.ts | 334 +++- .../src/site/generic-foundation.test.tsx | 17 +- .../nodes/src/site/generic-foundation.tsx | 101 +- packages/nodes/src/site/ground-veil.test.tsx | 57 + packages/nodes/src/site/ground-veil.tsx | 27 +- .../nodes/src/site/ground-window-system.tsx | 8 +- packages/nodes/src/site/renderer.tsx | 48 +- packages/nodes/src/site/terrain-renderer.tsx | 90 +- .../src/slab/__tests__/framing-cavity.test.ts | 243 +++ .../nodes/src/slab/__tests__/geometry.test.ts | 31 + .../src/slab/__tests__/step-geometry.test.ts | 21 +- packages/nodes/src/slab/deck-geometry.test.ts | 204 +++ packages/nodes/src/slab/deck-geometry.ts | 412 +++++ .../nodes/src/slab/dependency-tracker.test.ts | 1 + packages/nodes/src/slab/dependency-tracker.ts | 9 +- packages/nodes/src/slab/geometry.ts | 162 +- packages/nodes/src/slab/panel.tsx | 16 +- packages/nodes/src/slab/surface-split.ts | 86 +- packages/nodes/src/slab/system.tsx | 29 +- .../nodes/src/stair/construction-controls.tsx | 24 +- packages/nodes/src/stair/panel.tsx | 5 + packages/nodes/src/stair/parametrics.ts | 7 + .../src/stair/placement-defaults.test.ts | 41 + .../nodes/src/stair/placement-defaults.ts | 32 + packages/nodes/src/stair/renderer.tsx | 27 +- .../nodes/src/stair/structure-controls.tsx | 253 +++ packages/nodes/src/stair/tool.tsx | 29 +- packages/nodes/src/wall/actions.tsx | 58 +- .../nodes/src/wall/contextual-dimensions.ts | 134 +- .../nodes/src/wall/curtain-wall-materials.ts | 10 +- packages/nodes/src/wall/curve-tool.tsx | 5 +- packages/nodes/src/wall/definition.test.ts | 15 +- packages/nodes/src/wall/definition.ts | 40 +- .../src/wall/floorplan-affordances.test.ts | 38 + .../nodes/src/wall/floorplan-affordances.ts | 15 +- .../nodes/src/wall/floorplan-move.test.ts | 83 + packages/nodes/src/wall/floorplan-move.ts | 132 +- packages/nodes/src/wall/floorplan-tool.tsx | 2 + packages/nodes/src/wall/floorplan.test.ts | 40 +- packages/nodes/src/wall/floorplan.ts | 53 +- packages/nodes/src/wall/justification.test.ts | 4 +- .../nodes/src/wall/move-endpoint-tool.tsx | 1 + packages/nodes/src/wall/move-tool.test.tsx | 4 +- packages/nodes/src/wall/move-tool.tsx | 133 +- .../nodes/src/wall/multi-reference.test.ts | 127 +- packages/nodes/src/wall/paint-plan.test.ts | 2 +- packages/nodes/src/wall/paint.test.ts | 2 +- packages/nodes/src/wall/paint.ts | 18 +- packages/nodes/src/wall/panel-model.ts | 260 ++- packages/nodes/src/wall/panel.test.ts | 19 + packages/nodes/src/wall/panel.tsx | 471 +++--- packages/nodes/src/wall/parametrics.ts | 17 +- .../nodes/src/wall/railing-wall-adapter.ts | 45 + .../nodes/src/wall/railing-wall-floorplan.ts | 40 + .../nodes/src/wall/railing-wall-geometry.ts | 59 + .../nodes/src/wall/railing-wall-guard.test.ts | 163 ++ packages/nodes/src/wall/railing-wall-guard.ts | 572 +++++++ .../nodes/src/wall/railing-wall-materials.ts | 53 + .../src/wall/railing-wall-panel-model.ts | 181 ++ .../nodes/src/wall/railing-wall-panel.tsx | 133 ++ .../src/wall/railing-wall-system.test.ts | 72 + packages/nodes/src/wall/rectangle-command.ts | 10 +- packages/nodes/src/wall/rectangle-tool.tsx | 9 +- packages/nodes/src/wall/renderer.tsx | 6 +- packages/nodes/src/wall/system.tsx | 8 +- packages/nodes/src/wall/tool.test.ts | 54 + packages/nodes/src/wall/tool.tsx | 242 ++- packages/nodes/src/wall/treatments.test.ts | 55 +- packages/nodes/src/wall/treatments.tsx | 37 +- packages/nodes/src/window/floorplan-move.ts | 7 + packages/nodes/src/window/floorplan.ts | 19 + packages/nodes/src/window/move-tool.tsx | 8 + packages/nodes/src/window/panel.tsx | 3 + packages/nodes/src/window/tool.tsx | 4 + packages/nodes/src/zone/renderer.tsx | 3 +- .../src/components/viewer/glb-scene.tsx | 2 + packages/viewer/src/hooks/use-node-events.ts | 4 + packages/viewer/src/index.ts | 16 +- .../viewer/src/lib/foundation-materials.ts | 16 + .../src/lib/foundation-view-hidden.test.ts | 86 + .../viewer/src/lib/foundation-view-hidden.ts | 31 + packages/viewer/src/store/use-viewer.test.ts | 63 + packages/viewer/src/store/use-viewer.ts | 60 +- .../systems/ceiling/ceiling-system.test.ts | 38 +- .../src/systems/ceiling/ceiling-system.tsx | 12 + .../systems/door/door-cutout-proxy.test.ts | 5 +- .../src/systems/door/door-frame-depth.test.ts | 14 +- .../viewer/src/systems/door/door-system.tsx | 30 +- .../src/systems/level/level-system.test.ts | 20 +- .../src/systems/level/level-utils.test.ts | 12 +- .../viewer/src/systems/level/level-utils.ts | 20 +- .../src/systems/roof/roof-fascia.test.ts | 97 +- .../systems/roof/roof-framing-band.test.ts | 144 ++ .../src/systems/roof/roof-material-slots.ts | 13 + .../src/systems/roof/roof-materials.test.ts | 83 +- .../viewer/src/systems/roof/roof-materials.ts | 143 +- .../src/systems/roof/roof-plane-cases.test.ts | 6 +- .../src/systems/roof/roof-system.test.ts | 63 + .../viewer/src/systems/roof/roof-system.tsx | 704 ++++++-- .../stair/stair-structure-geometry.test.tsx | 102 ++ .../wall/opening-casing-geometry.test.ts | 198 +++ .../systems/wall/opening-casing-geometry.ts | 119 ++ .../systems/wall/opening-cutout-geometry.ts | 3 +- .../wall/wall-exposed-structure.test.ts | 24 +- .../wall/wall-finish-plain-golden.json | 4 +- .../systems/wall/wall-foundation-rim.test.ts | 145 ++ .../systems/wall/wall-interior-paint.test.ts | 109 ++ .../viewer/src/systems/wall/wall-materials.ts | 27 +- .../systems/wall/wall-room-corpus-golden.json | 4 +- .../src/systems/wall/wall-slab-room.test.ts | 251 +++ .../viewer/src/systems/wall/wall-system.tsx | 338 +++- .../src/systems/window/window-system.tsx | 26 + wiki/architecture/layers.md | 2 +- wiki/architecture/materials-and-themes.md | 6 + wiki/architecture/measurements.md | 1 + wiki/architecture/node-definitions.md | 12 + wiki/architecture/selection-managers.md | 33 + wiki/architecture/space-detection.md | 8 + wiki/architecture/tools.md | 16 +- 561 files changed, 57393 insertions(+), 2459 deletions(-) create mode 100644 apps/editor/lib/build-palette.test.ts create mode 100644 apps/editor/public/icons/beam.svg create mode 100644 apps/editor/public/icons/post.svg create mode 100644 packages/core/src/building/foundation.test.ts create mode 100644 packages/core/src/building/foundation.ts create mode 100644 packages/core/src/building/level-removal.ts create mode 100644 packages/core/src/building/level-wall-copy.test.ts create mode 100644 packages/core/src/building/level-wall-copy.ts create mode 100644 packages/core/src/commands/structure/attached-walls.test.ts create mode 100644 packages/core/src/commands/structure/attached-walls.ts create mode 100644 packages/core/src/commands/structure/move-separators.test.ts create mode 100644 packages/core/src/commands/structure/move-separators.ts create mode 100644 packages/core/src/commands/structure/wall-overlap.test.ts create mode 100644 packages/core/src/commands/structure/wall-overlap.ts create mode 100644 packages/core/src/lib/beam.test.ts create mode 100644 packages/core/src/lib/beam.ts create mode 100644 packages/core/src/lib/build-up.test.ts create mode 100644 packages/core/src/lib/build-up.ts create mode 100644 packages/core/src/lib/building-foundation.ts create mode 100644 packages/core/src/lib/deck-boards.test.ts create mode 100644 packages/core/src/lib/deck-boards.ts create mode 100644 packages/core/src/lib/deck-framing.test.ts create mode 100644 packages/core/src/lib/deck-framing.ts create mode 100644 packages/core/src/lib/designed-basement.test.ts create mode 100644 packages/core/src/lib/designed-foundation.test.ts create mode 100644 packages/core/src/lib/designed-foundation.ts create mode 100644 packages/core/src/lib/floor-build-up.test.ts create mode 100644 packages/core/src/lib/footing.test.ts create mode 100644 packages/core/src/lib/footing.ts create mode 100644 packages/core/src/lib/foundation-grading.test.ts create mode 100644 packages/core/src/lib/foundation-grading.ts create mode 100644 packages/core/src/lib/foundation-view.test.ts create mode 100644 packages/core/src/lib/foundation-view.ts create mode 100644 packages/core/src/lib/framing-post.test.ts create mode 100644 packages/core/src/lib/framing-post.ts create mode 100644 packages/core/src/lib/opening-casing.test.ts create mode 100644 packages/core/src/lib/opening-casing.ts create mode 100644 packages/core/src/lib/outdoor-room.test.ts create mode 100644 packages/core/src/lib/outdoor-room.ts create mode 100644 packages/core/src/lib/porch-framing-sync.ts create mode 100644 packages/core/src/lib/porch-framing.test.ts create mode 100644 packages/core/src/lib/porch-framing.ts create mode 100644 packages/core/src/lib/railing-rooms.test.ts create mode 100644 packages/core/src/lib/railing-rooms.ts create mode 100644 packages/core/src/lib/roof-eave.test.ts create mode 100644 packages/core/src/lib/roof-eave.ts create mode 100644 packages/core/src/lib/slab-room-plates.test.ts create mode 100644 packages/core/src/lib/slab-room.test.ts create mode 100644 packages/core/src/lib/slab-room.ts create mode 100644 packages/core/src/lib/surface-intent.ts create mode 100644 packages/core/src/schema/build-up.ts create mode 100644 packages/core/src/schema/nodes/beam.ts create mode 100644 packages/core/src/schema/nodes/footing.ts create mode 100644 packages/core/src/schema/nodes/foundation.ts create mode 100644 packages/core/src/schema/nodes/railing-wall.test.ts create mode 100644 packages/core/src/schema/nodes/railing-wall.ts create mode 100644 packages/core/src/schema/nodes/stair-structure.ts create mode 100644 packages/core/src/schema/wall-type.ts create mode 100644 packages/core/src/services/roof-bearing.test.ts create mode 100644 packages/core/src/systems/roof/roof-footprint.test.ts create mode 100644 packages/core/src/systems/stair/stair-structure.test.ts create mode 100644 packages/core/src/systems/stair/stair-structure.ts create mode 100644 packages/core/src/systems/wall/wall-types.test.ts create mode 100644 packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts create mode 100644 packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx create mode 100644 packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx create mode 100644 packages/editor/src/components/editor/separator-end-grip.tsx create mode 100644 packages/editor/src/components/editor/wall-pull-grip.tsx create mode 100644 packages/editor/src/components/systems/ceiling/ceiling-system.test.ts create mode 100644 packages/editor/src/components/tools/wall/wall-pull-snap.test.ts create mode 100644 packages/editor/src/components/tools/wall/wall-pull-snap.ts create mode 100644 packages/editor/src/components/ui/action-menu/view-toggles.test.ts create mode 100644 packages/editor/src/components/ui/build-panel/wall-type-controls.tsx create mode 100644 packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx create mode 100644 packages/editor/src/components/ui/level-wall-copy-prompt.tsx create mode 100644 packages/editor/src/components/ui/panels/building-foundation-section.tsx create mode 100644 packages/editor/src/components/ui/panels/deck-boards-fields.tsx create mode 100644 packages/editor/src/components/ui/panels/floor-build-up.test.tsx create mode 100644 packages/editor/src/components/ui/panels/foundation-view-panel.tsx create mode 100644 packages/editor/src/lib/building-foundation.test.ts create mode 100644 packages/editor/src/lib/building-foundation.ts create mode 100644 packages/editor/src/lib/floorplan/foundation-dimensions.test.tsx create mode 100644 packages/editor/src/lib/floorplan/foundation-dimensions.ts create mode 100644 packages/editor/src/lib/foundation-view.test.tsx create mode 100644 packages/editor/src/lib/foundation-view.ts create mode 100644 packages/editor/src/lib/layer-thickness.test.ts create mode 100644 packages/editor/src/lib/layer-thickness.ts create mode 100644 packages/editor/src/lib/material-swatch-style.ts create mode 100644 packages/editor/src/lib/orbit-near-plane.test.ts create mode 100644 packages/editor/src/lib/orbit-near-plane.ts create mode 100644 packages/editor/src/lib/selection-cycle.ts create mode 100644 packages/editor/src/lib/separator-edit.test.ts create mode 100644 packages/editor/src/lib/separator-edit.ts create mode 100644 packages/editor/src/lib/separator-lines.ts create mode 100644 packages/editor/src/lib/tool-shortcuts.ts create mode 100644 packages/editor/src/lib/wall-nudge.test.ts create mode 100644 packages/editor/src/lib/wall-nudge.ts create mode 100644 packages/editor/src/lib/wall-parallel-snap.test.ts create mode 100644 packages/editor/src/lib/wall-parallel-snap.ts create mode 100644 packages/editor/src/lib/wall-pull.test.ts create mode 100644 packages/editor/src/lib/wall-pull.ts create mode 100644 packages/editor/src/lib/wall-push-line-up.test.ts create mode 100644 packages/editor/src/lib/wall-run-snap.test.ts create mode 100644 packages/editor/src/lib/wall-run-snap.ts create mode 100644 packages/editor/src/lib/wall-type.test.ts create mode 100644 packages/editor/src/lib/wall-type.ts create mode 100644 packages/nodes/src/beam/actions/move-endpoint.test.ts create mode 100644 packages/nodes/src/beam/actions/move-endpoint.ts create mode 100644 packages/nodes/src/beam/definition.test.ts create mode 100644 packages/nodes/src/beam/definition.ts create mode 100644 packages/nodes/src/beam/draft-settings.ts create mode 100644 packages/nodes/src/beam/drafting.test.ts create mode 100644 packages/nodes/src/beam/drafting.ts create mode 100644 packages/nodes/src/beam/floorplan-affordances.ts create mode 100644 packages/nodes/src/beam/floorplan-move.ts create mode 100644 packages/nodes/src/beam/floorplan.test.ts create mode 100644 packages/nodes/src/beam/floorplan.ts create mode 100644 packages/nodes/src/beam/geometry.ts create mode 100644 packages/nodes/src/beam/index.ts create mode 100644 packages/nodes/src/beam/move-endpoint-tool.tsx create mode 100644 packages/nodes/src/beam/move-tool.tsx create mode 100644 packages/nodes/src/beam/panel-model.test.ts create mode 100644 packages/nodes/src/beam/panel-model.ts create mode 100644 packages/nodes/src/beam/panel.tsx create mode 100644 packages/nodes/src/beam/parametrics.ts create mode 100644 packages/nodes/src/beam/renderer.tsx create mode 100644 packages/nodes/src/beam/schema.ts create mode 100644 packages/nodes/src/beam/tool.test.ts create mode 100644 packages/nodes/src/beam/tool.tsx create mode 100644 packages/nodes/src/ceiling/build-up-section.tsx create mode 100644 packages/nodes/src/ceiling/multi-layers.test.ts create mode 100644 packages/nodes/src/ceiling/panel-model.ts create mode 100644 packages/nodes/src/ceiling/tool.test.ts create mode 100644 packages/nodes/src/column/post-options.ts create mode 100644 packages/nodes/src/column/post-placement.ts create mode 100644 packages/nodes/src/column/post-section.tsx create mode 100644 packages/nodes/src/column/post-tool.tsx create mode 100644 packages/nodes/src/column/post.test.ts create mode 100644 packages/nodes/src/door/actions.tsx create mode 100644 packages/nodes/src/door/swing.ts create mode 100644 packages/nodes/src/footing/definition.ts create mode 100644 packages/nodes/src/footing/drafting.ts create mode 100644 packages/nodes/src/footing/floorplan-affordances.ts create mode 100644 packages/nodes/src/footing/floorplan-move.ts create mode 100644 packages/nodes/src/footing/floorplan.test.ts create mode 100644 packages/nodes/src/footing/floorplan.ts create mode 100644 packages/nodes/src/footing/index.ts create mode 100644 packages/nodes/src/footing/panel.test.ts create mode 100644 packages/nodes/src/footing/panel.tsx create mode 100644 packages/nodes/src/footing/parametrics.ts create mode 100644 packages/nodes/src/footing/renderer.tsx create mode 100644 packages/nodes/src/footing/schema.ts create mode 100644 packages/nodes/src/footing/tool.test.ts create mode 100644 packages/nodes/src/footing/tool.tsx create mode 100644 packages/nodes/src/foundation/boundary-editor.tsx create mode 100644 packages/nodes/src/foundation/definition.ts create mode 100644 packages/nodes/src/foundation/family.ts create mode 100644 packages/nodes/src/foundation/floorplan-affordances.test.ts create mode 100644 packages/nodes/src/foundation/floorplan-affordances.ts create mode 100644 packages/nodes/src/foundation/floorplan.ts create mode 100644 packages/nodes/src/foundation/foundation.test.ts create mode 100644 packages/nodes/src/foundation/index.ts create mode 100644 packages/nodes/src/foundation/panel.tsx create mode 100644 packages/nodes/src/foundation/renderer.tsx create mode 100644 packages/nodes/src/foundation/schema.ts create mode 100644 packages/nodes/src/foundation/tool.tsx create mode 100644 packages/nodes/src/foundation/wall-face-snap.test.ts create mode 100644 packages/nodes/src/foundation/wall-face-snap.ts create mode 100644 packages/nodes/src/roof-segment/build-up-section.tsx create mode 100644 packages/nodes/src/roof-segment/multi-layers.test.ts create mode 100644 packages/nodes/src/roof-segment/panel-model.ts create mode 100644 packages/nodes/src/roof/floorplan-overrides.test.ts create mode 100644 packages/nodes/src/roof/floorplan-overrides.ts create mode 100644 packages/nodes/src/roof/floorplan-tool.test.ts create mode 100644 packages/nodes/src/roof/floorplan-tool.tsx create mode 100644 packages/nodes/src/roof/paint.ts create mode 100644 packages/nodes/src/roof/roof-plan-creases.ts create mode 100644 packages/nodes/src/roof/roof-ridge-mode.ts create mode 100644 packages/nodes/src/roof/wall-catch.test.ts create mode 100644 packages/nodes/src/roof/wall-catch.ts create mode 100644 packages/nodes/src/separator/floorplan-affordances.ts create mode 100644 packages/nodes/src/separator/floorplan.ts create mode 100644 packages/nodes/src/separator/selection.tsx create mode 100644 packages/nodes/src/shared/build-up-controls.tsx create mode 100644 packages/nodes/src/shared/layer-thickness.ts create mode 100644 packages/nodes/src/shared/opening-casing-move.test.tsx create mode 100644 packages/nodes/src/shared/opening-casing-move.ts create mode 100644 packages/nodes/src/shared/opening-casing-section.tsx create mode 100644 packages/nodes/src/shared/opening-casing.test.ts create mode 100644 packages/nodes/src/shared/opening-casing.ts create mode 100644 packages/nodes/src/site/foundation-view-shell.test.tsx create mode 100644 packages/nodes/src/site/foundation-view-shell.tsx create mode 100644 packages/nodes/src/slab/__tests__/framing-cavity.test.ts create mode 100644 packages/nodes/src/slab/deck-geometry.test.ts create mode 100644 packages/nodes/src/slab/deck-geometry.ts create mode 100644 packages/nodes/src/stair/placement-defaults.test.ts create mode 100644 packages/nodes/src/stair/placement-defaults.ts create mode 100644 packages/nodes/src/stair/structure-controls.tsx create mode 100644 packages/nodes/src/wall/panel.test.ts create mode 100644 packages/nodes/src/wall/railing-wall-adapter.ts create mode 100644 packages/nodes/src/wall/railing-wall-floorplan.ts create mode 100644 packages/nodes/src/wall/railing-wall-geometry.ts create mode 100644 packages/nodes/src/wall/railing-wall-guard.test.ts create mode 100644 packages/nodes/src/wall/railing-wall-guard.ts create mode 100644 packages/nodes/src/wall/railing-wall-materials.ts create mode 100644 packages/nodes/src/wall/railing-wall-panel-model.ts create mode 100644 packages/nodes/src/wall/railing-wall-panel.tsx create mode 100644 packages/nodes/src/wall/railing-wall-system.test.ts create mode 100644 packages/nodes/src/wall/tool.test.ts create mode 100644 packages/viewer/src/lib/foundation-view-hidden.test.ts create mode 100644 packages/viewer/src/lib/foundation-view-hidden.ts create mode 100644 packages/viewer/src/systems/roof/roof-framing-band.test.ts create mode 100644 packages/viewer/src/systems/roof/roof-material-slots.ts create mode 100644 packages/viewer/src/systems/stair/stair-structure-geometry.test.tsx create mode 100644 packages/viewer/src/systems/wall/opening-casing-geometry.test.ts create mode 100644 packages/viewer/src/systems/wall/opening-casing-geometry.ts create mode 100644 packages/viewer/src/systems/wall/wall-foundation-rim.test.ts create mode 100644 packages/viewer/src/systems/wall/wall-interior-paint.test.ts create mode 100644 packages/viewer/src/systems/wall/wall-slab-room.test.ts diff --git a/apps/editor/components/build-tab.tsx b/apps/editor/components/build-tab.tsx index bfc8ced4ed..f7984fc29e 100644 --- a/apps/editor/components/build-tab.tsx +++ b/apps/editor/components/build-tab.tsx @@ -23,8 +23,10 @@ import { useFloorplanMode, useTerraceDraft, useWallDrawVariant, + wallVariantTooltip, + withToolShortcut, } from '@pascal-app/editor' -import { useLiquidLineToolOptions } from '@pascal-app/nodes' +import { framingPostToolChoices, useLiquidLineToolOptions } from '@pascal-app/nodes' import { useViewer } from '@pascal-app/viewer' import Image from 'next/image' import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from 'react' @@ -37,6 +39,7 @@ import { import { activateBuildTool, activateFenceFeaturePlacement, + activatePostTool, activateRoofFeatureTool, activateRoofType, activateTerrainSculptMode, @@ -65,6 +68,8 @@ export function BuildTab() { const [kitchenOpen, setKitchenOpen] = useState(false) const [buildSearch, setBuildSearch] = useState('') const activeTool = useEditor((s) => s.tool) + // The column tool placing framing posts lights the Post tile, not Column. + const postArmed = useEditor((s) => framingPostToolChoices(s.toolDefaults.column) !== null) const selectedId = useViewer((s) => s.selection.selectedIds[0]) const selectedFenceFeature = useScene((s) => { const selected = selectedId ? s.nodes[selectedId as AnyNodeId] : undefined @@ -133,6 +138,8 @@ export function BuildTab() { if (type.id === 'roof') return mode === 'build' && (activeTool === 'roof' || isRoofFeatureActive) if (type.id === 'fence' && selectedFence) return true + if (type.kind === 'column') + return mode === 'build' && activeTool === 'column' && (type.id === 'post') === postArmed return mode === 'build' && activeTool === type.kind } @@ -154,6 +161,8 @@ export function BuildTab() { useEditor.getState().setMode('select') } else if (type.id === 'terrace') { startTerraceDraft() + } else if (type.id === 'post') { + activatePostTool() } else if (type.kind) { activateBuildTool(type.kind) } @@ -189,7 +198,7 @@ export function BuildTab() { handleTypeClick(type) }} onMouseEnter={() => triggerSFX('sfx:menu-hover')} - title={i18n._(type.label)} + title={withToolShortcut(i18n._(type.label), type.id)} /> ) const typesIn = (section: NonNullable) => @@ -255,6 +264,7 @@ export function BuildTab() { if (!(editor.mode === 'build' && editor.tool === 'wall')) activateBuildTool('wall') }} + tileProps={(variant) => ({ title: wallVariantTooltip(variant, i18n) })} /> {advancedTypes.map(renderTile)} + )} diff --git a/apps/editor/components/viewer-toolbar.tsx b/apps/editor/components/viewer-toolbar.tsx index a9ee1183db..2491fbd533 100644 --- a/apps/editor/components/viewer-toolbar.tsx +++ b/apps/editor/components/viewer-toolbar.tsx @@ -12,6 +12,7 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, sceneThemeName, + useCeilingsSetting, useEditor, useFloorplanAnnotationVisibility, useFloorplanMode, @@ -35,6 +36,7 @@ import { EyeOff, Footprints, Grid2X2, + Layers2, Layers3, Magnet, PenLine, @@ -382,6 +384,8 @@ const SUBMENU_CONTENT_CLASS = 'min-w-56 rounded-xl border-border/45 bg-popover/9 function DisplayMenu() { const i18n = usePascalI18n() const scene = useEditor(visibleScene) + const levelId = useViewer((state) => state.selection.levelId) + const { shown: showCeilings, setShown: setShowCeilings } = useCeilingsSetting(levelId) const showGrid = useViewer((state) => state.showGrid) const setShowGrid = useViewer((state) => state.setShowGrid) const showMeasurements = useViewer((state) => state.showMeasurements) @@ -477,6 +481,17 @@ function DisplayMenu() { )} ) : null} + {scene === '3d' || scene === 'split' ? ( + keepOpen(e, () => setShowCeilings(!showCeilings))}> + + Ceilings + {showCeilings ? ( + + ) : ( + + )} + + ) : null} {scene === '2d' || scene === 'split' ? ( <> diff --git a/apps/editor/lib/build-palette.test.ts b/apps/editor/lib/build-palette.test.ts new file mode 100644 index 0000000000..dd1bd647db --- /dev/null +++ b/apps/editor/lib/build-palette.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from 'bun:test' +import { registerNode } from '@pascal-app/core' +import { beamDefinition, columnDefinition, footingDefinition } from '@pascal-app/nodes' +import { collectBuildTypes } from './build-palette' + +// The palette lists the kinds that bring a plan tool: the footing had none, so it was missing. +test('the Build palette lists the footing under Advanced', () => { + registerNode(footingDefinition) + expect(collectBuildTypes('default').find((type) => type.kind === 'footing')).toMatchObject({ + label: { message: 'Footing' }, + section: 'advanced', + }) +}) + +// The beam brings its plan tool; the post is the column tool armed with a framing post. +test('the Build palette lists Beam, and Post beside Column, under Advanced', () => { + registerNode(beamDefinition) + registerNode(columnDefinition) + const types = collectBuildTypes('default') + expect(types.find((type) => type.kind === 'beam')).toMatchObject({ + label: { message: 'Beam' }, + section: 'advanced', + iconSrc: '/icons/beam.svg', + }) + const ids = types.map((type) => type.id) + expect(types.find((type) => type.id === 'post')).toMatchObject({ + kind: 'column', + section: 'advanced', + }) + expect(ids.indexOf('post')).toBe(ids.indexOf('column') + 1) +}) diff --git a/apps/editor/lib/build-palette.ts b/apps/editor/lib/build-palette.ts index 7a3ba56665..af1d540f2e 100644 --- a/apps/editor/lib/build-palette.ts +++ b/apps/editor/lib/build-palette.ts @@ -8,7 +8,7 @@ import { useEditor, useFloorplanMode, } from '@pascal-app/editor' -import { beginFenceFeaturePlacement } from '@pascal-app/nodes' +import { armFramingPostTool, beginFenceFeaturePlacement } from '@pascal-app/nodes' import { useViewer } from '@pascal-app/viewer' import { getRoofFootprintSource, type RoofFootprintSource } from '@/lib/build-tab-state' @@ -110,6 +110,14 @@ export const BASE_BUILD_TYPES: BuildType[] = [ kind: 'column', section: 'advanced', }, + // The column tool armed with a framing post (`activatePostTool`): one family with Column. + { + id: 'post', + label: /* i18n */ { id: 'app.lib.buildPalette.post', message: 'Post' }, + iconSrc: '/icons/post.svg', + kind: 'column', + section: 'advanced', + }, { id: 'shelf', label: /* i18n */ { id: 'app.lib.buildPalette.shelf', message: 'Shelf' }, @@ -253,6 +261,15 @@ export function activateBuildTool(kind: string): void { editor.setTool(kind) } +/** + * The Post tile: the column tool placing treated framing posts that stand up to the beam over + * them, at the size and base last picked (the tool's Place and Base rows). + */ +export function activatePostTool(): void { + activateBuildTool('column') + if (useEditor.getState().tool === 'column') armFramingPostTool() +} + export function activateFenceFeaturePlacement(kind: 'gate' | 'opening'): void { beginFenceFeaturePlacement(kind) } diff --git a/apps/editor/lib/build-panel-model.ts b/apps/editor/lib/build-panel-model.ts index 560e2e31c0..b0cb89faf2 100644 --- a/apps/editor/lib/build-panel-model.ts +++ b/apps/editor/lib/build-panel-model.ts @@ -9,7 +9,7 @@ import { useScene, } from '@pascal-app/core' import { type PanelToolOption, useEditor, useFloorplanMode } from '@pascal-app/editor' -import { useLiquidLineToolOptions } from '@pascal-app/nodes' +import { framingPostToolChoices, useLiquidLineToolOptions } from '@pascal-app/nodes' import { useViewer } from '@pascal-app/viewer' import type { XRWandBuildItem, XRWandBuildModel } from '@webxr/plugin' import { @@ -17,6 +17,7 @@ import { activateFenceFeaturePlacement, activateModularCabinetTool, activatePaintMode, + activatePostTool, activateRoofFeatureTool, activateRoofFootprintSource, activateRoofType, @@ -96,6 +97,7 @@ export function useBuildPanelModel(): XRWandBuildModel { return host?.type === 'fence' ? host : undefined }) const defaults = useEditor((state) => state.toolDefaults.roof) + const postArmed = useEditor((state) => framingPostToolChoices(state.toolDefaults.column) !== null) const floorplanMode = useFloorplanMode((state) => state.mode) useRegistryVersion() const features = collectRoofFeatures() @@ -124,12 +126,15 @@ export function useBuildPanelModel(): XRWandBuildModel { ? mepActive : type.id === 'kitchen' ? kitchenActive - : mode === 'build' && tool === type.kind, + : type.kind === 'column' + ? mode === 'build' && tool === 'column' && (type.id === 'post') === postArmed + : mode === 'build' && tool === type.kind, onSelect: () => { if (type.id === 'fence' && selectedFence) return if (type.mode === 'terrain-sculpt') activateTerrainSculptMode() else if (type.id === 'mep') activateBuildTool('duct-segment') else if (type.id === 'kitchen') activateModularCabinetTool() + else if (type.id === 'post') activatePostTool() else if (type.kind) activateBuildTool(type.kind) }, }), diff --git a/apps/editor/public/icons/beam.svg b/apps/editor/public/icons/beam.svg new file mode 100644 index 0000000000..4c35ba1f14 --- /dev/null +++ b/apps/editor/public/icons/beam.svg @@ -0,0 +1 @@ + diff --git a/apps/editor/public/icons/post.svg b/apps/editor/public/icons/post.svg new file mode 100644 index 0000000000..eeea97d10f --- /dev/null +++ b/apps/editor/public/icons/post.svg @@ -0,0 +1 @@ + diff --git a/packages/core/src/agent-operations/__fixtures__/cases.ts b/packages/core/src/agent-operations/__fixtures__/cases.ts index dcef7ba639..de8bf402ab 100644 --- a/packages/core/src/agent-operations/__fixtures__/cases.ts +++ b/packages/core/src/agent-operations/__fixtures__/cases.ts @@ -279,7 +279,7 @@ export function finishReadbackScene(): SceneGraph { const wall = scene.nodes.wall_ground as WallNode scene.nodes.wall_ground = { ...wall, - slots: { a: 'library:concrete-drywall' }, + slots: { a: 'library:preset-seattlegrey' }, faceRegions: [{ id: 'wainscot', face: 'a', v1: 0.9, finish: 'library:wood' }], } scene.nodes.wall_side = { @@ -510,8 +510,8 @@ export const GET_WALLS_CASES: AgentToolCase[] = [ resolvedHeight: 2.5, heightIsExplicit: true, wallFaceChains: { - a: { kind: 'default', ref: 'library:concrete-drywall' }, - b: { kind: 'default', ref: 'library:concrete-drywall' }, + a: { kind: 'default', ref: 'library:preset-seattlegrey' }, + b: { kind: 'default', ref: 'library:preset-seattlegrey' }, }, openings: [ { id: 'door_ground', type: 'door' }, @@ -524,8 +524,8 @@ export const GET_WALLS_CASES: AgentToolCase[] = [ resolvedHeight: 2.8, heightIsExplicit: false, wallFaceChains: { - a: { kind: 'default', ref: 'library:concrete-drywall' }, - b: { kind: 'default', ref: 'library:concrete-drywall' }, + a: { kind: 'default', ref: 'library:preset-seattlegrey' }, + b: { kind: 'default', ref: 'library:preset-seattlegrey' }, }, openings: [], }, @@ -581,8 +581,8 @@ export const FINISH_READBACK_CASES: AgentToolCase[] = [ { id: 'wall_ground', wallFaceChains: { - a: { kind: 'ref', ref: 'library:concrete-drywall' }, - b: { kind: 'default', ref: 'library:concrete-drywall' }, + a: { kind: 'ref', ref: 'library:preset-seattlegrey' }, + b: { kind: 'default', ref: 'library:preset-seattlegrey' }, }, }, { @@ -629,8 +629,8 @@ export const FINISH_READBACK_CASES: AgentToolCase[] = [ { id: 'wall_upper', wallFaceChains: { - a: { kind: 'default', ref: 'library:concrete-drywall' }, - b: { kind: 'default', ref: 'library:concrete-drywall' }, + a: { kind: 'default', ref: 'library:preset-seattlegrey' }, + b: { kind: 'default', ref: 'library:preset-seattlegrey' }, }, }, ], diff --git a/packages/core/src/agent-operations/create-roof.test.ts b/packages/core/src/agent-operations/create-roof.test.ts index 63ef1bfb39..24199236f1 100644 --- a/packages/core/src/agent-operations/create-roof.test.ts +++ b/packages/core/src/agent-operations/create-roof.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test' import { type AgentRefusal, isAgentRefusal } from '../agent-tools/refusal' -import { BuildingNode, LevelNode, WallNode } from '../schema' +import { BuildingNode, LevelNode, RoofSegmentNode, WallNode } from '../schema' import { planRoofLevel, type RoofLevelPlan } from '../services/roof-level' import { createRoof, grown } from './create-roof' import type { SceneNodes } from './types' @@ -52,6 +52,27 @@ describe('create_roof: a roof outline that stays on the walls', () => { }) }) +describe('create_roof: the heel', () => { + const box: Pt[] = [ + [0, 0], + [8, 0], + [8, 6], + [0, 6], + ] + const heels = (input: Parameters[1]) => + (createRoof(storey(box), input, { activeLevelId: null }).changes?.create ?? []) + .map(({ node }) => node) + .filter((node) => node.type === 'roof-segment') + .map((node) => (node as unknown as { wallHeight: number }).wallHeight) + + test('a new roof starts at the wall tops unless the caller asks for a heel', () => { + expect(heels({})).toEqual([0]) + expect(heels({ wallHeight: 0.4 })).toEqual([0.4]) + // a saved segment with no heel still loads with the schema's 0.5 + expect(RoofSegmentNode.parse({ id: 'rseg_saved' }).wallHeight).toBe(0.5) + }) +}) + describe('create_roof: the level above the covered storey', () => { const house = (above: ReturnType[]) => { const nodes = storey([ diff --git a/packages/core/src/agent-operations/wall-skirting.ts b/packages/core/src/agent-operations/wall-skirting.ts index e07770764d..3092f0ea30 100644 --- a/packages/core/src/agent-operations/wall-skirting.ts +++ b/packages/core/src/agent-operations/wall-skirting.ts @@ -70,6 +70,8 @@ export const setWallSkirting: AgentOperation = (nodes, inp }) if (wall.wallType === 'curtain') refuse('curtain_wall', `Wall ${id} is a curtain wall: it has no skirting.`, { target: id }) + if (wall.wallType === 'railing') + refuse('railing_wall', `Wall ${id} is a railing: it has no skirting.`, { target: id }) const faces = input.faces === undefined ? roomFaces(nodes, wall) : getWallTrimFaces(input.faces) const { skirting } = wallTrimPatch(wall, 'skirting', { enabled: input.enabled ?? true, diff --git a/packages/core/src/agent-tools/room-structure.ts b/packages/core/src/agent-tools/room-structure.ts index 07b62b3019..b2d10fcd05 100644 --- a/packages/core/src/agent-tools/room-structure.ts +++ b/packages/core/src/agent-tools/room-structure.ts @@ -19,6 +19,26 @@ const zoneIntentPatch = z.strictObject({ elevation: measurement('length', 'm').nullable().optional(), finish: finish.nullable().optional(), regions: paintRegions.nullable().optional(), + foundation: z.enum(['raised', 'slab']).nullable().optional(), + drop: measurement('length', 'm', { min: 0 }).nullable().optional(), + }) + .nullable() + .optional(), + roomType: z.enum(['porch', 'deck', 'patio']).nullable().optional(), + porchFraming: z + .strictObject({ + auto: z.boolean().nullable().optional(), + postSize: z.enum(['4x4', '6x6', '8x8']).nullable().optional(), + postSpacing: measurement('length', 'm', { min: 0.6 }).nullable().optional(), + beamSize: z.string().nullable().optional(), + }) + .nullable() + .optional(), + decking: z + .strictObject({ + boardSize: z.enum(['5/4x6', '2x6', '2x4', 'composite']).nullable().optional(), + gap: measurement('length', 'm', { positive: true }).nullable().optional(), + direction: z.enum(['parallel', 'diagonal', 'perpendicular']).nullable().optional(), }) .nullable() .optional(), @@ -195,7 +215,7 @@ export const setZoneIntentTool = { name: 'set_zone_intent', title: 'Set room intent', description: - 'Update room name, floor finish/elevation/regions, floor.footprint ("new" mints a unique floor key tied to this creator room, an existing key joins that floor on this level, null returns to the shared floor). Touching rooms with the same key share a base plate; disconnected pieces share construction settings. Conversion preserves world geometry in one edit and refuses when infeasible. get_zones exposes floor_choices. A keyed floor uses " floor" while the creator remains on that key; otherwise it uses the largest named room. Existing keys such as "own" without a creator use that fallback. A user-given plate name wins; the shared floor stays "Shared floor". Keyed rooms can have raised or sunken floor.elevation, stored as a level-local walking top. Slab thickness, foundation height and edge finish use set_room_floor_construction, updating every piece of the key. Update ceiling paint regions (ceiling.regions: [x, z] polygons with a finish, later wins), step finishes (floorStepFinish for every step of the room; floorStepOverrides: [{ key, step?, finish }] per doorway, key = the door id the step sits under or the lower room id), wall material and construction opt-outs. daylight { artificialLight, mechanicalVentilation } declares a habitable room lit and aired by artificial means (the IRC R303.1 exceptions to daylight; both true), stored in the room\'s metadata; a bedroom, living room or kitchen needs a window to outside or this declaration, above and below grade alike. Absent fields are unchanged; null clears.', + 'Update room name, floor finish/elevation/regions, floor.footprint ("new" mints a unique floor key tied to this creator room, an existing key joins that floor on this level, null returns to the shared floor). Touching rooms with the same key share a base plate; disconnected pieces share construction settings. Conversion preserves world geometry in one edit and refuses when infeasible. get_zones exposes floor_choices. A keyed floor uses " floor" while the creator remains on that key; otherwise it uses the largest named room. Existing keys such as "own" without a creator use that fallback. A user-given plate name wins; the shared floor stays "Shared floor". Keyed rooms can have raised or sunken floor.elevation, stored as a level-local walking top. floor.foundation ("raised" | "slab", null for the building type) sets what the room floor stands on: in a raised house a slab room (a garage) comes down onto its slab on grade, on an 8 in curb; a room named a garage takes "slab" unless its floor was chosen. Slab thickness, foundation height and edge finish use set_room_floor_construction, updating every piece of the key. Update ceiling paint regions (ceiling.regions: [x, z] polygons with a finish, later wins), step finishes (floorStepFinish for every step of the room; floorStepOverrides: [{ key, step?, finish }] per doorway, key = the door id the step sits under or the lower room id), wall material and construction opt-outs. daylight { artificialLight, mechanicalVentilation } declares a habitable room lit and aired by artificial means (the IRC R303.1 exceptions to daylight; both true), stored in the room\'s metadata; a bedroom, living room or kitchen needs a window to outside or this declaration, above and below grade alike. roomType ("porch" | "deck" | "patio", null for a room) makes the room outdoors: a porch is a concrete slab stepped floor.drop (1.5 in) under the house with a ceiling, a deck wood decking 1 in down and framed, a patio concrete and open. porchFraming { auto, postSize "4x4"|"6x6"|"8x8", postSpacing m, beamSize e.g. "6x8" } sets the posts of a covered porch (one at each outside corner, more at most postSpacing apart) and the beam on them under its ceiling; a deck or patio gets them only with auto true (to carry a roof over it). A size or the spacing rebuilds them, auto false leaves them as edited. decking { boardSize "5/4x6"|"2x6"|"2x4"|"composite", gap m (1/8, 3/16 or 1/4 in), direction "parallel"|"diagonal"|"perpendicular" to the house } sets a deck\'s boards (default 5/4x6, 1/4 in apart, along the house); its frame follows: joists 12 in on centre under diagonal 5/4 or composite boards (IRC R507.7), and along the house on beams square to it under boards running out from it. Absent fields are unchanged; null clears.', input: { zoneId: NodeId, patch: zoneIntentPatch }, } diff --git a/packages/core/src/agent-tools/wall-openings.ts b/packages/core/src/agent-tools/wall-openings.ts index eb493159fc..4a64913d96 100644 --- a/packages/core/src/agent-tools/wall-openings.ts +++ b/packages/core/src/agent-tools/wall-openings.ts @@ -58,7 +58,7 @@ export const addDoorTool = { name: 'add_door', title: 'Add door', description: - 'Add a door to an existing straight wall at t (0..1 along it), or a passage with no leaf (openingKind opening: a cased opening, an arch). The door slides to stay on the wall and reports clamped. Refused with a code, as in the editor: curved walls, walls shorter than the door, and overlapping another door, window or wall item unless force is set. Match the reference with the outline (rectangle, rounded, arch), doorType and style; what they cannot express (glass strips, a pattern of lites, carved panels) is written as a script in code, never left as not possible.', + 'Add a door to an existing straight wall at t (0..1 along it), or a passage with no leaf (openingKind opening: a cased opening, an arch). The door slides to stay on the wall and reports clamped. On a railing (wallType railing) it is a gap in the guard as tall as the railing, with no leaf, casing or threshold. Refused with a code, as in the editor: curved walls, walls shorter than the door, and overlapping another door, window or wall item unless force is set. Match the reference with the outline (rectangle, rounded, arch), doorType and style; what they cannot express (glass strips, a pattern of lites, carved panels) is written as a script in code, never left as not possible.', input: { wallId: NodeId.optional().describe('The wall to add the door to.'), nodeId: NodeId.optional().describe( @@ -106,7 +106,7 @@ export const addWindowTool = { name: 'add_window', title: 'Add window', description: - "Add a window to an existing straight wall at t (0..1 along it), centred vertically on the wall, or on sillHeight above the floor when given. It slides to stay on the wall and under the wall's ceiling, and reports clamped. Refused with a code, as in the editor: curved walls, walls shorter than the window, overlapping another door, window or wall item unless force is set, and a style on a window that is not Fixed (style_needs_fixed_window). Match the reference with the outline (rectangle, rounded, arch), windowType and panes (columns × rows, or a style); what they cannot express (glass strips, leaded lites, a feature frame) is written as a script in code, never left as not possible.", + "Add a window to an existing straight wall at t (0..1 along it), centred vertically on the wall, or on sillHeight above the floor when given. It slides to stay on the wall and under the wall's ceiling, and reports clamped. Refused with a code, as in the editor: curved walls, railings (railing_window: a railing takes a door, a gap in its guard), walls shorter than the window, overlapping another door, window or wall item unless force is set, and a style on a window that is not Fixed (style_needs_fixed_window). Match the reference with the outline (rectangle, rounded, arch), windowType and panes (columns × rows, or a style); what they cannot express (glass strips, leaded lites, a feature frame) is written as a script in code, never left as not possible.", input: { wallId: NodeId.optional().describe('The wall to add the window to.'), nodeId: NodeId.optional().describe( diff --git a/packages/core/src/building/corner-window.test.ts b/packages/core/src/building/corner-window.test.ts index f2b3383953..81a131a5ee 100644 --- a/packages/core/src/building/corner-window.test.ts +++ b/packages/core/src/building/corner-window.test.ts @@ -170,6 +170,24 @@ describe('by hand: wrap, keep in step, unwrap', () => { }) }) + test('its partner takes its casing, each face on the same side of the corner', () => { + const { nodes, window } = plain() + const cased = { ...window, casingWidth: 0.1, casingFaceA: true, casingFaceB: false } + const casingOf = (partner: WindowNode | null) => [ + partner?.casingWidth, + partner?.casingFaceA, + partner?.casingFaceB, + ] + // wall_a runs into the corner and wall_b on out of it: their `a` faces are on one side. + const onward = wrapCorner({ ...nodes, [window.id]: cased }, window.id).create + expect(casingOf(onward)).toEqual([0.1, true, false]) + // wall_b drawn back into the corner: its `a` is on the side of wall_a's `b`. + const wallB = nodes.wall_b as WallNode + const reversed = { ...wallB, start: wallB.end, end: wallB.start } + const back = wrapCorner({ ...nodes, wall_b: reversed, [window.id]: cased }, window.id).create + expect(casingOf(back)).toEqual([0.1, false, true]) + }) + // A window a few cm short of the corner still has a Corner section to wrap it. const onWall = (id: string, wall: string, x: number, width = 1.2) => WindowNode.parse({ diff --git a/packages/core/src/building/corner-window.ts b/packages/core/src/building/corner-window.ts index e21ea7cc12..5619547fd1 100644 --- a/packages/core/src/building/corner-window.ts +++ b/packages/core/src/building/corner-window.ts @@ -129,6 +129,7 @@ const SHARED = [ 'sill', 'sillDepth', 'sillThickness', + 'casingWidth', ] as const /** The partner's update for a change to one side of a corner window; null when nothing shared changed. */ @@ -153,8 +154,8 @@ export function cornerPartnerUpdates( * The editor's Wrap the corner: a window within `CORNER_REACH_M` of a corner where exactly one other * wall ends is slid to the corner and joined to the window waiting there on that wall, adopted * (`mergeWindows`); with none waiting, it gets a partner on that wall, of its width (as far as the - * wall allows), height, sill and type. `create` is the partner to add, if any; `updates` are the - * windows that change, by id. + * wall allows), height, sill, type and casing, each face's casing on its side of the corner. + * `create` is the partner to add, if any; `updates` are the windows that change, by id. */ export function wrapCorner( nodes: Nodes, @@ -210,11 +211,18 @@ export function wrapCorner( }).node as WindowNode const post = fusesAtCorner(window) ? 'none' : 'post' const slidTo = end === 'start' ? window.width / 2 : wallLengthOf(wall) - window.width / 2 + // Casing is kept per wall face. A wall running into the corner and one running on out of it + // have their `a` faces on the same side of it; two meeting end to end or start to start have + // them on opposite sides, so the partner takes the window's faces the other way round. + const swapFaces = end === otherEnd return { create: { ...partner, columnRatios: window.columnRatios, rowRatios: window.rowRatios, + casingWidth: window.casingWidth, + casingFaceA: swapFaces ? window.casingFaceB : window.casingFaceA, + casingFaceB: swapFaces ? window.casingFaceA : window.casingFaceB, corner: { end: otherEnd, partnerId: window.id, post }, } as WindowNode, updates: { diff --git a/packages/core/src/building/door-types.test.ts b/packages/core/src/building/door-types.test.ts index f06c2475e5..b39f9611f3 100644 --- a/packages/core/src/building/door-types.test.ts +++ b/packages/core/src/building/door-types.test.ts @@ -30,4 +30,10 @@ describe("the door panel's Type row", () => { const garage = doorTypeChange(door(), 'garage-sectional') expect(garage.position).toEqual([1, 1.2, 0]) }) + + test('keeps a door set down on a garage slab on the slab', () => { + // Its sill 0.6 m under the house floor (`doorSillFloorOf`): made a sectional door, it stays there. + const garage = doorTypeChange(door({ position: [1, 0.45, 0] }), 'garage-sectional') + expect(garage.position![1] - garage.height! / 2).toBeCloseTo(-0.6, 9) + }) }) diff --git a/packages/core/src/building/door-types.ts b/packages/core/src/building/door-types.ts index b3b3c216db..c6a167e189 100644 --- a/packages/core/src/building/door-types.ts +++ b/packages/core/src/building/door-types.ts @@ -162,9 +162,9 @@ function chosenStyle(door: DoorNode): DoorStyle | undefined { } /** - * The door panel's Type row: the type's fields and size, the door kept on the floor, and a style - * the person chose kept (a modern door made sliding stays modern; a plain one made French takes - * the French glazing). + * The door panel's Type row: the type's fields and size, the door kept on the floor it stands on + * (its sill where it was: a garage's slab, the house's floor), and a style the person chose kept + * (a modern door made sliding stays modern; a plain one made French takes the French glazing). */ export function doorTypeChange(door: DoorNode, type: DoorType): Partial { const { width, height } = DOOR_TYPE_SIZES[type] @@ -173,7 +173,7 @@ export function doorTypeChange(door: DoorNode, type: DoorType): Partial + +/** The site's ground `height` metres up everywhere around the house. */ +function raisedGround(height: number) { + const field = createTerrainField({ cols: 33, rows: 33, spacing: 1, origin: [-16, -16] }) + return encodeTerrainField({ + ...field, + heights: new Int16Array(field.heights).fill(Math.round(height * 100)), + }) +} + +function house(): Nodes { + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const walls = corners.map((start, index) => + WallNode.parse({ + id: `wall_g_${index}`, + parentId: 'level_g', + start, + end: corners[(index + 1) % 4], + thickness: 0.15, + }), + ) + const nodes = [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_g'], + metadata: { foundation: { type: 'raised', ffAboveGradeIn: 18, source: 'add_foundation' } }, + }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((w) => w.id), 'slab_g', 'slab_drive'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: corners, + elevation: 0.05, + thickness: 0.05, + }), + SlabNode.parse({ + id: 'slab_drive', + parentId: 'level_g', + polygon: [ + [12, 0], + [15, 0], + [15, 6], + [12, 6], + ], + elevation: 0, + metadata: { flatwork: 'driveway' }, + }), + ...walls, + ] + return Object.fromEntries(nodes.map((node) => [node.id, node])) as Nodes +} + +function apply(nodes: Nodes, update: Array<{ id: AnyNodeId; data: Partial }>): Nodes { + const next = { ...nodes } + for (const { id, data } of update) next[id] = { ...next[id], ...data } as AnyNode + return next +} + +test('switching the foundation on seats the floor over the high side of the ground, the record kept for its readers', () => { + const nodes = house() + const built = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true }, + }) + if (!built) throw new Error('no change') + const after = apply(nodes, built.update) + // The raised floor the generator wrote (18 in) is under the least a stem 8 in over grade allows. + expect(built.record.ffAboveGradeIn).toBe(22.125) + expect(built.record.type).toBe('raised') + expect(built.record.source).toBe('add_foundation') + expect(levelFloorWorldY(after, 'level_g')! - FLAT_GROUND_Y).toBeCloseTo(22.125 * IN, 6) + // The driveway stays on the ground. + const drive = after['slab_drive' as AnyNodeId] as SlabNode + const building = after['building_a' as AnyNodeId] as BuildingNode + expect(building.position[1] + drive.elevation).toBeCloseTo(0, 6) +}) + +test('seated on its foundation, the ground floor still bears on the ground; the storey over it rests on walls', () => { + const nodes = house() + const building = nodes['building_a' as AnyNodeId] as BuildingNode + nodes[building.id] = { ...building, children: ['level_g', 'level_up'] } as BuildingNode + nodes['level_up' as AnyNodeId] = LevelNode.parse({ + id: 'level_up', + parentId: 'building_a', + level: 1, + children: ['slab_up'], + }) + nodes['slab_up' as AnyNodeId] = SlabNode.parse({ + id: 'slab_up', + parentId: 'level_up', + plateRole: 'base', + polygon: (nodes['slab_g' as AnyNodeId] as SlabNode).polygon, + elevation: 0.05, + thickness: 0.05, + }) + const ground = nodes['slab_g' as AnyNodeId] as SlabNode + // The upper plate needs a room under it to rest on. + nodes['zone_g' as AnyNodeId] = ZoneNode.parse({ + id: 'zone_g', + parentId: 'level_g', + name: 'Living', + spaceRole: 'room', + polygon: ground.polygon, + }) + nodes[ground.id] = { ...ground, zoneIds: ['zone_g'] } as SlabNode + const set = (scene: Nodes, enabled: boolean) => + apply( + scene, + buildBuildingFoundationChanges({ + nodes: scene, + buildingId: 'building_a', + patch: { enabled }, + })!.update, + ) + const on = set(nodes, true) + // Switched off, the house stays where the seat put it: still a ground floor. + for (const after of [on, set(on, false)]) { + expect(levelFloorWorldY(after, 'level_g')! - FLAT_GROUND_Y).toBeCloseTo(22.125 * IN, 6) + const plate = after['slab_g' as AnyNodeId] as SlabNode + expect(floorPlateAtGroundContact(after, plate)).toBe(true) + expect(upperFloorHeightControl(after, plate)).toBeNull() + expect(upperFloorHeightControl(after, after['slab_up' as AnyNodeId] as SlabNode)).not.toBeNull() + } +}) + +// The plate's own raise stayed under the designed foundation: the seat lowered the house by it, and +// past 0.3 m the ground floor read as a basement and the Foundation section went. +for (const raise of [0.3, 0.5]) + test(`a ground floor raised ${raise} m on its own comes down onto the designed foundation`, () => { + const nodes = house() + const plate = nodes['slab_g' as AnyNodeId] as SlabNode + nodes[plate.id] = { + ...plate, + elevation: 0.05 + raise, + floorHeight: 0.05 + raise, + foundation: { type: 'solid' }, + } as SlabNode + const built = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true }, + })! + const after = apply(nodes, built.update) + const lowered = after[plate.id] as SlabNode + // The plate follows the ground again (the store derives its top), its block gone. + expect(Object.hasOwn(lowered, 'floorHeight') && lowered.floorHeight === undefined).toBe(true) + expect(lowered.foundation?.type).toBe('none') + expect(floorPlateAtGroundContact(after, lowered)).toBe(true) + // Seated as the house that was never raised. + const fresh = buildBuildingFoundationChanges({ + nodes: house(), + buildingId: 'building_a', + patch: { enabled: true }, + })! + expect(built.moveBy).toBeCloseTo(fresh.moveBy, 6) + expect(built.moveBy).toBeGreaterThan(0) + }) + +test('a ground floor moved down with its house stays one while its top stands on the ground', () => { + const nodes = house() + const building = nodes['building_a' as AnyNodeId] as BuildingNode + nodes[building.id] = { ...building, position: [0, -0.4, 0] } as BuildingNode + const plate = nodes['slab_g' as AnyNodeId] as SlabNode + // Raised 0.5 m on its own foundation, its top 0.15 m over the datum: on the ground. + const raised = { ...plate, elevation: 0.55, floorHeight: 0.55, foundation: { type: 'solid' } } + expect( + floorPlateAtGroundContact({ ...nodes, [plate.id]: raised as SlabNode }, raised as SlabNode), + ).toBe(true) + // On the plane, its top 0.35 m under the ground: a basement floor. + expect(floorPlateAtGroundContact(nodes, plate)).toBe(false) +}) + +test('a slab seats its top 8 in over grade; switching off leaves the building where it stands', () => { + const nodes = house() + const slab = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true, type: 'slab' }, + })! + const seated = apply(nodes, slab.update) + expect(levelFloorWorldY(seated, 'level_g')! - FLAT_GROUND_Y).toBeCloseTo(8 * IN, 6) + const off = buildBuildingFoundationChanges({ + nodes: seated, + buildingId: 'building_a', + patch: { enabled: false }, + })! + expect(off.moveBy).toBe(0) + const after = apply(seated, off.update) + expect( + buildingFoundationRecordOf(after['building_a' as AnyNodeId] as BuildingNode)?.enabled, + ).toBe(false) + expect(levelFloorWorldY(after, 'level_g')).toBeCloseTo(levelFloorWorldY(seated, 'level_g')!, 9) +}) + +test('a taller finish floor raises the house, the stem wall growing under it', () => { + const nodes = house() + const built = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true, ffAboveGradeIn: 30 }, + })! + const after = apply(nodes, built.update) + expect(levelFloorWorldY(after, 'level_g')! - FLAT_GROUND_Y).toBeCloseTo(30 * IN, 6) +}) + +test('a house the ground stands over is seated on it when the foundation goes on', () => { + const nodes = house() + const site = nodes['site_a' as AnyNodeId]! + nodes[site.id] = { ...site, terrain: raisedGround(1) } as AnyNode + const built = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true }, + })! + const after = apply(nodes, built.update) + expect(levelFloorWorldY(after, 'level_g')! - 1).toBeCloseTo(22.125 * IN, 6) + expect(designedFoundationReport(after, 'building_a')).not.toBeNull() +}) + +test('a basement under the ground floor keeps its own walls as the foundation walls; the house is seated with their top 8 in over grade', () => { + const nodes = house() + const building = nodes['building_a' as AnyNodeId] as BuildingNode + const upper = LevelNode.parse({ id: 'level_up', parentId: 'building_a', level: 1 }) + nodes[upper.id] = upper + nodes['level_g' as AnyNodeId] = { ...nodes['level_g' as AnyNodeId], height: 3 } as LevelNode + // The ground floor's plane a little over the ground, the basement's floor 2.85 m under it. + nodes[building.id] = { + ...building, + position: [0, -2.9, 0], + children: ['level_g', upper.id], + } as BuildingNode + const built = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { enabled: true }, + })! + // The ground floor bears on the basement walls under its plane (it has no plate), by the sill. + expect(built.moveBy).toBeCloseTo(FLAT_GROUND_Y + 8 * IN - (0.1 - 1.5 * IN), 6) + const report = designedFoundationReport(apply(nodes, built.update), 'building_a')! + expect(report.levelId).toBe('level_g') + expect(report.basement?.ownWalls).toBe(true) + expect(report.basement!.wallTopY - report.highestGradeY).toBeCloseTo(8 * IN, 6) +}) + +test('a floor at its least follows the stem top down as well as up; a floor set higher stays', () => { + let nodes = house() + const set = (patch: Parameters[0]['patch']) => { + const built = buildBuildingFoundationChanges({ nodes, buildingId: 'building_a', patch })! + nodes = apply(nodes, built.update) + return built.record + } + set({ enabled: true }) + expect(set({ topAboveGradeIn: 24 }).ffAboveGradeIn).toBe(38.125) + expect(set({ topAboveGradeIn: 8 }).ffAboveGradeIn).toBe(22.125) + expect(levelFloorWorldY(nodes, 'level_g')! - FLAT_GROUND_Y).toBeCloseTo(22.125 * IN, 6) + // Shallower framing keeps the stem top 8 in over grade. + expect(set({ floorFramingDepthIn: 9.25 }).ffAboveGradeIn).toBe(17.25) + // A finish floor set by hand outlives a change of type. + set({ ffAboveGradeIn: 30 }) + expect(set({ type: 'slab' }).ffAboveGradeIn).toBe(30) + expect(set({ type: 'raised' }).ffAboveGradeIn).toBe(30) +}) diff --git a/packages/core/src/building/foundation.ts b/packages/core/src/building/foundation.ts new file mode 100644 index 0000000000..5efa645b3d --- /dev/null +++ b/packages/core/src/building/foundation.ts @@ -0,0 +1,258 @@ +import { setFloorFoundation } from '../commands/structure/set-floor-foundation' +import { + type BuildingFoundationRecord, + type BuildingFoundationType, + buildingFoundationRecordOf, + buildingStoreyCount, + resolveBuildingFoundation, +} from '../lib/building-foundation' +import { + automaticFloorHeight, + floorPlateAtGroundContact, + groundFloorConstruction, +} from '../lib/floor-foundation-datum' +import { designedFoundationReport } from '../lib/generic-foundation' +import { isSiteFlatwork } from '../lib/site-flatwork' +import { slabRoomDoorChanges, slabRoomElevation } from '../lib/slab-room' +import type { AnyNode, AnyNodeId, BuildingNode, SlabNode } from '../schema' +import { getLevelElevations, levelFloorWorldY } from '../services/storey' + +const INCH = 0.0254 +const round = (value: number) => Math.round(value * 1e6) / 1e6 + +/** + * The Foundation panel's change: `patch` merged into the building's foundation record, the finish + * floor never under the least its type allows (a floor at that least moves with it), and, with + * the record on, the building seated so its floor top stands `ffAboveGradeIn` over the highest + * grade under its exterior walls (the stem wall grows down the slope from there); over a basement, + * so the basement walls' top stands `basementTopAboveGradeIn` over it (`designedBasement`). The + * designed foundation replaces the plates' own: a ground floor raised on one comes down onto the + * ground first. Flatwork on its levels (a driveway, a walk) stays on the ground. One change set, + * one undo step. `null` for an id that is no building. + */ +export function buildBuildingFoundationChanges({ + nodes, + buildingId, + patch, +}: { + nodes: Record + buildingId: string + patch: BuildingFoundationRecord +}) { + const building = nodes[buildingId as AnyNodeId] + if (building?.type !== 'building') return null + const stored = buildingFoundationRecordOf(building) + const storeys = buildingStoreyCount(nodes, buildingId) + const merged: BuildingFoundationRecord = { ...stored, ...patch } + // A floor at its type's least follows that least (a new type, the stem top or the framing + // moved, up or down); a floor set higher stays where it was set. + const before = resolveBuildingFoundation(stored, storeys) + if (patch.ffAboveGradeIn === undefined && before.ffAboveGradeIn - before.ffMinIn < 1e-6) + delete merged.ffAboveGradeIn + const resolved = resolveBuildingFoundation(merged, storeys) + const record: BuildingFoundationRecord = { + ...merged, + type: resolved.type, + ffAboveGradeIn: resolved.ffAboveGradeIn, + } + const metadata = { ...(building.metadata ?? {}), foundation: record } + const recorded = { + ...nodes, + [building.id]: { ...building, metadata }, + } as Record + // A house over a basement is seated whenever its record is on, said so or left on by default. + const seated = + record.enabled === true || + (resolved.enabled && designedFoundationReport(recorded, building.id)?.basement !== undefined) + const lowered = seated ? lowerOwnFoundations(recorded, building.id) : [] + const written = { ...recorded } + for (const { id, data } of lowered) written[id] = { ...written[id], ...data } as AnyNode + const moveBy = seated ? (designedSeatMove(written, building.id) ?? 0) : 0 + const [x, y, z] = (building as BuildingNode).position + const flatwork = Math.abs(moveBy) > 1e-6 ? flatworkOf(written, building.id) : [] + const moved = { + ...written, + [building.id]: { ...written[building.id as AnyNodeId], position: [x, round(y + moveBy), z] }, + } as Record + const rooms = slabRoomChanges(moved, building.id, before.type, resolved.type) + return { + buildingId: building.id, + record, + moveBy, + update: [ + { + id: building.id as AnyNodeId, + data: { + metadata, + ...(Math.abs(moveBy) > 1e-6 ? { position: [x, round(y + moveBy), z] } : {}), + } as Partial, + }, + ...lowered.filter((change) => !flatwork.some((slab) => slab.id === change.id)), + ...flatwork.map((slab) => ({ + id: slab.id as AnyNodeId, + data: { + ...lowered.find((change) => change.id === slab.id)?.data, + elevation: round((slab as { elevation: number }).elevation - moveBy), + } as Partial, + })), + ...rooms, + ], + } +} + +/** + * The building's rooms on a slab of their own (`floor.foundation: 'slab'`, a garage) kept on + * grade as the house is seated: in a raised house each one's floor at its slab's height over the + * ground (`slabRoomElevation`, in the scene as seated); a house no longer raised takes them back + * onto its own floor. + */ +function slabRoomChanges( + nodes: Record, + buildingId: string, + was: BuildingFoundationType, + type: BuildingFoundationType, +): Array<{ id: AnyNodeId; data: Partial }> { + if (was !== 'raised' && type !== 'raised') return [] + const elevations = getLevelElevations(nodes) + const changes: Array<{ id: AnyNodeId; data: Partial }> = [] + for (const zone of Object.values(nodes)) { + if (zone.type !== 'zone' || zone.floor?.foundation !== 'slab') continue + if (elevations.get(zone.parentId as AnyNodeId)?.buildingId !== buildingId) continue + const { elevation: _was, ...floor } = zone.floor + const elevation = type === 'raised' ? slabRoomElevation(nodes, zone) : null + if (elevation === (zone.floor.elevation ?? null)) continue + changes.push({ + id: zone.id as AnyNodeId, + data: { floor: elevation === null ? floor : { ...floor, elevation } } as Partial, + }) + changes.push(...slabRoomDoorChanges(nodes, zone, zone.floor.elevation ?? 0, elevation ?? 0)) + } + return changes +} + +/** + * How far the building moves to stand on its designed foundation as the record asks: its ground + * floor's top `ffAboveGradeIn` over the highest grade along the foundation, or over a basement the + * basement walls' top `basementTopAboveGradeIn` over it (`seatMove`). `null` where no foundation + * is designed under it (its record off, no closed outline). + */ +export function designedSeatMove( + nodes: Record, + buildingId: string, +): number | null { + const report = designedFoundationReport(nodes, buildingId) + if (!report) return null + const { foundation } = report + return report.basement + ? seatMove( + nodes, + buildingId, + (moved) => designedFoundationReport(moved, buildingId)?.basement?.wallTopY ?? null, + report.highestGradeY + foundation.basementTopAboveGradeIn * INCH, + ) + : seatMove( + nodes, + buildingId, + (moved) => levelFloorWorldY(moved, report.levelId), + report.highestGradeY + foundation.ffAboveGradeIn * INCH, + ) +} + +/** + * The changes that move the building `moveBy` metres in height to its seat, its flatwork (a + * driveway, a walk) staying on the ground. Empty for no move. + */ +export function buildSeatChanges( + nodes: Record, + buildingId: string, + moveBy: number, +): Array<{ id: AnyNodeId; data: Partial }> { + const building = nodes[buildingId as AnyNodeId] + if (building?.type !== 'building' || Math.abs(moveBy) <= 1e-6) return [] + const [x, y, z] = building.position + return [ + { id: building.id as AnyNodeId, data: { position: [x, round(y + moveBy), z] } }, + ...flatworkOf(nodes, buildingId).map((slab) => ({ + id: slab.id as AnyNodeId, + data: { elevation: round(slab.elevation - moveBy) } as Partial, + })), + ] +} + +/** The building's flatwork: slabs on its levels that lie on the ground, not on its foundation. */ +function flatworkOf(nodes: Record, buildingId: string): SlabNode[] { + const elevations = getLevelElevations(nodes) + return Object.values(nodes).filter( + (node): node is SlabNode => + node.type === 'slab' && + isSiteFlatwork(node) && + elevations.get(node.parentId as AnyNodeId)?.buildingId === buildingId, + ) +} + +/** + * The changes that bring the ground floor the designed foundation stands under down off its own + * "raised on a foundation" height, or the seat would lower the house by it and leave the plate's + * block standing on the designed foundation. Empty where nothing is raised, no foundation is + * designed (open outside walls), the house stands on a basement, or the floor can't move. + */ +function lowerOwnFoundations(nodes: Record, buildingId: string) { + const report = designedFoundationReport(nodes, buildingId) + if (!report || report.basement) return [] + const raised = Object.values(nodes).filter( + (node): node is SlabNode => + node.type === 'slab' && + node.plateRole === 'base' && + node.parentId === report.levelId && + floorPlateAtGroundContact(nodes, node) && + groundFloorConstruction(nodes, node).foundationHeight > 0, + ) + if (!raised.length) return [] + const plan = setFloorFoundation(nodes, { + slabIds: raised.map((plate) => plate.id), + patch: { foundationHeight: 0 }, + sameConstruction: true, + }) + return plan.changes.flatMap((change) => + change.op === 'update' ? [{ id: change.id as AnyNodeId, data: change.data }] : [], + ) +} + +/** + * How far the building moves for what `seat` measures (its ground floor's top, a basement's walls' + * top) to stand at `targetY`. A floor that follows the sculpted ground while its storey stands at + * the datum stops following it once the move lifts the storey off: the move is measured on the + * floor as it will stand (each ground plate at its automatic height in the moved scene), or the + * first seat on a slope buries the house by the rise the floor had taken from the ground. + */ +function seatMove( + nodes: Record, + buildingId: string, + seat: (moved: Record) => number | null, + targetY: number, +) { + const building = nodes[buildingId as AnyNodeId] as BuildingNode + const [x, y, z] = building.position + const elevations = getLevelElevations(nodes) + const plates = Object.values(nodes).filter( + (node): node is SlabNode => + node.type === 'slab' && + node.plateRole === 'base' && + node.floorHeight === undefined && + elevations.get(node.parentId as AnyNodeId)?.buildingId === buildingId, + ) + let moveBy = 0 + for (let pass = 0; pass < 3; pass++) { + const moved: Record = { + ...nodes, + [building.id]: { ...building, position: [x, y + moveBy, z] }, + } + for (const plate of plates) + if (floorPlateAtGroundContact(moved, plate)) + moved[plate.id] = { ...plate, elevation: automaticFloorHeight(moved, plate) } + const at = seat(moved) + if (at === null || Math.abs(targetY - at) < 1e-6) break + moveBy += targetY - at + } + return round(moveBy) +} diff --git a/packages/core/src/building/ground-level.ts b/packages/core/src/building/ground-level.ts index 9b5cdeb707..28ee104759 100644 --- a/packages/core/src/building/ground-level.ts +++ b/packages/core/src/building/ground-level.ts @@ -1,12 +1,15 @@ import { groundSeat } from '../lib/ground-seat' import type { AnyNode, AnyNodeId, BuildingNode, LevelNode } from '../schema' import { getLevelElevations } from '../services/storey' +import { designedSeatMove } from './foundation' /** * Making `levelId` the building's ground floor, as the level panel's "Set as ground floor" and the * agent's `set_ground_level` do: the building names it (`groundLevelId`), is seated so that level's * floor datum, the top of the foundation, stands over grade as the building's foundation - * construction seats it (`GroundSeat.aboveGrade`: 0.203 m on block, by region and style); its + * construction seats it (`GroundSeat.aboveGrade`: 0.203 m on block, by region and style), or as + * its designed foundation's own rule does (`GroundSeat.rule`: a slab's or a raised floor's top + * `ffAboveGradeIn`, a basement's walls' top `basementTopAboveGradeIn`, over the highest grade); its * position moves in height, the plan stays where it is, and the * floor indices are renumbered so it is index 0 and the ones under it are the basements. Every * floor keeps its place relative to the others. One change set, one undo step. `null` for an id @@ -35,8 +38,14 @@ export function buildGroundLevelChanges({ if (!seat) return null const [x, y, z] = (building as BuildingNode).position - // Micrometres: the seat is measured through sums of storey heights, and a stored height keeps no noise. - const moveBy = seat.atFoundationTop ? 0 : Math.round((seat.aboveGrade - seat.offset) * 1e6) / 1e6 + // Micrometres: the seat is measured through sums of storey heights, and a stored height keeps no + // noise. A designed foundation's seat is the one its panel makes (`designedSeatMove`). + const moveBy = seat.atFoundationTop + ? 0 + : Math.round( + ((seat.rule === 'construction' ? null : designedSeatMove(asNamed, building.id)) ?? + seat.aboveGrade - seat.offset) * 1e6, + ) / 1e6 const shift = level.level const renumbered = Object.values(nodes).filter( (node): node is LevelNode => diff --git a/packages/core/src/building/index.ts b/packages/core/src/building/index.ts index ef0afc77ad..54ec9e3ad1 100644 --- a/packages/core/src/building/index.ts +++ b/packages/core/src/building/index.ts @@ -1,9 +1,12 @@ export * from './corner-window' export * from './door-types' export * from './floor-item-fit' +export { buildBuildingFoundationChanges } from './foundation' export { buildGroundLevelChanges } from './ground-level' export * from './level-duplication' export { buildLevelBelowChanges } from './level-insertion' +export { buildLevelRemovalChanges } from './level-removal' +export { buildExteriorWallCopy, levelExteriorWalls } from './level-wall-copy' export * from './merge-windows' export * from './opening-style-presets' export { type RgbaImage, warpImage } from './photo-elevation' diff --git a/packages/core/src/building/level-insertion.test.ts b/packages/core/src/building/level-insertion.test.ts index b86f555282..e224f189a3 100644 --- a/packages/core/src/building/level-insertion.test.ts +++ b/packages/core/src/building/level-insertion.test.ts @@ -1,8 +1,16 @@ import { describe, expect, test } from 'bun:test' import { levelGradeStanding } from '../lib/grade-standing' -import { type AnyNode, type AnyNodeId, BuildingNode, LevelNode } from '../schema' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + DEFAULT_UPPER_FLOOR_THICKNESS, + LevelNode, + SlabNode, +} from '../schema' import { levelFloorWorldY } from '../services/storey' import { buildLevelBelowChanges } from './level-insertion' +import { buildLevelRemovalChanges } from './level-removal' type Nodes = Record @@ -113,4 +121,153 @@ describe('buildLevelBelowChanges', () => { { id: expect.any(String) as unknown as AnyNodeId, data: { baseElevation: 0 } }, ]) }) + + test('the floor over a new basement is framed: its top held, the framing hanging into the basement, which is that much taller', () => { + const { nodes, building } = house([[0, 0, 2.5]]) + const ground = Object.values(nodes).find((node) => node.type === 'level') as LevelNode + const plate = SlabNode.parse({ + parentId: ground.id, + plateRole: 'base', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + }) + nodes[plate.id] = plate + nodes[ground.id] = { ...ground, children: [plate.id] } + const next = apply(nodes, building, 2.5) + const framed = next.nodes[plate.id] as SlabNode + expect(framed.thickness).toBeCloseTo(DEFAULT_UPPER_FLOOR_THICKNESS, 9) + expect(framed.elevation).toBe(plate.elevation) + const hang = DEFAULT_UPPER_FLOOR_THICKNESS - plate.thickness + const basement = next.nodes[next.id] as LevelNode + expect(basement.height).toBeCloseTo(2.5 + hang, 6) + // The ground floor stays where it stood; the basement's rooms keep 2.5 m under the joists. + expect(levelFloorWorldY(next.nodes as never, ground.id)).toBeCloseTo(0.05, 6) + const underJoists = plate.elevation - DEFAULT_UPPER_FLOOR_THICKNESS + expect(underJoists - (basement.baseElevation ?? 0)).toBeCloseTo(2.5, 6) + }) +}) + +/** The deletion applied the way the editor does: the level and its contents go, then the updates. */ +function remove(nodes: Nodes, levelId: string): Nodes { + const update = buildLevelRemovalChanges({ nodes: nodes as never, levelId }) + const next: Nodes = { ...nodes } + const level = next[levelId] as LevelNode + for (const child of level.children) delete next[child] + delete next[levelId] + const building = next[level.parentId!] + if (building?.type === 'building') + next[building.id] = { + ...building, + children: building.children.filter((id) => id !== levelId), + } as AnyNode + for (const { id, data } of update) next[id] = { ...next[id], ...data } as AnyNode + return next +} + +describe('buildLevelRemovalChanges', () => { + test('a basement deleted again puts the floor over it back on the ground, its top held', () => { + const { nodes, building } = house([[0, 0, 2.5]]) + const ground = Object.values(nodes).find((node) => node.type === 'level') as LevelNode + const plate = SlabNode.parse({ + parentId: ground.id, + plateRole: 'base', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + }) + nodes[plate.id] = plate + nodes[ground.id] = { ...ground, children: [plate.id] } + const added = apply(nodes, building, 2.5) + expect((added.nodes[plate.id] as SlabNode).buildUp).toBeDefined() + const removed = remove(added.nodes, added.id) + const back = removed[plate.id] as SlabNode + expect(back.buildUp).toBeUndefined() + expect(back.thickness).toBe(plate.thickness) + expect(back.elevation).toBe(plate.elevation) + expect(levelFloorWorldY(removed as never, ground.id)).toBeCloseTo(0.05, 6) + }) + + test('a basement added and deleted again leaves the house where it stood', () => { + const { nodes, building } = house([ + [0, 0, 3], + [1, 0, 3], + ]) + const added = apply(nodes, building, 2.5) + expect(buildLevelRemovalChanges({ nodes: added.nodes as never, levelId: added.id })).toEqual([]) + expect(floors(remove(added.nodes, added.id))).toEqual([ + [0, 0], + [1, 3], + ]) + }) + + test('a basement made taller after it was added: the floors over it stay put when it goes', () => { + const { nodes, building } = house([ + [0, 0, 3], + [1, 0, 3], + ]) + const added = apply(nodes, building, 2.5) + added.nodes[added.id] = { ...added.nodes[added.id], height: 2.7432 } as AnyNode + const before = floors(added.nodes).slice(1) + expect(floors(remove(added.nodes, added.id))).toEqual(before) + }) + + test('a deleted middle floor closes up: the floors over it come down by its height', () => { + const { nodes } = house([ + [0, 0, 3], + [1, 0, 3], + [2, 0, 3], + ]) + const middle = Object.values(nodes).find( + (node) => node.type === 'level' && node.level === 1, + ) as LevelNode + // The floor over it closes up its number too. + expect(floors(remove(nodes, middle.id))).toEqual([ + [0, 0], + [1, 3], + ]) + }) + + test('a basement deleted from over another: the one under it comes up into its place and takes its number', () => { + const { nodes } = house([ + [-2, -5, 2.5], + [-1, 0, 2.5], + [0, 0, 2.5], + ]) + const first = Object.values(nodes).find( + (node) => node.type === 'level' && node.level === -1, + ) as LevelNode + expect(floors(remove(nodes, first.id))).toEqual([ + [-1, -2.5], + [0, 0], + ]) + }) + + test('the floor the house was seated on deleted: the building comes down so the floor now at grade sits on its foundation', () => { + // A basement set as the ground floor stood the house a storey up; it goes. + const { nodes, building } = house([ + [-1, -2.5, 2.5], + [0, 0, 2.5], + ]) + const levels = Object.values(nodes).filter((node): node is LevelNode => node.type === 'level') + const basement = levels.find((level) => level.level === -1)! + const ground = levels.find((level) => level.level === 0)! + const seatedOn = (nodes[building] as BuildingNode).position + nodes[building] = { + ...nodes[building], + groundLevelId: basement.id, + position: [seatedOn[0], 2.5 + 0.203, seatedOn[2]], + } as AnyNode + const update = buildLevelRemovalChanges({ nodes: nodes as never, levelId: basement.id }) + const moved = update.find((entry) => entry.id === building) + expect(moved?.data).toMatchObject({ groundLevelId: ground.id }) + const after = remove(nodes, basement.id) + expect(levelFloorWorldY(after as never, ground.id)).toBeCloseTo(0.203, 3) + }) }) diff --git a/packages/core/src/building/level-insertion.ts b/packages/core/src/building/level-insertion.ts index 467b4c2503..00e13202bb 100644 --- a/packages/core/src/building/level-insertion.ts +++ b/packages/core/src/building/level-insertion.ts @@ -1,13 +1,38 @@ -import { type AnyNode, type AnyNodeId, LevelNode } from '../schema' +import { DEFAULT_FLOOR_BUILD_UP, floorBuildUpPatch } from '../lib/build-up' +import { designedFoundationReport } from '../lib/generic-foundation' +import { isSiteFlatwork } from '../lib/site-flatwork' +import { + type AnyNode, + type AnyNodeId, + DEFAULT_UPPER_FLOOR_THICKNESS, + LevelNode, + type SlabNode, +} from '../schema' import { getAuthoredLevelElevations } from '../services/storey' +import { buildSeatChanges, designedSeatMove } from './foundation' + +/** A floor of the house the level stands on: not flatwork (a drive) and not a recess (a pool). */ +export const isHouseFloor = (node: AnyNode | undefined): node is SlabNode => + node?.type === 'slab' && + node.plateRole === 'base' && + node.recessed !== true && + !isSiteFlatwork(node) && + node.polygon.length >= 3 /** - * A new floor under the building's lowest, as the editor's "+" below makes it, without lifting the - * building. Levels stack from the lowest one's own `baseElevation`, so a new lowest floor left at 0 + * A new floor under the building's lowest, as the editor's "+" below makes it. Levels stack from + * the lowest one's own `baseElevation`, so a new lowest floor left at 0 * would push every floor above it up by its height: a basement added to a house stood the house on * the ground a storey too high (and a second basement, two storeys). The new floor takes the offset * that puts it one storey under where the lowest floor stood, and the old lowest floor gives its own - * offset up, so every existing floor keeps its place. One change set, one undo step. + * offset up, so every existing floor keeps its place. The floor over the new one is framed now: the + * old lowest floor's plates take the default framed floor's layers (`DEFAULT_FLOOR_BUILD_UP`: + * flooring, sheathing, joists, the ceiling under them) and its thickness, their tops held, so the + * framing hangs into the new storey, as an upper floor's does over the rooms under it; the new + * storey is that much taller, its rooms keeping `height` under the joists. Where the building's + * designed foundation then stands it on the basement, the house is seated on it, its walls' top + * `basementTopAboveGradeIn` over the highest grade (`basementSeat`); without one it stays where it + * stood. One change set, one undo step. */ export function buildLevelBelowChanges({ nodes, @@ -30,19 +55,60 @@ export function buildLevelBelowChanges({ const standsAt = lowest ? (getAuthoredLevelElevations(nodes).get(lowest.id)?.baseY ?? lowest.baseElevation) : 0 + const framed = (lowest?.children ?? []).flatMap((id) => { + const plate = nodes[id] + return isHouseFloor(plate) && plate.thickness < DEFAULT_UPPER_FLOOR_THICKNESS - 1e-6 + ? [plate] + : [] + }) + const hang = Math.max( + 0, + ...framed.map((plate) => DEFAULT_UPPER_FLOOR_THICKNESS - plate.thickness), + ) + const round = (value: number) => Math.round(value * 1e6) / 1e6 const newLevel = LevelNode.parse({ level: (lowest?.level ?? 1) - 1, - height, + height: round(height + hang), children: [], parentId: buildingId, - baseElevation: lowest ? standsAt - height : 0, + baseElevation: lowest ? round(standsAt - height - hang) : 0, }) + const update = [ + ...(lowest && lowest.baseElevation !== 0 + ? [{ id: lowest.id as AnyNodeId, data: { baseElevation: 0 } as Partial }] + : []), + ...framed.map((plate) => ({ + id: plate.id as AnyNodeId, + data: floorBuildUpPatch(DEFAULT_FLOOR_BUILD_UP) as Partial, + })), + ] return { newLevel, create: [{ node: newLevel as AnyNode, parentId: buildingId as AnyNodeId }], - update: - lowest && lowest.baseElevation !== 0 - ? [{ id: lowest.id as AnyNodeId, data: { baseElevation: 0 } as Partial }] - : [], + update: [...update, ...basementSeat(nodes, buildingId, newLevel, update)], + } +} + +/** + * The house seated on the basement just added, where its designed foundation stands it on one: the + * basement walls' top `basementTopAboveGradeIn` over the highest grade along them + * (`designedSeatMove`), measured on the floors as the insertion leaves them. Empty where nothing is + * designed (the record off, no closed outline) or the house stands there already. + */ +function basementSeat( + nodes: Readonly>, + buildingId: string, + newLevel: LevelNode, + update: Array<{ id: AnyNodeId; data: Partial }>, +) { + const building = nodes[buildingId] + if (building?.type !== 'building') return [] + const after: Record = { + ...(nodes as Record), + [newLevel.id]: newLevel as AnyNode, + [building.id]: { ...building, children: [...building.children, newLevel.id] } as AnyNode, } + for (const { id, data } of update) after[id] = { ...after[id], ...data } as AnyNode + if (!designedFoundationReport(after, buildingId)?.basement) return [] + return buildSeatChanges(after, buildingId, designedSeatMove(after, buildingId) ?? 0) } diff --git a/packages/core/src/building/level-removal.ts b/packages/core/src/building/level-removal.ts new file mode 100644 index 0000000000..73cad2cff9 --- /dev/null +++ b/packages/core/src/building/level-removal.ts @@ -0,0 +1,127 @@ +import { groundSeat } from '../lib/ground-seat' +import { + type AnyNode, + type AnyNodeId, + type BuildingNode, + type LevelNode, + SlabNode, +} from '../schema' +import { getAuthoredLevelElevations, getStoredLevelHeight } from '../services/storey' +import { buildGroundLevelChanges } from './ground-level' +import { isHouseFloor } from './level-insertion' + +/** A floor plate on the ground as the schema makes one. */ +const GROUND_PLATE_THICKNESS = SlabNode.shape.thickness.parse(undefined) + +type Update = { id: AnyNodeId; data: Partial } + +/** `nodes` without `levelId` and everything on it, its building no longer listing it. */ +function withoutLevel(nodes: Record, level: LevelNode) { + const next = { ...nodes } + const drop = (id: AnyNodeId) => { + const node = next[id] + if (!node) return + if ('children' in node && Array.isArray(node.children)) + for (const child of node.children) drop(child as AnyNodeId) + delete next[id] + } + drop(level.id as AnyNodeId) + const building = level.parentId ? next[level.parentId as AnyNodeId] : undefined + if (building?.type === 'building') + next[building.id as AnyNodeId] = { + ...building, + children: building.children.filter((id) => id !== level.id), + } as AnyNode + return next +} + +/** + * What deleting a level changes beside it, as the level menu's "Delete level" does, for one undo + * step with the deletion: the building stands as it did. Taking out the lowest floor (a basement) + * leaves the floors over it where they stand, the next one taking the offset the stack stood on + * (levels stack from the lowest one's own `baseElevation`), and its floor, framed over the basement, + * is a plate on the ground again, its top held; a basement deleted from over another lets the one + * under it come up into its place. A ground floor seated on its + * foundation stays seated: where the deletion takes the floor the building stood on out from under + * it (the level named the ground floor, a basement set as one), the building comes down so the + * floor now at grade sits on the foundation. The floor numbers close up on the deleted one's side + * of the ground floor, which stays 0. Empty for an id that is no level of a building. + */ +export function buildLevelRemovalChanges({ + nodes, + levelId, +}: { + nodes: Record + levelId: string +}): Update[] { + const level = nodes[levelId as AnyNodeId] + if (level?.type !== 'level') return [] + const building = level.parentId ? nodes[level.parentId as AnyNodeId] : undefined + if (building?.type !== 'building') return [] + const levels = (building as BuildingNode).children.flatMap((id) => { + const node = nodes[id as AnyNodeId] + return node?.type === 'level' && node.id !== level.id ? [node] : [] + }) + if (levels.length === 0) return [] + + const updates: Update[] = [] + const merge = ({ id, data }: Update) => { + const index = updates.findIndex((entry) => entry.id === id) + if (index === -1) updates.push({ id, data }) + else + updates[index] = { + id, + data: { ...updates[index]!.data, ...data } as Partial, + } + } + // The floors stay numbered in a row from the ground floor's 0: those past the deleted one close + // up towards it. + for (const other of levels) { + const past = level.level > 0 ? other.level > level.level : other.level < level.level + if (level.level !== 0 && past) + merge({ + id: other.id as AnyNodeId, + data: { level: other.level + (level.level > 0 ? -1 : 1) } as Partial, + }) + } + const lowest = levels.reduce((low, next) => (next.level < low.level ? next : low)) + if (level.level < lowest.level) { + // The next floor up stood on the deleted one: it takes over the offset the stack stood on. + const elevations = getAuthoredLevelElevations(nodes) + const standsAt = elevations.get(lowest.id)?.baseY + if (standsAt !== undefined && Math.abs(standsAt - (lowest.baseElevation ?? 0)) > 1e-6) + merge({ + id: lowest.id as AnyNodeId, + data: { baseElevation: Math.round(standsAt * 1e6) / 1e6 } as Partial, + }) + for (const id of lowest.children) { + const plate = nodes[id as AnyNodeId] + if (isHouseFloor(plate) && plate.buildUp) + merge({ + id: plate.id as AnyNodeId, + data: { buildUp: undefined, thickness: GROUND_PLATE_THICKNESS } as Partial, + }) + } + } else if (level.level < 0) { + // A basement over another: the floors under it come up into its place, the stack starting + // that much higher, so the ground floor and everything over it stay where they stand. + const rise = getStoredLevelHeight(level) + (level.baseElevation ?? 0) + if (Math.abs(rise) > 1e-6) + merge({ + id: lowest.id as AnyNodeId, + data: { + baseElevation: Math.round(((lowest.baseElevation ?? 0) + rise) * 1e6) / 1e6, + } as Partial, + }) + } + + const before = groundSeat(nodes, building.id) + const after = withoutLevel(nodes, level) + for (const { id, data } of updates) after[id] = { ...after[id], ...data } as AnyNode + const seat = groundSeat(after, building.id) + if (!(before?.seated && seat && !seat.seated)) return updates + const reseat = buildGroundLevelChanges({ nodes: after, levelId: seat.levelId }) + if (!reseat || reseat.unchanged) return updates + for (const update of reseat.update) merge(update) + return updates +} diff --git a/packages/core/src/building/level-wall-copy.test.ts b/packages/core/src/building/level-wall-copy.test.ts new file mode 100644 index 0000000000..fb242f6b71 --- /dev/null +++ b/packages/core/src/building/level-wall-copy.test.ts @@ -0,0 +1,187 @@ +import { expect, test } from 'bun:test' +import { createZone } from '../commands/structure/create-zone' +import { structureChangeBatch } from '../commands/structure/shared' +import { FOUNDATION_CONSTRUCTIONS } from '../lib/foundation-construction' +import { foundationWallConstruction } from '../lib/foundation-wall' +import { initSpaceDetectionSync } from '../lib/space-detection' +import { + type AnyNode, + BuildingNode, + generateId, + LevelNode, + type WallNode, + WallNode as WallSchema, + WindowNode, +} from '../schema' +import useScene, { clearSceneHistory } from '../store/use-scene' +import { WALL_ASSEMBLY_PRESETS, wallAssemblyPatch } from '../systems/wall/wall-assembly' +import { buildExteriorWallCopy } from './level-wall-copy' + +test('a new floor copies the exterior walls below in place, and its rooms come from them', async () => { + const previousFrame = globalThis.requestAnimationFrame + const previousCancel = globalThis.cancelAnimationFrame + // A frame that runs in a microtask: a callback run inside the call would let the store + // store the frame id after the flush cleared it, and a later test would cancel that id. + globalThis.requestAnimationFrame = ((callback: FrameRequestCallback) => { + queueMicrotask(() => callback(0)) + return 1 + }) as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = () => {} + const building = BuildingNode.parse({ id: 'building_copy', children: ['level_copy_0'] }) + const ground = LevelNode.parse({ + id: 'level_copy_0', + parentId: building.id, + level: 0, + height: 2.8, + }) + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + readOnly: false, + } as never) + clearSceneHistory() + const stop = initSpaceDetectionSync(useScene, { + getState: () => ({ spaces: {}, setSpaces: () => {} }), + }) + const settle = async () => { + for (let index = 0; index < 6; index++) await Promise.resolve() + } + const nodes = () => useScene.getState().nodes + const walls = (levelId: string) => + Object.values(nodes()).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === levelId, + ) + try { + const room = createZone(nodes(), { + levelId: ground.id, + polygon: [ + [0, 0], + [8, 0], + [8, 5], + [0, 5], + ], + enclose: true, + mintId: generateId, + }) + useScene.getState().applyNodeChanges(structureChangeBatch(room.changes)) + await settle() + const partition = WallSchema.parse({ parentId: ground.id, start: [4, 0], end: [4, 5] }) + const front = walls(ground.id).find((wall) => wall.start[1] === 0 && wall.end[1] === 0)! + const window = WindowNode.parse({ parentId: front.id, position: [2, 1.2, 0] }) + useScene.getState().applyNodeChanges({ + create: [ + { node: partition, parentId: ground.id }, + { node: window, parentId: front.id }, + ], + }) + await settle() + const upper = LevelNode.parse({ + id: 'level_copy_1', + parentId: building.id, + level: 1, + height: 2.8, + }) + useScene.getState().applyNodeChanges({ create: [{ node: upper, parentId: building.id }] }) + await settle() + + const create = buildExteriorWallCopy({ + nodes: nodes(), + fromLevelId: ground.id, + toLevelId: upper.id, + }) + expect(create).toHaveLength(4) + const before = useScene.temporal.getState().pastStates.length + useScene.getState().applyNodeChanges({ create }) + await settle() + expect(useScene.temporal.getState().pastStates.length).toBe(before + 1) + + const copied = walls(upper.id) + expect(copied).toHaveLength(4) + expect(copied.every((wall) => wall.children.length === 0 && wall.height === undefined)).toBe( + true, + ) + const key = (wall: WallNode) => JSON.stringify([...[wall.start, wall.end].sort()]) + expect(copied.map(key).sort()).toEqual( + walls(ground.id) + .filter((wall) => wall.id !== partition.id) + .map(key) + .sort(), + ) + const rooms = Object.values(nodes()).filter( + (node) => node.type === 'zone' && node.parentId === upper.id && node.spaceRole === 'room', + ) + expect(rooms).toHaveLength(1) + } finally { + stop() + clearSceneHistory() + useScene.getState().unloadScene() + globalThis.requestAnimationFrame = previousFrame + globalThis.cancelAnimationFrame = previousCancel + } +}) + +test('foundation walls copied up from a basement take the exterior build given, walls copied down get the foundation', () => { + const square: [number, number][] = [ + [-4, -4], + [4, -4], + [4, 4], + [-4, 4], + ] + const building = BuildingNode.parse({ + id: 'building_basement_copy', + children: ['level_basement_copy', 'level_main_copy'], + }) + const basement = LevelNode.parse({ + id: 'level_basement_copy', + parentId: building.id, + level: -1, + baseElevation: -2.5, + height: 2.5, + }) + const main = LevelNode.parse({ + id: 'level_main_copy', + parentId: building.id, + level: 0, + height: 2.5, + }) + const nodes: Record = Object.fromEntries( + [building, basement, main].map((node) => [node.id, node]), + ) + const walls = (levelId: string, construction: Partial) => + square.map((start, at) => + WallSchema.parse({ + id: `wall_${levelId}_${at}`, + parentId: levelId, + start, + end: square[(at + 1) % 4], + ...construction, + }), + ) + const foundation = foundationWallConstruction(Object.values(FOUNDATION_CONSTRUCTIONS)[0]!) + for (const wall of walls(basement.id, foundation)) nodes[wall.id] = wall + const siding = wallAssemblyPatch(structuredClone(WALL_ASSEMBLY_PRESETS[0]!.assembly)) + const up = buildExteriorWallCopy({ + nodes, + fromLevelId: basement.id, + toLevelId: main.id, + exteriorConstruction: siding, + }) + expect(up).toHaveLength(4) + for (const { node } of up) { + expect(node.thickness).toBeCloseTo(siding.thickness!, 6) + expect(node.assembly).toEqual(siding.assembly) + } + const above: Record = Object.fromEntries( + [building, basement, main].map((node) => [node.id, node]), + ) + for (const wall of walls(main.id, siding)) above[wall.id] = wall + const down = buildExteriorWallCopy({ + nodes: above, + fromLevelId: main.id, + toLevelId: basement.id, + exteriorConstruction: siding, + }) + expect(down).toHaveLength(4) + for (const { node } of down) expect(node.assembly).not.toEqual(siding.assembly) +}) diff --git a/packages/core/src/building/level-wall-copy.ts b/packages/core/src/building/level-wall-copy.ts new file mode 100644 index 0000000000..f56710c480 --- /dev/null +++ b/packages/core/src/building/level-wall-copy.ts @@ -0,0 +1,99 @@ +import { exteriorWallLoops } from '../lib/exterior-wall-loops' +import { levelHoldsFoundationWalls, withFoundationConstruction } from '../lib/foundation-wall' +import { type AnyNode, type AnyNodeId, generateId, type LevelNode, WallNode } from '../schema' +import { wallFollowsStorey } from '../systems/wall/wall-top' + +/** The visible walls of `levelId` on its outline: the walls a closed circuit's outside runs along. */ +export function levelExteriorWalls(nodes: Record, levelId: string): WallNode[] { + const walls = Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && node.parentId === levelId && node.visible !== false, + ) + const outline = new Set( + exteriorWallLoops(walls).flatMap((loop) => loop.map((face) => face.wallId)), + ) + return walls.filter((wall) => outline.has(wall.id)) +} + +/** + * Copy a level's exterior walls, in place, onto another level of the building (a floor just added + * above or below it): the same plan positions, thickness, construction and finishes, so the new + * storey's outside lines up with the one it stands on. What belongs to the source storey stays + * there: its doors and windows, its support and terrain settings, and a height that only meant + * "as tall as this storey" (the copy follows its own storey). A copy between a basement and a floor + * over the ground takes the construction of where it lands: walls copied down to a basement get the + * foundation build, and foundation walls copied up take `exteriorConstruction` (the editor passes + * its current exterior wall type; without one they are plain walls). Rooms then come from the copied + * walls as from drawn ones. Returns the create entries for `applyNodeChanges`; empty when the + * source has no closed outline. + */ +export function buildExteriorWallCopy({ + nodes, + fromLevelId, + toLevelId, + mintId = () => generateId('wall'), + exteriorConstruction = {}, +}: { + nodes: Record + fromLevelId: string + toLevelId: string + mintId?: () => string + /** An above-grade exterior wall's build (assembly and thickness), for walls copied up from a basement. */ + exteriorConstruction?: Pick, 'assembly' | 'thickness'> +}): Array<{ node: WallNode; parentId: AnyNodeId }> { + const from = nodes[fromLevelId] + const to = nodes[toLevelId] + if (from?.type !== 'level' || to?.type !== 'level' || from.id === to.id) return [] + const graph = nodes as Record + const sourceFoundation = levelHoldsFoundationWalls(graph, fromLevelId) + const targetFoundation = levelHoldsFoundationWalls(graph, toLevelId) + const reconstruct = sourceFoundation !== targetFoundation + const storeyHeight = (from as LevelNode).height ?? null + const copies = levelExteriorWalls(nodes, fromLevelId).map((wall) => { + const { + id: _id, + children: _children, + parentId: _parentId, + metadata: _metadata, + supportSlabId: _support, + supportOffset: _offset, + fillToTerrain: _fill, + underpinning: _underpinning, + faceRegions: _regions, + height, + assembly, + thickness, + ...kept + } = wall + return WallNode.parse({ + ...kept, + id: mintId(), + parentId: toLevelId, + children: [], + ...(height !== undefined && !wallFollowsStorey(wall, storeyHeight) ? { height } : {}), + ...(!reconstruct + ? { assembly, thickness } + : targetFoundation + ? {} + : structuredClone( + Object.fromEntries( + Object.entries({ + assembly: exteriorConstruction.assembly, + thickness: exteriorConstruction.thickness, + }).filter(([, value]) => value !== undefined), + ), + )), + }) + }) + if (!copies.length) return [] + const levelWalls = [ + ...Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === toLevelId, + ), + ...copies, + ] + const built = targetFoundation + ? withFoundationConstruction(graph, toLevelId, levelWalls, copies, {}) + : copies + return built.map((node) => ({ node, parentId: toLevelId as AnyNodeId })) +} diff --git a/packages/core/src/building/merge-windows.ts b/packages/core/src/building/merge-windows.ts index 637b26e47b..c6d8859e05 100644 --- a/packages/core/src/building/merge-windows.ts +++ b/packages/core/src/building/merge-windows.ts @@ -40,6 +40,7 @@ const SHARED = [ 'sill', 'sillDepth', 'sillThickness', + 'casingWidth', ] as const export type WindowMerge = { diff --git a/packages/core/src/building/wall-openings.test.ts b/packages/core/src/building/wall-openings.test.ts index 7e0adcfc5b..b7e8ecb57a 100644 --- a/packages/core/src/building/wall-openings.test.ts +++ b/packages/core/src/building/wall-openings.test.ts @@ -1,7 +1,8 @@ import { describe, expect, test } from 'bun:test' import { type AgentRefusal, isAgentRefusal } from '../agent-tools/refusal' import { addDoorTool, addWindowTool } from '../agent-tools/wall-openings' -import { openingScene, WALL_OPENING_CASES } from './__fixtures__/wall-opening-cases' +import { type LevelNode, RAILING_GUARD_HEIGHT, railingWallDefaults, WallNode } from '../schema' +import { OPENING_SCENE, openingScene, WALL_OPENING_CASES } from './__fixtures__/wall-opening-cases' import { placedDoorFace, planWallOpening } from './wall-openings' // Layer 1 of 3: the operation both agent surfaces call. The cases are the spec. @@ -80,3 +81,46 @@ describe('the face a door placed by hand takes', () => { expect(placedDoorFace(wallOf('wall_main'), 'front')).toBe('front') }) }) + +// Not a shared case: a railing's openings are gaps in its guard. +describe('an opening on a railing', () => { + const railed = () => { + const { nodes } = openingScene() + const railing = WallNode.parse({ + id: 'wall_railing', + parentId: OPENING_SCENE.levelId, + start: [0, 20], + end: [4, 20], + ...railingWallDefaults(), + }) + const level = nodes[OPENING_SCENE.levelId] as LevelNode + return { + ...nodes, + [railing.id]: railing, + [level.id]: { ...level, children: [...level.children, railing.id] }, + } + } + + test('a door is a gap as tall as the guard, standing on the floor, with no casing or threshold', () => { + const planned = planWallOpening(railed(), { kind: 'door', wallId: 'wall_railing', t: 0.5 }) + expect(planned.node).toMatchObject({ + openingKind: 'opening', + height: RAILING_GUARD_HEIGHT, + threshold: false, + casingFaceA: false, + casingFaceB: false, + }) + expect(planned.node.position[1]).toBeCloseTo(RAILING_GUARD_HEIGHT / 2, 6) + }) + + test('a window is refused: a guard has nothing for it to fill', () => { + let error: unknown + try { + planWallOpening(railed(), { kind: 'window', wallId: 'wall_railing', t: 0.5 }) + } catch (thrown) { + error = thrown + } + expect(isAgentRefusal(error)).toBe(true) + expect((error as AgentRefusal).code).toBe('railing_window') + }) +}) diff --git a/packages/core/src/building/wall-openings.ts b/packages/core/src/building/wall-openings.ts index 4ac793350b..9f65cf3d58 100644 --- a/packages/core/src/building/wall-openings.ts +++ b/packages/core/src/building/wall-openings.ts @@ -7,6 +7,7 @@ import { scriptSource, } from '../lib/geometry-script-node' import { wallSupportForNodes } from '../lib/opening-floor-datum' +import { doorSillFloorOf } from '../lib/slab-room' import { type AnyNode, type AnyNodeId, @@ -20,6 +21,7 @@ import { } from '../schema' import { getCurtainWallConfig } from '../schema/nodes/curtain-wall' import type { DoorType, WindowType } from '../schema/nodes/opening-types' +import { isRailingWall, railingOpeningFields } from '../schema/nodes/railing-wall' import { getWallTop } from '../services/storey' import { resolveWallExteriorSide } from '../systems/wall/wall-assembly' import { getWallCurveLength, isCurvedWall } from '../systems/wall/wall-curve' @@ -62,17 +64,22 @@ export function resolveWallOpeningCeiling( const openingMargin = (wall: WallNode) => wall.wallType === 'curtain' ? getCurtainWallConfig(wall).perimeterWidth : 0 -/** A door's centre: slid along the wall to stay on it, standing on the floor. */ +/** + * A door's centre: slid along the wall to stay on it, standing on the floor it opens from + * (`doorSillFloorOf`: a garage's slab in its walls to the outside, given the scene). + */ export function clampDoorToWall( wall: WallNode, localX: number, width: number, height: number, + nodes?: Readonly>, ): { clampedX: number; clampedY: number } { const margin = openingMargin(wall) const length = getWallCurveLength(wall) const clampedX = Math.max(margin + width / 2, Math.min(length - margin - width / 2, localX)) - return { clampedX, clampedY: height / 2 } + const floor = nodes ? doorSillFloorOf(nodes as Record, wall) : 0 + return { clampedX, clampedY: floor + height / 2 } } /** A window's centre: slid to stay on the wall, and between the floor and the wall's ceiling. */ @@ -285,6 +292,14 @@ export function planWallOpening(nodes: Nodes, input: WallOpeningInput) { `Wall ${wallId} is curved; doors and windows go on straight walls, as in the editor.`, { wallId }, ) + // A railing's openings are gaps in its guard: a door becomes one, a window has none to fill. + if (kind === 'window' && isRailingWall(wall)) + refuse( + 'railing_window', + `Wall ${wallId} is a railing: it takes a door, which becomes a gap in the guard, not a window.`, + { wallId }, + ) + const railingGap = kind === 'door' && isRailingWall(wall) ? railingOpeningFields(wall) : null const { t, position } = input if (t !== undefined && position !== undefined && Math.abs(t - position) > 1e-9) @@ -316,7 +331,7 @@ export function planWallOpening(nodes: Nodes, input: WallOpeningInput) { if (compiled) requireMeasured(compiled, { label: `A ${kind}`, tool: `add_${kind}` }) const [scriptedWidth, scriptedHeight] = compiled ? scriptedSize(compiled.manifest) : [] const width = scriptedWidth ?? input.width ?? DEFAULTS[kind].width - const height = scriptedHeight ?? input.height ?? DEFAULTS[kind].height + const height = railingGap?.height ?? scriptedHeight ?? input.height ?? DEFAULTS[kind].height const wallLength = lengthOf(wall) if (wallLength < width) refuse( @@ -330,13 +345,13 @@ export function planWallOpening(nodes: Nodes, input: WallOpeningInput) { // below stands one that is too tall on the floor and reports it clamped. const rawY = kind === 'door' - ? height / 2 + ? doorSillFloorOf(nodes as Record, wall) + height / 2 : input.sillHeight === undefined ? resolveWallOpeningCeiling(wall, nodes as Record) / 2 : input.sillHeight + height / 2 const { clampedX, clampedY } = kind === 'door' - ? clampDoorToWall(wall, rawX, width, height) + ? clampDoorToWall(wall, rawX, width, height, nodes as Record) : clampWindowToWall(wall, rawX, rawY, width, height, nodes as Record) if (!input.force) { @@ -390,6 +405,7 @@ export function planWallOpening(nodes: Nodes, input: WallOpeningInput) { ...(input.style ? doorStyleLook(input.style as DoorStyle) : {}), ...(input.doorType ? { doorType: input.doorType } : {}), ...(input.openingKind ? { openingKind: input.openingKind } : {}), + ...railingGap, }) : WindowNode.parse({ ...base, diff --git a/packages/core/src/commands/structure/attached-walls.test.ts b/packages/core/src/commands/structure/attached-walls.test.ts new file mode 100644 index 0000000000..1eb28661b8 --- /dev/null +++ b/packages/core/src/commands/structure/attached-walls.test.ts @@ -0,0 +1,95 @@ +import { describe, expect, test } from 'bun:test' +import { extractRooms } from '../../lib/room-graph' +import { type AnyNode, DoorNode, LevelNode, WallNode } from '../../schema' +import { planAttachedWallUpdates } from '../../systems/wall/wall-move' +import { planAttachedWallChanges } from './attached-walls' +import { setWallGeometry } from './set-wall-geometry' +import { applyToScratch, structureChangeBatch } from './shared' + +const LEVEL = 'level_attached' +const wall = (id: string, start: [number, number], end: [number, number]) => + WallNode.parse({ id: `wall_${id}`, parentId: LEVEL, start, end }) + +// A 10 × 6 box whose top wall is one wall, a partition T'd into its middle +// from below and running down to the bottom wall. +function house(): Record { + const nodes: AnyNode[] = [ + LevelNode.parse({ id: LEVEL }), + wall('top', [0, 0], [10, 0]), + wall('right', [10, 0], [10, 6]), + wall('bottom', [10, 6], [0, 6]), + wall('left', [0, 6], [0, 0]), + wall('part', [4, 0], [4, 6]), + ] + return Object.fromEntries(nodes.map((node) => [node.id, node])) +} + +describe('walls T-joined into a moving wall', () => { + test('stretch along their own line to stay on it', () => { + const nodes = house() + const updates = planAttachedWallUpdates( + [nodes.wall_part as WallNode], + [0, 0], + [10, 0], + [0, -1], + [10, -1], + ) + expect(updates).toEqual([{ id: 'wall_part', start: [4, -1], end: [4, 6] }]) + }) + + test('let go when the move carries the join past the wall ends', () => { + const updates = planAttachedWallUpdates( + [wall('part', [4, 0], [4, 6])], + [0, 0], + [10, 0], + [5, -1], + [10, -1], + ) + expect(updates).toEqual([]) + }) + + test('a push of the whole wall takes them along, their doors staying put', () => { + const nodes = house() + // A door 1 m down the partition from the top wall. + const door = DoorNode.parse({ + id: 'door_part', + parentId: 'wall_part', + wallId: 'wall_part', + position: [1, 1.05, 0], + }) + nodes[door.id] = door + nodes.wall_part = { ...nodes.wall_part, children: [door.id] } as AnyNode + const plan = setWallGeometry(nodes, { + wallId: 'wall_top', + start: [0, -1], + end: [10, -1], + mintId: (kind) => `${kind}_new`, + }) + const after = applyToScratch(nodes, structureChangeBatch(plan.changes)) + const part = after.wall_part as WallNode + expect([part.start, part.end]).toEqual([ + [4, -1], + [4, 6], + ]) + // Still 1 m below the old top line, now 2 m from the moved one. + expect((after.door_part as DoorNode).position[0]).toBeCloseTo(2, 9) + }) + + test('a wall whose door the stretch would leave off its end lets go', () => { + const nodes = house() + const door = DoorNode.parse({ + id: 'door_part', + parentId: 'wall_part', + wallId: 'wall_part', + position: [0.5, 1.05, 0], + }) + nodes[door.id] = door + // Pushing the top wall down 1 m would put the door above the partition's start. + expect(planAttachedWallChanges(nodes, 'wall_top', [0, 0], [10, 0], [0, 1], [10, 1])).toEqual([]) + }) +}) + +test('rooms close on a partition T-joined into a wall left whole', () => { + const walls = Object.values(house()).filter((node): node is WallNode => node.type === 'wall') + expect(extractRooms(walls)).toHaveLength(2) +}) diff --git a/packages/core/src/commands/structure/attached-walls.ts b/packages/core/src/commands/structure/attached-walls.ts new file mode 100644 index 0000000000..3383163b04 --- /dev/null +++ b/packages/core/src/commands/structure/attached-walls.ts @@ -0,0 +1,80 @@ +import type { AnyNode, AnyNodeId, SeparatorNode, WallNode } from '../../schema' +import { planAttachedWallUpdates, type WallPlanPoint } from '../../systems/wall/wall-move' +import { planAttachedSeparatorUpdates } from './move-separators' + +/** + * The writes that keep walls T'd into a moving wall on it + * (`planAttachedWallUpdates`): each such wall's new ends and, where its start + * moved, the doors, windows and wall items on it shifted along so they stay + * where they were. A wall whose opening the stretch would leave off its ends + * lets go instead. The invisible walls on it come along too + * (`planAttachedSeparatorUpdates`), so a porch they close off it stays closed. + * `exclude` are walls the move already rewrites. + */ +export function planAttachedWallChanges( + nodes: Readonly>, + movingWallId: string, + originalStart: WallPlanPoint, + originalEnd: WallPlanPoint, + nextStart: WallPlanPoint, + nextEnd: WallPlanPoint, + exclude: ReadonlySet = new Set(), +): Array<{ id: AnyNodeId; data: Partial }> { + const moving = nodes[movingWallId] + if (moving?.type !== 'wall') return [] + const walls = Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && + node.parentId === moving.parentId && + node.id !== moving.id && + !exclude.has(node.id), + ) + const children = Object.values(nodes).filter( + (node) => 'position' in node && Array.isArray(node.position) && node.parentId, + ) + const changes: Array<{ id: AnyNodeId; data: Partial }> = [] + for (const update of planAttachedWallUpdates( + walls, + originalStart, + originalEnd, + nextStart, + nextEnd, + )) { + const wall = nodes[update.id] as WallNode + const length = Math.hypot(update.end[0] - update.start[0], update.end[1] - update.start[1]) + const ux = (update.end[0] - update.start[0]) / length + const uz = (update.end[1] - update.start[1]) / length + // How far the old start sits along the wall from the new one. + const shift = (wall.start[0] - update.start[0]) * ux + (wall.start[1] - update.start[1]) * uz + const hosted = children.filter((child) => child.parentId === wall.id) + const moved = hosted.map((child) => { + const position = (child as AnyNode & { position: [number, number, number] }).position + return { child, position, x: position[0] + shift } + }) + if (moved.some(({ x }) => x < 0 || x > length)) continue + changes.push({ id: wall.id as AnyNodeId, data: { start: update.start, end: update.end } }) + if (Math.abs(shift) < 1e-9) continue + for (const { child, position, x } of moved) { + changes.push({ + id: child.id as AnyNodeId, + data: { + position: [x, position[1], position[2]], + ...('wallT' in child ? { wallT: x / length } : {}), + } as Partial, + }) + } + } + const separators = Object.values(nodes).filter( + (node): node is SeparatorNode => + node.type === 'separator' && node.parentId === moving.parentId && !exclude.has(node.id), + ) + for (const update of planAttachedSeparatorUpdates( + separators, + originalStart, + originalEnd, + nextStart, + nextEnd, + )) + changes.push({ id: update.id as AnyNodeId, data: { start: update.start, end: update.end } }) + return changes +} diff --git a/packages/core/src/commands/structure/floor-foundation-lift.test.ts b/packages/core/src/commands/structure/floor-foundation-lift.test.ts index 7af011ac7f..593dde79dc 100644 --- a/packages/core/src/commands/structure/floor-foundation-lift.test.ts +++ b/packages/core/src/commands/structure/floor-foundation-lift.test.ts @@ -3,7 +3,7 @@ import { GROUND_SUPPORT_ID } from '../../hooks/spatial-grid/support-host-id' import { floorConstructionLift, liftedManualSlab } from '../../lib/floor-construction-lift' import { automaticFloorHeight } from '../../lib/floor-foundation-datum' import { expandFloorIntentChanges, floorIntentConflicts } from '../../lib/floor-intent-changes' -import { wallSupportForNodes } from '../../lib/opening-floor-datum' +import { getOpeningFloorDatum, wallSupportForNodes } from '../../lib/opening-floor-datum' import { type AnyNode, BuildingNode, @@ -15,6 +15,7 @@ import { RoofSegmentNode, SlabNode, WallNode, + WindowNode, ZoneNode, } from '../../schema' import { resolveCeilingHeight } from '../../services/level-height' @@ -516,3 +517,87 @@ test('a tiny room carried under the upper storey retains its own floor step', () plan.changes.find((change) => change.op === 'update' && change.id === zone.id), ).toMatchObject({ data: { floor: { elevation: 0.75 } } }) }) + +test('a thicker upper floor pushes that storey up: its walls, windows, ceilings and the storeys above rise; the ceiling below holds; one undo step', () => { + const building = BuildingNode.parse({ id: 'building_lift' }) + const levels = [0, 1, 2].map((level) => + LevelNode.parse({ id: `level_lift${level}`, parentId: building.id, level, height: 3 }), + ) + building.children = levels.map((level) => level.id) + const nodes: Record = Object.fromEntries( + [building, ...levels].map((node) => [node.id, node]), + ) + const polygon = rect(0) + for (const level of levels.slice(0, 2)) { + const zone = ZoneNode.parse({ + id: `zone_up${level.level}`, + parentId: level.id, + name: `Room ${level.level}`, + spaceRole: 'room', + polygon, + }) + nodes[zone.id] = zone + polygon.forEach((start, at) => { + const wall = WallNode.parse({ + id: `wall_up${level.level}_${at}`, + parentId: level.id, + start, + end: polygon[(at + 1) % 4], + }) + nodes[wall.id] = wall + }) + const ceiling = CeilingNode.parse({ + id: `ceiling_up${level.level}`, + parentId: level.id, + polygon, + boundary: 'auto', + autoFromWalls: true, + zoneId: zone.id, + }) + nodes[ceiling.id] = ceiling + } + const opening = WindowNode.parse({ id: 'window_up', parentId: 'wall_up1_0', position: [2, 1, 0] }) + nodes[opening.id] = opening + nodes.wall_up1_0 = { ...nodes.wall_up1_0, children: [opening.id] } as WallNode + nodes.slab_up2 = SlabNode.parse({ id: 'slab_up2', parentId: levels[2]!.id, polygon }) + for (const level of levels) + level.children = Object.values(nodes) + .filter((node) => node.parentId === level.id) + .map((node) => node.id) as LevelNode['children'] + const before = reconcileStructureOnLoad(nodes).nodes + const plate = Object.values(before).find( + (node): node is SlabNode => + node.type === 'slab' && node.plateRole === 'base' && node.parentId === 'level_lift1', + )! + const lift = 0.3556 - plate.thickness + const wall = before.wall_up1_0 as WallNode + const upperCeiling = before.ceiling_up1 as CeilingNode + const lowerCeiling = before.ceiling_up0 as CeilingNode + useScene.setState({ nodes: before, rootNodeIds: ['building_lift'], readOnly: false }) + useScene.temporal.getState().clear() + const plan = setFloorFoundation(before, { slabId: plate.id, patch: { thickness: 0.3556 } }) + expect(plan.conflicts).toEqual([]) + useScene.getState().applyNodeChanges(structureChangeBatch(plan.changes)) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + const after = reconcileStructureOnLoad(useScene.getState().nodes).nodes + expect(after[plate.id]).toMatchObject({ thickness: 0.3556 }) + expect(wallSupportForNodes(wall, after).elevation).toBeCloseTo( + wallSupportForNodes(wall, before).elevation + lift, + ) + expect(getWallPlaneTop(wall, wall.parentId!, after)).toBeCloseTo( + getWallPlaneTop(wall, wall.parentId!, before) + lift, + ) + expect(getOpeningFloorDatum(wall, opening, after)).toBeCloseTo( + getOpeningFloorDatum(wall, opening, before) + lift, + ) + expect(resolveCeilingHeight(upperCeiling, after)).toBeCloseTo( + resolveCeilingHeight(upperCeiling, before) + lift, + ) + expect(getLevelElevations(after).get('level_lift2')!.baseY).toBeCloseTo(6 + lift) + expect(resolveCeilingHeight(lowerCeiling, after)).toBeCloseTo( + resolveCeilingHeight(lowerCeiling, before), + ) + expect(getWallPlaneTop(before.wall_up0_0 as WallNode, 'level_lift0', after)).toBe(3) + useScene.temporal.getState().undo() + expect(useScene.getState().nodes).toEqual(before) +}) diff --git a/packages/core/src/commands/structure/index.ts b/packages/core/src/commands/structure/index.ts index 63456e9c21..06b0d6a737 100644 --- a/packages/core/src/commands/structure/index.ts +++ b/packages/core/src/commands/structure/index.ts @@ -5,6 +5,7 @@ export { resolveZoneTransformHosts, type SupportHostAssignment, } from './apply-zone-transform' +export { planAttachedWallChanges } from './attached-walls' export { type CreateMezzanineInput, createMezzanine } from './create-mezzanine' export { type CreateZoneInput, @@ -29,6 +30,14 @@ export { } from './floor-opening' export { type LockOutsideFacesInput, lockOutsideFaces } from './lock-outside-faces' export { mergeZones } from './merge-zones' +export { + joinedSeparatorIds, + planAttachedSeparatorUpdates, + SEPARATOR_JOIN_REACH, + type SeparatorSpan, + setSeparatorEnd, + translateSeparators, +} from './move-separators' export { type MezzanineStairPlan, planMezzanineStair } from './plan-mezzanine-stair' export { planWallDeletion } from './plan-wall-deletion' export { rebaseFloorReference } from './rebase-floor-reference' @@ -55,3 +64,12 @@ export { structureChangeBatch, } from './shared' export { type TransformZoneInput, transformZone, type ZoneTransformPlan } from './transform-zone' +export { + finishWallEdit, + retypeOutlineBridges, + sameWallConstruction, + trimWallOverlaps, + WALL_CONSTRUCTION_FIELDS, + wallConstructionPatch, + wallsRunAlong, +} from './wall-overlap' diff --git a/packages/core/src/commands/structure/move-separators.test.ts b/packages/core/src/commands/structure/move-separators.test.ts new file mode 100644 index 0000000000..d926735e47 --- /dev/null +++ b/packages/core/src/commands/structure/move-separators.test.ts @@ -0,0 +1,388 @@ +import { describe, expect, test } from 'bun:test' +import { area } from '../../lib/polygon-boolean' +import { reconcileLevelStructure } from '../../lib/structure-kernel' +import { + type AnyNode, + type AnyNodeId, + LevelNode, + railingWallDefaults, + type SeparatorNode, + WallNode, + type ZoneNode, +} from '../../schema' +import { planSeparatorInsertion } from '../../systems/wall/wall-topology' +import { createZone } from './create-zone' +import { setSeparatorEnd, translateSeparators } from './move-separators' +import { setWallGeometry } from './set-wall-geometry' +import { + applyToScratch, + type StructureNodes, + type StructurePlan, + structureChangeBatch, +} from './shared' + +type Point = [number, number] +const LEVEL = 'level_porch' + +function mint() { + let i = 0 + return (kind: string) => `${kind}_moved${++i}` +} + +/** The level's structure settled after an edit, as the live sync does it. */ +function settle(before: StructureNodes, after: StructureNodes) { + let i = 0 + const derived = reconcileLevelStructure({ + levelId: LEVEL, + nodes: after, + previousNodes: before, + mintId: (kind) => { + let id: string + do id = `${kind}_derived${++i}` + while (after[id]) + return id + }, + }) + return applyToScratch(after, structureChangeBatch(derived.patches)) +} + +function apply(nodes: StructureNodes, plan: StructurePlan) { + expect(plan.conflicts).toBeUndefined() + return settle(nodes, applyToScratch(nodes, structureChangeBatch(plan.changes))) +} + +/** + * An 8 × 4 house with a porch off its z = 4 side, closed by invisible walls drawn left, along + * the bottom (one piece, or as `bottom` gives it) and right, and `railed` sides railings instead. + */ +function porch( + bottom: Array<[Point, Point]> = [ + [ + [2, 6], + [6, 6], + ], + ], + railed: ReadonlySet = new Set(), +) { + const level = LevelNode.parse({ id: LEVEL }) + const empty: StructureNodes = { [level.id]: level } + const house = apply( + empty, + createZone(empty, { + levelId: LEVEL, + polygon: [ + [0, 0], + [8, 0], + [8, 4], + [0, 4], + ], + enclose: true, + mintId: mint(), + name: 'Kitchen', + }), + ) + let next = house + const sides: Array<[Point, Point]> = [ + [ + [2, 4], + [2, 6], + ], + ...bottom, + [ + [6, 6], + [6, 4], + ], + ] + sides.forEach(([start, end], index) => { + if (railed.has(index)) { + const railing = WallNode.parse({ + ...railingWallDefaults(), + id: `wall_railing${index}`, + parentId: LEVEL, + start, + end, + }) + next = { ...next, [railing.id]: railing } + return + } + const plan = planSeparatorInsertion(next as Record, { + levelId: LEVEL as AnyNodeId, + start, + end, + joinRadius: 0.05, + mintId: () => `separator_side${index}`, + }) + expect(plan).not.toBeNull() + next = applyToScratch(next, plan!.changes) + }) + return settle(house, next) +} + +const near = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]) < 1e-6 +const separators = (nodes: StructureNodes) => + Object.values(nodes).filter((node): node is SeparatorNode => node.type === 'separator') +/** The invisible wall from `a` to `b`, either way round. */ +const between = (nodes: StructureNodes, a: Point, b: Point) => + separators(nodes).find( + (node) => + (near(node.start, a) && near(node.end, b)) || (near(node.start, b) && near(node.end, a)), + ) +const outdoorRoom = (nodes: StructureNodes) => + Object.values(nodes).find( + (node): node is ZoneNode => node.type === 'zone' && node.name !== 'Kitchen', + ) +const roomArea = (zone: ZoneNode | undefined) => + zone ? area([{ outer: zone.polygon, holes: zone.holes }]) : 0 +const whole = (separator: SeparatorNode | undefined) => ({ + separatorId: separator!.id, + t0: 0, + t1: 1, +}) + +describe('a porch closed by invisible walls', () => { + test('its edge pushed out and back in stays a closed porch, the sides stretching with it', () => { + const nodes = porch() + expect(roomArea(outdoorRoom(nodes))).toBeCloseTo(8, 6) + const bottom = between(nodes, [2, 6], [6, 6]) + const out = apply( + nodes, + translateSeparators(nodes, { spans: [whole(bottom)], offset: [0, 1], mintId: mint() }), + ) + // The one moved invisible wall drags both its neighbours; their far ends stay on the house. + expect(between(out, [2, 7], [6, 7])?.id).toBe(bottom!.id) + expect(between(out, [2, 4], [2, 7])).toBeDefined() + expect(between(out, [6, 7], [6, 4])).toBeDefined() + expect(separators(out)).toHaveLength(3) + const porchRoom = outdoorRoom(out) + expect(porchRoom?.roomType).toBe('porch') + expect(roomArea(porchRoom)).toBeCloseTo(12, 6) + expect(porchRoom?.boundarySeparatorIds).toHaveLength(3) + + const back = apply( + out, + translateSeparators(out, { spans: [whole(bottom)], offset: [0, -2], mintId: mint() }), + ) + expect(roomArea(outdoorRoom(back))).toBeCloseTo(4, 6) + expect(between(back, [2, 4], [2, 5])).toBeDefined() + expect(between(back, [6, 5], [6, 4])).toBeDefined() + }) + + test('a side moved along the house slides its end on the house wall and stretches the edge it meets', () => { + const nodes = porch() + const left = between(nodes, [2, 4], [2, 6]) + const after = apply( + nodes, + translateSeparators(nodes, { spans: [whole(left)], offset: [-1, 0], mintId: mint() }), + ) + expect(between(after, [1, 4], [1, 6])?.id).toBe(left!.id) + expect(between(after, [1, 6], [6, 6])).toBeDefined() + expect(between(after, [6, 6], [6, 4])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(10, 6) + // The house wall is never moved by its invisible walls. + const walls = (scene: StructureNodes) => + Object.values(scene) + .filter((node) => node.type === 'wall') + .map((node) => JSON.stringify([(node as WallNode).start, (node as WallNode).end])) + .sort() + expect(walls(after)).toEqual(walls(nodes)) + }) + + test('a side carried off the end of the house leaves an invisible wall back to it', () => { + const nodes = porch() + const left = between(nodes, [2, 4], [2, 6]) + const after = apply( + nodes, + translateSeparators(nodes, { spans: [whole(left)], offset: [-3, 0], mintId: mint() }), + ) + expect(between(after, [-1, 4], [-1, 6])).toBeDefined() + // The jog runs back along the house wall's line; the stretch under the wall is absorbed. + expect(between(after, [-1, 4], [0, 4])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(14, 6) + }) + + test('an edge drawn in two pieces pushes as one line', () => { + const nodes = porch([ + [ + [2, 6], + [4, 6], + ], + [ + [4, 6], + [6, 6], + ], + ]) + const pieces = [between(nodes, [2, 6], [4, 6]), between(nodes, [4, 6], [6, 6])] + const after = apply( + nodes, + translateSeparators(nodes, { + spans: pieces.map(whole), + offset: [0, 1.5], + mintId: mint(), + }), + ) + expect(between(after, [2, 7.5], [4, 7.5])).toBeDefined() + expect(between(after, [4, 7.5], [6, 7.5])).toBeDefined() + expect(separators(after)).toHaveLength(4) + expect(roomArea(outdoorRoom(after))).toBeCloseTo(14, 6) + }) + + test('part of an edge moves alone, joined back to the rest by a jog', () => { + const nodes = porch() + const bottom = between(nodes, [2, 6], [6, 6])! + const t = bottom.start[0] === 2 ? ([0, 0.5] as const) : ([0.5, 1] as const) + const after = apply( + nodes, + translateSeparators(nodes, { + spans: [{ separatorId: bottom.id, t0: t[0], t1: t[1] }], + offset: [0, 1], + mintId: mint(), + }), + ) + expect(between(after, [2, 7], [4, 7])?.id).toBe(bottom.id) + expect(between(after, [4, 6], [6, 6])).toBeDefined() + expect(between(after, [4, 6], [4, 7])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(10, 6) + }) + + test('a corner drawn a little open still moves together, and closes', () => { + const nodes = porch() + const bottom = between(nodes, [2, 6], [6, 6])! + // Drawn by hand 3 cm short of the side: the room still closes there. + const [shortStart, shortEnd] = near(bottom.start, [2, 6]) + ? [[2.03, 6] as Point, bottom.end] + : [bottom.start, [2.03, 6] as Point] + const open = settle(nodes, { + ...nodes, + [bottom.id]: { ...bottom, start: shortStart, end: shortEnd }, + }) + expect(roomArea(outdoorRoom(open))).toBeGreaterThan(7.8) + const after = apply( + open, + translateSeparators(open, { + spans: [whole(open[bottom.id] as SeparatorNode)], + offset: [0, 1], + mintId: mint(), + }), + ) + const left = separators(after).find( + (node) => near(node.start, [2, 4]) || near(node.end, [2, 4]), + )! + const corner = near(left.start, [2, 4]) ? left.end : left.start + expect(corner).toEqual([2.03, 7]) + expect(roomArea(outdoorRoom(after))).toBeGreaterThan(11.8) + }) + + test('an invisible wall T’d into the edge stretches with it', () => { + let nodes = porch() + const divider = planSeparatorInsertion(nodes as Record, { + levelId: LEVEL as AnyNodeId, + start: [4, 4], + end: [4, 6], + joinRadius: 0.05, + mintId: () => 'separator_divider', + }) + nodes = settle(nodes, applyToScratch(nodes, divider!.changes)) + const bottom = between(nodes, [2, 6], [6, 6]) + const after = apply( + nodes, + translateSeparators(nodes, { spans: [whole(bottom)], offset: [0, 1], mintId: mint() }), + ) + expect(between(after, [4, 4], [4, 7])?.id).toBe('separator_divider') + }) + + test('a corner dragged takes the invisible wall joined there with it; Alt leaves it', () => { + const nodes = porch() + const bottom = between(nodes, [2, 6], [6, 6])! + const endpoint = near(bottom.start, [2, 6]) ? 'start' : 'end' + const after = apply( + nodes, + setSeparatorEnd(nodes, { separatorId: bottom.id, endpoint, point: [1, 7] }), + ) + expect(between(after, [1, 7], [6, 6])).toBeDefined() + expect(between(after, [2, 4], [1, 7])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(11, 6) + + const detached = setSeparatorEnd(nodes, { + separatorId: bottom.id, + endpoint, + point: [1, 7], + detach: true, + }) + const moved = applyToScratch(nodes, structureChangeBatch(detached.changes)) + expect(between(moved, [2, 4], [2, 6])).toBeDefined() + }) +}) + +test('an end T’d into a slanted wall slides along it', () => { + const level = LevelNode.parse({ id: LEVEL }) + const slanted = WallNode.parse({ + id: 'wall_slanted', + parentId: LEVEL, + start: [0, 0], + end: [10, 5], + }) + const separator = planSeparatorInsertion( + { [level.id]: level, [slanted.id]: slanted } as Record, + { + levelId: LEVEL as AnyNodeId, + start: [4, 2], + end: [4, 8], + joinRadius: 0.05, + mintId: () => 'separator_tee', + }, + )!.separators[0]! + const nodes: StructureNodes = { + [level.id]: level, + [slanted.id]: slanted, + [separator.id]: separator, + } + const plan = translateSeparators(nodes, { + spans: [whole(separator)], + offset: [1, 0], + mintId: mint(), + }) + const after = applyToScratch(nodes, structureChangeBatch(plan.changes)) as StructureNodes + const moved = after[separator.id] as SeparatorNode + expect(moved.start[0]).toBeCloseTo(5, 9) + expect(moved.start[1]).toBeCloseTo(2.5, 9) + expect(moved.end).toEqual([5, 8]) +}) + +describe('a wall pushed with invisible walls on it', () => { + test('carries the porch’s sides T’d into it', () => { + const nodes = porch() + const house = Object.values(nodes).find( + (node): node is WallNode => node.type === 'wall' && node.start[1] === 4 && node.end[1] === 4, + )! + const after = apply( + nodes, + setWallGeometry(nodes, { + wallId: house.id, + start: [house.start[0], 3], + end: [house.end[0], 3], + mintId: mint(), + }), + ) + expect(between(after, [2, 3], [2, 6])).toBeDefined() + expect(between(after, [6, 6], [6, 3])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(12, 6) + }) + + test('a railing edge pushed out takes the invisible sides joined at its ends', () => { + const nodes = porch(undefined, new Set([1])) + expect(outdoorRoom(nodes)).toBeDefined() + const railing = nodes.wall_railing1 as WallNode + const after = apply( + nodes, + setWallGeometry(nodes, { + wallId: railing.id, + start: [railing.start[0], 7], + end: [railing.end[0], 7], + mintId: mint(), + }), + ) + expect(between(after, [2, 4], [2, 7])).toBeDefined() + expect(between(after, [6, 7], [6, 4])).toBeDefined() + expect(roomArea(outdoorRoom(after))).toBeCloseTo(12, 6) + }) +}) diff --git a/packages/core/src/commands/structure/move-separators.ts b/packages/core/src/commands/structure/move-separators.ts new file mode 100644 index 0000000000..cb217f1d5c --- /dev/null +++ b/packages/core/src/commands/structure/move-separators.ts @@ -0,0 +1,440 @@ +import { WALL_JUNCTION_TOLERANCE } from '../../lib/room-graph' +import { type AnyNode, SeparatorNode, type WallNode } from '../../schema' +import { isCurvedWall } from '../../systems/wall/wall-curve' +import { + getPlannedLinkedWallUpdates, + planAttachedWallUpdates, + planWallMoveJunctions, + type WallPlanPoint, +} from '../../systems/wall/wall-move' +import { WALL_MIN_LENGTH } from '../../systems/wall/wall-topology' +import { + at, + conflict, + diffStructure, + type Point, + type StructureMintId, + type StructureNodes, + type StructurePlan, +} from './shared' + +/** + * Invisible walls (room separators) move the way walls do, so the rooms they close stay closed: + * a separator joined end to end with a moving one stretches to its moved end (or takes a jog + * where it runs on in line), one T'd into its body slides along its own line to stay on it, and + * an end of the moving one sitting on a wall, a railing or another separator slides along that + * line. Walls never move with them. + */ + +/** A stretch of an invisible wall: `[0, 1]` is all of it. */ +export type SeparatorSpan = { separatorId: string; t0: number; t1: number } + +/** + * How near invisible walls meet: ends this near are one corner and an end this near a line is on + * it, as far as the room graph joins them (`WALL_JUNCTION_TOLERANCE`). A move puts such ends + * exactly together, so a corner drawn a little open closes. + */ +export const SEPARATOR_JOIN_REACH = WALL_JUNCTION_TOLERANCE + +type Line = { start: Point; end: Point } +type Boundary = WallNode | SeparatorNode + +const JOIN = SEPARATOR_JOIN_REACH +const SAME_POINT = 1e-6 +const WHOLE = 1e-4 +// Lines within about 1° of each other run alongside: one end can't slide along the other. +const MIN_SINE = Math.sin(Math.PI / 180) +// The shortest invisible wall a move may leave, as a wall's (`setWallGeometry`). +const MIN_LENGTH = 0.05 + +const distance = (a: Point, b: Point) => Math.hypot(b[0] - a[0], b[1] - a[1]) +const near = (a: Point, b: Point) => distance(a, b) <= JOIN + +/** Where `point` sits along `line` (metres from its start) and how far off its line. */ +function station(point: Point, line: Line) { + const dx = line.end[0] - line.start[0] + const dz = line.end[1] - line.start[1] + const length = Math.hypot(dx, dz) + if (length < 1e-9) return { along: 0, across: distance(point, line.start), length } + return { + along: ((point[0] - line.start[0]) * dx + (point[1] - line.start[1]) * dz) / length, + across: Math.abs((point[1] - line.start[1]) * dx - (point[0] - line.start[0]) * dz) / length, + length, + } +} + +/** Whether `point` is on the segment `line`, its ends included. */ +function onLine(point: Point, line: Line) { + const { along, across, length } = station(point, line) + return length > 1e-9 && across <= JOIN && along >= -JOIN && along <= length + JOIN +} + +/** Where the line through `moving` meets the segment `host`, if it does at a real angle. */ +function meetingOn(moving: Line, host: Line): Point | null { + const r = [moving.end[0] - moving.start[0], moving.end[1] - moving.start[1]] as const + const u = [host.end[0] - host.start[0], host.end[1] - host.start[1]] as const + const hostLength = Math.hypot(u[0], u[1]) + const denominator = r[0] * u[1] - r[1] * u[0] + if (Math.abs(denominator) <= MIN_SINE * Math.hypot(r[0], r[1]) * hostLength) return null + const qp = [host.start[0] - moving.start[0], host.start[1] - moving.start[1]] as const + const t = (qp[0] * r[1] - qp[1] * r[0]) / denominator + if (t * hostLength < -JOIN || t * hostLength > hostLength + JOIN) return null + return [host.start[0] + u[0] * t, host.start[1] + u[1] * t] +} + +/** `separator` with its ends at `point` (within the join reach) put exactly on it. */ +function ontoCorner(separator: SeparatorNode, point: Point): SeparatorNode { + const start = near(separator.start, point) ? point : separator.start + const end = near(separator.end, point) ? point : separator.end + return start === separator.start && end === separator.end + ? separator + : { ...separator, start, end } +} + +/** `separator` with its ends on the body of `line` (clear of its ends) put exactly on it. */ +function ontoBody(separator: SeparatorNode, line: Line): SeparatorNode { + let next = separator + for (const end of ['start', 'end'] as const) { + const { along, across, length } = station(separator[end], line) + if (across > JOIN || along <= JOIN || along >= length - JOIN) continue + next = { ...next, [end]: at(line.start, line.end, along / length) } + } + return next +} + +const touches = (separator: SeparatorNode, line: Line) => + [separator.start, separator.end].some((p) => near(p, line.start) || near(p, line.end)) + +/** How far `other` lies off the line through `from` along `move`: 0 when it runs with the move. */ +function offMove(from: Point, move: Point, other: Point) { + const length = Math.hypot(move[0], move[1]) + if (length < SAME_POINT) return Number.POSITIVE_INFINITY + return Math.abs(move[0] * (other[1] - from[1]) - move[1] * (other[0] - from[0])) / length +} + +/** + * `separator` (joined at a corner of `line`) as the junction rules should read it: one running + * with its corner's move to within the join reach (a side drawn a little out of square, or put + * onto the corner by `ontoCorner`) reads as running exactly with it, so it stretches as a square + * corner's wall does instead of taking a jog. + */ +function squaredForMove(separator: SeparatorNode, line: Line, next: Line) { + let read = { id: separator.id, start: separator.start, end: separator.end } + for (const corner of ['start', 'end'] as const) { + const from = line[corner] + const move: Point = [next[corner][0] - from[0], next[corner][1] - from[1]] + for (const end of ['start', 'end'] as const) { + if (distance(separator[end], from) > SAME_POINT) continue + const far = end === 'start' ? 'end' : 'start' + if (offMove(from, move, separator[far]) > JOIN) continue + const length = Math.hypot(move[0], move[1]) + const along = + ((separator[far][0] - from[0]) * move[0] + (separator[far][1] - from[1]) * move[1]) / length + read = { + ...read, + [far]: [from[0] + (move[0] / length) * along, from[1] + (move[1] / length) * along], + } + } + } + return read +} + +function levelSeparators(nodes: Record, levelId: string | null) { + return Object.values(nodes).filter( + (node): node is SeparatorNode => node.type === 'separator' && node.parentId === levelId, + ) +} + +/** The invisible walls joined at `endpoint` of `separator`: the ones a drag of that end takes. */ +export function joinedSeparatorIds( + nodes: StructureNodes, + separator: SeparatorNode, + endpoint: 'start' | 'end', +): string[] { + const point = separator[endpoint] + return levelSeparators(nodes, separator.parentId).flatMap((node) => + node.id !== separator.id && (near(node.start, point) || near(node.end, point)) ? [node.id] : [], + ) +} + +/** + * Pushes invisible-wall stretches `offset` as one line: a room's edge of separators, a selected + * separator's side arrow. A stretch shorter than its separator is split off first and only it + * moves, the rest staying joined to it by a jog. An end that sat on a wall, a railing or another + * separator slides along it to where the moved line meets it; one the move carries off its end + * leaves a short invisible wall back to where it was, so the room stays closed. Separators joined + * to the moving ones end to end follow the junction rules of a wall move, and those T'd into + * their bodies stretch along their own lines. One plan, one undo step. + */ +export function translateSeparators( + nodes: StructureNodes, + input: { spans: readonly SeparatorSpan[]; offset: Point; mintId: StructureMintId }, +): StructurePlan { + const spans = new Map() + for (const span of input.spans) { + const t0 = Math.max(0, Math.min(span.t0, span.t1)) + const t1 = Math.min(1, Math.max(span.t0, span.t1)) + const held = spans.get(span.separatorId) + spans.set(span.separatorId, { + t0: Math.min(t0, held?.t0 ?? t0), + t1: Math.max(t1, held?.t1 ?? t1), + }) + } + const first = nodes[input.spans[0]?.separatorId ?? ''] + if ( + first?.type !== 'separator' || + [...spans.keys()].some( + (id) => nodes[id]?.type !== 'separator' || nodes[id]?.parentId !== first.parentId, + ) + ) + throw Error('Select an invisible wall.') + const [dx, dz] = input.offset + if (!(Number.isFinite(dx) && Number.isFinite(dz))) throw Error('Invalid move.') + if (Math.hypot(dx, dz) < 1e-9) return { changes: [] } + const levelId = first.parentId + const shift = ([x, z]: Point): Point => [x + dx, z + dz] + const scratch: Record = { ...nodes } + + // The stretches that move, cut out of their separators. + const rest = new Map() + for (const [id, { t0, t1 }] of spans) { + if (t1 - t0 < WHOLE) continue + const separator = scratch[id] as SeparatorNode + const point = (t: number) => at(separator.start, separator.end, t) + for (const [from, to] of [ + [0, t0], + [t1, 1], + ] as const) + if (to - from > WHOLE) { + const remainder = SeparatorNode.parse({ + ...separator, + id: input.mintId('separator'), + start: point(from), + end: point(to), + }) + scratch[remainder.id] = remainder + } + const piece = { start: point(t0), end: point(t1) } + scratch[id] = { ...separator, ...piece } + rest.set(id, piece) + } + if (!rest.size) return { changes: [] } + + const still = Object.values(scratch).filter( + (node): node is Boundary => + node.parentId === levelId && + !rest.has(node.id) && + (node.type === 'separator' || (node.type === 'wall' && !isCurvedWall(node))), + ) + const bridges: Line[] = [] + const moved = new Map() + for (const [id, line] of rest) { + const shifted = { start: shift(line.start), end: shift(line.end) } + const next = { ...shifted } + for (const end of ['start', 'end'] as const) { + const point = line[end] + // A joint inside the moving line moves with it. + if ( + [...rest].some( + ([other, piece]) => other !== id && (near(piece.start, point) || near(piece.end, point)), + ) + ) + continue + // Separators joined here end to end follow the move instead. + const hosts = still.filter( + (node) => + !(node.type === 'separator' && (near(node.start, point) || near(node.end, point))) && + onLine(point, node), + ) + if (!hosts.length) continue + let slid: Point | null = null + for (const host of hosts) { + const meeting = meetingOn(shifted, host) + if (meeting && (!slid || distance(meeting, shifted[end]) < distance(slid, shifted[end]))) + slid = meeting + } + if (slid) next[end] = slid + else if (!still.some((node) => onLine(shifted[end], node))) + bridges.push({ start: point, end: shifted[end] }) + } + if (distance(next.start, next.end) < MIN_LENGTH) + return conflict('invalid-geometry', [id], 'The invisible wall would be too short there.') + moved.set(id, next) + } + + const deleted = new Set() + for (const [id, line] of rest) { + const next = moved.get(id)! + const others = levelSeparators(scratch, levelId).filter( + (node) => !rest.has(node.id) && !deleted.has(node.id), + ) + // Joined end to end: put on the corner exactly, so the junction rules move them. + const joined = others + .filter((node) => touches(node, line)) + .map((node) => ontoCorner(ontoCorner(node, line.start), line.end)) + // T'd into its body: read before the joined ones move, so a moved end never reads as one. + const attached = planAttachedWallUpdates( + others.filter((node) => !touches(node, line)).map((node) => ontoBody(node, line)), + line.start, + line.end, + next.start, + next.end, + ) + const read = joined.map((node) => squaredForMove(node, line, next)) + const junctions = planWallMoveJunctions(read, line.start, line.end, next.start, next.end) + // The rules move the joined ends; each separator keeps its own far end. + const linked = getPlannedLinkedWallUpdates( + junctions, + line.start, + line.end, + next.start, + next.end, + ).map((update) => { + const proxy = read.find((entry) => entry.id === update.id)! + const own = joined.find((entry) => entry.id === update.id)! + return { + id: update.id, + start: update.start === proxy.start ? own.start : update.start, + end: update.end === proxy.end ? own.end : update.end, + } + }) + for (const update of [...linked, ...attached]) { + if (distance(update.start, update.end) < WALL_MIN_LENGTH) { + delete scratch[update.id] + deleted.add(update.id) + } else + scratch[update.id] = { + ...(scratch[update.id] as SeparatorNode), + start: update.start, + end: update.end, + } + } + for (const separator of junctions.wallsToDelete) { + delete scratch[separator.id] + deleted.add(separator.id) + } + for (const bridge of junctions.bridgePlans) + bridges.push({ + start: bridge.originalPoint, + end: bridge.movedEndpoint === 'start' ? next.start : next.end, + }) + scratch[id] = { ...(scratch[id] as SeparatorNode), start: next.start, end: next.end } + } + + const made: Line[] = [] + for (const bridge of bridges) { + if (distance(bridge.start, bridge.end) < WALL_MIN_LENGTH) continue + const same = (a: Point, b: Point) => distance(a, b) <= SAME_POINT + if ( + made.some( + (line) => + (same(line.start, bridge.start) && same(line.end, bridge.end)) || + (same(line.start, bridge.end) && same(line.end, bridge.start)), + ) + ) + continue + made.push(bridge) + const separator = SeparatorNode.parse({ + id: input.mintId('separator'), + parentId: levelId, + start: bridge.start, + end: bridge.end, + }) + scratch[separator.id] = separator + } + return { changes: diffStructure(nodes, scratch) } +} + +/** + * Moves one end of an invisible wall to `point`, as a wall's end drag does: the separators joined + * at that end follow it, stretching and turning about their far ends, and those T'd into its + * body slide along their own lines to stay on it. `detach` (Alt) leaves them where they are. + */ +export function setSeparatorEnd( + nodes: StructureNodes, + input: { separatorId: string; endpoint: 'start' | 'end'; point: Point; detach?: boolean }, +): StructurePlan { + const separator = nodes[input.separatorId] + if (separator?.type !== 'separator') throw Error('Select an invisible wall.') + if (!input.point.every(Number.isFinite)) throw Error('Invalid point.') + const line: Line = { start: separator.start, end: separator.end } + const next: Line = { ...line, [input.endpoint]: input.point } + if (distance(next.start, next.end) < MIN_LENGTH) + return conflict('invalid-geometry', [separator.id], 'The invisible wall would be too short.') + const scratch: Record = { ...nodes } + scratch[separator.id] = { ...separator, start: next.start, end: next.end } + if (!input.detach) { + const from = line[input.endpoint] + const others = levelSeparators(nodes, separator.parentId).filter( + (node) => node.id !== separator.id, + ) + const updates = [ + ...others.flatMap((other) => { + const start = near(other.start, from) ? input.point : other.start + const end = near(other.end, from) ? input.point : other.end + return start === other.start && end === other.end ? [] : [{ id: other.id, start, end }] + }), + ...planAttachedWallUpdates( + others.filter((other) => !touches(other, line)).map((other) => ontoBody(other, line)), + line.start, + line.end, + next.start, + next.end, + ), + ] + for (const update of updates) { + if (distance(update.start, update.end) < WALL_MIN_LENGTH) delete scratch[update.id] + else + scratch[update.id] = { + ...(scratch[update.id] as SeparatorNode), + start: update.start, + end: update.end, + } + } + } + return { changes: diffStructure(nodes, scratch) } +} + +/** + * The invisible walls a moving wall carries along, so the rooms they close stay closed: one T'd + * into its body slides along its own line onto the moved line, as a wall T'd into it does, and + * one joined at a moving end follows that end while the end moves along the separator's own line + * (a corner pushed square). A corner the move turns leaves its separators where they are, on the + * wall it stretched; an end the move would carry off the wall lets go. + */ +export function planAttachedSeparatorUpdates( + separators: readonly SeparatorNode[], + originalStart: WallPlanPoint, + originalEnd: WallPlanPoint, + nextStart: WallPlanPoint, + nextEnd: WallPlanPoint, +): Array<{ id: SeparatorNode['id']; start: WallPlanPoint; end: WallPlanPoint }> { + const line = { start: originalStart, end: originalEnd } + const updates = planAttachedWallUpdates( + separators.filter((separator) => !touches(separator, line)).map((s) => ontoBody(s, line)), + originalStart, + originalEnd, + nextStart, + nextEnd, + ) + const corners = [ + [originalStart, nextStart], + [originalEnd, nextEnd], + ] as const + for (const separator of separators) { + if (!touches(separator, line)) continue + let next: Line | null = null + for (const end of ['start', 'end'] as const) { + const corner = corners.find(([from]) => near(separator[end], from)) + if (!corner) continue + const [from, to] = corner + const other = separator[end === 'start' ? 'end' : 'start'] + if (distance(other, from) < SAME_POINT) continue + if (offMove(from, [to[0] - from[0], to[1] - from[1]], other) > JOIN) continue + next = { ...(next ?? { start: separator.start, end: separator.end }), [end]: to } + } + if (next && distance(next.start, next.end) >= WALL_MIN_LENGTH) + updates.push({ id: separator.id, start: next.start, end: next.end }) + } + return updates +} diff --git a/packages/core/src/commands/structure/set-wall-geometry.ts b/packages/core/src/commands/structure/set-wall-geometry.ts index 2a66fa49bd..5094effd97 100644 --- a/packages/core/src/commands/structure/set-wall-geometry.ts +++ b/packages/core/src/commands/structure/set-wall-geometry.ts @@ -1,6 +1,8 @@ import { type AnyNodeId, WallNode } from '../../schema' import { planWallJustification } from '../../systems/wall/wall-frame' import { getPlannedLinkedWallUpdates, planWallMoveJunctions } from '../../systems/wall/wall-move' +import { WALL_MIN_LENGTH } from '../../systems/wall/wall-topology' +import { planAttachedWallChanges } from './attached-walls' import { applyToScratch, conflict, @@ -10,6 +12,7 @@ import { type StructureNodes, type StructurePlan, } from './shared' +import { retypeOutlineBridges, trimWallOverlaps } from './wall-overlap' export function setWallGeometry( nodes: StructureNodes, @@ -63,22 +66,61 @@ export function setWallGeometry( hosted.map((n) => n.id), 'Moving this junction would remove a wall with hosted objects.', ) + const linkedUpdates = getPlannedLinkedWallUpdates(plan, wall.start, wall.end, start, end) + // A jog shorter than a wall can be (the wall landed on the line it left) + // is no wall: the walls already meet there. + const bridges = plan.bridgePlans.flatMap((bridge) => { + const to = bridge.movedEndpoint === 'start' ? start : end + return Math.hypot(to[0] - bridge.originalPoint[0], to[1] - bridge.originalPoint[1]) < + WALL_MIN_LENGTH + ? [] + : [ + { + node: WallNode.parse({ + ...bridge.wall, + id: input.mintId('wall'), + children: [], + start: bridge.originalPoint, + end: to, + }), + parentId: wall.parentId as AnyNodeId, + }, + ] + }) scratch = applyToScratch(scratch, { - update: getPlannedLinkedWallUpdates(plan, wall.start, wall.end, start, end).map( - ({ id, ...data }) => ({ id, data }), - ), + update: linkedUpdates.map(({ id, ...data }) => ({ id, data })), delete: consumed, - create: plan.bridgePlans.map((bridge) => ({ - node: WallNode.parse({ - ...bridge.wall, - id: input.mintId('wall'), - children: [], - start: bridge.originalPoint, - end: bridge.movedEndpoint === 'start' ? start : end, - }), - parentId: wall.parentId as AnyNodeId, - })), + create: bridges, }) + scratch[wall.id] = next + // Walls T'd into it stay on it, stretched along their own line. + const attached = planAttachedWallChanges( + scratch, + wall.id, + wall.start, + wall.end, + start, + end, + new Set([...consumed, ...linkedUpdates.map(({ id }) => id)]), + ) + scratch = applyToScratch(scratch, { update: attached, delete: [], create: [] }) + // The moved wall, the walls it stretched and its jogs take over any + // stretch of a parallel wall they now run along; jogs left on the outline + // are outside walls. + const bridgeIds = bridges.map(({ node }) => node.id) + retypeOutlineBridges(scratch, wall.id, bridgeIds) + const blocked = trimWallOverlaps( + scratch, + [ + wall.id, + ...linkedUpdates.map(({ id }) => id).filter((id) => scratch[id]), + ...attached.flatMap(({ id }) => (scratch[id]?.type === 'wall' ? [id] : [])), + ...bridgeIds, + ], + () => input.mintId('wall'), + ) + if (blocked) return conflict(blocked.code, blocked.nodeIds, blocked.message) + return { changes: diffStructure(nodes, scratch) } } scratch[wall.id] = next return { changes: diffStructure(nodes, scratch) } diff --git a/packages/core/src/commands/structure/set-zone-intent.ts b/packages/core/src/commands/structure/set-zone-intent.ts index f2f0b8499e..b08f88f83f 100644 --- a/packages/core/src/commands/structure/set-zone-intent.ts +++ b/packages/core/src/commands/structure/set-zone-intent.ts @@ -1,15 +1,23 @@ import { z } from 'zod' import { floorIntentConflicts } from '../../lib/floor-intent-changes' import { floorScriptPaintRefusal } from '../../lib/floor-plate-paint' +import { + isOutdoorRoomDefaultName, + isOutdoorRoomType, + nextOutdoorRoomName, + OUTDOOR_ROOM_DEFAULTS, +} from '../../lib/outdoor-room' import { ownFloorIntentChanges } from '../../lib/own-floor-intent' import { drawnFloorElevationConflict } from '../../lib/room-drawn-floor' import { roundFloorElevation } from '../../lib/room-floor-feasibility' -import { ZoneNode } from '../../schema' +import { isGarageName, slabRoomDoorChanges, slabRoomElevation } from '../../lib/slab-room' +import { type AnyNode, type AnyNodeId, ZoneNode } from '../../schema' import { mezzanineElevationConflict } from './mezzanine-content' import { requireZone, type StructureNodes, type StructurePlan } from './shared' const floor = ZoneNode.shape.floor.unwrap() const ceiling = ZoneNode.shape.ceiling.unwrap() +const decking = ZoneNode.shape.decking.unwrap() export const ZoneIntentPatch = z.strictObject({ name: z.string().nullable().optional(), floor: z @@ -19,6 +27,31 @@ export const ZoneIntentPatch = z.strictObject({ elevation: floor.shape.elevation.unwrap().nullable().optional(), finish: floor.shape.finish.unwrap().nullable().optional(), regions: floor.shape.regions.unwrap().nullable().optional(), + foundation: floor.shape.foundation.unwrap().nullable().optional(), + drop: floor.shape.drop.unwrap().nullable().optional(), + }) + .nullable() + .optional(), + roomType: ZoneNode.shape.roomType.unwrap().nullable().optional(), + // A porch's posts and beam (`lib/porch-framing`), a deck's or patio's once switched on (`auto` + // true). Choosing a size or the spacing rebuilds them from the settings, automatic again, + // unless the patch says otherwise. + porchFraming: z + .strictObject({ + auto: z.boolean().nullable().optional(), + postSize: z.string().nullable().optional(), + postSpacing: z.number().positive().nullable().optional(), + beamSize: z.string().nullable().optional(), + }) + .nullable() + .optional(), + // A deck's boards (`lib/deck-boards`): the stock, the gap and the way they run; null clears one + // back to its default, a null `decking` all three. + decking: z + .strictObject({ + boardSize: decking.shape.boardSize.unwrap().nullable().optional(), + gap: decking.shape.gap.unwrap().nullable().optional(), + direction: decking.shape.direction.unwrap().nullable().optional(), }) .nullable() .optional(), @@ -44,12 +77,95 @@ export const ZoneIntentPatch = z.strictObject({ }) export type ZoneIntentPatch = z.infer +/** + * The patch with what the room's foundation asks of its floor: a choice of slab or raised floor, + * or a room named a garage, which stands on a slab unless its floor was chosen already. + */ +function withRoomFoundation( + nodes: StructureNodes, + zone: ZoneNode, + patch: ZoneIntentPatch, +): ZoneIntentPatch { + const asked = patch.floor?.foundation + const named = + asked === undefined && zone.floor?.foundation === undefined && isGarageName(patch.name) + ? 'slab' + : undefined + if (asked === undefined && named === undefined) return patch + const choice = asked ?? named ?? null + const scene = nodes as Record + // In a raised house a slab room's floor comes down to its slab on grade; a room back on the + // raised floor goes back up to it. + const lowered = choice === 'slab' ? slabRoomElevation(scene, zone) : null + const raised = choice !== 'slab' && zone.floor?.foundation === 'slab' + return { + ...patch, + floor: { + ...patch.floor, + foundation: choice, + ...(lowered !== null && patch.floor?.elevation === undefined ? { elevation: lowered } : {}), + ...(raised && patch.floor?.elevation === undefined ? { elevation: null } : {}), + }, + } +} + +/** + * The patch with what a change of room type brings (`lib/outdoor-room`): a porch, deck or patio + * steps its floor down from the house's, takes its floor finish, a ceiling or none, and its name + * while the room still has a default one; back to an ordinary room drops them again. What the + * patch sets itself wins. + */ +function withRoomType( + nodes: StructureNodes, + zone: ZoneNode, + patch: ZoneIntentPatch, +): ZoneIntentPatch { + const asked = patch.roomType + if (asked === undefined || (asked ?? undefined) === zone.roomType) return patch + const previous = isOutdoorRoomType(zone.roomType) ? OUTDOOR_ROOM_DEFAULTS[zone.roomType] : null + const next = isOutdoorRoomType(asked) ? OUTDOOR_ROOM_DEFAULTS[asked] : null + // A finish the old type gave the room goes with it; one the user picked stays. + const finish = zone.floor?.finish + const typeFinish = finish === undefined || finish === previous?.finish + const defaultName = /^Room \d+$/.test(zone.name) || isOutdoorRoomDefaultName(zone.name) + const names = Object.values(nodes).flatMap((node) => + node.type === 'zone' && node.parentId === zone.parentId && node.id !== zone.id + ? [node.name] + : [], + ) + return { + ...patch, + ...(patch.name === undefined && next && defaultName + ? { name: nextOutdoorRoomName(asked as keyof typeof OUTDOOR_ROOM_DEFAULTS, names) } + : {}), + ...(patch.hasCeiling === undefined ? { hasCeiling: next ? next.covered : true } : {}), + floor: { + ...patch.floor, + ...(patch.floor?.drop === undefined && patch.floor?.elevation === undefined + ? { drop: next ? next.drop : null } + : {}), + ...(patch.floor?.finish === undefined && typeFinish + ? { finish: next ? next.finish : null } + : {}), + }, + } +} + +/** + * A room's intent: its name, floor, ceiling and finishes. Its floor's foundation (`floor.foundation`) + * moves its floor with it in a raised house: a slab room (a garage, by choice or by its name) comes + * down to its slab on grade (`slabRoomElevation`), and back up when it goes back on the raised floor. + */ export function setZoneIntent( nodes: StructureNodes, input: { zoneId: string; patch: ZoneIntentPatch }, ): StructurePlan { const zone = requireZone(nodes, input.zoneId) - const patch = ZoneIntentPatch.parse(input.patch) + const patch = withRoomFoundation( + nodes, + zone, + withRoomType(nodes, zone, ZoneIntentPatch.parse(input.patch)), + ) const painted = zone.floor?.script && (patch.floor?.finish ?? patch.floor?.regions) const scripted = painted ? floorScriptPaintRefusal(zone) : undefined if (scripted) @@ -87,6 +203,36 @@ export function setZoneIntent( ? patch.floorStepOverrides : undefined if (patch.name !== undefined) data.name = patch.name ?? '' + if (patch.roomType !== undefined) data.roomType = patch.roomType ?? undefined + if (patch.porchFraming !== undefined) { + const asked = patch.porchFraming + const framing: Record = asked === null ? {} : { ...zone.porchFraming } + if (asked) + for (const key of ['auto', 'postSize', 'postSpacing', 'beamSize'] as const) { + if (asked[key] === null) delete framing[key] + else if (asked[key] !== undefined) framing[key] = asked[key] + } + const resized = + asked && (['postSize', 'postSpacing', 'beamSize'] as const).some((key) => key in asked) + if (resized && asked.auto === undefined) framing.auto = true + // A porch's are automatic by default, stored only when off; a deck's or patio's are off by + // default, stored when switched on. + const roomType = patch.roomType !== undefined ? (patch.roomType ?? undefined) : zone.roomType + if (roomType === 'porch' && framing.auto === true) delete framing.auto + data.porchFraming = Object.keys(framing).length + ? (framing as ZoneNode['porchFraming']) + : undefined + } + if (patch.decking !== undefined) { + const asked = patch.decking + const boards: Record = asked === null ? {} : { ...zone.decking } + if (asked) + for (const key of ['boardSize', 'gap', 'direction'] as const) { + if (asked[key] === null) delete boards[key] + else if (asked[key] !== undefined) boards[key] = asked[key] + } + data.decking = Object.keys(boards).length ? (boards as ZoneNode['decking']) : undefined + } if (patch.wallMaterial !== undefined) data.wallMaterial = patch.wallMaterial ?? undefined for (const key of ['hasFloor', 'hasCeiling'] as const) if (patch[key] !== undefined) data[key] = patch[key] === false ? false : undefined @@ -97,7 +243,15 @@ export function setZoneIntent( if (patch.floor !== undefined) { data.floor = patch.floor === null ? undefined : { ...zone.floor } if (patch.floor) - for (const key of ['elevation', 'finish', 'regions', 'thickness', 'footprint'] as const) { + for (const key of [ + 'elevation', + 'finish', + 'regions', + 'thickness', + 'footprint', + 'foundation', + 'drop', + ] as const) { const value = patch.floor[key] if (value === null) delete data.floor![key] else if (value !== undefined) @@ -141,8 +295,17 @@ export function setZoneIntent( } ZoneNode.parse({ ...zone, ...data }) const own = ownFloorIntentChanges(nodes, Object.keys(data).length ? [{ id: zone.id, data }] : []) + // A slab room's doors to the outside open from its slab: their sills move with its floor. + const sill = (room: ZoneNode) => + room.floor?.foundation === 'slab' ? (room.floor.elevation ?? 0) : 0 + const doors = slabRoomDoorChanges( + nodes as Record, + zone, + sill(zone), + sill({ ...zone, ...data } as ZoneNode), + ) return { - changes: own.updates.map((update) => ({ op: 'update', ...update })), + changes: [...own.updates, ...doors].map((update) => ({ op: 'update', ...update })), ...(own.conflicts.length ? { conflicts: own.conflicts } : {}), } } diff --git a/packages/core/src/commands/structure/wall-overlap.test.ts b/packages/core/src/commands/structure/wall-overlap.test.ts new file mode 100644 index 0000000000..fd785a4a38 --- /dev/null +++ b/packages/core/src/commands/structure/wall-overlap.test.ts @@ -0,0 +1,281 @@ +import { describe, expect, test } from 'bun:test' +import { type AnyNode, DoorNode, LevelNode, WallNode, ZoneNode } from '../../schema' +import { planWallInsertion } from '../../systems/wall/wall-topology' +import { setWallGeometry } from './set-wall-geometry' +import { applyToScratch, structureChangeBatch } from './shared' +import { finishWallEdit, retypeOutlineBridges, trimWallOverlaps } from './wall-overlap' + +const LEVEL = 'level_overlap' + +function scene(...nodes: AnyNode[]): Record { + const level = LevelNode.parse({ id: LEVEL }) + return Object.fromEntries( + [level, ...nodes.map((node) => ({ ...node, parentId: node.parentId ?? LEVEL }) as AnyNode)].map( + (node) => [node.id, node], + ), + ) +} +const wall = (id: string, start: [number, number], end: [number, number], extra = {}) => + WallNode.parse({ id: `wall_${id}`, start, end, ...extra }) +function minter() { + let i = 0 + return () => `wall_cut${++i}` +} +const walls = (nodes: Record) => + Object.values(nodes).filter((node): node is WallNode => node.type === 'wall') +const ends = (node: AnyNode | undefined) => { + const w = node as WallNode + return [w.start.map((v) => +v.toFixed(6)), w.end.map((v) => +v.toFixed(6))] +} + +describe('trimWallOverlaps', () => { + test('a wall stretched over the end of a collinear wall cuts it back to meet it', () => { + const nodes = scene(wall('placed', [1, 5], [10, 5]), wall('old', [-2, 5], [3, 5])) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())).toBeNull() + expect(ends(nodes.wall_old)).toEqual([ + [-2, 5], + [1, 5], + ]) + expect(ends(nodes.wall_placed)).toEqual([ + [1, 5], + [10, 5], + ]) + }) + + test('a wall landing inside a longer one splits it round itself', () => { + const nodes = scene(wall('placed', [3, 0], [5, 0]), wall('long', [0, 0], [10, 0])) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())).toBeNull() + expect(ends(nodes.wall_long)).toEqual([ + [0, 0], + [3, 0], + ]) + expect(ends(nodes.wall_cut1)).toEqual([ + [5, 0], + [10, 0], + ]) + expect(walls(nodes)).toHaveLength(3) + }) + + test('a wall it covers whole is deleted, its door moved onto the placed wall', () => { + const door = DoorNode.parse({ id: 'door_d', position: [1, 0, 0], width: 0.9 }) + const nodes = scene( + wall('placed', [0, 0], [6, 0]), + { ...wall('short', [4, 0], [2, 0]), children: [door.id] } as AnyNode, + { ...door, parentId: 'wall_short', wallId: 'wall_short' } as AnyNode, + ) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())).toBeNull() + expect(nodes.wall_short).toBeUndefined() + const moved = nodes.door_d as DoorNode + expect(moved.parentId).toBe('wall_placed') + // 1 m from (4, 0) back toward (2, 0) is x = 3 on the placed wall. + expect(moved.position[0]).toBeCloseTo(3) + expect((nodes.wall_placed as WallNode).children).toContain('door_d') + }) + + test('a door across the cut stops the edit', () => { + const door = DoorNode.parse({ id: 'door_d', position: [3, 0, 0], width: 0.9 }) + const nodes = scene( + wall('placed', [3.2, 0], [10, 0]), + { ...wall('old', [0, 0], [5, 0]), children: [door.id] } as AnyNode, + { ...door, parentId: 'wall_old', wallId: 'wall_old' } as AnyNode, + ) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())?.code).toBe('hosted-openings') + }) + + test('a thinner wall face-aligned inside a thicker one takes its stretch over', () => { + // Outside 0.2 thick on the line z = 0; a partition 0.1 thick, 5 cm in. + const nodes = scene( + wall('partition', [2, 0.05], [6, 0.05], { thickness: 0.1 }), + wall('outside', [0, 0], [8, 0], { thickness: 0.2 }), + ) + expect(trimWallOverlaps(nodes, ['wall_partition'], minter())).toBeNull() + expect(ends(nodes.wall_outside)).toEqual([ + [0, 0], + [2, 0], + ]) + expect(ends(nodes.wall_cut1)).toEqual([ + [6, 0], + [8, 0], + ]) + }) + + test('walls side by side, face to face, are left alone', () => { + const nodes = scene( + wall('placed', [0, 0], [6, 0], { thickness: 0.1 }), + wall('beside', [0, 0.1], [6, 0.1], { thickness: 0.1 }), + ) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())).toBeNull() + expect(ends(nodes.wall_beside)).toEqual([ + [0, 0.1], + [6, 0.1], + ]) + }) + + test('rooms that named the cut wall name its pieces and the placed wall', () => { + const zone = ZoneNode.parse({ + id: 'zone_r', + name: 'Room', + polygon: [ + [0, 0], + [10, 0], + [10, 4], + ], + boundaryWallIds: ['wall_long'], + }) + const nodes = scene(wall('placed', [3, 0], [5, 0]), wall('long', [0, 0], [10, 0]), zone) + expect(trimWallOverlaps(nodes, ['wall_placed'], minter())).toBeNull() + expect((nodes.zone_r as ZoneNode).boundaryWallIds).toEqual([ + 'wall_long', + 'wall_cut1', + 'wall_placed', + ]) + }) +}) + +describe('outline jogs', () => { + // A 10 × 6 outline 0.2 thick, a 0.1 partition at x = 5 splitting top and bottom. + const house = () => + scene( + wall('top1', [0, 0], [5, 0], { thickness: 0.2 }), + wall('top2', [5, 0], [10, 0], { thickness: 0.2 }), + wall('right', [10, 0], [10, 6], { thickness: 0.2 }), + wall('bot1', [10, 6], [5, 6], { thickness: 0.2 }), + wall('bot2', [5, 6], [0, 6], { thickness: 0.2 }), + wall('left', [0, 6], [0, 0], { thickness: 0.2 }), + wall('part', [5, 0], [5, 6], { thickness: 0.1 }), + ) + + test('pushing an outside wall out past a partition makes an outside jog', () => { + const nodes = house() + const plan = setWallGeometry(nodes, { + wallId: 'wall_top1', + start: [0, -1], + end: [5, -1], + mintId: (kind) => `${kind}_jog`, + }) + const after = applyToScratch(nodes, structureChangeBatch(plan.changes)) + expect(ends(after.wall_jog)).toEqual([ + [5, 0], + [5, -1], + ]) + expect((after.wall_jog as WallNode).thickness).toBe(0.2) + expect((after.wall_part as WallNode).thickness).toBe(0.1) + }) + + test('a jog inside the house keeps the wall it continues', () => { + const nodes = house() + const inside = wall('jog', [5, 3], [7, 3], { thickness: 0.1 }) + nodes[inside.id] = { ...inside, parentId: LEVEL } as AnyNode + retypeOutlineBridges(nodes, 'wall_top1', ['wall_jog']) + expect((nodes.wall_jog as WallNode).thickness).toBe(0.1) + }) +}) + +describe('setWallGeometry', () => { + test('landing exactly on the line of the wall below leaves no zero-length wall', () => { + // A room's left wall at x = 3 over a wider room below whose top runs from x = 0. + const nodes = scene( + wall('w2', [3, 0], [3, 5]), + wall('topRight', [3, 0], [10, 0]), + wall('belowLeft', [0, 5], [3, 5]), + wall('belowRight', [3, 5], [10, 5]), + wall('w3', [0, 10], [0, 5]), + ) + const plan = setWallGeometry(nodes, { + wallId: 'wall_w2', + start: [0, 0], + end: [0, 5], + mintId: (kind) => `${kind}_new`, + }) + const after = applyToScratch(nodes, structureChangeBatch(plan.changes)) + for (const w of walls(after)) { + expect(Math.hypot(w.end[0] - w.start[0], w.end[1] - w.start[1])).toBeGreaterThan(0.01) + } + expect(after.wall_belowLeft).toBeUndefined() + expect(ends(after.wall_belowRight)).toEqual([ + [0, 5], + [10, 5], + ]) + }) + + test('a wall stretched by a move takes over the collinear wall it now runs along', () => { + const nodes = scene( + wall('w2', [3, 0], [3, 5]), + wall('c', [3, 5], [10, 5]), + wall('e', [-2, 5], [2, 5]), + ) + const plan = setWallGeometry(nodes, { + wallId: 'wall_w2', + start: [1, 0], + end: [1, 5], + mintId: (kind) => `${kind}_new`, + }) + const after = applyToScratch(nodes, structureChangeBatch(plan.changes)) + expect(ends(after.wall_c)).toEqual([ + [1, 5], + [10, 5], + ]) + expect(ends(after.wall_e)).toEqual([ + [-2, 5], + [1, 5], + ]) + }) +}) + +describe('drawing over a wall', () => { + test('a wall drawn along part of another takes that stretch over', () => { + const nodes = scene(wall('old', [0, 0], [4, 0], { thickness: 0.1 })) + const result = planWallInsertion(nodes as never, { + levelId: LEVEL as never, + start: [2, 0], + end: [6, 0], + joinRadius: 0.05, + wallDefaults: { thickness: 0.2 }, + replaceCovered: true, + mintId: minter(), + }) + expect(result.ok).toBe(true) + if (!result.ok) return + const changes = finishWallEdit(nodes as never, result.plan.changes, { + winnerIds: result.plan.insertedWalls.map((w) => w.id), + mintId: minter(), + }) + const after = walls(applyToScratch(nodes as never, changes)) + const covering = after.filter( + (w) => Math.min(w.start[0], w.end[0]) < 4 - 1e-6 && Math.max(w.start[0], w.end[0]) > 2 + 1e-6, + ) + // One wall, the new one, along [2, 4]; the old one keeps [0, 2]. + expect(covering).toHaveLength(1) + expect(covering[0]!.thickness).toBe(0.2) + expect(after).toHaveLength(2) + }) + + test('a wall drawn exactly over another replaces it instead of being refused', () => { + const nodes = scene(wall('old', [0, 0], [4, 0], { thickness: 0.1 })) + const refused = planWallInsertion(nodes as never, { + levelId: LEVEL as never, + start: [0, 0], + end: [4, 0], + joinRadius: 0.05, + }) + expect(refused.ok).toBe(false) + const result = planWallInsertion(nodes as never, { + levelId: LEVEL as never, + start: [0, 0], + end: [4, 0], + joinRadius: 0.05, + wallDefaults: { thickness: 0.2 }, + replaceCovered: true, + mintId: minter(), + }) + expect(result.ok).toBe(true) + if (!result.ok) return + const changes = finishWallEdit(nodes as never, result.plan.changes, { + winnerIds: result.plan.insertedWalls.map((w) => w.id), + mintId: minter(), + }) + const after = walls(applyToScratch(nodes as never, changes)) + expect(after).toHaveLength(1) + expect(after[0]!.thickness).toBe(0.2) + }) +}) diff --git a/packages/core/src/commands/structure/wall-overlap.ts b/packages/core/src/commands/structure/wall-overlap.ts new file mode 100644 index 0000000000..30824ba711 --- /dev/null +++ b/packages/core/src/commands/structure/wall-overlap.ts @@ -0,0 +1,334 @@ +import { enclosedWallIds } from '../../lib/exterior-wall-loops' +import type { AnyNode, AnyNodeId, WallNode } from '../../schema' +import { isCurvedWall } from '../../systems/wall/wall-curve' +import { getWallFaceOffsets } from '../../systems/wall/wall-frame' +import { + WALL_MIN_LENGTH, + type WallTopologyChanges, + wallAttachmentSpan, +} from '../../systems/wall/wall-topology' +import type { StructureConflict } from './shared' +import { rehostWallChild, remapWallReferences, wallSlice } from './zone-wall-merge' + +/** + * A wall placed, moved or stretched over part of a parallel wall takes that + * stretch over: the other wall keeps only what lies past the placed wall's + * ends (cut back, split in two round it, or deleted when covered), so no + * length of wall is doubled. Doors, windows and wall items on the stretch + * taken over move onto the placed wall; rooms that named the cut wall name its + * pieces and the placed wall. + * + * Two walls run along each other when they are parallel and the body centre of + * the thinner lies inside the thicker's body: the same line, or a wall + * face-aligned inside a thicker one. Walls side by side, face to face, are + * left alone. The wall moves, the wall tool and the push arrows all finish + * through here. + */ + +// Walls within about 1° of each other count as parallel. +const PARALLEL_TOLERANCE = Math.sin(Math.PI / 180) +const ON_LINE = 1e-6 +const SPAN_TOLERANCE = 1e-4 + +type Nodes = Record +type Vec = [number, number] + +const dot = (a: Vec, b: Vec) => a[0] * b[0] + a[1] * b[1] +const sub = (a: Vec, b: Vec): Vec => [a[0] - b[0], a[1] - b[1]] + +function straightFrame(wall: WallNode) { + if (isCurvedWall(wall)) return null + const delta = sub(wall.end, wall.start) + const length = Math.hypot(delta[0], delta[1]) + if (length < WALL_MIN_LENGTH) return null + const dir: Vec = [delta[0] / length, delta[1] / length] + return { dir, normal: [-dir[1], dir[0]] as Vec, length } +} + +/** The wall's body across `normal` (a unit vector square to it), from `origin`. */ +function bodyAcross(wall: WallNode, wallNormal: Vec, normal: Vec, origin: Vec): [number, number] { + const { a, b } = getWallFaceOffsets(wall) + const line = dot(sub(wall.start, origin), normal) + const sign = Math.sign(dot(wallNormal, normal)) || 1 + return [line + Math.min(a * sign, b * sign), line + Math.max(a * sign, b * sign)] +} + +/** True when `other` runs along `wall`: parallel, the thinner body centred inside the thicker. */ +function runsAlong(wall: WallNode, other: WallNode) { + const own = straightFrame(wall) + const theirs = straightFrame(other) + if (!(own && theirs)) return false + if (Math.abs(own.dir[0] * theirs.dir[1] - own.dir[1] * theirs.dir[0]) > PARALLEL_TOLERANCE) { + return false + } + const ownBody = bodyAcross(wall, own.normal, theirs.normal, other.start) + const theirBody = bodyAcross(other, theirs.normal, theirs.normal, other.start) + const [thin, thick] = + ownBody[1] - ownBody[0] <= theirBody[1] - theirBody[0] + ? [ownBody, theirBody] + : [theirBody, ownBody] + const middle = (thin[0] + thin[1]) / 2 + return middle > thick[0] + SPAN_TOLERANCE && middle < thick[1] - SPAN_TOLERANCE +} + +/** Where `winner` covers `other`, as `[lo, hi]` of `other`; null under a wall's length. */ +function coveredSpan(winner: WallNode, other: WallNode) { + const frame = straightFrame(other) + if (!frame) return null + const station = (point: Vec) => dot(sub(point, other.start), frame.dir) / frame.length + const s0 = station(winner.start) + const s1 = station(winner.end) + const lo = Math.max(0, Math.min(s0, s1)) + const hi = Math.min(1, Math.max(s0, s1)) + return (hi - lo) * frame.length < WALL_MIN_LENGTH ? null : { frame, s0, s1, lo, hi } +} + +/** The walls on `wall`'s level it runs along for more than a sliver: what it would take over. */ +export function wallsRunAlong(nodes: Readonly, wall: WallNode): WallNode[] { + return Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && + node.id !== wall.id && + node.parentId === wall.parentId && + runsAlong(wall, node) && + coveredSpan(wall, node) !== null, + ) +} + +function wallAttachments(nodes: Nodes, wall: WallNode) { + return Object.values(nodes).filter( + (node) => node.parentId === wall.id || ('wallId' in node && node.wallId === wall.id), + ) +} + +/** A kept piece of a cut wall: `[t0, t1]` of it, the first keeping its id. */ +type Piece = { id: string; t0: number; t1: number } + +/** + * Cut `winner`'s stretch out of `other` in `nodes` (mutated). Null when done, + * a conflict when an opening straddles a cut. + */ +function cutAlong( + nodes: Nodes, + winner: WallNode, + other: WallNode, + mintId: () => string, +): StructureConflict | null { + const covered = coveredSpan(winner, other) + if (!covered) return null + const { frame, s0, s1, lo, hi } = covered + + // Where the cut wall now ends: on the winner's own end when the two share a + // line, so they meet exactly; else straight across from it on its own line. + const sameLine = + Math.abs(dot(sub(winner.start, other.start), frame.normal)) < ON_LINE && + Math.abs(dot(sub(winner.end, other.start), frame.normal)) < ON_LINE + const pointAt = (t: number): Vec => { + if (t <= 0) return [other.start[0], other.start[1]] + if (t >= 1) return [other.end[0], other.end[1]] + if (sameLine) { + if (Math.abs(s0 - t) * frame.length < ON_LINE) return [winner.start[0], winner.start[1]] + if (Math.abs(s1 - t) * frame.length < ON_LINE) return [winner.end[0], winner.end[1]] + } + return [ + other.start[0] + (other.end[0] - other.start[0]) * t, + other.start[1] + (other.end[1] - other.start[1]) * t, + ] + } + + const spans = [ + [0, lo], + [hi, 1], + ].filter(([t0, t1]) => (t1! - t0!) * frame.length >= WALL_MIN_LENGTH) + const pieces: Piece[] = spans.map(([t0, t1], index) => ({ + id: index === 0 ? other.id : mintId(), + t0: t0!, + t1: t1!, + })) + + // Every attachment goes to the kept piece it sits on, or onto the winner + // when it sits on the stretch taken over; one across a cut stops the edit. + const placed = new Map() + for (const child of wallAttachments(nodes, other)) { + const span = wallAttachmentSpan(child) + if (!span) { + placed.set(child.id, pieces[0] ?? 'winner') + continue + } + const t0 = span.min / frame.length + const t1 = span.max / frame.length + const tolerance = SPAN_TOLERANCE / frame.length + const piece = pieces.find((p) => t0 >= p.t0 - tolerance && t1 <= p.t1 + tolerance) + if (piece) placed.set(child.id, piece) + else if (t0 >= lo - tolerance && t1 <= hi + tolerance) placed.set(child.id, 'winner') + else + return { + code: 'hosted-openings', + nodeIds: [child.id], + message: 'A door or window would sit across where the walls meet.', + } + } + + const slices = pieces.map((piece) => + wallSlice(other, pointAt(piece.t0), pointAt(piece.t1), piece.id, [piece.t0, piece.t1]), + ) + const winnerChildren = new Set(winner.children ?? []) + for (const child of wallAttachments(nodes, other)) { + const target = placed.get(child.id) + if (target === 'winner') { + rehostWallChild(nodes, child, other, winner) + winnerChildren.add(child.id) + continue + } + if (!target) continue + const slice = slices[pieces.indexOf(target)]! + slice.children = [...(slice.children ?? []), child.id] as WallNode['children'] + if (!('position' in child && Array.isArray(child.position))) { + nodes[child.id] = { ...child, parentId: slice.id } as AnyNode + continue + } + const x = child.position[0] - target.t0 * frame.length + const length = (target.t1 - target.t0) * frame.length + nodes[child.id] = { + ...child, + parentId: slice.id, + ...('wallId' in child ? { wallId: slice.id } : {}), + position: [x, child.position[1], child.position[2]], + ...('wallT' in child ? { wallT: length > 1e-6 ? x / length : 0 } : {}), + } as AnyNode + } + nodes[winner.id] = { ...winner, children: [...winnerChildren] } as AnyNode + remapWallReferences(nodes, other, [...slices, nodes[winner.id] as WallNode]) + delete nodes[other.id] + for (const slice of slices) nodes[slice.id] = slice + return null +} + +/** + * Let each wall of `winnerIds` take over the stretches of parallel walls it + * runs along (`nodes` is mutated). Walls among the winners don't cut each + * other. A conflict when an opening would straddle a cut; `nodes` may then + * be half done, so callers work on a copy. + */ +export function trimWallOverlaps( + nodes: Nodes, + winnerIds: readonly string[], + mintId: () => string, +): StructureConflict | null { + const winners = new Set(winnerIds) + for (const id of winnerIds) { + const winner = nodes[id] + if (winner?.type !== 'wall' || !straightFrame(winner)) continue + for (const other of wallsRunAlong(nodes, winner).filter((node) => !winners.has(node.id))) { + const conflict = cutAlong(nodes, nodes[id] as WallNode, other, mintId) + if (conflict) return conflict + } + } + return null +} + +/** + * The fields that make a wall's construction (its wall type): assembly, + * thickness, finishes and trims. Position, support, paint regions, height and + * justification stay with each wall. + */ +export const WALL_CONSTRUCTION_FIELDS = [ + 'thickness', + 'wallType', + 'curtainWall', + 'railing', + 'assembly', + 'material', + 'materialPreset', + 'interiorMaterial', + 'interiorMaterialPreset', + 'exteriorMaterial', + 'exteriorMaterialPreset', + 'slots', + 'legacyFaceMaterials', + 'skirting', + 'crown', + 'chairRail', + 'finishScripts', + 'cap', +] as const satisfies ReadonlyArray + +/** `wall`'s construction as a patch for another wall (unset fields cleared). */ +export function wallConstructionPatch(wall: WallNode): Partial { + const patch: Record = {} + for (const field of WALL_CONSTRUCTION_FIELDS) patch[field] = structuredClone(wall[field]) + return patch as Partial +} + +/** True when two walls are built the same (`WALL_CONSTRUCTION_FIELDS`). */ +export function sameWallConstruction(a: WallNode, b: WallNode): boolean { + return JSON.stringify(wallConstructionPatch(a)) === JSON.stringify(wallConstructionPatch(b)) +} + +/** + * The jog walls a move bridges in (`bridgeIds`) copy the wall they continue — + * often a partition, when an outside wall is pushed out past one. A jog left + * on the outline of the storey, beside a moved wall that is on it too, is an + * outside wall: it takes the moved wall's construction instead (`nodes` is + * mutated). + */ +export function retypeOutlineBridges( + nodes: Nodes, + movedWallId: string, + bridgeIds: readonly string[], +): void { + const moved = nodes[movedWallId] + if (moved?.type !== 'wall' || bridgeIds.length === 0) return + const walls = Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && node.parentId === moved.parentId && node.visible !== false, + ) + const enclosed = enclosedWallIds(walls) + if (enclosed.has(moved.id)) return + const patch = wallConstructionPatch(moved) + for (const id of bridgeIds) { + const bridge = nodes[id] + if (bridge?.type !== 'wall' || enclosed.has(bridge.id)) continue + nodes[id] = { ...bridge, ...patch } as AnyNode + } +} + +/** + * `changes` (a planned wall edit against `nodes`) with the overlaps its + * `winnerIds` now run along trimmed, and its outline jogs (`bridgeIds`, beside + * `movedWallId`) retyped. The plan unchanged when an opening would straddle a + * cut. + */ +export function finishWallEdit( + nodes: Record, + changes: WallTopologyChanges, + options: { + winnerIds: readonly string[] + movedWallId?: string + bridgeIds?: readonly string[] + mintId: () => string + }, +): WallTopologyChanges { + const scratch: Nodes = { ...nodes } + for (const id of changes.delete) delete scratch[id] + for (const { node, parentId } of changes.create) + scratch[node.id] = { ...node, parentId: parentId ?? node.parentId } as AnyNode + for (const { id, data } of changes.update) + if (scratch[id]) scratch[id] = { ...scratch[id], ...data } as AnyNode + if (options.movedWallId) { + retypeOutlineBridges(scratch, options.movedWallId, options.bridgeIds ?? []) + } + const trimmed = { ...scratch } + const conflict = trimWallOverlaps(trimmed, options.winnerIds, options.mintId) + const after = conflict ? scratch : trimmed + const before = nodes as Nodes + return { + create: Object.values(after) + .filter((node) => !before[node.id]) + .map((node) => ({ node, parentId: node.parentId as AnyNodeId })), + update: Object.values(after) + .filter((node) => before[node.id] && node !== before[node.id]) + .map((node) => ({ id: node.id as AnyNodeId, data: node as Partial })), + delete: Object.keys(before).filter((id) => !after[id]) as AnyNodeId[], + } +} diff --git a/packages/core/src/contracts/reference-inventory.ts b/packages/core/src/contracts/reference-inventory.ts index 204b9908e1..6a52650aa4 100644 --- a/packages/core/src/contracts/reference-inventory.ts +++ b/packages/core/src/contracts/reference-inventory.ts @@ -81,7 +81,10 @@ const MATERIAL_FIELDS = [ 'edgeMaterial', 'exteriorMaterial', 'fasciaMaterial', + 'gableLeftMaterial', + 'gableRightMaterial', 'glassMaterial', + 'heelMaterial', 'interiorMaterial', 'panelMaterial', 'railingMaterial', @@ -660,7 +663,14 @@ export const EXISTING_REFERENCES: readonly ExistingReference[] = [ note: 'Capture provenance: stable across re-capture (R9).', }), ), - ...(['assembly.layers[].src', 'assembly.backing[].src'] as const).map((path) => + ...( + [ + 'assembly.layers[].src', + 'assembly.backing[].src', + 'wallTypes[].assembly.layers[].src', + 'wallTypes[].assembly.backing[].src', + ] as const + ).map((path) => row({ kind: '*', path, @@ -668,7 +678,14 @@ export const EXISTING_REFERENCES: readonly ExistingReference[] = [ note: 'Layer provenance (F2): a preset strips it so it never claims a source element.', }), ), - ...(['assembly.layers[].slot', 'assembly.backing[].slot'] as const).map((path) => + ...( + [ + 'assembly.layers[].slot', + 'assembly.backing[].slot', + 'wallTypes[].assembly.layers[].slot', + 'wallTypes[].assembly.backing[].slot', + ] as const + ).map((path) => row({ kind: '*', path, @@ -676,11 +693,19 @@ export const EXISTING_REFERENCES: readonly ExistingReference[] = [ note: "A key into the host's own `slots`; a missing key falls back to `layer:`, then the role default.", }), ), + ...(['assembly.presetId', 'wallTypes[].assembly.presetId'] as const).map((path) => + row({ + kind: '*', + path, + ...policy('asset', 'content', 'freeze', 'keep'), + note: 'The assembly preset a layer stack was made from.', + }), + ), row({ kind: '*', - path: 'assembly.presetId', + path: 'buildUp.presetId', ...policy('asset', 'content', 'freeze', 'keep'), - note: 'The assembly preset a layer stack was made from.', + note: 'The build-up preset a roof plane or ceiling took its layers from.', }), // ─── Typed provenance (D5) ──────────────────────────────────────── @@ -739,11 +764,23 @@ export const NON_REFERENCES: readonly { kind: string; path: string; reason: stri { kind: 'block', path: 'topology.vertices[].id', reason: 'Defines a topology key.' }, { kind: 'block', path: 'topology.edges[].id', reason: 'Defines a topology key.' }, { kind: 'block', path: 'topology.faces[].id', reason: 'Defines a topology key.' }, - ...(['assembly.layers[].id', 'assembly.backing[].id'] as const).map((path) => ({ + ...( + [ + 'assembly.layers[].id', + 'assembly.backing[].id', + 'wallTypes[].assembly.layers[].id', + 'wallTypes[].assembly.backing[].id', + ] as const + ).map((path) => ({ kind: '*', path, reason: 'Defines an assembly layer key (the `#layer:` address).', })), + { + kind: 'site', + path: 'wallTypes[].id', + reason: "Defines a project wall type's key (what a wall's `assembly.presetId` names).", + }, { kind: 'gutter', path: 'outlets[].id', @@ -961,6 +998,12 @@ export const METADATA_REFERENCES: readonly ExistingReference[] = [ targetKinds: ['slab'], note: 'The deck a balcony railing or deck belongs to.', }), + meta('porchOf', { + ...policy('node', 'internal', 'drop', 'strip'), + targetKinds: ['zone'], + dependents: ['metadata.porchPart', 'metadata.porchSeed'], + note: "The porch room a post or beam was made for by the structure kernel (lib/porch-framing-sync), which part of its framing and its shape as made; a porch gone takes its parts with it, one edited by hand stays the user's own.", + }), ] const described = (reason: string, paths: readonly string[]) => @@ -1000,6 +1043,7 @@ export const METADATA_NON_REFERENCES: readonly { path: string; reason: string }[ 'cabinetAdjacencyRevision', 'cabinetLayoutRevision', 'cabinetPresetNominalWidth', + 'decking', 'deferParentRebuild', 'drawingCoordinationLocked', 'floor', @@ -1094,6 +1138,10 @@ export const METADATA_NON_REFERENCES: readonly { path: string; reason: string }[ "A room's declaration of artificial light and mechanical ventilation (set_zone_intent): booleans, not ids.", ['daylight'], ), + ...described( + "A building's foundation record (type, support, finish floor over grade, sizes in inches): numbers, flags and enums; names nothing.", + ['foundation', 'foundation.ffAboveGradeIn', 'foundation.type'], + ), ...described('Site-plan facts kept on the node: values, not ids.', [ 'flatwork', 'flatworkDims', diff --git a/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts b/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts index 50db417f90..ccb4783d17 100644 --- a/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts +++ b/packages/core/src/hooks/spatial-grid/spatial-grid-sync.ts @@ -1,3 +1,4 @@ +import { isDeckSlab } from '../../lib/deck-framing' import { resolveLevelId } from '../../lib/node-ancestry' import { isFloorPlate } from '../../lib/plate-surface' import { subtractPolygonsFromPolygon } from '../../lib/polygon-union' @@ -524,6 +525,11 @@ export function markTerrainSupportDependents( for (const node of Object.values(nodes)) { if (node.type === 'slab') { + // A deck's posts run down to the ground under each, on whatever storey it stands. + if (isDeckSlab(node)) { + markDirty(node.id) + continue + } const level = nodes[resolveLevelId(node, nodes)] if ( node.plateRole === 'base' && diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 678a19007a..46ca00cee1 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -132,6 +132,32 @@ export { resolveAssemblyStack, } from './lib/assembly-stack' export { loadAssetUrl, saveAsset } from './lib/asset-storage' +export { + BEAM_MATERIAL_LABELS, + BEAM_MATERIAL_LIBRARY_REFS, + BEAM_TOP_REFERENCE_LABELS, + BEAM_TOP_REFERENCES, + type BeamInput, + type BeamPlan, + type BeamTopReference, + beamBottomElevation, + beamCeilingPlane, + beamOutline, + beamPlan, + beamSizeLabel, + beamSizePreset, + beamSizePresetPatch, + beamsOverPoint, + beamTopElevation, + beamTopReferenceOf, + beamTopReferencePatch, + createBeam, + inchFractionLabel, + type LevelFloorAt, + levelFloorAt, + matchBeamSizePreset, + postStationsAlong, +} from './lib/beam' export { belowGradeFloorY, belowGradeFootprints, @@ -140,6 +166,39 @@ export { GROUND_PIT_PLATE_CLEARANCE, groundPitFloorY, } from './lib/below-grade-footprints' +export { + CEILING_BUILD_UP_PRESETS, + type CeilingBuildUpPreset, + ceilingBuildUpThickness, + ceilingCapLift, + DEFAULT_FLOOR_BUILD_UP, + DEFAULT_RAISED_FLOOR_BUILD_UP, + DRYWALL_THICKNESSES, + FLOOR_BUILD_UP_PRESETS, + FLOORING_THICKNESS, + type FloorBuildUpPreset, + FRAMING_SPACINGS, + floorBuildUpMatches, + floorBuildUpOf, + floorBuildUpPatch, + floorBuildUpThickness, + floorLayerDepths, + floorStructureDepth, + I_JOIST_DEPTH, + LUMBER_DEPTH, + PANEL_THICKNESSES, + PLASTER_THICKNESS, + ROOF_BUILD_UP_PRESETS, + ROOFING_THICKNESS, + type RoofBuildUpPreset, + roofBuildUpDepths, + roofBuildUpOfDeck, + roofBuildUpPatch, + roofStructureDepth, + SUBFLOOR_THICKNESSES, + UNDERLAYMENT_THICKNESS, + WOOD_PLANK_THICKNESS, +} from './lib/build-up' export { footprintOutline, levelFootprintLoops, @@ -148,6 +207,25 @@ export { turnPlanPoint, yawOf, } from './lib/building-footprint' +export { + BUILDING_FOUNDATION_DIMENSIONS, + BUILDING_FOUNDATION_LIMITS, + type BuildingFoundation, + type BuildingFoundationDimension, + type BuildingFoundationRecord, + type BuildingFoundationSupport, + type BuildingFoundationType, + buildingFoundationDefaults, + buildingFoundationOf, + buildingFoundationRecordOf, + buildingStoreyCount, + FOUNDATION_SILL_IN, + ffMinimumIn, + isBuildingFoundationOn, + resolveBuildingFoundation, + SLAB_EDGE_TAPER_IN, + slabEdgeTaper, +} from './lib/building-foundation' export { CEILING_SURFACE_ROLE, type CeilingSurfaceCell, @@ -167,7 +245,64 @@ export { } from './lib/corner-snap' export { criticallyDamped } from './lib/critically-damped' export { isCutterName, resolveCutterHost } from './lib/cutter-host' +export { + DECK_BOARD_DIRECTIONS, + DECK_BOARD_GAPS, + DECK_BOARD_JOINT_GAP, + DECK_BOARD_MAX_LENGTH, + DECK_BOARD_SIZES, + DECK_BOARD_STOCK, + DEFAULT_DECK_BOARDS, + type DeckBoardAxes, + type DeckBoardDirection, + type DeckBoardJoists, + type DeckBoardRun, + type DeckBoardSettings, + type DeckBoardSize, + type DeckBoardStock, + deckBoardRun, + deckBoardSettings, + deckBoardThickness, + deckJoistSpacingLimit, + deckSlabBoards, + layoutDeckBoards, +} from './lib/deck-boards' +export { + DECK_BEAM_INSET, + DECK_BOARD_OVERHANG, + DECK_DROPPED_BEAM, + DECK_JOIST_SPANS, + DECK_MIN_POST_HEIGHT, + DECK_POST, + DECK_POST_END_INSET, + DECK_POST_SPACING, + type DeckAxes, + type DeckBeamKind, + type DeckBeamPiece, + type DeckFramingInput, + type DeckFramingPlan, + type DeckHanger, + type DeckJoistPiece, + type DeckJoistSpan, + type DeckLumber, + type DeckPiece, + type DeckPieceRole, + type DeckPostPiece, + type DeckWall, + deckAxes, + deckLedgerEdge, + isDeckSlab, + planDeckFraming, +} from './lib/deck-framing' export { deepestDrawnY, lowestGroundY } from './lib/deepest-drawn' +export { + type BasementReport, + type DesignedFoundationReport, + type FoundationOutline, + type FoundationRimStep, + foundationOutlines, + SLAB_ROOM_CURB, +} from './lib/designed-foundation' export { clampDoorOperationState, getDoorRenderOpenAmount, @@ -240,6 +375,16 @@ export { withFloorStepOverride, withoutFloorStepOverrideKeys, } from './lib/floor-step-finish' +export { + FOOTING_MINIMUM, + type FootingDesign, + type FootingPlan, + footingDesign, + footingLevelOf, + footingMembers, + footingOutline, + footingPlan, +} from './lib/footing' export { FOUNDATION_CONSTRUCTIONS, type FoundationConstruction, @@ -249,8 +394,56 @@ export { foundationReadingOf, foundationRegionOf, } from './lib/foundation-construction' +export { + FOUNDATION_GRADING, + finishedGradeAt, + type GradingPad, + gradedTerrainFieldOf, + gradeTerrainField, + isExcavation, +} from './lib/foundation-grading' export { FOUNDATION_STANDARD } from './lib/foundation-standard' -export { type FoundationMember, genericFoundation } from './lib/generic-foundation' +export { + buildingFoundationMembers, + buildingFoundationNodes, + type FoundationBadge, + type FoundationPlanShape, + type FoundationView, + foundationBadge, + foundationPlan, + foundationViewHiddenIds, + foundationViewOf, + traceFoundationOutlines, +} from './lib/foundation-view' +export { + createFramingPost, + DEFAULT_FRAMING_POST_SIZE, + FRAMING_POST_BASE_LABELS, + FRAMING_POST_MATERIAL_REF, + FRAMING_POST_MAX_HEIGHT, + FRAMING_POST_MIN_HEIGHT, + FRAMING_POST_SIZES, + type FramingPostInput, + type FramingPostSize, + type FramingPostTop, + framingPostBasePatch, + framingPostFields, + framingPostHeightLimit, + framingPostSize, + framingPostTopAt, + isFramingPost, +} from './lib/framing-post' +export { + designedFoundationReport, + type FoundationMember, + foundationGradingPads, + foundationRimBottom, + foundationRimBottoms, + foundationRimSteps, + foundationSharedStemTops, + genericFoundation, + isHouseOutline, +} from './lib/generic-foundation' export { ceilingSeatY, isScriptedNode, @@ -278,6 +471,7 @@ export { GROUND_FLOOR_ABOVE_GRADE, GROUND_SEAT_TOLERANCE, type GroundSeat, + type GroundSeatRule, gradeUnderLevel, groundSeat, } from './lib/ground-seat' @@ -322,6 +516,18 @@ export { hostedCutterHoles, withHostedCutterHoles, } from './lib/object-cuts' +export { + DEFAULT_OPENING_CASING_WIDTH, + OPENING_CASING_PROUD, + type OpeningCasingBoard, + type OpeningCasingFace, + type OpeningCasingHost, + openingCasingBoards, + openingCasingFaces, + openingCasingWidth, + openingHostFacing, + resolveOpeningCasingHost, +} from './lib/opening-casing' export { getOpeningFloorDatum, getOpeningWallCut, @@ -335,6 +541,18 @@ export { resolveHostedOpeningFrameDepth, resolveOpeningFrameDepth, } from './lib/opening-frame-depth' +export { + DECK_FLOOR_DROP, + DECKING_THICKNESS, + isOutdoorRoom, + isOutdoorRoomType, + nextOutdoorRoomName, + OUTDOOR_ROOM_DEFAULTS, + OUTDOOR_ROOM_TYPES, + type OutdoorRoomDefaults, + type OutdoorRoomType, + PORCH_FLOOR_DROP, +} from './lib/outdoor-room' export { classifyPlateSideAt, clearPlateSurfaceCaches, @@ -389,6 +607,34 @@ export { subtractPolygonsFromPolygon, unionPolygons, } from './lib/polygon-union' +export { + DEFAULT_PORCH_FRAMING, + PORCH_POST_SETBACK, + PORCH_POST_SPACING, + type PorchBeamPlan, + type PorchFramingPlan, + type PorchFramingSettings, + type PorchPostPlan, + planPorchFraming, + porchFramingSettings, +} from './lib/porch-framing' +export { type PorchPartMetadata, porchPartOf } from './lib/porch-framing-sync' +export { + type EavePoint, + eaveContains, + FASCIA_DROP, + FASCIA_STOCK, + FASCIA_THICKNESS, + type FasciaStock, + fasciaStockFor, + LEVEL_CUT_RUN, + type RoofEaveInput, + type RoofEaveProfile, + roofEaveProfile, + roofSegmentEaveInput, + roofSegmentEaveProfile, + SOFFIT_THICKNESS, +} from './lib/roof-eave' export { compareRoofOverlapIdentity, getRoofPlanBounds, @@ -483,6 +729,12 @@ export { slabPolygonContextFromGeometry, snapSlabEdgeToWallBand, } from './lib/slab-polygon' +export { + doorSillFloorOf, + isGarageName, + roomFoundationOf, + slabRoomElevation, +} from './lib/slab-room' export { deriveSlotId, isSlotMaterialName, @@ -621,6 +873,7 @@ export { getLibraryMaterialIdFromRef, getLibraryMaterialsVersion, getMaterialPresetByRef, + getMaterialSwatch, getMaterialsForCategory, getSceneMaterialIdFromRef, isCommunityMaterialId, @@ -633,6 +886,7 @@ export { type MaterialRef, type MaterialSource, type MaterialSurface, + type MaterialSwatch, materialColorPaint, type ParsedMaterialRef, parseMaterialColor, @@ -836,12 +1090,15 @@ export { isSplineFence, sampleFenceSpline, } from './systems/fence/fence-spline' -export { planOwnedFloorOpenings } from './systems/owned-floor-openings' +export { authorStairOpeningPatches, planOwnedFloorOpenings } from './systems/owned-floor-openings' export { resolveRoofElevation, resolveRoofWallTopElevation } from './systems/roof/roof-elevation' export { RoofElevationSystem } from './systems/roof/roof-elevation-system' export { fitRoofFootprint, + outdoorRoomRoofOutline, type RoofFootprintTarget, + type RoofOutlineEdge, + resolveOutdoorRoomRoofFootprintOnLevel, resolveRoomRoofFootprint, resolveRoomRoofFootprintOnLevel, } from './systems/roof/roof-footprint' @@ -854,6 +1111,7 @@ export { type SlabElevationClamp, type WallSlabSupport, } from './systems/slab/slab-support' +export { stairBaseElevation } from './systems/stair/stair-base-elevation' export { measureStairHeadroom, resolveStairWalkingSurfaces, @@ -932,6 +1190,42 @@ export { planStairSizingEdit, type StairDiagnostic, } from './systems/stair/stair-sizing' +export { + autoStairStringerCount, + DEFAULT_STAIR_NOSING, + formatStairInches, + inferStairStructure, + type ResolvedStairConstruction, + type ResolvedStairStructure, + resolveStairStringerLayout, + resolveStairStructure, + STAIR_CODE, + STAIR_HOUSING_DEPTH, + STAIR_LANDING_JOIST, + STAIR_LANDING_JOIST_SPACING, + STAIR_LANDING_POST, + STAIR_RISER_THICKNESS, + STAIR_STEEL_STRINGER, + STAIR_STRINGER_STOCK, + STAIR_TREAD_STOCK, + type StairFrameBox, + type StairStringerBand, + type StairStringerCut, + type StairStringerFlight, + type StairStringerLayout, + type StairStringerSlice, + type StairStructureHost, + type StairStructureIssue, + stairIsExterior, + stairLandingFrame, + stairStringerCut, + stairStringerEdges, + stairStringerFlight, + stairStringerSlices, + stairStructureConstruction, + stairStructureConstructionOf, + stairStructureDiagnostics, +} from './systems/stair/stair-structure' export { planStairSweepEdit } from './systems/stair/stair-sweep-edit' export { resolveStairWalkingPaths, @@ -946,6 +1240,7 @@ export { type StairWinderPoint, } from './systems/stair/stair-winder' export { + assemblyLayerSlotKey, assemblyThickness, BRICK_AIR_SPACE, BRICK_VENEER, @@ -959,7 +1254,14 @@ export { GYPSUM_SHEATHING, getWallAssemblyPreset, getWallLayerPolylines, + isExteriorWallAssembly, isLegacyWallAssembly, + isWallCladdingProduct, + LGS_STUD_350, + LGS_STUD_550, + LGS_STUD_800, + parseWallTypes, + projectWallTypes, type ResolvedWallAssembly, resolveWallAssembly, resolveWallExteriorSide, @@ -968,23 +1270,35 @@ export { STUCCO_3_COAT, STUD_2X4, STUD_2X6, + STUD_2X8, + STUD_SPACING_16, + STUD_SPACING_24, WALL_ASSEMBLY_PRESETS, WALL_FINISH_LIBRARY_REF, + WALL_TYPE_CORES, type WallAssemblyLayer, type WallAssemblyLayerRole, type WallAssemblyPreset, type WallLayerMiterData, type WallLayerPolyline, + type WallTypeCore, WSP_SHEATHING, + wallAssemblyCladding, wallAssemblyExposedStructure, wallAssemblyExteriorFinish, + wallAssemblyFaceFinish, wallAssemblyFinishRef, + wallAssemblyFinishSlots, wallAssemblyFraming, wallAssemblyFromLegacy, wallAssemblyPatch, wallAssemblyToLegacy, wallAssemblyUnverifiedNote, + wallCladdingProducts, wallLayerBoundaryOffsets, + wallPaintProducts, + wallTypeAssembly, + wallTypeCoreOf, } from './systems/wall/wall-assembly' export { constrainWallCurveOffsetToAvoidIntersections, @@ -1062,6 +1376,7 @@ export { getLinkedWallUpdates, getPerpendicularWallMoveAxis, getPlannedLinkedWallUpdates, + planAttachedWallUpdates, planWallMoveJunctions, type WallMoveAxis, type WallMoveBridgePlan, @@ -1085,8 +1400,10 @@ export { wallFollowsStorey, } from './systems/wall/wall-top' export { + type DrawnWallTypes, findOpenWallEnds, planJoinOpenWallEnd, + planSeparatorInsertion, planWallEndRejoins, planWallInsertion, planWallSplitAtPoint, diff --git a/packages/core/src/lib/beam.test.ts b/packages/core/src/lib/beam.test.ts new file mode 100644 index 0000000000..de3f0a9a15 --- /dev/null +++ b/packages/core/src/lib/beam.test.ts @@ -0,0 +1,271 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + AnyNode as AnyNodeSchema, + BEAM_SIZE_PRESETS, + BeamNode, + BuildingNode, + LevelNode, + SiteNode, + SlabNode, +} from '../schema' +import { + beamBottomElevation, + beamCeilingPlane, + beamOutline, + beamSizeLabel, + beamSizePreset, + beamSizePresetPatch, + beamsOverPoint, + beamTopElevation, + beamTopReferenceOf, + beamTopReferencePatch, + createBeam, + inchFractionLabel, + levelFloorAt, + matchBeamSizePreset, + postStationsAlong, +} from './beam' + +const IN = 0.0254 +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes + +/** Two storeys 2.7 m apart; the ground floor's slab tops out 0.05 m over its plane, 0.2 m thick. */ +function house(beams: BeamNode[], above: SlabNode[] = []) { + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ id: 'building_a', parentId: 'site_a', children: ['level_g', 'level_u'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + height: 2.7, + children: ['slab_g', ...beams.map((b) => b.id)], + }), + LevelNode.parse({ + id: 'level_u', + parentId: 'building_a', + level: 1, + height: 2.7, + children: above.map((s) => s.id), + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + elevation: 0.05, + thickness: 0.2, + }), + ...beams, + ...above, + ]) +} + +const beam = (fields: Partial = {}) => + BeamNode.parse({ id: 'beam_a', parentId: 'level_g', start: [2, 4], end: [6, 4], ...fields }) + +describe('beam sizes', () => { + test('the stock sizes carry their dressed sections', () => { + const size = (id: string) => beamSizePreset(id)! + expect(size('4x8').width).toBeCloseTo(3.5 * IN) + expect(size('4x8').depth).toBeCloseTo(7.25 * IN) + expect(size('6x8').width).toBeCloseTo(5.5 * IN) + expect(size('6x8').depth).toBeCloseTo(7.5 * IN) + expect(size('6x12').depth).toBeCloseTo(11.5 * IN) + // a built-up beam is its plies side by side + expect(size('(3)2x10')).toMatchObject({ size: '2x10', plies: 3, label: '(3) 2x10' }) + expect(size('(3)2x10').width).toBeCloseTo(4.5 * IN) + expect(size('(2)lvl-1-3/4x11-7/8').width).toBeCloseTo(3.5 * IN) + expect(size('W8x10').material).toBe('steel') + // a label finds its size as well as an id + expect(beamSizePreset('(2) 2x10')?.id).toBe('(2)2x10') + expect(new Set(BEAM_SIZE_PRESETS.map((preset) => preset.id)).size).toBe( + BEAM_SIZE_PRESETS.length, + ) + }) + + test('a beam reads as a framing plan prints its size', () => { + expect(beamSizeLabel(createBeam({ start: [0, 0], end: [3, 0] }))).toBe('6x8') + expect(beamSizeLabel(createBeam({ start: [0, 0], end: [3, 0], preset: '(2)2x10' }))).toBe( + '(2) 2x10', + ) + expect( + beamSizeLabel( + createBeam({ start: [0, 0], end: [3, 0], preset: '6x8', material: 'pressure-treated' }), + ), + ).toBe('6x8 PT') + expect( + beamSizeLabel(createBeam({ start: [0, 0], end: [3, 0], preset: 'lvl-1-3/4x11-7/8' })), + ).toBe('1-3/4x11-7/8 LVL') + expect(beamSizeLabel(createBeam({ start: [0, 0], end: [3, 0], preset: 'W8x10' }))).toBe('W8x10') + // a section that is no stock size prints its actual inches + expect( + beamSizeLabel(createBeam({ start: [0, 0], end: [3, 0], width: 3.5 * IN, depth: 9 * IN })), + ).toBe('3-1/2x9') + // a schema default is the 6x8 it is sized as, though it names no size + expect(beamSizeLabel(BeamNode.parse({ start: [0, 0], end: [3, 0] }))).toBe('6x8') + }) + + test('inches print in sixteenths, reduced', () => { + expect(inchFractionLabel(3.5 * IN)).toBe('3-1/2') + expect(inchFractionLabel(11.875 * IN)).toBe('11-7/8') + expect(inchFractionLabel(0.75 * IN)).toBe('3/4') + expect(inchFractionLabel(9 * IN)).toBe('9') + }) + + test('a size matches its preset, and switching size keeps a treated beam treated', () => { + const treated = createBeam({ + start: [0, 0], + end: [3, 0], + preset: '(2)2x10', + material: 'pressure-treated', + }) + expect(matchBeamSizePreset(treated)?.id).toBe('(2)2x10') + expect(beamSizePresetPatch(treated, '6x10')).toMatchObject({ + size: '6x10', + plies: 1, + material: 'pressure-treated', + }) + expect(beamSizePresetPatch(treated, 'glulam-5-1/8x12').material).toBe('glulam') + expect(matchBeamSizePreset({ ...treated, width: 2 * IN })).toBeNull() + }) +}) + +describe('createBeam', () => { + test('builds a beam on its level from a centreline and a size', () => { + const made = createBeam({ + start: [1, 2], + end: [5, 2], + preset: '(2) 2x12', + topElevation: 2.4, + levelId: 'level_g', + supportSlabId: 'slab_porch', + }) + expect(made.type).toBe('beam') + expect(made.id).toStartWith('beam_') + expect(made).toMatchObject({ + parentId: 'level_g', + start: [1, 2], + end: [5, 2], + size: '2x12', + plies: 2, + material: 'sawn', + placement: 'dropped', + topElevation: 2.4, + supportSlabId: 'slab_porch', + }) + expect(made.depth).toBeCloseTo(11.25 * IN) + // it is a node the scene's union accepts + expect(AnyNodeSchema.parse(made).type).toBe('beam') + }) + + test('a custom section names no size; an unknown size falls back to the 6x8', () => { + const custom = createBeam({ start: [0, 0], end: [2, 0], width: 0.1, depth: 0.3, plies: 2 }) + expect(custom.size).toBeUndefined() + expect(custom).toMatchObject({ width: 0.1, depth: 0.3, plies: 2 }) + expect(createBeam({ start: [0, 0], end: [2, 0], preset: 'nope' }).size).toBe('6x8') + }) + + test('its plan outline is its width either side of the centreline', () => { + const outline = beamOutline(createBeam({ start: [0, 0], end: [4, 0], preset: '6x8' })) + const half = 2.75 * IN + expect(outline.map(([x, z]) => [x, Number(z.toFixed(6))])).toEqual([ + [0, Number(half.toFixed(6))], + [4, Number(half.toFixed(6))], + [4, Number((-half).toFixed(6))], + [0, Number((-half).toFixed(6))], + ]) + }) +}) + +describe('beam elevation', () => { + test('a dropped beam hangs under the ceiling plane, a flush one sits in the ceiling framing', () => { + const dropped = beam() + const nodes = house([dropped]) + expect(beamCeilingPlane(nodes, dropped)).toBeCloseTo(2.7) + expect(beamTopElevation(nodes, dropped)).toBeCloseTo(2.7) + expect(beamBottomElevation(nodes, dropped)).toBeCloseTo(2.7 - 7.5 * IN) + const flush = beam({ placement: 'flush' }) + expect(beamBottomElevation(house([flush]), flush)).toBeCloseTo(2.7) + }) + + test('a stored top stands, and a slab of the level above lowers the plane over it', () => { + const set = beam({ topElevation: 2.2 }) + expect(beamTopElevation(house([set]), set)).toBe(2.2) + const covered = beam() + const above = SlabNode.parse({ + id: 'slab_u', + parentId: 'level_u', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + elevation: 0.05, + thickness: 0.3, + }) + // the slab's body reaches 0.25 m under the upper storey's plane + expect(beamTopElevation(house([covered], [above]), covered)).toBeCloseTo(2.45) + }) + + test('the top references set it from the ceiling plane or the floor', () => { + const subject = beam() + const nodes = house([subject]) + expect(levelFloorAt(nodes, 'level_g', [5, 4])).toEqual({ + top: 0.05, + underside: 0.05 - 0.2, + slabId: 'slab_g', + }) + expect(levelFloorAt(nodes, 'level_g', [20, 4]).slabId).toBeNull() + expect(beamTopReferenceOf(nodes, subject)).toBe('ceiling-dropped') + const flushFloor = { ...subject, ...beamTopReferencePatch(nodes, subject, 'floor-flush') } + expect(flushFloor).toMatchObject({ topElevation: 0.05, placement: 'flush' }) + expect(beamTopReferenceOf(nodes, flushFloor)).toBe('floor-flush') + const underFloor = { ...subject, ...beamTopReferencePatch(nodes, subject, 'floor-under') } + expect(underFloor.topElevation).toBeCloseTo(-0.15) + expect(beamTopReferenceOf(nodes, underFloor)).toBe('floor-under') + expect(beamTopReferenceOf(nodes, { ...subject, topElevation: 1.9 })).toBeNull() + expect(beamTopReferencePatch(nodes, flushFloor, 'ceiling-flush')).toEqual({ + topElevation: undefined, + placement: 'flush', + }) + }) +}) + +describe('posts under beams', () => { + test('finds the beams over a point, the lowest first', () => { + const high = beam({ id: 'beam_high' as BeamNode['id'] }) + const low = beam({ id: 'beam_low' as BeamNode['id'], topElevation: 2.2 }) + const nodes = house([high, low]) + expect(beamsOverPoint(nodes, 'level_g', [4, 4]).map((b) => b.id)).toEqual([ + 'beam_low', + 'beam_high', + ]) + // at an end, inside the width + expect(beamsOverPoint(nodes, 'level_g', [6, 4 + 2 * IN])).toHaveLength(2) + // past the end, beside it + expect(beamsOverPoint(nodes, 'level_g', [6.2, 4])).toHaveLength(0) + expect(beamsOverPoint(nodes, 'level_g', [4, 4.2])).toHaveLength(0) + }) + + test('posts stand at both ends and evenly between, no further apart than asked', () => { + expect(postStationsAlong([0, 0], [6, 0], 2.4)).toEqual([ + [0, 0], + [2, 0], + [4, 0], + [6, 0], + ]) + expect(postStationsAlong([0, 0], [2, 0], 2.4)).toEqual([ + [0, 0], + [2, 0], + ]) + }) +}) diff --git a/packages/core/src/lib/beam.ts b/packages/core/src/lib/beam.ts new file mode 100644 index 0000000000..01017a4c1c --- /dev/null +++ b/packages/core/src/lib/beam.ts @@ -0,0 +1,408 @@ +import type { AnyNode, AnyNodeId, LevelNode, SlabNode } from '../schema' +import { + BEAM_SIZE_PRESETS, + type BeamMaterial, + BeamNode, + type BeamPlacement, + type BeamSizePreset, + type BeamSizePresetId, + DEFAULT_BEAM_SIZE_PRESET_ID, +} from '../schema/nodes/beam' +import { DEFAULT_LEVEL_HEIGHT } from '../services/level-height' +import { getWallPlaneTop } from '../services/storey' +import { pointInPolygon, pointOnPolygonBoundary } from '../systems/slab/slab-support' +import { isSiteFlatwork } from './site-flatwork' + +type Nodes = Record +type P = [number, number] + +const INCH = 0.0254 +/** Two sections within this of each other are one size (a 32nd of an inch). */ +const SIZE_TOLERANCE = INCH / 32 + +/** + * The stock size with this id or label (`BEAM_SIZE_PRESETS`): '(2)2x10' and '(2) 2x10' are both + * the two-ply 2x10. + */ +export function beamSizePreset(idOrLabel: string): BeamSizePreset | undefined { + return BEAM_SIZE_PRESETS.find((preset) => preset.id === idOrLabel || preset.label === idOrLabel) +} + +/** Whether a beam of `material` can be a preset of `presetMaterial`: sawn stock comes treated too. */ +function materialFits(material: BeamMaterial, presetMaterial: BeamMaterial): boolean { + if (presetMaterial === 'sawn') return material === 'sawn' || material === 'pressure-treated' + return material === presetMaterial +} + +/** + * The stock size a beam is, if it is one: as many plies of the same nominal size (when it names + * one), the same section, and a material that size comes in. + */ +export function matchBeamSizePreset( + beam: Pick & { size?: string }, +): BeamSizePreset | null { + for (const preset of BEAM_SIZE_PRESETS) { + if (preset.plies !== beam.plies) continue + if (beam.size !== undefined && beam.size !== preset.size) continue + if (!materialFits(beam.material, preset.material)) continue + if (Math.abs(preset.width - beam.width) > SIZE_TOLERANCE) continue + if (Math.abs(preset.depth - beam.depth) > SIZE_TOLERANCE) continue + return preset + } + return null +} + +/** + * A length in inches and sixteenths, as a framing size prints it: 3.5 in reads "3-1/2", 0.75 in + * "3/4", 9 in "9". + */ +export function inchFractionLabel(metres: number, denominator = 16): string { + const total = Math.round((Math.abs(metres) / INCH) * denominator) + const whole = Math.floor(total / denominator) + let numerator = total % denominator + let over = denominator + while (numerator > 0 && numerator % 2 === 0) { + numerator /= 2 + over /= 2 + } + if (numerator === 0) return `${whole}` + return whole > 0 ? `${whole}-${numerator}/${over}` : `${numerator}/${over}` +} + +/** What a plan adds after the size to say what a beam is made of; a W shape is steel by its name. */ +const MATERIAL_SUFFIX: Record = { + sawn: '', + 'pressure-treated': ' PT', + lvl: ' LVL', + glulam: ' GLB', + steel: '', +} + +/** What each material is called in a list or a panel. */ +export const BEAM_MATERIAL_LABELS: Record = { + sawn: 'Sawn lumber', + 'pressure-treated': 'Pressure-treated', + lvl: 'LVL', + glulam: 'Glulam', + steel: 'Steel', +} + +/** + * How each material looks, from the material library: fir for sawn lumber, a weathered grey-brown + * for treated, the pale veneer of an LVL, the warm laminations of a glulam, steel. + */ +export const BEAM_MATERIAL_LIBRARY_REFS: Record = { + sawn: 'library:wood-woodfine2', + 'pressure-treated': 'library:wood-woodplank19', + lvl: 'library:wood-woodfine22', + glulam: 'library:wood-woodfine13', + steel: 'library:metal-steel', +} + +/** + * A beam's size as a framing plan prints it: "6x8", "(2) 2x10", "6x8 PT", "1-3/4x11-7/8 LVL", + * "W8x10"; a section that is no stock size by its actual inches ("3-1/2x9-1/4"). + */ +export function beamSizeLabel( + beam: Pick & { size?: string }, +): string { + const suffix = MATERIAL_SUFFIX[beam.material] + const size = beam.size ?? matchBeamSizePreset(beam)?.size + if (size) return `${beam.plies > 1 ? `(${beam.plies}) ` : ''}${size}${suffix}` + return `${inchFractionLabel(beam.width)}x${inchFractionLabel(beam.depth)}${suffix}` +} + +/** + * The fields that make a beam a stock size. Sawn sizes keep a treated beam treated; any other + * size brings its own material. + */ +export function beamSizePresetPatch( + beam: Pick, + presetId: string, +): Partial { + const preset = beamSizePreset(presetId) + if (!preset) return {} + return { + size: preset.size, + plies: preset.plies, + width: preset.width, + depth: preset.depth, + material: materialFits(beam.material, preset.material) ? beam.material : preset.material, + } +} + +/** What `createBeam` builds a beam from. */ +export type BeamInput = { + /** The centreline, level-local plan points `[x, z]`. */ + start: readonly [number, number] + end: readonly [number, number] + /** + * A stock size by id or label (`BEAM_SIZE_PRESETS`), a 6x8 by default (an unknown one too). + * Ignored when `width` and `depth` are given. + */ + preset?: BeamSizePresetId | (string & {}) + /** A custom section, metres (every ply together across), in place of a stock size. */ + width?: number + depth?: number + /** How many plies a custom section is nailed up from (a stock size brings its own). */ + plies?: number + /** What it is made of; else what the stock size is (sawn lumber for a custom section). */ + material?: BeamMaterial + placement?: BeamPlacement + /** Its top, level-local metres; absent = the level's ceiling plane over it. */ + topElevation?: number + /** The level it goes on: its `parentId`. */ + levelId?: string + /** The slab it belongs to, if any (a porch's or a deck's). */ + supportSlabId?: string + name?: string + metadata?: Record +} + +/** + * A beam node from a centreline and a size, ready for `createNode` on its level: what the beam + * tool draws, and what code that frames a porch or a deck makes. + */ +export function createBeam(input: BeamInput): BeamNode { + const custom = input.width !== undefined && input.depth !== undefined + const preset = custom + ? undefined + : (beamSizePreset(input.preset ?? DEFAULT_BEAM_SIZE_PRESET_ID) ?? + beamSizePreset(DEFAULT_BEAM_SIZE_PRESET_ID)) + return BeamNode.parse({ + start: [input.start[0], input.start[1]], + end: [input.end[0], input.end[1]], + width: custom ? input.width : preset?.width, + depth: custom ? input.depth : preset?.depth, + size: preset?.size, + plies: custom ? (input.plies ?? 1) : preset?.plies, + material: input.material ?? preset?.material ?? 'sawn', + placement: input.placement ?? 'dropped', + ...(input.topElevation !== undefined ? { topElevation: input.topElevation } : {}), + ...(input.levelId ? { parentId: input.levelId } : {}), + ...(input.supportSlabId ? { supportSlabId: input.supportSlabId } : {}), + ...(input.name ? { name: input.name } : {}), + ...(input.metadata ? { metadata: input.metadata } : {}), + }) +} + +/** A beam's plan, level-local: its centreline, run, the way it runs and across it. */ +export type BeamPlan = { + start: P + end: P + length: number + /** Unit vector start → end. */ + direction: P + /** Unit vector to the left of the run (the wall convention's +normal). */ + normal: P + width: number +} + +export function beamPlan(beam: Pick): BeamPlan { + const dx = beam.end[0] - beam.start[0] + const dz = beam.end[1] - beam.start[1] + const length = Math.hypot(dx, dz) + const direction: P = length > 1e-9 ? [dx / length, dz / length] : [1, 0] + return { + start: [beam.start[0], beam.start[1]], + end: [beam.end[0], beam.end[1]], + length, + direction, + normal: [-direction[1], direction[0]], + width: beam.width, + } +} + +/** The beam's outline in plan, level-local: four corners, start's left first. */ +export function beamOutline(beam: Pick): P[] { + const { start, end, normal, width } = beamPlan(beam) + const [nx, nz] = [normal[0] * (width / 2), normal[1] * (width / 2)] + return [ + [start[0] + nx, start[1] + nz], + [end[0] + nx, end[1] + nz], + [end[0] - nx, end[1] - nz], + [start[0] - nx, start[1] - nz], + ] +} + +/** + * The ceiling plane over a beam, level-local: the top of the level's walls there, the storey's + * height or the underside of a slab of the level above that covers the beam (`getWallPlaneTop` + * over the beam's width, as for a wall). + */ +export function beamCeilingPlane( + nodes: Nodes, + beam: Pick, +): number { + if (!beam.parentId) return DEFAULT_LEVEL_HEIGHT + return getWallPlaneTop( + { start: beam.start, end: beam.end, thickness: beam.width }, + beam.parentId, + nodes, + ) +} + +/** + * Where a beam's top is, level-local. A stored `topElevation` stands; without one the beam + * follows the ceiling plane over it: a dropped beam hangs under it, the joists bearing on its + * top, and a flush one sits in the ceiling framing with its underside on it. + */ +export function beamTopElevation( + nodes: Nodes, + beam: Pick< + BeamNode, + 'start' | 'end' | 'width' | 'depth' | 'parentId' | 'placement' | 'topElevation' + >, +): number { + if (beam.topElevation !== undefined) return beam.topElevation + const plane = beamCeilingPlane(nodes, beam) + return beam.placement === 'flush' ? plane + beam.depth : plane +} + +/** Where a beam's underside is, level-local: what a post under it stands up to. */ +export function beamBottomElevation( + nodes: Nodes, + beam: Pick< + BeamNode, + 'start' | 'end' | 'width' | 'depth' | 'parentId' | 'placement' | 'topElevation' + >, +): number { + return beamTopElevation(nodes, beam) - beam.depth +} + +/** The floor at a plan point of a level, level-local: the highest floor slab there, its top and underside. */ +export type LevelFloorAt = { top: number; underside: number; slabId: string | null } + +function slabHolds(slab: SlabNode, [x, z]: P): boolean { + if (!(pointInPolygon(x, z, slab.polygon) || pointOnPolygonBoundary(x, z, slab.polygon))) + return false + for (const hole of slab.holes ?? []) { + if (hole.length >= 3 && pointInPolygon(x, z, hole)) return false + } + return true +} + +/** + * The floor a level stands on at a plan point: the highest of its floor slabs there (not a pit, + * not the site's paving), its top and the underside of its body; the level's own plane, 0, where + * none is. + */ +export function levelFloorAt( + nodes: Nodes, + levelId: string, + point: readonly [number, number], +): LevelFloorAt { + const level = nodes[levelId as LevelNode['id']] + let floor: LevelFloorAt = { top: 0, underside: 0, slabId: null } + if (level?.type !== 'level') return floor + let best: SlabNode | null = null + for (const childId of level.children) { + const child = nodes[childId as AnyNodeId] + if (child?.type !== 'slab' || child.visible === false) continue + if (child.recessed === true || isSiteFlatwork(child) || child.polygon.length < 3) continue + if (!slabHolds(child, [point[0], point[1]])) continue + if (!best || child.elevation > best.elevation) best = child + } + if (best) + floor = { top: best.elevation, underside: best.elevation - best.thickness, slabId: best.id } + return floor +} + +/** + * Where a beam's top is set from, as the panel lists them: under the ceiling plane (a dropped + * beam, the default) or flush in the ceiling framing, both following the plane; flush with the + * floor (its top the floor's) or under the floor's framing (a girder in a crawl space, a deck's + * beam under its joists), both measured at the beam's middle. + */ +export type BeamTopReference = 'ceiling-dropped' | 'ceiling-flush' | 'floor-flush' | 'floor-under' + +export const BEAM_TOP_REFERENCES: readonly BeamTopReference[] = [ + 'ceiling-dropped', + 'ceiling-flush', + 'floor-flush', + 'floor-under', +] + +export const BEAM_TOP_REFERENCE_LABELS: Record = { + 'ceiling-dropped': 'Under ceiling plane', + 'ceiling-flush': 'Flush in ceiling', + 'floor-flush': 'Flush with floor', + 'floor-under': 'Under floor', +} + +function beamFloor(nodes: Nodes, beam: Pick): LevelFloorAt { + if (!beam.parentId) return { top: 0, underside: 0, slabId: null } + const middle: P = [(beam.start[0] + beam.end[0]) / 2, (beam.start[1] + beam.end[1]) / 2] + return levelFloorAt(nodes, beam.parentId, middle) +} + +/** The fields that put a beam's top at `reference`. */ +export function beamTopReferencePatch( + nodes: Nodes, + beam: Pick, + reference: BeamTopReference, +): Pick { + if (reference === 'ceiling-dropped') return { topElevation: undefined, placement: 'dropped' } + if (reference === 'ceiling-flush') return { topElevation: undefined, placement: 'flush' } + const floor = beamFloor(nodes, beam) + return reference === 'floor-flush' + ? { topElevation: floor.top, placement: 'flush' } + : { topElevation: floor.underside, placement: 'dropped' } +} + +/** Which of the references a beam's top is set from now, or null for a top typed in by hand. */ +export function beamTopReferenceOf( + nodes: Nodes, + beam: Pick, +): BeamTopReference | null { + if (beam.topElevation === undefined) + return beam.placement === 'flush' ? 'ceiling-flush' : 'ceiling-dropped' + const floor = beamFloor(nodes, beam) + if (beam.placement === 'flush' && Math.abs(beam.topElevation - floor.top) < 1e-6) + return 'floor-flush' + if (beam.placement === 'dropped' && Math.abs(beam.topElevation - floor.underside) < 1e-6) + return 'floor-under' + return null +} + +/** + * The beams of a level over a plan point, the lowest underside first: those whose outline, + * grown by `margin` all round, holds the point. What a post set there stands up to. + */ +export function beamsOverPoint( + nodes: Nodes, + levelId: string, + point: readonly [number, number], + margin = 0.01, +): BeamNode[] { + const found: { beam: BeamNode; bottom: number }[] = [] + for (const node of Object.values(nodes)) { + if (node?.type !== 'beam' || node.parentId !== levelId || node.visible === false) continue + const { start, length, direction, normal, width } = beamPlan(node) + const rx = point[0] - start[0] + const rz = point[1] - start[1] + const along = rx * direction[0] + rz * direction[1] + const across = rx * normal[0] + rz * normal[1] + if (along < -margin || along > length + margin) continue + if (Math.abs(across) > width / 2 + margin) continue + found.push({ beam: node, bottom: beamBottomElevation(nodes, node) }) + } + return found.sort((a, b) => a.bottom - b.bottom).map(({ beam }) => beam) +} + +/** + * Where posts stand under a beam run: at both ends and evenly between, no two further apart than + * `maxSpacing`. A porch's open edge gets its posts this way. + */ +export function postStationsAlong( + start: readonly [number, number], + end: readonly [number, number], + maxSpacing: number, +): P[] { + const length = Math.hypot(end[0] - start[0], end[1] - start[1]) + const spans = maxSpacing > 0 ? Math.max(1, Math.ceil(length / maxSpacing - 1e-9)) : 1 + return Array.from({ length: spans + 1 }, (_, index) => { + const t = index / spans + return [start[0] + (end[0] - start[0]) * t, start[1] + (end[1] - start[1]) * t] as P + }) +} diff --git a/packages/core/src/lib/build-up.test.ts b/packages/core/src/lib/build-up.test.ts new file mode 100644 index 0000000000..cc9568e27b --- /dev/null +++ b/packages/core/src/lib/build-up.test.ts @@ -0,0 +1,181 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + CEILING_BUILD_UP_PRESETS, + CEILING_CLAMP_MARGIN, + CeilingBuildUp, + CeilingNode, + ceilingBuildUpThickness, + ceilingCapLift, + LevelNode, + ROOF_BUILD_UP_PRESETS, + RoofBuildUp, + RoofSegmentNode, + roofBuildUpDepths, + roofBuildUpPatch, + roofStructureDepth, + SlabNode, +} from '../index' + +const IN = 0.0254 + +const plane = (buildUp: Partial = {}): RoofBuildUp => ({ + roofing: { kind: 'architectural-shingle', thickness: 0.5 * IN }, + sheathing: { kind: 'osb', thickness: (7 / 16) * IN }, + structure: { system: 'rafters', size: '2x10', spacing: 16 * IN }, + ...buildUp, +}) + +describe('roof plane layers', () => { + test('a plane without layers keeps the deck and shingle it always drew', () => { + const old = RoofSegmentNode.parse({ id: 'rseg_old', type: 'roof-segment' }) + expect(old.buildUp).toBeUndefined() + expect([old.deckThickness, old.shingleThickness]).toEqual([0.1, 0.05]) + }) + + test('the deck is structure and sheathing, the covering roofing and underlayment', () => { + const { deck, covering } = roofBuildUpDepths( + plane({ underlayment: { kind: 'synthetic', thickness: 0.02 * IN } }), + ) + expect(deck / IN).toBeCloseTo(9.25 + 7 / 16, 9) + expect(covering / IN).toBeCloseTo(0.52, 9) + }) + + test('cavity insulation fills the rafters, rigid board and an interior finish add to the deck', () => { + const bare = roofBuildUpDepths(plane()).deck + expect(roofBuildUpDepths(plane({ insulation: { kind: 'batt', thickness: 6 * IN } })).deck).toBe( + bare, + ) + expect( + roofBuildUpDepths(plane({ insulation: { kind: 'batt', thickness: 10 * IN } })).deck / IN, + ).toBeCloseTo(10 + 7 / 16, 9) + expect( + roofBuildUpDepths( + plane({ + insulation: { kind: 'rigid', thickness: 2 * IN }, + finish: { kind: 'drywall', thickness: 0.5 * IN }, + }), + ).deck / IN, + ).toBeCloseTo(9.25 + 7 / 16 + 2.5, 9) + }) + + test('setting the layers writes the deck and shingle thickness the plane draws with', () => { + const buildUp = plane({ structure: { system: 'trusses', size: '2x6', spacing: 24 * IN } }) + const node = RoofSegmentNode.parse({ + id: 'rseg_layered', + type: 'roof-segment', + ...roofBuildUpPatch(buildUp), + }) + expect(node.buildUp).toEqual(buildUp) + // a 2x6 top chord on its 2x6 heel + expect(node.deckThickness / IN).toBeCloseTo(2 * 5.5 + 7 / 16, 9) + expect(node.shingleThickness / IN).toBeCloseTo(0.5, 9) + }) + + test('a truss stands its top chord on its heel: two chord depths, where a rafter is one', () => { + expect( + roofStructureDepth({ system: 'rafters', size: '2x8', spacing: 16 * IN }) / IN, + ).toBeCloseTo(7.25, 9) + expect( + roofStructureDepth({ system: 'trusses', size: '2x4', spacing: 24 * IN }) / IN, + ).toBeCloseTo(7, 9) + }) + + test('the default preset is 2x4 trusses on their heel under 7/16 in OSB', () => { + const [first] = ROOF_BUILD_UP_PRESETS + expect(RoofBuildUp.parse(first!.buildUp)).toEqual(first!.buildUp) + expect(roofBuildUpDepths(first!.buildUp).deck / IN).toBeCloseTo(7 + 7 / 16, 9) + for (const preset of ROOF_BUILD_UP_PRESETS) expect(preset.buildUp.presetId).toBe(preset.id) + }) +}) + +describe('ceiling layers', () => { + test('a ceiling without layers has no thickness', () => { + const old = CeilingNode.parse({ id: 'ceiling_old', polygon: [] }) + expect(old.buildUp).toBeUndefined() + }) + + test('the thickness rises from the underside: finish, framing or the fill over it, decking', () => { + expect( + ceilingBuildUpThickness({ + finish: { kind: 'drywall', thickness: 0.5 * IN }, + framing: { system: 'joists', size: '2x6', spacing: 16 * IN }, + }) / IN, + ).toBeCloseTo(6, 9) + expect( + ceilingBuildUpThickness({ + finish: { kind: 'drywall', thickness: 0.625 * IN }, + framing: { system: 'truss-chords', size: '2x4', spacing: 24 * IN }, + insulation: { kind: 'blown', thickness: 12 * IN }, + }) / IN, + ).toBeCloseTo(12.625, 9) + expect( + ceilingBuildUpThickness({ + framing: { system: 'joists', size: '2x8', spacing: 16 * IN }, + decking: { kind: 'plywood', thickness: 0.75 * IN }, + }) / IN, + ).toBeCloseTo(8, 9) + expect(ceilingBuildUpThickness({})).toBe(0) + }) + + test('every preset parses', () => { + for (const preset of CEILING_BUILD_UP_PRESETS) + expect(CeilingBuildUp.parse(preset.buildUp)).toEqual(preset.buildUp) + }) + + const square: [number, number][] = [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], + ] + const joists: CeilingBuildUp = { + finish: { kind: 'drywall', thickness: 0.5 * IN }, + framing: { system: 'joists', size: '2x6', spacing: 16 * IN }, + } + const scene = (ceiling: Partial, floorAbove: boolean) => { + const nodes: AnyNode[] = [ + BuildingNode.parse({ id: 'building_a', children: ['level_0', 'level_1'] }), + LevelNode.parse({ id: 'level_0', level: 0, height: 2.5, parentId: 'building_a' }), + LevelNode.parse({ + id: 'level_1', + level: 1, + height: 2.5, + parentId: 'building_a', + children: floorAbove ? ['slab_above'] : [], + }), + SlabNode.parse({ id: 'slab_above', polygon: square, elevation: 0, thickness: 0.3 }), + ] + const node = CeilingNode.parse({ + id: 'ceiling_a', + parentId: 'level_0', + polygon: square, + ...ceiling, + }) + return { + node, + nodes: Object.fromEntries([...nodes, node].map((n) => [n.id, n])) as Record< + AnyNodeId, + AnyNode + >, + } + } + + test('the upper face stands on the layers under a roof, and short of the floor above', () => { + const open = scene({ height: 2.4, buildUp: joists }, false) + expect(ceilingCapLift(open.node, open.nodes)).toBeCloseTo(6 * IN, 9) + // the floor above hangs 0.3 m from 2.5 m: 2.2 m, less the margin, is as high as it goes + const under = scene({ height: 2.1, buildUp: joists }, true) + expect(ceilingCapLift(under.node, under.nodes)).toBeCloseTo(0.1 - CEILING_CLAMP_MARGIN, 9) + const bare = scene({ height: 2.1 }, true) + expect(ceilingCapLift(bare.node, bare.nodes)).toBe(0) + }) +}) + +// A 320 px inspector shows about 26 characters beside "Preset"; longer labels were cut with "…". +test('every layers preset label fits the Preset select uncut', () => { + for (const preset of [...ROOF_BUILD_UP_PRESETS, ...CEILING_BUILD_UP_PRESETS]) + expect(preset.label.length).toBeLessThanOrEqual(26) +}) diff --git a/packages/core/src/lib/build-up.ts b/packages/core/src/lib/build-up.ts new file mode 100644 index 0000000000..5a70b8e6f4 --- /dev/null +++ b/packages/core/src/lib/build-up.ts @@ -0,0 +1,388 @@ +import type { + CeilingBuildUp, + FloorBuildUp, + FlooringKind, + FloorStructure, + IJoistDepth, + NominalLumber, + RoofBuildUp, + RoofingKind, + RoofStructure, + UnderlaymentKind, +} from '../schema/build-up' +import type { CeilingNode } from '../schema/nodes/ceiling' +import type { SlabNode } from '../schema/nodes/slab' +import type { AnyNode, AnyNodeId } from '../schema/types' +import { resolveCeilingHeight } from '../services/level-height' +import { CEILING_CLAMP_MARGIN, getCoveringSlabUndersideAt } from '../services/storey' + +const inches = (value: number) => value * 0.0254 + +/** + * Actual depth of dressed lumber (2021 IRC R602.3 / ALSC PS 20): what a + * rafter, truss chord or ceiling joist of that size adds to its layer. + */ +export const LUMBER_DEPTH: Record = { + '2x4': inches(3.5), + '2x6': inches(5.5), + '2x8': inches(7.25), + '2x10': inches(9.25), + '2x12': inches(11.25), +} + +/** + * Typical installed thickness of each roofing, the envelope the plane draws as + * its covering: two courses of shingle at the laps, a tile's profile, a seam's + * height. + */ +export const ROOFING_THICKNESS: Record = { + 'asphalt-3-tab': inches(0.25), + 'architectural-shingle': inches(0.5), + 'standing-seam-metal': inches(1), + 'concrete-tile': inches(2), + 'clay-tile': inches(2.5), + 'wood-shake': inches(0.75), + slate: inches(0.5), +} + +export const UNDERLAYMENT_THICKNESS: Record = { + 'felt-15': inches(0.03), + 'felt-30': inches(0.05), + synthetic: inches(0.02), + 'ice-and-water': inches(0.04), +} + +/** Roof sheathing and attic decking panels, thinnest first. */ +export const PANEL_THICKNESSES: readonly number[] = [ + 7 / 16, + 15 / 32, + 1 / 2, + 19 / 32, + 5 / 8, + 3 / 4, +].map(inches) + +/** Framing spacings on centre: 12, 16, 19.2 and 24 in. */ +export const FRAMING_SPACINGS: readonly number[] = [12, 16, 19.2, 24].map(inches) + +export const DRYWALL_THICKNESSES: readonly number[] = [0.5, 0.625].map(inches) + +/** Engineered wood I-joists' depths. */ +export const I_JOIST_DEPTH: Record = { + '9-1/2': inches(9.5), + '11-7/8': inches(11.875), + '14': inches(14), + '16': inches(16), +} + +/** Typical installed thickness of each flooring, its underlayment or setting bed with it. */ +export const FLOORING_THICKNESS: Record = { + hardwood: inches(0.75), + 'engineered-wood': inches(0.5), + laminate: inches(0.375), + 'vinyl-plank': inches(0.25), + 'ceramic-tile': inches(0.5), + carpet: inches(0.5), +} + +/** Floor sheathing panels: ⅝ and ¾ in T&G for joists 16 and 24 in on centre, 23/32 in rated. */ +export const SUBFLOOR_THICKNESSES: readonly number[] = [19 / 32, 5 / 8, 23 / 32, 3 / 4].map(inches) +/** Three-coat plaster on lath. */ +export const PLASTER_THICKNESS = inches(0.875) +export const WOOD_PLANK_THICKNESS = inches(0.75) + +/** Batt and blown fill the framing cavity (and mound over it when deeper); rigid board adds its own. */ +function framedDepth( + framingDepth: number, + insulation: { kind: string; thickness: number } | undefined, +): number { + if (!insulation) return framingDepth + if (insulation.kind === 'rigid') return framingDepth + insulation.thickness + return Math.max(framingDepth, insulation.thickness) +} + +/** + * How deep a roof plane's structure stands over the plate plane its deck rises + * from: a rafter's depth, its underside on the plates; a truss two chord + * depths, its top chord on the bottom chord at the heel. The heel is plumb, a + * chord's depth times the slope's cosine square to it, so two depths are the + * most it takes: on a pitch θ the top chords sit d(1 − cos θ) under the + * sheathing, never through it. + */ +export function roofStructureDepth(structure: RoofStructure): number { + const depth = LUMBER_DEPTH[structure.size] + return structure.system === 'trusses' ? 2 * depth : depth +} + +/** + * The two thicknesses a roof plane draws with: `deck` (finish, structure, + * insulation and sheathing) and `covering` (roofing and underlayment). + */ +export function roofBuildUpDepths(buildUp: RoofBuildUp): { deck: number; covering: number } { + return { + deck: + (buildUp.finish?.thickness ?? 0) + + framedDepth(roofStructureDepth(buildUp.structure), buildUp.insulation) + + buildUp.sheathing.thickness, + covering: buildUp.roofing.thickness + (buildUp.underlayment?.thickness ?? 0), + } +} + +/** + * The patch that sets a roof plane's layers: the build-up and the deck and + * covering thicknesses it derives, together. A writer never sets one without + * the others. + */ +export function roofBuildUpPatch(buildUp: RoofBuildUp): { + buildUp: RoofBuildUp + deckThickness: number + shingleThickness: number +} { + const { deck, covering } = roofBuildUpDepths(buildUp) + return { buildUp, deckThickness: deck, shingleThickness: covering } +} + +/** + * The layers a plane without any is drawn as: the default preset over the framing its deck + * holds, rafters whose depth with the preset's sheathing makes it (a generated roof's 2x6 + * rafters), else the default preset itself: its trusses on their heel, or the one chord the + * schema's 0.100 m deck held before the heel counted. "Edit layers" writes them, the deck + * unchanged where it matched and deepened by the heel on that old deck; Bones frames such a + * plane by them, so its rafters fit the deck drawn until a roof choice writes the plane layers of + * its own. + */ +export function roofBuildUpOfDeck(deckThickness: number): RoofBuildUp { + const preset = ROOF_BUILD_UP_PRESETS[0]!.buildUp + const framing = deckThickness - preset.sheathing.thickness - (preset.finish?.thickness ?? 0) + if (Math.abs(roofStructureDepth(preset.structure) - framing) < 1e-3) return preset + const size = (Object.keys(LUMBER_DEPTH) as NominalLumber[]).find( + (lumber) => Math.abs(LUMBER_DEPTH[lumber] - framing) < 1e-3, + ) + if (!size || size === preset.structure.size) return preset + const { presetId: _preset, ...layers } = preset + return { ...layers, structure: { system: 'rafters', size, spacing: preset.structure.spacing } } +} + +/** A floor's joists' depth: the lumber's dressed depth, or the I-joist's. */ +export function floorStructureDepth(structure: FloorStructure): number { + return structure.system === 'i-joists' + ? (I_JOIST_DEPTH[structure.size as IJoistDepth] ?? 0) + : (LUMBER_DEPTH[structure.size as NominalLumber] ?? 0) +} + +/** A framed floor's layers' depths, top down, metres; an absent layer is 0. */ +export function floorLayerDepths(buildUp: FloorBuildUp): { + flooring: number + sheathing: number + joists: number + ceiling: number +} { + return { + flooring: buildUp.flooring?.thickness ?? 0, + sheathing: buildUp.sheathing?.thickness ?? 0, + joists: floorStructureDepth(buildUp.structure), + ceiling: buildUp.ceiling?.thickness ?? 0, + } +} + +/** A framed floor's thickness: its layers summed. */ +export function floorBuildUpThickness(buildUp: FloorBuildUp): number { + const { flooring, sheathing, joists, ceiling } = floorLayerDepths(buildUp) + return Math.round((flooring + sheathing + joists + ceiling) * 1e6) / 1e6 +} + +/** + * The patch that sets a floor plate's layers: the build-up and the thickness it derives, together. + * A writer never sets one without the other. + */ +export function floorBuildUpPatch(buildUp: FloorBuildUp): { + buildUp: FloorBuildUp + thickness: number +} { + return { buildUp, thickness: floorBuildUpThickness(buildUp) } +} + +/** + * A framed floor plate's layers: its own, else the default framed floor. A plate saved before its + * layers were kept (a 14 in floor, a hand-set thickness) reads the default until a layer is set; + * `floorBuildUpMatches` says whether its thickness is still the layers' sum. + */ +export function floorBuildUpOf(plate: Pick): FloorBuildUp { + return plate.buildUp ?? DEFAULT_FLOOR_BUILD_UP +} + +/** Whether a plate's thickness is its layers' sum (a writer set them together). */ +export function floorBuildUpMatches(plate: Pick): boolean { + return Math.abs(floorBuildUpThickness(floorBuildUpOf(plate)) - plate.thickness) < 1e-4 +} + +/** A ceiling's thickness, up from its finished underside. */ +export function ceilingBuildUpThickness(buildUp: CeilingBuildUp): number { + return ( + (buildUp.finish?.thickness ?? 0) + + framedDepth(buildUp.framing ? LUMBER_DEPTH[buildUp.framing.size] : 0, buildUp.insulation) + + (buildUp.decking?.thickness ?? 0) + ) +} + +/** + * How far above its underside a ceiling's upper face stands: its build-up's + * thickness, stopped short of the floor of the level above (a slab covering + * any vertex or the centroid, as the height clamp samples it) so the two + * never share a plane. 0 without a build-up. + */ +export function ceilingCapLift( + ceiling: Pick, + nodes: Record, +): number { + const thickness = ceiling.buildUp ? ceilingBuildUpThickness(ceiling.buildUp) : 0 + if (!(thickness > 0) || typeof ceiling.parentId !== 'string') return Math.max(0, thickness) + const underside = resolveCeilingHeight(ceiling, nodes) + const polygon = ceiling.polygon + let lift = thickness + if (polygon.length > 0) { + const cx = polygon.reduce((sum, [x]) => sum + x, 0) / polygon.length + const cz = polygon.reduce((sum, [, z]) => sum + z, 0) / polygon.length + for (const [x, z] of [...polygon, [cx, cz] as [number, number]]) { + const above = getCoveringSlabUndersideAt(ceiling.parentId, nodes, x, z) + if (above !== null) lift = Math.min(lift, above - CEILING_CLAMP_MARGIN - underside) + } + } + return Math.max(0, lift) +} + +export type RoofBuildUpPreset = { id: string; label: string; buildUp: RoofBuildUp } +export type FloorBuildUpPreset = { id: string; label: string; buildUp: FloorBuildUp } +export type CeilingBuildUpPreset = { id: string; label: string; buildUp: CeilingBuildUp } + +const roofPreset = (id: string, label: string, buildUp: Omit) => ({ + id, + label, + buildUp: { presetId: id, ...buildUp }, +}) + +const ceilingPreset = (id: string, label: string, buildUp: Omit) => ({ + id, + label, + buildUp: { presetId: id, ...buildUp }, +}) + +const shingles = { + kind: 'architectural-shingle', + thickness: ROOFING_THICKNESS['architectural-shingle'], +} as const +const synthetic = { kind: 'synthetic', thickness: UNDERLAYMENT_THICKNESS.synthetic } as const +const osb = (thickness = inches(7 / 16)) => ({ kind: 'osb', thickness }) as const +const plywood = (thickness: number) => ({ kind: 'plywood', thickness }) as const +const drywall = (thickness = inches(0.5)) => ({ kind: 'drywall', thickness }) as const + +/** + * The first is the default: 2x4 trusses under 7/16 in OSB, a 7-7/16 in deck + * (the top chord on its heel). A roof plane without layers draws the schema's + * 0.100 m, one chord and its sheathing. Labels stay short enough + * for the inspector's Preset select to show uncut; the fields below it name + * every layer. + */ +export const ROOF_BUILD_UP_PRESETS: readonly RoofBuildUpPreset[] = [ + roofPreset('asphalt-trusses-24', 'Asphalt on trusses @ 24"', { + roofing: shingles, + underlayment: synthetic, + sheathing: osb(), + structure: { system: 'trusses', size: '2x4', spacing: inches(24) }, + }), + roofPreset('asphalt-2x10-16', 'Asphalt on 2x10 @ 16"', { + roofing: shingles, + underlayment: synthetic, + sheathing: osb(), + structure: { system: 'rafters', size: '2x10', spacing: inches(16) }, + }), + roofPreset('metal-trusses-24', 'Metal on trusses @ 24"', { + roofing: { kind: 'standing-seam-metal', thickness: ROOFING_THICKNESS['standing-seam-metal'] }, + underlayment: synthetic, + sheathing: osb(), + structure: { system: 'trusses', size: '2x4', spacing: inches(24) }, + }), + roofPreset('tile-2x8-16', 'Tile on 2x8 @ 16"', { + roofing: { kind: 'concrete-tile', thickness: ROOFING_THICKNESS['concrete-tile'] }, + underlayment: { kind: 'felt-30', thickness: UNDERLAYMENT_THICKNESS['felt-30'] }, + sheathing: plywood(inches(15 / 32)), + structure: { system: 'rafters', size: '2x8', spacing: inches(16) }, + }), + roofPreset('cathedral-2x12-16', 'Cathedral 2x12 @ 16"', { + roofing: shingles, + underlayment: { kind: 'ice-and-water', thickness: UNDERLAYMENT_THICKNESS['ice-and-water'] }, + sheathing: plywood(inches(19 / 32)), + structure: { system: 'rafters', size: '2x12', spacing: inches(16) }, + insulation: { kind: 'batt', thickness: LUMBER_DEPTH['2x12'] }, + finish: drywall(), + }), +] + +export const CEILING_BUILD_UP_PRESETS: readonly CeilingBuildUpPreset[] = [ + ceilingPreset('drywall-2x6-16', 'Drywall on 2x6 @ 16"', { + finish: drywall(), + framing: { system: 'joists', size: '2x6', spacing: inches(16) }, + }), + ceilingPreset('trusses-24-blown', 'Truss chords @ 24", blown', { + finish: drywall(inches(0.625)), + framing: { system: 'truss-chords', size: '2x4', spacing: inches(24) }, + insulation: { kind: 'blown', thickness: inches(12) }, + }), + ceilingPreset('storage-2x8-16', 'Attic storage 2x8 @ 16"', { + finish: drywall(), + framing: { system: 'joists', size: '2x8', spacing: inches(16) }, + insulation: { kind: 'batt', thickness: LUMBER_DEPTH['2x8'] }, + decking: plywood(inches(3 / 4)), + }), +] + +const floorPreset = (id: string, label: string, buildUp: Omit) => ({ + id, + label, + buildUp: { presetId: id, ...buildUp }, +}) + +const hardwood = { kind: 'hardwood', thickness: FLOORING_THICKNESS.hardwood } as const +const subfloor = plywood(inches(5 / 8)) + +/** + * The first is the default framed floor: ¾ in hardwood on ⅝ in T&G plywood, 2x12 joists 16 in on + * centre, ½ in drywall on the ceiling under them, 13⅛ in. A raised floor's framing (over a crawl + * space, no ceiling) is the second. + */ +export const FLOOR_BUILD_UP_PRESETS: readonly FloorBuildUpPreset[] = [ + floorPreset('hardwood-2x12-16', 'Hardwood on 2x12 @ 16"', { + flooring: hardwood, + sheathing: subfloor, + structure: { system: 'joists', size: '2x12', spacing: inches(16) }, + ceiling: drywall(), + }), + floorPreset('crawl-2x12-16', 'Over a crawl space, 2x12 @ 16"', { + flooring: hardwood, + sheathing: subfloor, + structure: { system: 'joists', size: '2x12', spacing: inches(16) }, + }), + floorPreset('tile-2x10-16', 'Tile on 2x10 @ 16"', { + flooring: { kind: 'ceramic-tile', thickness: FLOORING_THICKNESS['ceramic-tile'] }, + sheathing: plywood(inches(3 / 4)), + structure: { system: 'joists', size: '2x10', spacing: inches(16) }, + ceiling: drywall(), + }), + floorPreset('carpet-tji-11-7-8-19', 'Carpet on 11-7/8" I-joists @ 19.2"', { + flooring: { kind: 'carpet', thickness: FLOORING_THICKNESS.carpet }, + sheathing: { kind: 'osb', thickness: inches(23 / 32) }, + structure: { system: 'i-joists', size: '11-7/8', spacing: inches(19.2) }, + ceiling: drywall(), + }), + floorPreset('exposed-2x10-16', 'Exposed 2x10 @ 16", no ceiling', { + flooring: hardwood, + sheathing: subfloor, + structure: { system: 'joists', size: '2x10', spacing: inches(16) }, + }), +] + +/** The framed floor a storey over rooms gets, and a basement's ceiling: the first preset. */ +export const DEFAULT_FLOOR_BUILD_UP: FloorBuildUp = FLOOR_BUILD_UP_PRESETS[0]!.buildUp + +/** A raised floor's framing over its crawl space: no ceiling under the joists. */ +export const DEFAULT_RAISED_FLOOR_BUILD_UP: FloorBuildUp = FLOOR_BUILD_UP_PRESETS[1]!.buildUp diff --git a/packages/core/src/lib/building-foundation.ts b/packages/core/src/lib/building-foundation.ts new file mode 100644 index 0000000000..2b7f8ab3b4 --- /dev/null +++ b/packages/core/src/lib/building-foundation.ts @@ -0,0 +1,330 @@ +import type { AnyNode, AnyNodeId, BuildingNode, FloorBuildUp, LevelNode } from '../schema' +import { servesAsRoofLevel } from '../services/roof-level' +import { getLevelElevations } from '../services/storey' +import { DEFAULT_RAISED_FLOOR_BUILD_UP, floorBuildUpThickness } from './build-up' +import { groundLevelOf } from './ground-level' + +const INCH = 0.0254 + +/** The sill a raised floor's joists bear on, over the stem wall's top, inches. */ +export const FOUNDATION_SILL_IN = 1.5 + +/** A raised floor's framing depth, inches: its layers and the sill, to a ten-thousandth. */ +function floorFramingIn(buildUp: FloorBuildUp): number { + return Math.round((floorBuildUpThickness(buildUp) / INCH + FOUNDATION_SILL_IN) * 1e4) / 1e4 +} + +/** What the floor of the ground storey is: a concrete slab on the ground, or framing over a crawl space. */ +export type BuildingFoundationType = 'slab' | 'raised' +/** What carries the perimeter down to the soil: a continuous footing, or a grade beam on piles. */ +export type BuildingFoundationSupport = 'footing' | 'piles' + +/** + * The building's foundation as `metadata.foundation` stores it, every number in inches. `type` and + * `ffAboveGradeIn` are the record the generators (Architect's `add_foundation`, plugin-generate) + * have always written and Bones, the sections and the sheets read; the rest is the designed + * foundation's, written by the editor's Foundation panel. The engine draws this foundation, sized + * as stored, unless `enabled` is `false`: a building with no record stands on the default, a slab + * on grade; switched off, it stands on the generic one. Anything left out takes the code default + * ({@link resolveBuildingFoundation}). + */ +export type BuildingFoundationRecord = { + /** `false` switches the designed foundation off; absent, it is on. */ + enabled?: boolean + type?: BuildingFoundationType + support?: BuildingFoundationSupport + /** Finish floor over the highest grade under the exterior walls. */ + ffAboveGradeIn?: number + source?: string + slabThicknessIn?: number + /** The slab's turned-down edge: its bottom under the grade outside, its width there. */ + edgeDepthIn?: number + edgeWidthIn?: number + /** + * The 45° taper from the top of the edge's plumb inside face to the slab's underside: how far it + * runs in (as far as it rises), 6 to 12 in. Absent, the rule sets it ({@link slabEdgeTaper}). + */ + edgeTaperIn?: number + stemThicknessIn?: number + /** The least the foundation's top (the slab's, or the stem wall's) stands over the grade beside it. */ + topAboveGradeIn?: number + /** + * Floor top down to the stem wall's top: the floor's layers and the sill. Read off `floorBuildUp`; + * a record that sets only this (an older one, a generator's) keeps it. + */ + floorFramingDepthIn?: number + /** A raised floor's layers over its sill, top down, metres, as a floor plate's (no ceiling). */ + floorBuildUp?: FloorBuildUp + footingWidthIn?: number + footingThicknessIn?: number + /** The footing's bottom under the grade beside it. */ + footingDepthIn?: number + /** Footings step down a slope; off, they stay level at the depth the lowest grade asks. */ + stepped?: boolean + stepHeightIn?: number + minStepRunIn?: number + /** + * A raised floor's stem wall, over the grade beside it, before its side steps: past it the footing + * steps down the slope and the stem drops to `topAboveGradeIn` over grade, a cripple wall over it. + */ + maxStemIn?: number + /** A raised floor's crawl space: the dirt under it dug out to this under the joists (IRC R317.1). */ + crawlClearanceIn?: number + /** + * A house over a basement: its walls' top (where the floor over them bears on its sill) over the + * highest grade along the house. More stands a daylight basement further out of the ground. + */ + basementTopAboveGradeIn?: number + /** A basement's poured walls, under the house's outline where the basement draws none of its own. */ + basementWallThicknessIn?: number + /** + * A raised house's room on a slab of its own (a garage): the slab's top over the highest grade + * along the room, the ground held that far under it. + */ + slabRoomTopAboveGradeIn?: number + gradeBeamWidthIn?: number + gradeBeamDepthIn?: number + pileDiameterIn?: number + pileSpacingIn?: number + pileLengthIn?: number +} + +/** The numbers a record holds, in the order the panel lists them. */ +export const BUILDING_FOUNDATION_DIMENSIONS = [ + 'ffAboveGradeIn', + 'slabThicknessIn', + 'edgeDepthIn', + 'edgeWidthIn', + 'stemThicknessIn', + 'topAboveGradeIn', + 'floorFramingDepthIn', + 'footingWidthIn', + 'footingThicknessIn', + 'footingDepthIn', + 'stepHeightIn', + 'minStepRunIn', + 'maxStemIn', + 'crawlClearanceIn', + 'basementTopAboveGradeIn', + 'basementWallThicknessIn', + 'slabRoomTopAboveGradeIn', + 'gradeBeamWidthIn', + 'gradeBeamDepthIn', + 'pileDiameterIn', + 'pileSpacingIn', + 'pileLengthIn', +] as const satisfies readonly (keyof BuildingFoundationRecord)[] + +export type BuildingFoundationDimension = (typeof BUILDING_FOUNDATION_DIMENSIONS)[number] + +/** Every number resolved, inches, with the storey count the defaults were read for. */ +export type BuildingFoundation = Required< + Omit +> & { + source: string | null + /** The slab edge's taper as set, inside its range; `null`: the rule sets it ({@link slabEdgeTaper}). */ + edgeTaperIn: number | null + storeys: number + /** The least finish floor the record allows: the slab's top, or the stem's top plus the floor framing, over the highest grade. */ + ffMinIn: number +} + +/** + * The common US residential numbers (IRC chapters 4 and 5), the ones a record left out takes. + * + * - Slab on grade: 4 in (R506.1 asks 3½), its top 8 in over grade (R317.1: wood sills bear on it); the + * edge turned down 12 in under grade (R403.1.4), 12 in wide at the bottom, its inside face plumb + * from there, square to the bottom, then in at 45° to the slab ({@link slabEdgeTaper}). + * - Raised floor: a cast concrete stem wall, 6 in for one storey and 8 in for more (Table + * R404.1.2), its top at least 8 in over grade (R317.1, R404.1.6); on a footing 8 in thick, 12, 15 + * or 23 in wide for one, two or three storeys (Table R403.1(1), 1500 psf soil), its bottom 12 in + * under grade. A side whose stem would stand over 36 in out of the ground steps down the slope + * (R403.1.5: the top of a footing is level), 8 in steps at least 24 in apart, its stem dropping to + * 8 in over grade under a cripple wall. The floor framing over it: ¾ in flooring, ⅝ in T&G + * sheathing, 2x12 joists (11¼ in), 1½ in sill; the crawl space dug out to 18 in under the joists + * (R317.1), the footing's bottom 12 in under it as under the grade outside (R403.1.4). + * - Over a basement: 8 in poured walls (Table R404.1.2(1)) on the footing, their top 8 in over the + * highest grade (R317.1: the sill and the floor framing on it stay off the ground); the slab inside. + * - A raised house's room on a slab (a garage): its slab's top 4 in over grade, its walls on an 8 in + * curb over the slab (R317.1), the curb on the house's footing. + * - Piles: a 12 × 24 in grade beam on 12 in piles 8 ft apart and at every corner, 10 ft deep. + * Engineered sizes: the defaults only draw them. + */ +export function buildingFoundationDefaults( + storeys: number, +): Omit { + return { + enabled: true, + type: 'slab', + support: 'footing', + ffAboveGradeIn: 8, + slabThicknessIn: 4, + edgeDepthIn: 12, + edgeWidthIn: 12, + edgeTaperIn: null, + stemThicknessIn: storeys > 1 ? 8 : 6, + topAboveGradeIn: 8, + floorFramingDepthIn: floorFramingIn(DEFAULT_RAISED_FLOOR_BUILD_UP), + floorBuildUp: DEFAULT_RAISED_FLOOR_BUILD_UP, + footingWidthIn: storeys >= 3 ? 23 : storeys === 2 ? 15 : 12, + footingThicknessIn: 8, + footingDepthIn: 12, + stepped: true, + stepHeightIn: 8, + minStepRunIn: 24, + maxStemIn: 36, + crawlClearanceIn: 18, + basementTopAboveGradeIn: 8, + basementWallThicknessIn: 8, + slabRoomTopAboveGradeIn: 4, + gradeBeamWidthIn: 12, + gradeBeamDepthIn: 24, + pileDiameterIn: 12, + pileSpacingIn: 96, + pileLengthIn: 120, + } +} + +/** The range each number may take, inches (the slope in degrees). */ +export const BUILDING_FOUNDATION_LIMITS: Record = { + ffAboveGradeIn: [0, 120], + slabThicknessIn: [3.5, 12], + edgeDepthIn: [0, 60], + edgeWidthIn: [6, 48], + stemThicknessIn: [4, 16], + topAboveGradeIn: [0, 48], + floorFramingDepthIn: [4, 30], + footingWidthIn: [8, 60], + footingThicknessIn: [6, 36], + footingDepthIn: [6, 120], + stepHeightIn: [2, 48], + minStepRunIn: [12, 120], + maxStemIn: [12, 120], + crawlClearanceIn: [12, 48], + basementTopAboveGradeIn: [0, 120], + basementWallThicknessIn: [6, 16], + slabRoomTopAboveGradeIn: [0, 24], + gradeBeamWidthIn: [8, 36], + gradeBeamDepthIn: [12, 60], + pileDiameterIn: [6, 48], + pileSpacingIn: [36, 240], + pileLengthIn: [24, 600], +} + +/** The slab edge's 45° taper: the least and the most it runs in (and rises), inches. */ +export const SLAB_EDGE_TAPER_IN: readonly [number, number] = [6, 12] + +/** + * A slab edge's 45° taper, metres: how far it runs in, as far as it rises, from the top of the + * edge's plumb inside face to the slab's underside, for an edge reaching `depth` under that + * underside. `taper` where the record sets one, else half the depth held to 6 to 12 in, so a + * deeper edge grows plumb, not wider; never more than the depth (a shallow edge tapers from its + * bottom). + */ +export function slabEdgeTaper(depth: number, taper: number | null = null): number { + const [least, most] = SLAB_EDGE_TAPER_IN + const run = taper ?? Math.min(inches(most), Math.max(inches(least), depth / 2)) + return Math.max(0, Math.min(depth, run)) +} + +/** The record a building stores, as written; `null` where it stores none. */ +export function buildingFoundationRecordOf( + building: Pick | null | undefined, +): BuildingFoundationRecord | null { + const stored = (building?.metadata as { foundation?: unknown } | undefined)?.foundation + return stored && typeof stored === 'object' ? (stored as BuildingFoundationRecord) : null +} + +/** The building's storeys: its levels from the ground level up, the roof level left out. */ +export function buildingStoreyCount(nodes: Record, buildingId: string): number { + const ground = groundLevelOf(nodes, buildingId) + const elevations = getLevelElevations(nodes) + let count = 0 + for (const node of Object.values(nodes)) { + if (node.type !== 'level' || elevations.get(node.id)?.buildingId !== buildingId) continue + if (ground && (node as LevelNode).level < ground.level.level) continue + if (servesAsRoofLevel(nodes, node as LevelNode)) continue + count += 1 + } + return Math.max(1, count) +} + +/** + * The building's foundation with every number filled: the stored value inside its range, or the + * default for the building's storey count (the slab edge's taper, stored none, is left to the + * rule). The finish floor never goes under the least the type allows (`ffMinIn`). + */ +export function resolveBuildingFoundation( + record: BuildingFoundationRecord | null, + storeys: number, +): BuildingFoundation { + const defaults = buildingFoundationDefaults(storeys) + const type: BuildingFoundationType = record?.type === 'raised' ? 'raised' : 'slab' + const support: BuildingFoundationSupport = record?.support === 'piles' ? 'piles' : 'footing' + const resolved = { ...defaults, type, support } as BuildingFoundation + for (const key of BUILDING_FOUNDATION_DIMENSIONS) { + const value = record?.[key] + const [min, max] = BUILDING_FOUNDATION_LIMITS[key] + resolved[key] = + typeof value === 'number' && Number.isFinite(value) + ? Math.min(max, Math.max(min, value)) + : defaults[key] + } + // The floor framing is the floor's layers and the sill. A record that set only the depth (an + // older record, a generator's) keeps it; its layers read the default until one is set. + const floorBuildUp = record?.floorBuildUp ?? DEFAULT_RAISED_FLOOR_BUILD_UP + resolved.floorBuildUp = floorBuildUp + if (record?.floorBuildUp || typeof record?.floorFramingDepthIn !== 'number') + resolved.floorFramingDepthIn = floorFramingIn(floorBuildUp) + const [least, most] = SLAB_EDGE_TAPER_IN + const taper = record?.edgeTaperIn + resolved.edgeTaperIn = + typeof taper === 'number' && Number.isFinite(taper) + ? Math.min(most, Math.max(least, taper)) + : null + resolved.enabled = record?.enabled !== false + resolved.stepped = record?.stepped !== false + resolved.source = typeof record?.source === 'string' ? record.source : null + resolved.storeys = storeys + resolved.ffMinIn = ffMinimumIn(resolved) + const storedFloor = record?.ffAboveGradeIn + resolved.ffAboveGradeIn = Math.max( + resolved.ffMinIn, + typeof storedFloor === 'number' && Number.isFinite(storedFloor) + ? storedFloor + : resolved.ffMinIn, + ) + return resolved +} + +/** The least finish floor over the highest grade: the slab's top, or the stem's top plus the framing on it. */ +export function ffMinimumIn( + foundation: Pick, +): number { + return foundation.type === 'raised' + ? foundation.topAboveGradeIn + foundation.floorFramingDepthIn + : foundation.topAboveGradeIn +} + +/** The building's foundation, resolved; `null` for an id that is no building. */ +export function buildingFoundationOf( + nodes: Record, + buildingId: string, +): BuildingFoundation | null { + const building = nodes[buildingId as AnyNodeId] + if (building?.type !== 'building') return null + return resolveBuildingFoundation( + buildingFoundationRecordOf(building), + buildingStoreyCount(nodes, buildingId), + ) +} + +/** + * Whether the building draws its designed foundation: unless its record is switched off, a building + * with no record standing on the default slab. + */ +export function isBuildingFoundationOn(building: Pick | undefined) { + return building !== undefined && buildingFoundationRecordOf(building)?.enabled !== false +} + +export const inches = (value: number) => value * INCH diff --git a/packages/core/src/lib/deck-boards.test.ts b/packages/core/src/lib/deck-boards.test.ts new file mode 100644 index 0000000000..0720e13c18 --- /dev/null +++ b/packages/core/src/lib/deck-boards.test.ts @@ -0,0 +1,249 @@ +import { describe, expect, test } from 'bun:test' +import { setZoneIntent } from '../commands/structure/set-zone-intent' +import { applyToScratch, structureChangeBatch } from '../commands/structure/shared' +import { enclosed } from '../commands/structure/structure.test' +import { type AnyNode, type SlabNode, WallNode, type ZoneNode } from '../schema' +import { railingWallDefaults } from '../schema/nodes/railing-wall' +import { + DECK_BOARD_JOINT_GAP, + DECK_BOARD_MAX_LENGTH, + DECK_BOARD_STOCK, + DEFAULT_DECK_BOARDS, + deckBoardSettings, + deckBoardThickness, + deckSlabBoards, + layoutDeckBoards, +} from './deck-boards' +import { area, difference, type MultiPolygon, type Polygon, union } from './polygon-boolean' +import { reconcileLevelStructure } from './structure-kernel' + +const IN = 0.0254 +const FT = 12 * IN +const LEVEL = 'level_test' + +/** Settle the level's structure as the live sync does after an edit. */ +function settle(before: Record, after: Record) { + let i = 0 + const derived = reconcileLevelStructure({ + levelId: LEVEL, + nodes: after, + previousNodes: before, + mintId: (kind) => { + let id: string + do id = `${kind}_derived${++i}` + while (after[id]) + return id + }, + }) + return applyToScratch(after, structureChangeBatch(derived.patches)) +} + +/** The kitchen (8 × 4) with a deck fenced off its z = 4 side by railings. */ +function withDeck() { + const { nodes } = enclosed() + let next: Record = nodes + const sides: Array<[[number, number], [number, number]]> = [ + [ + [2, 4], + [2, 6], + ], + [ + [2, 6], + [6, 6], + ], + [ + [6, 6], + [6, 4], + ], + ] + sides.forEach(([start, end], index) => { + const railing = WallNode.parse({ + ...railingWallDefaults(), + id: `wall_railing${index}`, + parentId: LEVEL, + start, + end, + }) + next = { ...next, [railing.id]: railing } + }) + return settle(nodes, next) +} + +describe("a deck room's boards", () => { + const deckOf = (nodes: Record) => + Object.values(nodes).find( + (node): node is ZoneNode => node.type === 'zone' && node.roomType === 'deck', + )! + const plateOf = (nodes: Record, zone: ZoneNode) => + Object.values(nodes).find( + (node): node is SlabNode => + node.type === 'slab' && node.plateRole !== 'base' && !!node.zoneIds?.includes(zone.id), + )! + const edit = (nodes: Record, zone: ZoneNode, decking: unknown) => + settle( + nodes, + applyToScratch( + nodes, + structureChangeBatch( + setZoneIntent(nodes, { + zoneId: zone.id, + patch: { decking } as Parameters[1]['patch'], + }).changes, + ), + ), + ) + + test('are kept on the room, cleared by null; its floor plate declares their thickness, the frame under them', () => { + const nodes = withDeck() + const deck = deckOf(nodes) + expect(deck.decking).toBeUndefined() + expect(plateOf(nodes, deck).metadata?.decking).toBeCloseTo(IN, 9) + const set = edit(nodes, deck, { boardSize: '2x6', direction: 'diagonal' }) + expect(deckOf(set).decking).toEqual({ boardSize: '2x6', direction: 'diagonal' }) + expect(plateOf(set, deck).metadata?.decking).toBeCloseTo(1.5 * IN, 9) + const gap = edit(set, deck, { gap: IN / 8, direction: null }) + expect(deckOf(gap).decking).toEqual({ boardSize: '2x6', gap: IN / 8 }) + const cleared = edit(gap, deck, null) + expect(deckOf(cleared).decking).toBeUndefined() + expect(plateOf(cleared, deck).metadata?.decking).toBeCloseTo(IN, 9) + }) +}) +const AXES = { origin: [0, 0] as const, u: [1, 0] as const, v: [0, 1] as const } +const rect = (w: number, d: number): MultiPolygon => [ + { + outer: [ + [0, 0], + [w, 0], + [w, d], + [0, d], + ], + holes: [], + }, +] + +/** A board's extent along and across plan axis `along`. */ +function extent(board: Polygon, along: readonly [number, number]) { + const across: [number, number] = [-along[1], along[0]] + const a = board.outer.map(([x, z]) => x * along[0] + z * along[1]) + const c = board.outer.map(([x, z]) => x * across[0] + z * across[1]) + return { length: Math.max(...a) - Math.min(...a), width: Math.max(...c) - Math.min(...c), a, c } +} + +test('a deck with no settings gets 5/4x6 boards, 1 in by 5 1/2 in, a quarter inch apart, along the house', () => { + expect(deckBoardSettings(undefined)).toEqual(DEFAULT_DECK_BOARDS) + expect(DEFAULT_DECK_BOARDS).toEqual({ boardSize: '5/4x6', gap: IN / 4, direction: 'parallel' }) + expect(deckBoardThickness(DEFAULT_DECK_BOARDS)).toBeCloseTo(IN, 9) + expect(DECK_BOARD_STOCK['5/4x6'].width).toBeCloseTo(5.5 * IN, 9) + expect(DECK_BOARD_STOCK.composite.thickness).toBeCloseTo((15 / 16) * IN, 9) + expect( + deckBoardSettings({ decking: { boardSize: '2x4', gap: IN / 8, direction: 'diagonal' } }), + ).toEqual({ boardSize: '2x4', gap: IN / 8, direction: 'diagonal' }) + // a deck room's plate takes its room's boards; a generated deck's 1 1/2 in decking is 2x stock + const zone = { roomType: 'deck' as const, decking: { boardSize: '2x6' as const } } + expect(deckSlabBoards({ zoneIds: ['zone_a'] }, () => zone).boardSize).toBe('2x6') + expect( + deckSlabBoards({ metadata: { floor: 'deck', decking: 1.5 * IN } }, () => null).boardSize, + ).toBe('2x6') + expect(deckSlabBoards({ metadata: { floor: 'deck' } }, () => null)).toEqual(DEFAULT_DECK_BOARDS) +}) + +describe('boards along the house over a 12 × 10 ft deck', () => { + const outline = rect(12 * FT, 10 * FT) + const boards = layoutDeckBoards(outline, AXES, DEFAULT_DECK_BOARDS) + const width = 5.5 * IN + + test('rows of full boards from the outer edge in, a quarter inch apart, the last ripped against the house', () => { + const rows = boards + .map((board) => extent(board, [1, 0])) + .sort((a, b) => Math.max(...b.c) - Math.max(...a.c)) + expect(rows).toHaveLength(Math.ceil((10 * FT + IN / 4) / (width + IN / 4))) + expect(Math.max(...rows[0]!.c)).toBeCloseTo(10 * FT, 4) + for (const row of rows) expect(row.length).toBeCloseTo(12 * FT, 4) + for (const row of rows.slice(0, -1)) expect(row.width).toBeCloseTo(width, 3) + for (let i = 1; i < rows.length; i++) + expect(Math.min(...rows[i - 1]!.c) - Math.max(...rows[i]!.c)).toBeCloseTo(IN / 4, 3) + expect(rows.at(-1)!.width).toBeLessThan(width) + }) + + test('the boards cover the deck but for the gaps, and nothing past its outline', () => { + expect(area(difference(union(boards), outline))).toBeCloseTo(0, 6) + const covered = area(union(boards)) / area(outline) + expect(covered).toBeGreaterThan(0.94) + expect(covered).toBeLessThan(0.97) + }) +}) + +test('diagonal boards run at 45° to the house; boards out from it run square to it', () => { + const outline = rect(10 * FT, 8 * FT) + const diagonal = layoutDeckBoards(outline, AXES, { + ...DEFAULT_DECK_BOARDS, + direction: 'diagonal', + }) + const k = Math.SQRT1_2 + for (const board of diagonal) + expect(extent(board, [k, k]).width).toBeLessThanOrEqual(5.5 * IN + 1e-4) + expect(area(difference(union(diagonal), outline))).toBeCloseTo(0, 6) + const out = layoutDeckBoards(outline, AXES, { + ...DEFAULT_DECK_BOARDS, + direction: 'perpendicular', + }) + for (const board of out) expect(extent(board, [0, 1]).length).toBeCloseTo(8 * FT, 4) +}) + +test('boards are cut along any outline: an L-shaped deck', () => { + const ell: MultiPolygon = [ + { + outer: [ + [0, 0], + [6, 0], + [6, 2], + [2, 2], + [2, 5], + [0, 5], + ], + holes: [], + }, + ] + const boards = layoutDeckBoards(ell, AXES, DEFAULT_DECK_BOARDS) + expect(area(difference(union(boards), ell))).toBeCloseTo(0, 6) + expect(area(union(boards)) / area(ell)).toBeGreaterThan(0.93) + // the long rows stop at the notch + for (const board of boards) { + const { a, c } = extent(board, [1, 0]) + if (Math.min(...c) > 2 + 1e-6) expect(Math.max(...a)).toBeLessThanOrEqual(2 + 1e-6) + } +}) + +test('a run past 16 ft is laid in lengths butted over the joists, the joints of neighbouring rows staggered', () => { + const outline = rect(30 * FT, 4 * FT) + const stations = Array.from({ length: 23 }, (_, i) => 0.75 * IN + i * 16 * IN) + const boards = layoutDeckBoards(outline, AXES, DEFAULT_DECK_BOARDS, { + origin: [0, 0], + axis: [1, 0], + stations, + }) + const rows = new Map>() + for (const board of boards) { + const { a, c } = extent(board, [1, 0]) + const key = Math.max(...c).toFixed(3) + rows.set(key, [...(rows.get(key) ?? []), { from: Math.min(...a), to: Math.max(...a) }]) + } + const joints: number[][] = [] + for (const pieces of rows.values()) { + pieces.sort((p, q) => p.from - q.from) + expect(pieces.length).toBeGreaterThan(1) + for (const piece of pieces) + expect(piece.to - piece.from).toBeLessThanOrEqual(DECK_BOARD_MAX_LENGTH + 1e-6) + const row: number[] = [] + for (let i = 1; i < pieces.length; i++) { + const joint = (pieces[i - 1]!.to + pieces[i]!.from) / 2 + expect(pieces[i]!.from - pieces[i - 1]!.to).toBeCloseTo(DECK_BOARD_JOINT_GAP, 3) + // over a joist + expect(Math.min(...stations.map((s) => Math.abs(s - joint)))).toBeLessThan(1e-4) + row.push(joint) + } + joints.push(row) + } + for (let i = 1; i < joints.length; i++) + expect(joints[i]![0]).not.toBeCloseTo(joints[i - 1]![0]!, 3) +}) diff --git a/packages/core/src/lib/deck-boards.ts b/packages/core/src/lib/deck-boards.ts new file mode 100644 index 0000000000..e694808f96 --- /dev/null +++ b/packages/core/src/lib/deck-boards.ts @@ -0,0 +1,269 @@ +import type { ZoneNode } from '../schema' +import { area, intersection, type MultiPolygon, type Polygon } from './polygon-boolean' + +/** + * A deck's boards (a deck room, `lib/outdoor-room`): their stock, the gap between them and the way + * they run, kept on the room (`decking`, absent = the defaults) and laid by the engine over the + * deck's frame (`lib/deck-framing`). Sections are the boards' actual (dressed) ones: a 5/4x6 is + * 1 in by 5 1/2 in. The boards run along the house (square to the joists), at 45° (the joists + * then closer, IRC Table R507.7) or out from the house (the joists turned to run along it). + */ + +const IN = 0.0254 +const FT = 12 * IN + +type P = readonly [number, number] + +export const DECK_BOARD_SIZES = ['5/4x6', '2x6', '2x4', 'composite'] as const +export type DeckBoardSize = (typeof DECK_BOARD_SIZES)[number] + +export const DECK_BOARD_DIRECTIONS = ['parallel', 'diagonal', 'perpendicular'] as const +/** The boards' run against the house: along it, at 45° to it, or out from it. */ +export type DeckBoardDirection = (typeof DECK_BOARD_DIRECTIONS)[number] + +export type DeckBoardStock = { + label: string + /** Actual section, metres. */ + thickness: number + width: number + /** + * The widest joist spacing the boards may span (IRC Table R507.7): square to the joists, and + * at 45° to them. Composite boards go by their maker's listing (R507.2.2); these are the + * common 16 in and 12 in. + */ + maxJoistSpacing: { square: number; diagonal: number } +} + +export const DECK_BOARD_STOCK: Record = { + '5/4x6': { + label: '5/4x6', + thickness: 1 * IN, + width: 5.5 * IN, + maxJoistSpacing: { square: 16 * IN, diagonal: 12 * IN }, + }, + '2x6': { + label: '2x6', + thickness: 1.5 * IN, + width: 5.5 * IN, + maxJoistSpacing: { square: 24 * IN, diagonal: 16 * IN }, + }, + '2x4': { + label: '2x4', + thickness: 1.5 * IN, + width: 3.5 * IN, + maxJoistSpacing: { square: 24 * IN, diagonal: 16 * IN }, + }, + composite: { + label: 'Composite', + thickness: (15 / 16) * IN, + width: 5.5 * IN, + maxJoistSpacing: { square: 16 * IN, diagonal: 12 * IN }, + }, +} + +/** The gaps boards are laid at, narrowest first: 1/8, 3/16 and 1/4 in. */ +export const DECK_BOARD_GAPS = [IN / 8, (3 * IN) / 16, IN / 4] as const + +/** The longest board laid in one piece (16 ft stock); longer runs butt over a joist. */ +export const DECK_BOARD_MAX_LENGTH = 16 * FT +/** The gap at a butt joint. */ +export const DECK_BOARD_JOINT_GAP = IN / 8 + +export type DeckBoardSettings = { + boardSize: DeckBoardSize + /** Metres between boards. */ + gap: number + direction: DeckBoardDirection +} + +export const DEFAULT_DECK_BOARDS: DeckBoardSettings = { + boardSize: '5/4x6', + gap: IN / 4, + direction: 'parallel', +} + +const isBoardSize = (value: unknown): value is DeckBoardSize => + typeof value === 'string' && (DECK_BOARD_SIZES as readonly string[]).includes(value) +const isDirection = (value: unknown): value is DeckBoardDirection => + typeof value === 'string' && (DECK_BOARD_DIRECTIONS as readonly string[]).includes(value) + +/** A deck's boards: its own settings, else the defaults (an unknown size or run is the default). */ +export function deckBoardSettings( + zone: Pick | null | undefined, +): DeckBoardSettings { + const own = zone?.decking + return { + boardSize: isBoardSize(own?.boardSize) ? own.boardSize : DEFAULT_DECK_BOARDS.boardSize, + gap: typeof own?.gap === 'number' && own.gap > 0 ? own.gap : DEFAULT_DECK_BOARDS.gap, + direction: isDirection(own?.direction) ? own.direction : DEFAULT_DECK_BOARDS.direction, + } +} + +/** + * The boards a deck slab (`isDeckSlab`) is laid with: its room's, when it is a deck room's floor + * plate; else the defaults, in 2x stock when the slab declares decking that thick + * (`metadata.decking`, a generated deck's). + */ +export function deckSlabBoards( + slab: { zoneIds?: readonly string[]; metadata?: unknown }, + zoneOf: (id: string) => Pick | null | undefined, +): DeckBoardSettings { + const zone = slab.zoneIds?.length === 1 ? zoneOf(slab.zoneIds[0]!) : undefined + if (zone?.roomType === 'deck') return deckBoardSettings(zone) + const decking = (slab.metadata as { decking?: unknown } | undefined)?.decking + return typeof decking === 'number' && decking >= 1.25 * IN + ? { ...DEFAULT_DECK_BOARDS, boardSize: '2x6' } + : DEFAULT_DECK_BOARDS +} + +/** The boards' thickness: how far under the walking surface the joists' tops stand. */ +export function deckBoardThickness(settings: Pick): number { + return DECK_BOARD_STOCK[settings.boardSize].thickness +} + +/** The widest joist spacing these boards allow, laid as they run (Table R507.7). */ +export function deckJoistSpacingLimit( + settings: Pick, +): number { + const { maxJoistSpacing } = DECK_BOARD_STOCK[settings.boardSize] + return settings.direction === 'diagonal' ? maxJoistSpacing.diagonal : maxJoistSpacing.square +} + +/** The deck's frame axes (`deckAxes`): `u` along the house edge, `v` square to it, into the deck. */ +export type DeckBoardAxes = { origin: P; u: P; v: P } + +export type DeckBoardRun = { + /** The way the boards run, a plan unit vector. */ + along: [number, number] + /** Square to it: the way the rows are laid. */ + across: [number, number] +} + +/** Which way a deck's boards run over its frame. */ +export function deckBoardRun(axes: DeckBoardAxes, direction: DeckBoardDirection): DeckBoardRun { + const { u, v } = axes + if (direction === 'perpendicular') return { along: [v[0], v[1]], across: [u[0], u[1]] } + if (direction === 'diagonal') { + const k = Math.SQRT1_2 + return { + along: [(u[0] + v[0]) * k, (u[1] + v[1]) * k], + across: [(v[0] - u[0]) * k, (v[1] - u[1]) * k], + } + } + return { along: [u[0], u[1]], across: [v[0], v[1]] } +} + +/** + * The joists a long board butts over: their centrelines, the lines where a point's distance + * along `axis` from `origin` is one of `stations`. + */ +export type DeckBoardJoists = { origin: P; axis: P; stations: readonly number[] } + +/** A sliver under this (m²) is not laid. */ +const MIN_BOARD_AREA = 1e-4 +/** No board is cut shorter than this at a butt joint. */ +const MIN_BOARD_PIECE = 2 * FT + +/** + * The boards over `outline` (a deck's top): rows of the board's width, the gap apart, run as the + * settings say and cut along the outline, any shape. Boards running along the house start full at + * its outer edge, the last row ripped against the house; the others are centred, ripped alike at + * both sides. A row longer than 16 ft is laid in lengths butted over the joists (`joists`), the + * joints of neighbouring rows staggered. + */ +export function layoutDeckBoards( + outline: MultiPolygon, + axes: DeckBoardAxes, + settings: DeckBoardSettings, + joists?: DeckBoardJoists, +): Polygon[] { + const { along, across } = deckBoardRun(axes, settings.direction) + const { width } = DECK_BOARD_STOCK[settings.boardSize] + const pitch = width + settings.gap + const [ox, oz] = axes.origin + const alongOf = (p: P) => (p[0] - ox) * along[0] + (p[1] - oz) * along[1] + const acrossOf = (p: P) => (p[0] - ox) * across[0] + (p[1] - oz) * across[1] + const at = (a: number, c: number): [number, number] => [ + ox + along[0] * a + across[0] * c, + oz + along[1] * a + across[1] * c, + ] + let aMin = Number.POSITIVE_INFINITY + let aMax = Number.NEGATIVE_INFINITY + let cMin = Number.POSITIVE_INFINITY + let cMax = Number.NEGATIVE_INFINITY + for (const { outer } of outline) + for (const p of outer) { + aMin = Math.min(aMin, alongOf(p)) + aMax = Math.max(aMax, alongOf(p)) + cMin = Math.min(cMin, acrossOf(p)) + cMax = Math.max(cMax, acrossOf(p)) + } + if (!(cMax - cMin > 1e-6 && aMax - aMin > 1e-6)) return [] + const rows = Math.ceil((cMax - cMin + settings.gap) / pitch - 1e-9) + // Along the house the first row is a full board at the outer edge; otherwise centred. + const first = + settings.direction === 'parallel' + ? cMax - width + : cMin - (rows * pitch - settings.gap - (cMax - cMin)) / 2 + const step = settings.direction === 'parallel' ? -pitch : pitch + const band = (a0: number, a1: number, c0: number, c1: number): Polygon => ({ + outer: [at(a0, c0), at(a1, c0), at(a1, c1), at(a0, c1)], + holes: [], + }) + // A joist's centreline crossing a row's middle, as a distance along the row. + const crossings = (c: number): number[] => { + if (!joists) return [] + const [ax, az] = joists.axis + const dot = along[0] * ax + along[1] * az + if (Math.abs(dot) < 1e-6) return [] + const offset = + (ox - joists.origin[0]) * ax + + (oz - joists.origin[1]) * az + + c * (across[0] * ax + across[1] * az) + return joists.stations.map((s) => (s - offset) / dot).sort((a, b) => a - b) + } + const boards: Polygon[] = [] + for (let row = 0; row < rows; row++) { + const c0 = first + step * row + const pieces = intersection(band(aMin - 1, aMax + 1, c0, c0 + width), outline).filter( + (piece) => area([piece]) >= MIN_BOARD_AREA, + ) + const joints = crossings(c0 + width / 2) + for (const piece of pieces) { + let start = Number.POSITIVE_INFINITY + let end = Number.NEGATIVE_INFINITY + for (const p of piece.outer) { + start = Math.min(start, alongOf(p)) + end = Math.max(end, alongOf(p)) + } + // Butt joints, staggered by row: the first length a third shorter each row in three. + const cuts: number[] = [] + let from = start + let limit = DECK_BOARD_MAX_LENGTH * (1 - (row % 3) / 3) + while (end - from > DECK_BOARD_MAX_LENGTH + 1e-9) { + const joint = joints + .filter( + (a) => a > from + MIN_BOARD_PIECE && a <= from + limit && a < end - MIN_BOARD_PIECE, + ) + .at(-1) + if (joint === undefined) break + cuts.push(joint) + from = joint + limit = DECK_BOARD_MAX_LENGTH + } + if (!cuts.length) { + boards.push(piece) + continue + } + const half = DECK_BOARD_JOINT_GAP / 2 + const bounds = [start - 1, ...cuts.flatMap((a) => [a - half, a + half]), end + 1] + for (let i = 0; i + 1 < bounds.length; i += 2) + boards.push( + ...intersection(band(bounds[i]!, bounds[i + 1]!, c0 - 1, c0 + width + 1), piece).filter( + (part) => area([part]) >= MIN_BOARD_AREA, + ), + ) + } + } + return boards +} diff --git a/packages/core/src/lib/deck-framing.test.ts b/packages/core/src/lib/deck-framing.test.ts new file mode 100644 index 0000000000..304531714e --- /dev/null +++ b/packages/core/src/lib/deck-framing.test.ts @@ -0,0 +1,303 @@ +import { describe, expect, test } from 'bun:test' +import { DECK_BOARD_STOCK } from './deck-boards' +import { + DECK_BEAM_INSET, + DECK_BOARD_OVERHANG, + DECK_POST_SPACING, + type DeckPiece, + type DeckWall, + planDeckFraming, +} from './deck-framing' +import { containsPoint } from './polygon-boolean' + +const IN = 0.0254 +const FT = 12 * IN + +// The house's rear wall runs along x at z = 0; a 16 × 12 ft deck lies behind it, +z. +const WALL: DeckWall = { start: [-1, 0], end: [8, 0], thickness: 0.14 } +const DECK: Array<[number, number]> = [ + [0, 0], + [16 * FT, 0], + [16 * FT, 12 * FT], + [0, 12 * FT], +] +const TOP = 0.6 +const HIGH = TOP - 36 * IN +const LOW = TOP - 18 * IN +const BURIED = TOP - 8 * IN + +const bottomOf = (piece: DeckPiece) => piece.position[1] - piece.dims[1] / 2 +const topOf = (piece: DeckPiece) => piece.position[1] + piece.dims[1] / 2 +/** A piece's plan footprint corners. */ +const corners = (piece: DeckPiece): Array<[number, number]> => { + const [x, , z] = piece.position + const [length, , width] = piece.dims + const c = Math.cos(piece.yaw) + const s = -Math.sin(piece.yaw) + return [ + [-1, -1], + [1, -1], + [1, 1], + [-1, 1], + ].map(([a, b]) => [ + x + (c * a * length - s * b * width) / 2, + z + (s * a * length + c * b * width) / 2, + ]) +} +const within = (outline: Array<[number, number]>, piece: DeckPiece) => + corners(piece).every( + ([x, z]) => + containsPoint([{ outer: outline, holes: [] }], [x, z]) || + outline.some(([px, pz], i) => { + const [qx, qz] = outline[(i + 1) % outline.length]! + const t = Math.max( + 0, + Math.min( + 1, + ((x - px) * (qx - px) + (z - pz) * (qz - pz)) / ((qx - px) ** 2 + (qz - pz) ** 2), + ), + ) + return Math.hypot(x - px - (qx - px) * t, z - pz - (qz - pz) * t) < 1e-6 + }), + ) + +describe('a 16 × 12 ft deck on the house wall, 3 ft over the grade, boards along the house', () => { + const plan = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + }) + const t = plan.joist!.thickness + + test('a ledger on the wall; joists square to it at 16 in from side to side, under the boards; rims set in so the boards overhang them, none at the house', () => { + expect(plan.axes?.ledgerEdge).toBe(0) + expect(plan.ledger?.position[2]).toBeCloseTo(t / 2, 9) + expect(plan.ledger?.dims[0]).toBeCloseTo(16 * FT - 2 * DECK_BOARD_OVERHANG, 9) + const xs = plan.joists.map((joist) => joist.position[0]).sort((a, b) => a - b) + expect(xs[0]).toBeCloseTo(DECK_BOARD_OVERHANG + t / 2, 9) + expect(xs.at(-1)).toBeCloseTo(16 * FT - DECK_BOARD_OVERHANG - t / 2, 9) + for (let i = 1; i < xs.length; i++) + expect(xs[i]! - xs[i - 1]!).toBeLessThanOrEqual(16 * IN + 1e-9) + for (const joist of plan.joists) { + expect(topOf(joist)).toBeCloseTo(TOP - IN, 9) + expect(joist.dims[0]).toBeCloseTo(12 * FT - DECK_BOARD_OVERHANG - 2 * t, 9) + expect(joist.hangers).toHaveLength(1) + } + // the end joists are the side rims + expect(plan.joists.filter((joist) => joist.atRim)).toHaveLength(2) + expect(plan.rims).toHaveLength(3) + const outer = plan.rims.find((rim) => rim.position[2] > 3)! + expect(outer.position[2]).toBeCloseTo(12 * FT - DECK_BOARD_OVERHANG - t / 2, 9) + }) + + test('one 4x8 beam dropped under the joists 16 in in from the outer rim, on 4x4 posts to the grade no more than 8 ft apart', () => { + expect(plan.beam).toBe('dropped') + expect(plan.beams).toHaveLength(1) + const [beam] = plan.beams as [DeckPiece] + expect(beam.size).toBe('4x8') + expect(beam.position[2]).toBeCloseTo(12 * FT - DECK_BOARD_OVERHANG - DECK_BEAM_INSET, 9) + expect(topOf(beam)).toBeCloseTo(TOP - IN - plan.joist!.depth, 9) + const posts = plan.posts.slice().sort((a, b) => a.position[0] - b.position[0]) + expect(posts.length).toBeGreaterThanOrEqual(3) + for (let i = 1; i < posts.length; i++) + expect(posts[i]!.position[0] - posts[i - 1]!.position[0]).toBeLessThanOrEqual( + DECK_POST_SPACING, + ) + for (const post of posts) { + expect(post.size).toBe('4x4') + expect(bottomOf(post)).toBeCloseTo(HIGH, 9) + expect(topOf(post)).toBeCloseTo(bottomOf(beam), 9) + } + }) + + test('every piece lies inside the outline', () => { + for (const piece of [plan.ledger!, ...plan.rims, ...plan.joists, ...plan.beams]) + expect(within(DECK, piece)).toBe(true) + }) +}) + +test('diagonal boards close the joists to 12 in under 5/4 or composite boards; 2x boards span 16 in at 45°', () => { + const at = (boardSize: 'composite' | '2x6') => + planDeckFraming({ + polygon: DECK, + top: TOP, + decking: DECK_BOARD_STOCK[boardSize].thickness, + walls: [WALL], + gradeY: HIGH, + boards: { boardSize, gap: IN / 4, direction: 'diagonal' }, + }) + const diagonal = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + boards: { boardSize: '5/4x6', gap: IN / 4, direction: 'diagonal' }, + }) + expect(diagonal.joistSpacing).toBeCloseTo(12 * IN, 9) + expect(diagonal.spacingForBoards).toBe(true) + const xs = diagonal.joists.map((joist) => joist.position[0]).sort((a, b) => a - b) + for (let i = 1; i < xs.length; i++) + expect(xs[i]! - xs[i - 1]!).toBeLessThanOrEqual(12 * IN + 1e-9) + expect(at('composite').joistSpacing).toBeCloseTo(12 * IN, 9) + expect(at('2x6').joistSpacing).toBeCloseTo(16 * IN, 9) + expect(at('2x6').spacingForBoards).toBe(false) +}) + +test('boards out from the house turn the joists to run along it, on beams square to the house at both sides; the ledger hangs none', () => { + const plan = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + boards: { boardSize: '5/4x6', gap: IN / 4, direction: 'perpendicular' }, + }) + expect(plan.joistsAlongHouse).toBe(true) + for (const joist of plan.joists) { + // along x, the house's run + expect(Math.abs(Math.cos(joist.yaw))).toBeCloseTo(1, 9) + expect(joist.hangers).toEqual([]) + } + const zs = plan.joists.map((joist) => joist.position[2]).sort((a, b) => a - b) + for (let i = 1; i < zs.length; i++) + expect(zs[i]! - zs[i - 1]!).toBeLessThanOrEqual(16 * IN + 1e-9) + expect(plan.beams).toHaveLength(2) + const xs = plan.beams.map((beam) => beam.position[0]).sort((a, b) => a - b) + expect(xs[0]).toBeCloseTo(DECK_BOARD_OVERHANG + DECK_BEAM_INSET, 9) + expect(xs[1]).toBeCloseTo(16 * FT - DECK_BOARD_OVERHANG - DECK_BEAM_INSET, 9) + for (const beam of plan.beams) expect(Math.abs(Math.sin(beam.yaw))).toBeCloseTo(1, 9) + // posts under both beams, both ends of each + expect(plan.posts.length).toBeGreaterThanOrEqual(4) + expect(new Set(plan.posts.map((post) => post.position[0].toFixed(3))).size).toBe(2) + // the joists span between the beams: sized for that, not the deck's width + expect(plan.joist?.size).toBe('2x10') + for (const piece of [plan.ledger!, ...plan.rims, ...plan.joists, ...plan.beams]) + expect(within(DECK, piece)).toBe(true) +}) + +test('a deck too wide for the joists gets a beam between, its joists sized for the shorter span', () => { + const wide: Array<[number, number]> = [ + [0, 0], + [30 * FT, 0], + [30 * FT, 12 * FT], + [0, 12 * FT], + ] + const plan = planDeckFraming({ + polygon: wide, + top: TOP, + decking: IN, + walls: [{ start: [-1, 0], end: [10, 0], thickness: 0.14 }], + gradeY: HIGH, + boards: { boardSize: '5/4x6', gap: IN / 4, direction: 'perpendicular' }, + }) + // 27 ft between the outer beams: two bays of 13 ft 7 in, a 2x10's + expect(plan.beams).toHaveLength(3) + expect(plan.spanPastTable).toBe(false) + expect(plan.joist?.size).toBe('2x10') +}) + +test('a low deck doubles its beam inside the outer rim, the joists hung on it; below grade nothing props it', () => { + const low = planDeckFraming({ polygon: DECK, top: TOP, decking: IN, walls: [WALL], gradeY: LOW }) + expect(low.beam).toBe('flush') + const t = low.joist!.thickness + const [beam] = low.beams as [DeckPiece] + expect(beam.size).toBe(low.joist!.size) + expect(topOf(beam)).toBeCloseTo(TOP - IN, 9) + expect(beam.position[2]).toBeCloseTo(12 * FT - DECK_BOARD_OVERHANG - 1.5 * t, 9) + for (const joist of low.joists) { + expect(joist.dims[0]).toBeCloseTo(12 * FT - DECK_BOARD_OVERHANG - 3 * t, 9) + expect(joist.hangers).toHaveLength(2) + } + for (const post of low.posts) expect(bottomOf(post)).toBeCloseTo(LOW, 9) + const buried = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: BURIED, + }) + expect(buried.beam).toBe('unsupported') + expect(buried.posts).toEqual([]) + expect(buried.joists.length).toBeGreaterThan(0) +}) + +test('an L-shaped deck frames along its outline: a rim on every free edge, a beam under each outer edge, every joist inside', () => { + const ell: Array<[number, number]> = [ + [0, 0], + [20 * FT, 0], + [20 * FT, 8 * FT], + [10 * FT, 8 * FT], + [10 * FT, 14 * FT], + [0, 14 * FT], + ] + const plan = planDeckFraming({ + polygon: ell, + top: TOP, + decking: IN, + walls: [{ start: [-1, 0], end: [22 * FT, 0], thickness: 0.14 }], + gradeY: HIGH, + }) + expect(plan.irregular).toBe(true) + expect(plan.rims).toHaveLength(5) + expect(plan.beams).toHaveLength(2) + for (const piece of [plan.ledger!, ...plan.rims, ...plan.joists, ...plan.beams]) + expect(within(ell, piece)).toBe(true) + const short = plan.joists.filter((joist) => joist.position[0] > 10 * FT + 1) + const long = plan.joists.filter((joist) => joist.position[0] < 10 * FT - 1) + for (const joist of short) expect(joist.position[2] + joist.dims[0] / 2).toBeLessThan(8 * FT) + expect(Math.max(...long.map((joist) => joist.position[2] + joist.dims[0] / 2))).toBeGreaterThan( + 12 * FT, + ) +}) + +test('posts run to the ground under each; posts already standing on a beam line carry it', () => { + const sloped = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + gradeAt: (x) => HIGH - 0.05 * x, + }) + for (const post of sloped.posts) + expect(bottomOf(post)).toBeCloseTo(HIGH - 0.05 * post.position[0], 9) + const line = 12 * FT - DECK_BOARD_OVERHANG - DECK_BEAM_INSET + const carried = planDeckFraming({ + polygon: DECK, + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + carriers: [ + [1, line + 0.1], + [16 * FT - 1, line - 0.1], + ], + }) + expect(carried.beams[0]?.carried).toBe(true) + expect(carried.posts).toEqual([]) +}) + +test('a deck with no wall on any edge stands free: rims all round, beams under both ends of the joists', () => { + const free = planDeckFraming({ polygon: DECK, top: TOP, decking: IN, walls: [], gradeY: HIGH }) + expect(free.axes?.ledgerEdge).toBeNull() + expect(free.ledger).toBeNull() + expect(free.rims).toHaveLength(4) + expect(free.beams).toHaveLength(2) + for (const joist of free.joists) expect(joist.hangers).toEqual([]) + const sliver = planDeckFraming({ + polygon: [ + [0, 0], + [0.2, 0], + [0.2, 0.2], + [0, 0.2], + ], + top: TOP, + decking: IN, + walls: [WALL], + gradeY: HIGH, + }) + expect([...sliver.rims, ...sliver.joists, ...sliver.beams, ...sliver.posts]).toEqual([]) +}) diff --git a/packages/core/src/lib/deck-framing.ts b/packages/core/src/lib/deck-framing.ts new file mode 100644 index 0000000000..f328c0394f --- /dev/null +++ b/packages/core/src/lib/deck-framing.ts @@ -0,0 +1,696 @@ +import { DEFAULT_DECK_BOARDS, type DeckBoardSettings, deckJoistSpacingLimit } from './deck-boards' + +/** + * A wood deck's frame, planned once for the engine, which draws it under the deck's boards, and + * for the framing plugin, which books, labels and cites it: a deck room's floor plate, or any + * slab tagged `metadata.floor: 'deck'`. Framed to IRC R507: + * + * - a LEDGER on the house edge (the deck edge along a house wall), the joist size; + * - JOISTS at the spacing (closer under diagonal boards, Table R507.7), sized from a span table: + * square to the house under boards that run along it or at 45°, along the house under boards + * that run out from it; + * - RIMS on every other edge, set in so the boards overhang them an inch (none at the house); + * - a BEAM under each edge the joists run out to that is no ledger: DROPPED 16 in in under the + * joists (a 4x8, the joists cantilevering past it) where the deck stands high enough for posts + * under it, else FLUSH, a second joist-size board doubled inside the rim, the joists hung on + * it; more beams between where the joists would span past the table. Joists along the house + * bear on beams square to it at both ends, as joists hung off a ledger cannot run that way; + * - 4x4 POSTS under each beam, at both ends and no more than 8 ft apart, down to the ground under + * each, unless posts already standing there to grade carry it (a porch cover's). + * + * Any outline frames along its edges: an L-shaped deck gets a rim on every edge and a beam under + * each outer edge. Everything is level-local metres; a `yaw` is three.js's (local +x → world + * (cos θ, −sin θ)), a piece's `dims` its length along its run, its height and its thickness. + */ + +const IN = 0.0254 +const FT = 12 * IN +const EPS = 1e-9 + +type P = readonly [number, number] +type V2 = [number, number] + +/** A dropped beam sits this far in from the edge it carries (16 in), the joists cantilevering past it. */ +export const DECK_BEAM_INSET = 16 * IN +/** Posts at both beam ends (2 in in) and no more than 8 ft apart. */ +export const DECK_POST_SPACING = 96 * IN +/** The shortest post worth a post base (Simpson ABU/ABA need it); below this the beam goes flush. */ +export const DECK_MIN_POST_HEIGHT = 6 * IN +/** A post's face stands this far in from its beam's end. */ +export const DECK_POST_END_INSET = 2 * IN +/** The boards overhang the rim this much on a free edge; at the house they stop at the wall. */ +export const DECK_BOARD_OVERHANG = 1 * IN +/** An edge whose midpoint is within this of a wall's face is on the house. */ +const LEDGER_TOLERANCE = 0.3 +/** A post standing within this of a beam's line carries it. */ +const CARRIER_REACH = 12 * IN +const CARRIER_END_REACH = 6 * IN +/** The narrowest deck framed. */ +const MIN_DECK = 12 * IN +/** A post shorter than this is not drawn: the beam sits on the ground there. */ +const MIN_DRAWN_POST = 0.05 +/** A hanger's seat: this far along the joist from its end. */ +const HANGER_SEAT = 0.375 * IN + +/** A stock section: its name, thickness (across) and depth (down), metres. */ +export type DeckLumber = { size: string; thickness: number; depth: number } +/** A joist size and the most it spans. */ +export type DeckJoistSpan = DeckLumber & { span: number } + +/** + * The joists a deck takes by the span they reach, first that spans: SPF #2 at 40 psf live, the + * floor table's arithmetic (R502.3.1(2); IRC R507.6 governs deck joists), as the framing plugin's + * default span table has them. + */ +export const DECK_JOIST_SPANS: readonly DeckJoistSpan[] = [ + { size: '2x8', thickness: 1.5 * IN, depth: 7.25 * IN, span: 12.25 * FT }, + { size: '2x10', thickness: 1.5 * IN, depth: 9.25 * IN, span: 15 * FT }, + { size: '2x12', thickness: 1.5 * IN, depth: 11.25 * IN, span: 17.4 * FT }, +] +/** A dropped beam: a 4x8 (span per Table R507.5(1)). */ +export const DECK_DROPPED_BEAM: DeckLumber = { size: '4x8', thickness: 3.5 * IN, depth: 7.25 * IN } +/** A deck post (Table R507.4). */ +export const DECK_POST: DeckLumber = { size: '4x4', thickness: 3.5 * IN, depth: 3.5 * IN } + +/** A wall a deck edge may bear on: its centreline and thickness. */ +export type DeckWall = { start: P; end: P; thickness: number; curved?: boolean } + +/** + * A slab that is a wood deck, boards on a frame: a deck room's floor plate, or any slab tagged + * `metadata.floor: 'deck'` as the framing plugin reads it (a generated deck). + */ +export function isDeckSlab(slab: { metadata?: unknown; recessed?: boolean }): boolean { + return (slab.metadata as { floor?: unknown } | undefined)?.floor === 'deck' && !slab.recessed +} + +const sub = (a: P, b: P): V2 => [a[0] - b[0], a[1] - b[1]] +const dot = (a: P, b: P) => a[0] * b[0] + a[1] * b[1] +const cross = (a: P, b: P) => a[0] * b[1] - a[1] * b[0] + +function distanceToWall(p: P, wall: DeckWall): number { + const [dx, dz] = sub(wall.end, wall.start) + const dd = dx * dx + dz * dz + if (dd < EPS) return Math.hypot(p[0] - wall.start[0], p[1] - wall.start[1]) + const t = Math.max( + 0, + Math.min(1, ((p[0] - wall.start[0]) * dx + (p[1] - wall.start[1]) * dz) / dd), + ) + return Math.hypot(p[0] - (wall.start[0] + dx * t), p[1] - (wall.start[1] + dz * t)) +} + +/** How near edge `i` lies on a wall (parallel within 15°, its midpoint within the tolerance), or null. */ +function wallDistance(polygon: readonly P[], i: number, walls: readonly DeckWall[]): number | null { + const a = polygon[i]! + const b = polygon[(i + 1) % polygon.length]! + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (length < EPS) return null + const along: V2 = [(b[0] - a[0]) / length, (b[1] - a[1]) / length] + const mid: V2 = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] + let best: number | null = null + for (const wall of walls) { + if (wall.curved) continue + const [dx, dz] = sub(wall.end, wall.start) + const wallLength = Math.hypot(dx, dz) + if (wallLength < EPS) continue + const d = distanceToWall(mid, wall) + if (d > LEDGER_TOLERANCE + wall.thickness / 2) continue + if (Math.abs(cross(along, [dx / wallLength, dz / wallLength])) > Math.sin((15 * Math.PI) / 180)) + continue + if (best === null || d < best) best = d + } + return best +} + +/** The deck edge that lies on a wall (the nearest such), where the ledger goes; null when none does. */ +export function deckLedgerEdge(polygon: readonly P[], walls: readonly DeckWall[]): number | null { + let best: { i: number; d: number } | null = null + for (let i = 0; i < polygon.length; i++) { + const d = wallDistance(polygon, i, walls) + if (d !== null && (!best || d < best.d)) best = { i, d } + } + return best ? best.i : null +} + +/** + * A deck's frame: `u` along its ledger edge (the longest edge of a deck standing free), `v` + * square to it pointing into the deck, from the edge's first corner. + */ +export type DeckAxes = { + /** The deck edge on the house, or null: the deck stands free. */ + ledgerEdge: number | null + /** The edge the frame is squared to: the ledger edge, else the longest. */ + baseEdge: number + origin: V2 + u: V2 + v: V2 +} + +export function deckAxes(polygon: readonly P[], walls: readonly DeckWall[]): DeckAxes | null { + if (polygon.length < 3) return null + const ledgerEdge = deckLedgerEdge(polygon, walls) + let base = ledgerEdge ?? 0 + if (ledgerEdge === null) { + let longest = 0 + for (let i = 0; i < polygon.length; i++) { + const [dx, dz] = sub(polygon[(i + 1) % polygon.length]!, polygon[i]!) + const length = Math.hypot(dx, dz) + if (length > longest) { + longest = length + base = i + } + } + } + const a = polygon[base]! + const [dx, dz] = sub(polygon[(base + 1) % polygon.length]!, a) + const length = Math.hypot(dx, dz) + if (length < EPS) return null + const u: V2 = [dx / length, dz / length] + // the vertex average decides which perpendicular points into the deck + let cx = 0 + let cz = 0 + for (const p of polygon) { + cx += p[0] + cz += p[1] + } + cx /= polygon.length + cz /= polygon.length + let v: V2 = [-u[1], u[0]] + if ((cx - a[0]) * v[0] + (cz - a[1]) * v[1] < 0) v = [u[1], -u[0]] + return { ledgerEdge, baseEdge: base, origin: [a[0], a[1]], u, v } +} + +export type DeckFramingInput = { + /** The deck's outline, plan [x, z]. */ + polygon: readonly P[] + /** Height of the walking surface: the boards' top. */ + top: number + /** The boards' thickness: the joists' tops stand this far under `top`. */ + decking: number + /** Walls a deck edge may lie on (the house's), for the ledger. */ + walls: readonly DeckWall[] + /** How the boards run and what they are (`lib/deck-boards`): the joists follow. */ + boards?: DeckBoardSettings + /** The joist spacing asked for, 16 in unless said; the boards may close it (Table R507.7). */ + joistSpacing?: number + /** The joist sizes, first that spans (the framing plugin's span table): `DECK_JOIST_SPANS` unless said. */ + joists?: readonly DeckJoistSpan[] + /** The ground the beam is chosen against: the grade under the deck's middle. */ + gradeY: number + /** The ground under each post; absent, `gradeY` under every post. */ + gradeAt?: (x: number, z: number) => number + /** Posts already standing to grade (a porch cover's): a beam line two of them stand on gets none of its own. */ + carriers?: readonly P[] + /** How far the boards overhang the rims, `DECK_BOARD_OVERHANG` unless said. */ + overhang?: number +} + +export type DeckPieceRole = 'ledger' | 'joist' | 'rim' | 'beam' | 'post' + +/** One member: its stock, its centre, its `dims` ([run, height, across]) and its yaw. */ +export type DeckPiece = { + role: DeckPieceRole + size: string + position: [number, number, number] + dims: [number, number, number] + yaw: number +} + +/** Where a joist end hangs in a hanger, on the ledger or a flush beam, and the yaw of what it hangs on. */ +export type DeckHanger = { position: [number, number, number]; yaw: number } + +export type DeckJoistPiece = DeckPiece & { + role: 'joist' + /** A joist on a rim's line (the end joist a side rim is): the rim stands in for it where it is drawn. */ + atRim: boolean + hangers: DeckHanger[] +} + +export type DeckBeamPiece = DeckPiece & { + role: 'beam' + /** Posts already standing to grade carry it (`carriers`): it gets none of its own. */ + carried: boolean + /** One between the outer beams, where the joists would span past the table. */ + between: boolean +} + +export type DeckPostPiece = DeckPiece & { + role: 'post' + /** The ground it stands on, where its footing is. */ + bottom: number +} + +export type DeckBeamKind = 'dropped' | 'flush' | 'unsupported' + +export type DeckFramingPlan = { + axes: DeckAxes | null + /** 'dropped' under the joists on posts, 'flush' doubled inside the rim, 'unsupported': no room for a post. */ + beam: DeckBeamKind + joist: DeckJoistSpan | null + /** Even its largest size spans short of the joists. */ + spanPastTable: boolean + joistSpacing: number + /** The spacing is closer than asked, for the boards (Table R507.7). */ + spacingForBoards: boolean + /** The joists run along the house (the boards run out from it). */ + joistsAlongHouse: boolean + /** The outline is no rectangle square to its frame: framed along its edges. */ + irregular: boolean + /** The beam's lumber: a 4x8 dropped, the joist size flush. */ + beamLumber: DeckLumber | null + /** The joists' tops, the beams' and the posts' (under the beams). */ + joistTop: number + beamTop: number + postTop: number + /** The posts' height over `gradeY`: under `DECK_MIN_POST_HEIGHT`, no post fits. */ + postHeight: number + /** The way the joists are laid out, a plan unit vector, and their stations along it from `axes.origin`. */ + stationAxis: V2 | null + stations: number[] + ledger: DeckPiece | null + rims: DeckPiece[] + joists: DeckJoistPiece[] + beams: DeckBeamPiece[] + posts: DeckPostPiece[] +} + +/** The outline with each edge moved in by its offset (signed area kept), or null when that collapses it. */ +function insetEdges(polygon: readonly P[], offsets: readonly number[]): V2[] | null { + const n = polygon.length + let twice = 0 + for (let i = 0; i < n; i++) twice += cross(polygon[i]!, polygon[(i + 1) % n]!) + const sign = twice > 0 ? 1 : -1 + const lines = polygon.map((a, i) => { + const [dx, dz] = sub(polygon[(i + 1) % n]!, a) + const length = Math.hypot(dx, dz) || 1 + const e: V2 = [dx / length, dz / length] + const inward: V2 = sign > 0 ? [-e[1], e[0]] : [e[1], -e[0]] + const d = offsets[i] ?? 0 + return { p: [a[0] + inward[0] * d, a[1] + inward[1] * d] as V2, e } + }) + const inset = polygon.map((_, i): V2 => { + const before = lines[(i - 1 + n) % n]! + const after = lines[i]! + const turn = cross(before.e, after.e) + if (Math.abs(turn) < 1e-9) return after.p + const t = cross(sub(after.p, before.p), after.e) / turn + return [before.p[0] + before.e[0] * t, before.p[1] + before.e[1] * t] + }) + let insetTwice = 0 + for (let i = 0; i < n; i++) insetTwice += cross(inset[i]!, inset[(i + 1) % n]!) + return insetTwice * sign > 0.5 * Math.abs(twice) ? inset : null +} + +/** The outline without repeated corners. */ +function cleanOutline(polygon: readonly P[]): V2[] { + const out: V2[] = [] + for (const p of polygon) { + const last = out.at(-1) + if (!last || Math.hypot(p[0] - last[0], p[1] - last[1]) > 1e-6) out.push([p[0], p[1]]) + } + while ( + out.length > 1 && + Math.hypot(out[0]![0] - out.at(-1)![0], out[0]![1] - out.at(-1)![1]) <= 1e-6 + ) + out.pop() + return out +} + +export function planDeckFraming(input: DeckFramingInput): DeckFramingPlan { + const boards = input.boards ?? DEFAULT_DECK_BOARDS + const asked = input.joistSpacing ?? 16 * IN + const spacing = Math.min(asked, deckJoistSpacingLimit(boards)) + const joistsAlongHouse = boards.direction === 'perpendicular' + const ladder = input.joists?.length ? input.joists : DECK_JOIST_SPANS + const joistTop = input.top - input.decking + const plan: DeckFramingPlan = { + axes: null, + beam: 'unsupported', + joist: null, + spanPastTable: false, + joistSpacing: spacing, + spacingForBoards: spacing < asked - EPS, + joistsAlongHouse, + irregular: false, + beamLumber: null, + joistTop, + beamTop: joistTop, + postTop: joistTop, + postHeight: 0, + stationAxis: null, + stations: [], + ledger: null, + rims: [], + joists: [], + beams: [], + posts: [], + } + const outline = cleanOutline(input.polygon) + if (outline.length < 3) return plan + const axes = deckAxes(outline, input.walls) + if (!axes) return plan + plan.axes = axes + const n = outline.length + const ledgerEdge = axes.ledgerEdge + + // ---- the frame's outline: the free edges in by the boards' overhang ---- + const overhang = input.overhang ?? DECK_BOARD_OVERHANG + const offsets = outline.map((_, i) => + wallDistance(outline, i, input.walls) === null ? overhang : 0, + ) + const frame = insetEdges(outline, offsets) ?? outline + // Frame coordinates: s along the joists' layout (stations), j along the joists. + const S = joistsAlongHouse ? axes.v : axes.u + const J = joistsAlongHouse ? axes.u : axes.v + const [ox, oz] = axes.origin + const toFrame = (p: P): V2 => [ + (p[0] - ox) * S[0] + (p[1] - oz) * S[1], + (p[0] - ox) * J[0] + (p[1] - oz) * J[1], + ] + const toPlan = (s: number, j: number): V2 => [ox + S[0] * s + J[0] * j, oz + S[1] * s + J[1] * j] + const yawOf = (s: number, j: number) => { + const x = S[0] * s + J[0] * j + const z = S[1] * s + J[1] * j + return Math.atan2(-z, x) + } + const yawS = yawOf(1, 0) + const yawJ = yawOf(0, 1) + const fq = frame.map(toFrame) + let twice = 0 + for (let i = 0; i < n; i++) twice += cross(fq[i]!, fq[(i + 1) % n]!) + const ccw = twice > 0 + const edges = fq.map((a, i) => { + const b = fq[(i + 1) % n]! + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + const e: V2 = length > EPS ? [(b[0] - a[0]) / length, (b[1] - a[1]) / length] : [1, 0] + const inward: V2 = ccw ? [-e[1], e[0]] : [e[1], -e[0]] + // An 'end' edge runs across the joists: they run out to it. A 'side' edge runs with them. + const kind: 'end' | 'side' = Math.abs(e[0]) >= Math.abs(e[1]) ? 'end' : 'side' + return { a, b, e, inward, length, kind } + }) + let sMin = Number.POSITIVE_INFINITY + let sMax = Number.NEGATIVE_INFINITY + let jMin = Number.POSITIVE_INFINITY + let jMax = Number.NEGATIVE_INFINITY + for (const [s, j] of fq) { + sMin = Math.min(sMin, s) + sMax = Math.max(sMax, s) + jMin = Math.min(jMin, j) + jMax = Math.max(jMax, j) + } + if (sMax - sMin < MIN_DECK || jMax - jMin < MIN_DECK) return plan + plan.irregular = + n !== 4 || edges.some((edge) => Math.min(Math.abs(edge.e[0]), Math.abs(edge.e[1])) > 1e-6) + + // ---- the joists: sized for the span between their supports, beams between past the table ---- + // A ledger carries the joists' house ends unless they run along it. + const hung = ledgerEdge !== null && !joistsAlongHouse + const near = hung ? jMin : jMin + DECK_BEAM_INSET + const far = jMax - DECK_BEAM_INSET + const run = Math.max(far - near, 0) + const longest = ladder.at(-1)! + const bays = Math.max(1, Math.ceil(run / longest.span - 1e-9)) + const joist = ladder.find((size) => run / bays <= size.span + EPS) ?? longest + plan.joist = joist + plan.spanPastTable = run / bays > longest.span + EPS + const t = joist.thickness + const d = joist.depth + const centerY = joistTop - d / 2 + + // ---- the beam: dropped where a post fits under a 4x8 under the joists, else flush ---- + const dropped = joistTop - d - DECK_DROPPED_BEAM.depth - input.gradeY >= DECK_MIN_POST_HEIGHT + const beamLumber: DeckLumber = dropped ? DECK_DROPPED_BEAM : joist + const beamTop = dropped ? joistTop - d : joistTop + const postTop = beamTop - beamLumber.depth + const postHeight = postTop - input.gradeY + const canPost = postHeight >= DECK_MIN_POST_HEIGHT + plan.beam = dropped ? 'dropped' : canPost ? 'flush' : 'unsupported' + plan.beamLumber = beamLumber + plan.beamTop = beamTop + plan.postTop = postTop + plan.postHeight = postHeight + + const pieceAt = ( + role: DeckPieceRole, + size: string, + s: number, + j: number, + y: number, + dims: [number, number, number], + yaw: number, + ): DeckPiece => { + const [x, z] = toPlan(s, j) + return { role, size, position: [x, y, z], dims, yaw } + } + // A run along an edge direction, its yaw taken along +s or +j, as the frame's own axes are. + const runYaw = (e: V2) => { + const flip = Math.abs(e[0]) >= Math.abs(e[1]) ? e[0] < 0 : e[1] < 0 + return flip ? yawOf(-e[0], -e[1]) : yawOf(e[0], e[1]) + } + const isConvex = (i: number) => { + const turn = cross(edges[(i - 1 + n) % n]!.e, edges[i]!.e) + return ccw ? turn > 1e-9 : turn < -1e-9 + } + const isReflex = (i: number) => { + const turn = cross(edges[(i - 1 + n) % n]!.e, edges[i]!.e) + return ccw ? turn < -1e-9 : turn > 1e-9 + } + + // ---- the ledger and the rims: one along every edge, a board the joist size inside it ---- + // Where an end edge meets a side edge, the end's board runs to the corner and the side's stops at + // it (at an inside corner the end's runs on past it), so the corners close. + for (let i = 0; i < n; i++) { + const edge = edges[i]! + const prev = edges[(i - 1 + n) % n]! + const next = edges[(i + 1) % n]! + const startTrim = + edge.kind === 'side' && prev.kind === 'end' && isConvex(i) + ? t + : edge.kind === 'end' && prev.kind === 'side' && isReflex(i) + ? -t + : 0 + const j1 = (i + 1) % n + const endTrim = + edge.kind === 'side' && next.kind === 'end' && isConvex(j1) + ? t + : edge.kind === 'end' && next.kind === 'side' && isReflex(j1) + ? -t + : 0 + const length = edge.length - startTrim - endTrim + if (length < t) continue + const mid = startTrim + length / 2 + const s = edge.a[0] + edge.e[0] * mid + edge.inward[0] * (t / 2) + const j = edge.a[1] + edge.e[1] * mid + edge.inward[1] * (t / 2) + const piece = pieceAt( + i === ledgerEdge ? 'ledger' : 'rim', + joist.size, + s, + j, + centerY, + [length, d, t], + runYaw(edge.e), + ) + if (i === ledgerEdge) plan.ledger = piece + else plan.rims.push(piece) + } + + // ---- the beams: under each end edge the joists run out to, and between past the table ---- + type BeamLine = { + /** The beam's centreline, frame coordinates, its full run inside the frame. */ + from: V2 + to: V2 + /** Its own edge, or null for one between. */ + edge: number | null + yaw: number + thickness: number + } + const lines: BeamLine[] = [] + const inset = dropped ? DECK_BEAM_INSET : t + t / 2 + edges.forEach((edge, i) => { + if (edge.kind !== 'end' || i === ledgerEdge) return + const offset: V2 = [edge.inward[0] * inset, edge.inward[1] * inset] + lines.push({ + from: [edge.a[0] + offset[0], edge.a[1] + offset[1]], + to: [edge.b[0] + offset[0], edge.b[1] + offset[1]], + edge: i, + yaw: runYaw(edge.e), + thickness: dropped ? beamLumber.thickness : t, + }) + }) + // The beams between: lines across the frame at even spacing, where it lies across them. + const middles: number[] = [] + if (bays > 1) { + const first = hung ? jMin : jMin + inset + const last = jMax - inset + for (let k = 1; k < bays; k++) middles.push(first + ((last - first) * k) / bays) + } + /** Where the frame lies across the line j = `j`: [s0, s1] runs, with the edges they end on. */ + const across = (j: number) => { + const hits: Array<{ s: number; edge: number }> = [] + edges.forEach((edge, i) => { + const { a, b } = edge + if ((a[1] <= j && j < b[1]) || (b[1] <= j && j < a[1])) + hits.push({ s: a[0] + ((j - a[1]) * (b[0] - a[0])) / (b[1] - a[1]), edge: i }) + }) + hits.sort((p, q) => p.s - q.s) + const runs: Array<[number, number]> = [] + for (let k = 0; k + 1 < hits.length; k += 2) runs.push([hits[k]!.s, hits[k + 1]!.s]) + return runs + } + for (const j of middles) + for (const [s0, s1] of across(j)) + lines.push({ + from: [s0, j], + to: [s1, j], + edge: null, + yaw: yawS, + thickness: dropped ? beamLumber.thickness : 2 * t, + }) + + const carriers = (input.carriers ?? []).map(toFrame) + const endInset = dropped ? DECK_POST_END_INSET : t + for (const line of lines) { + const dir = sub(line.to, line.from) + const full = Math.hypot(dir[0], dir[1]) + if (full < EPS) continue + const e: V2 = [dir[0] / full, dir[1] / full] + const length = full - 2 * endInset + if (length < DECK_POST.thickness) continue + const mid: V2 = [(line.from[0] + line.to[0]) / 2, (line.from[1] + line.to[1]) / 2] + const carried = + carriers.filter((c) => { + const along = dot(sub(c, line.from), e) + return ( + Math.abs(cross(e, sub(c, line.from))) <= CARRIER_REACH && + along >= -CARRIER_END_REACH && + along <= full + CARRIER_END_REACH + ) + }).length >= 2 + const beam = pieceAt( + 'beam', + beamLumber.size, + mid[0], + mid[1], + beamTop - beamLumber.depth / 2, + [length, beamLumber.depth, line.thickness], + line.yaw, + ) as DeckBeamPiece + beam.carried = carried + beam.between = line.edge === null + plan.beams.push(beam) + if (!canPost || carried) continue + const count = Math.max(2, Math.ceil(length / DECK_POST_SPACING) + 1) + const span = length - 2 * DECK_POST_END_INSET + for (let k = 0; k < count; k++) { + const along = -length / 2 + DECK_POST_END_INSET + (span * k) / (count - 1) + const s = mid[0] + e[0] * along + const j = mid[1] + e[1] * along + const [x, z] = toPlan(s, j) + const bottom = input.gradeAt ? input.gradeAt(x, z) : input.gradeY + const height = postTop - bottom + if (!(height >= MIN_DRAWN_POST)) continue + plan.posts.push({ + role: 'post', + size: DECK_POST.size, + position: [x, postTop - height / 2, z], + dims: [DECK_POST.thickness, height, DECK_POST.depth], + yaw: line.yaw, + bottom, + }) + } + } + + // ---- the joists: at the spacing from one side to the other, each run between its supports ---- + const flushEdges = new Set( + dropped ? [] : lines.flatMap((line) => (line.edge === null ? [] : [line.edge])), + ) + const stations: number[] = [] + for (let k = 0; ; k++) { + const s = sMin + t / 2 + k * spacing + if (s > sMax - t / 2 + EPS) break + stations.push(s) + } + if ((stations.at(-1) ?? Number.NEGATIVE_INFINITY) < sMax - t / 2 - EPS) + stations.push(sMax - t / 2) + plan.stationAxis = [S[0], S[1]] + plan.stations = stations + // A station on a side edge's board: the rim (or a ledger along the joists) is that joist. + const sideRims = edges.filter((edge) => edge.kind === 'side' && Math.abs(edge.e[0]) < 1e-6) + for (const s of stations) { + const hits: Array<{ j: number; edge: number }> = [] + edges.forEach((edge, i) => { + const { a, b } = edge + if ((a[0] <= s && s < b[0]) || (b[0] <= s && s < a[0])) + hits.push({ j: a[1] + ((s - a[0]) * (b[1] - a[1])) / (b[0] - a[0]), edge: i }) + }) + hits.sort((p, q) => p.j - q.j) + for (let k = 0; k + 1 < hits.length; k += 2) { + const lo = hits[k]! + const hi = hits[k + 1]! + // in past the board on the edge each end meets: the rim, the ledger, a flush beam's two + const band = (edge: number) => { + const thick = flushEdges.has(edge) ? 2 * t : t + return thick / Math.max(Math.abs(edges[edge]!.inward[1]), 0.3) + } + const hangsOn = (edge: number) => edge === ledgerEdge || flushEdges.has(edge) + const segments: Array<{ + j0: number + j1: number + hung0: boolean + hung1: boolean + yaw0: number + yaw1: number + }> = [ + { + j0: lo.j + band(lo.edge), + j1: hi.j - band(hi.edge), + hung0: hangsOn(lo.edge), + hung1: hangsOn(hi.edge), + yaw0: runYaw(edges[lo.edge]!.e), + yaw1: runYaw(edges[hi.edge]!.e), + }, + ] + // a flush beam between cuts the run: the joists hang on both its faces + if (!dropped) + for (const j of middles) { + const at = segments.findIndex((seg) => j - t > seg.j0 + EPS && j + t < seg.j1 - EPS) + if (at < 0) continue + const seg = segments[at]! + segments.splice( + at, + 1, + { ...seg, j1: j - t, hung1: true, yaw1: yawS }, + { ...seg, j0: j + t, hung0: true, yaw0: yawS }, + ) + } + const atRim = sideRims.some( + (edge) => + Math.abs(s - edge.a[0]) < t - EPS && + Math.max(edge.a[1], edge.b[1]) > lo.j + EPS && + Math.min(edge.a[1], edge.b[1]) < hi.j - EPS, + ) + for (const seg of segments) { + const length = seg.j1 - seg.j0 + if (length < t) continue + const piece = pieceAt( + 'joist', + joist.size, + s, + (seg.j0 + seg.j1) / 2, + centerY, + [length, d, t], + yawJ, + ) as DeckJoistPiece + piece.atRim = atRim + piece.hangers = [] + const hanger = (j: number, yaw: number) => { + const [x, z] = toPlan(s, j) + piece.hangers.push({ position: [x, centerY, z], yaw }) + } + if (seg.hung0) hanger(seg.j0 + HANGER_SEAT, seg.yaw0) + if (seg.hung1) hanger(seg.j1 - HANGER_SEAT, seg.yaw1) + plan.joists.push(piece) + } + } + } + return plan +} diff --git a/packages/core/src/lib/designed-basement.test.ts b/packages/core/src/lib/designed-basement.test.ts new file mode 100644 index 0000000000..a736cf03ab --- /dev/null +++ b/packages/core/src/lib/designed-basement.test.ts @@ -0,0 +1,307 @@ +import { expect, test } from 'bun:test' +import { buildBuildingFoundationChanges } from '../building/foundation' +import { buildGroundLevelChanges } from '../building/ground-level' +import { buildLevelBelowChanges } from '../building/level-insertion' +import { buildLevelRemovalChanges } from '../building/level-removal' +import { buildExteriorWallCopy } from '../building/level-wall-copy' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + LevelNode, + SiteNode, + SlabNode, + WallNode, +} from '../schema' +import { levelFloorWorldY, levelPlaneWorldY } from '../services/storey' +import { foundationOutlines } from './designed-foundation' +import { finishedGradeAt, gradeTerrainField, isExcavation } from './foundation-grading' +import { + designedFoundationReport, + foundationGradingPads, + genericFoundation, +} from './generic-foundation' +import { groundSeat } from './ground-seat' +import { encodeTerrainField } from './terrain-codec' +import { createTerrainField, heightAt } from './terrain-field' +import { terrainFieldOf } from './terrain-source' + +const IN = 0.0254 +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes + +const CORNERS: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] + +/** Ground falling 1 m in 10 m along x, 0 at x 0. */ +function slope() { + const field = createTerrainField({ cols: 81, rows: 81, spacing: 0.5, origin: [-15, -15] }) + for (let row = 0; row < field.rows; row++) + for (let col = 0; col < field.cols; col++) { + const x = field.origin[0] + col * field.spacing + field.heights[row * field.cols + col] = Math.round((-0.1 * x) / field.step) + } + return encodeTerrainField(field) +} + +/** A 10 × 8 m slab house on its designed foundation (on by default), on flat ground or `terrain`. */ +function slabHouse(terrain?: ReturnType): Nodes { + const walls = CORNERS.map((start, index) => + WallNode.parse({ + id: `wall_${index}`, + parentId: 'level_g', + start, + end: CORNERS[(index + 1) % 4], + thickness: 0.15, + }), + ) + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'], ...(terrain ? { terrain } : {}) }), + BuildingNode.parse({ id: 'building_a', parentId: 'site_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((wall) => wall.id), 'slab_g'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: CORNERS, + elevation: 0.05, + thickness: 0.05, + }), + ...walls, + ]) +} + +/** A basement added under the lowest level as the level panel's "+" adds one. */ +function withBasement(nodes: Nodes): { nodes: Nodes; basement: LevelNode } { + const { newLevel, create, update } = buildLevelBelowChanges({ + nodes, + buildingId: 'building_a', + height: 2.5, + }) + const next: Nodes = { ...nodes } + for (const { node } of create) next[node.id as AnyNodeId] = node + const building = next['building_a' as AnyNodeId] as BuildingNode + next[building.id] = { ...building, children: [...building.children, newLevel.id] } + for (const { id, data } of update) next[id] = { ...next[id], ...data } as AnyNode + return { nodes: next, basement: newLevel } +} + +function apply(nodes: Nodes, update: Array<{ id: AnyNodeId; data: Partial }>): Nodes { + const next = { ...nodes } + for (const { id, data } of update) next[id] = { ...next[id], ...data } as AnyNode + return next +} + +/** The house's foundation outline, the ground floor walls' outside faces (the building at the origin). */ +const houseOutline = (nodes: Nodes) => + foundationOutlines( + Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === 'level_g', + ), + )[0]!.edges.map((edge) => edge.a) + +test('on flat ground a basement grades nothing: the ground is dug out inside its walls’ outside faces to its slab’s underside', () => { + expect(foundationGradingPads(slabHouse()).map((pad) => pad.kind ?? 'pad')).toEqual(['pad']) + const { nodes, basement } = withBasement(slabHouse()) + const pads = foundationGradingPads(nodes) + expect(pads.map((pad) => pad.kind)).toEqual(['excavation']) + const report = designedFoundationReport(nodes, 'building_a')! + expect(report.levelId).toBe(basement.id) + expect(report.basement?.ownWalls).toBe(false) + // The empty basement's floor is its plane: its 4 in slab under it, dug out to the slab's underside. + expect(report.floorTopY).toBeCloseTo(levelPlaneWorldY(nodes, basement.id)!, 9) + const slabBottom = report.floorTopY - 4 * IN + expect(pads[0]!.y).toBeCloseTo(slabBottom, 9) + expect(report.basement!.excavationY).toBeCloseTo(slabBottom, 9) + // The dig's polygon is the foundation's outer outline. + const outline = houseOutline(nodes) + expect(pads[0]!.outline).toHaveLength(outline.length) + pads[0]!.outline.forEach(([x, z], index) => { + expect(x).toBeCloseTo(outline[index]![0], 9) + expect(z).toBeCloseTo(outline[index]![1], 9) + }) + // Poured walls from the footings up to the floor framing over the basement, the slab inside. + const members = genericFoundation(nodes) + const walls = members.filter((member) => member.kind === 'stem') + expect(walls).toHaveLength(4) + for (const wall of walls) { + expect(wall.width).toBeCloseTo(8 * IN, 9) + expect(wall.bottomY).toBeCloseTo(slabBottom, 9) + expect(wall.topY).toBeCloseTo(report.basement!.wallTopY, 9) + expect(wall.levelId).toBe(basement.id) + } + const footings = members.filter((member) => member.kind === 'footing') + expect(footings).toHaveLength(4) + for (const footing of footings) expect(footing.bottomY).toBeCloseTo(slabBottom - 8 * IN, 9) + const slab = members.find((member) => member.kind === 'slab')! + expect(slab.bottomY).toBeCloseTo(slabBottom, 9) + expect(slab.topY).toBeCloseTo(report.floorTopY - 0.002, 9) + expect(members.some((member) => member.kind === 'turndown')).toBe(false) + // The walls carry the floor framed over the basement: their top under its joists, by the sill. + const floor = levelFloorWorldY(nodes, 'level_g')! + expect(report.basement!.wallTopY).toBeCloseTo(floor - (13.125 - 0.5 + 1.5) * IN, 6) +}) + +test('on a slope the ground outside a basement lies as it was; the house is seated with its walls’ top 8 in over the high side, adjustably', () => { + const house = slabHouse(slope()) + const { nodes } = withBasement(house) + const natural = terrainFieldOf(nodes['site_a' as AnyNodeId] as SiteNode)! + // The insertion seats the house in the same change set: the framed floor over the basement + // would stand in the ground where the slab stood, so the house rises. + const position = (scene: Nodes) => (scene['building_a' as AnyNodeId] as BuildingNode).position + expect(position(nodes)[1]).toBeGreaterThan(position(house)[1] + 0.3) + expect( + buildBuildingFoundationChanges({ nodes, buildingId: 'building_a', patch: {} })!.moveBy, + ).toBeCloseTo(0, 6) + const report = designedFoundationReport(nodes, 'building_a')! + expect(report.basement!.wallTopY - report.highestGradeY).toBeCloseTo(8 * IN, 6) + // The high side is x 0, the ground there about at 0; about 1 m lower at x 10. + expect(report.highestGradeY).toBeCloseTo(0.0075, 2) + expect(report.lowestGradeY).toBeCloseTo(-1.0075, 2) + const pads = foundationGradingPads(nodes) + expect(pads.map((pad) => pad.kind)).toEqual(['excavation']) + // Nothing is graded: the ground drawn is the natural ground, open over the dig. + expect( + gradeTerrainField( + natural, + pads.filter((pad) => !isExcavation(pad)), + ), + ).toBe(natural) + for (const [x, z] of [ + [-0.2, 4], + [-3, 4], + [10.2, 4], + [13, 4], + [5, -0.2], + [5, 8.2], + [-2, -2], + [12, 10], + ] as Array<[number, number]>) { + const ground = heightAt(natural, x, z) + expect(finishedGradeAt(pads, x, z, ground)).toBe(ground) + } + // Inside the walls' outside faces the ground is dug down to the slab's underside. + expect(finishedGradeAt(pads, 5, 4, heightAt(natural, 5, 4))).toBeCloseTo( + report.basement!.excavationY, + 9, + ) + // A daylight basement: its walls' top 30 in over the high side, the house raised in one change. + const daylight = buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { basementTopAboveGradeIn: 30 }, + })! + expect(daylight.moveBy).toBeCloseTo(22 * IN, 6) + const raised = designedFoundationReport(apply(nodes, daylight.update), 'building_a')! + expect(raised.basement!.wallTopY - raised.highestGradeY).toBeCloseTo(30 * IN, 6) +}) + +test('deleting the basement brings the slab’s grading back, the house re-seated on its slab', () => { + const house = slabHouse(slope()) + expect(foundationGradingPads(house).map((pad) => pad.kind ?? 'pad')).toEqual(['pad']) + const { nodes, basement } = withBasement(house) + expect(foundationGradingPads(nodes).map((pad) => pad.kind)).toEqual(['excavation']) + expect(groundSeat(nodes, 'building_a')).toMatchObject({ rule: 'basement', seated: true }) + const after = apply(nodes, buildLevelRemovalChanges({ nodes, levelId: basement.id })) + delete after[basement.id as AnyNodeId] + const building = after['building_a' as AnyNodeId] as BuildingNode + after[building.id] = { + ...building, + children: building.children.filter((id) => id !== basement.id), + } + // The house the basement raised would float on its slab: it comes down onto it, the slab's top + // 8 in over the high side, the ground graded to it again. + const report = designedFoundationReport(after, 'building_a')! + expect(report.basement).toBeUndefined() + expect(report.floorTopY - report.highestGradeY).toBeCloseTo(8 * IN, 6) + const pads = foundationGradingPads(after) + expect(pads.map((pad) => pad.kind ?? 'pad')).toEqual(['pad']) + expect(pads[0]!.y).toBeCloseTo(report.floorTopY - 8 * IN, 6) + expect(groundSeat(after, 'building_a')).toMatchObject({ + rule: 'slab', + seated: true, + atFoundationTop: true, + }) +}) + +test('the ground line seats by each foundation’s own rule: a raised floor, a slab, a basement', () => { + // A raised floor seated by its panel is seated for the ground line too, not 14 in too high. + const raised = slabHouse(slope()) + raised['building_a' as AnyNodeId] = { + ...raised['building_a' as AnyNodeId], + metadata: { foundation: { enabled: true, type: 'raised' } }, + } as BuildingNode + const on = apply( + raised, + buildBuildingFoundationChanges({ nodes: raised, buildingId: 'building_a', patch: {} })!.update, + ) + expect(groundSeat(on, 'building_a')).toMatchObject({ + rule: 'raised', + askedIn: 22.125, + seated: true, + atFoundationTop: true, + }) + // Sunk under its seat, the line's click seats it as the panel does. + const sunk = apply(on, [ + { + id: 'building_a' as AnyNodeId, + data: { position: [0, -0.4, 0], groundLevelId: 'level_g' } as Partial, + }, + ]) + expect(groundSeat(sunk, 'building_a')).toMatchObject({ rule: 'raised', seated: false }) + const reseated = apply(sunk, buildGroundLevelChanges({ nodes: sunk, levelId: 'level_g' })!.update) + const report = designedFoundationReport(reseated, 'building_a')! + expect(report.floorTopY - report.highestGradeY).toBeCloseTo(22.125 * IN, 6) + // A basement's walls' top too, and switched off the construction's 8 in again. + const { nodes } = withBasement(slabHouse(slope())) + const low = apply(nodes, [ + { id: 'building_a' as AnyNodeId, data: { position: [0, 0, 0] } as Partial }, + ]) + expect(groundSeat(low, 'building_a')).toMatchObject({ rule: 'basement', seated: false }) + const up = apply(low, buildGroundLevelChanges({ nodes: low, levelId: 'level_g' })!.update) + const basement = designedFoundationReport(up, 'building_a')! + expect(basement.basement!.wallTopY - basement.highestGradeY).toBeCloseTo(8 * IN, 6) + const off = apply(up, [ + { + id: 'building_a' as AnyNodeId, + data: { metadata: { foundation: { enabled: false } } } as Partial, + }, + ]) + expect(groundSeat(off, 'building_a')?.rule).toBe('construction') + expect(groundSeat(off, 'building_a')?.askedIn).toBeCloseTo(8, 1) +}) + +test('a basement with walls of its own stands them on their footing, dug out to its floor along their outside faces', () => { + const added = withBasement(slabHouse()) + const copies = buildExteriorWallCopy({ + nodes: added.nodes, + fromLevelId: 'level_g', + toLevelId: added.basement.id, + }) + const nodes: Nodes = { ...added.nodes } + for (const { node } of copies) nodes[node.id as AnyNodeId] = node + nodes[added.basement.id as AnyNodeId] = { + ...added.basement, + children: copies.map(({ node }) => node.id), + } + const report = designedFoundationReport(nodes, 'building_a')! + expect(report.basement?.ownWalls).toBe(true) + // Only the footing under its walls: they are the foundation walls. + const members = genericFoundation(nodes) + expect(new Set(members.map((member) => member.kind))).toEqual(new Set(['footing'])) + expect(members.every((member) => member.levelId === added.basement.id)).toBe(true) + const pads = foundationGradingPads(nodes) + expect(pads.map((pad) => pad.kind)).toEqual(['excavation']) + expect(pads[0]!.y).toBeCloseTo(levelPlaneWorldY(nodes, added.basement.id)!, 9) + const faces = foundationOutlines(copies.map(({ node }) => node))[0]!.edges.map((edge) => edge.a) + expect(pads[0]!.outline).toEqual(faces) +}) diff --git a/packages/core/src/lib/designed-foundation.test.ts b/packages/core/src/lib/designed-foundation.test.ts new file mode 100644 index 0000000000..55ae9328fa --- /dev/null +++ b/packages/core/src/lib/designed-foundation.test.ts @@ -0,0 +1,566 @@ +import { expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FoundationNode, + LevelNode, + SiteNode, + SlabNode, + WallNode, +} from '../schema' +import { DEFAULT_RAISED_FLOOR_BUILD_UP } from './build-up' +import { + type BuildingFoundationRecord, + buildingFoundationOf, + resolveBuildingFoundation, + slabEdgeTaper, +} from './building-foundation' +import { foundationOutlines } from './designed-foundation' +import { + designedFoundationReport, + type FoundationMember, + foundationGradingPads, + foundationRimBottom, + foundationRimSteps, + genericFoundation, +} from './generic-foundation' +import { FLAT_GROUND_Y, groundHeightAt } from './site-ground-height' +import { encodeTerrainField } from './terrain-codec' +import { createTerrainField } from './terrain-field' + +const IN = 0.0254 +/** A raised floor's framing: ¾ in flooring, ⅝ in sheathing, 2x12 joists, 1½ in sill. */ +const FRAMING = 0.75 + 0.625 + 11.25 + 1.5 +/** The least a raised floor stands over grade: its stem 8 in out, the framing on it. */ +const RAISED_FF = 8 + FRAMING +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes +const ofKind = (members: FoundationMember[], kind: FoundationMember['kind']) => + members.filter((member) => member.kind === kind) + +function ring(corners: Array<[number, number]>, thickness = 0.1) { + return corners.map((start, index) => + WallNode.parse({ + id: `wall_g_${index}`, + parentId: 'level_g', + start, + end: corners[(index + 1) % corners.length], + thickness, + }), + ) +} + +const BOX: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] + +/** + * A one-storey house, its floor top 0.05 m over the level plane, standing on the record given; `y` + * lifts the building (seats it over the ground). + */ +function house( + record: BuildingFoundationRecord | null, + { + terrain, + corners = BOX, + y = 0, + }: { terrain?: string; corners?: Array<[number, number]>; y?: number } = {}, +) { + const walls = ring(corners) + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'], ...(terrain ? { terrain } : {}) }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_g'], + position: [0, y, 0], + ...(record ? { metadata: { foundation: record } } : {}), + }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((w) => w.id), 'slab_g'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: corners, + elevation: 0.05, + thickness: 0.05, + }), + ...walls, + ]) +} + +/** The east part of the site (x >= 5) stands 1 m over the west, the house across the step. */ +function eastStep() { + const field = createTerrainField({ cols: 33, rows: 33, spacing: 1, origin: [-16, -16] }) + const heights = new Int16Array(field.heights) + for (let row = 0; row < field.rows; row += 1) + for (let col = 0; col < field.cols; col += 1) + if (field.origin[0] + col * field.spacing >= 5) heights[row * field.cols + col] = 100 + return encodeTerrainField({ ...field, heights }) +} + +test('a record left empty takes the code numbers, by storeys', () => { + const one = resolveBuildingFoundation(null, 1) + // On unless switched off: a building with no record stands on a slab. + expect(one.enabled).toBe(true) + expect(resolveBuildingFoundation({ enabled: false }, 1).enabled).toBe(false) + expect(one.type).toBe('slab') + // The edge's taper is left to the rule (`slabEdgeTaper`). + expect([one.slabThicknessIn, one.edgeDepthIn, one.edgeWidthIn, one.edgeTaperIn]).toEqual([ + 4, + 12, + 12, + null, + ]) + expect([one.stemThicknessIn, one.footingWidthIn, one.footingThicknessIn]).toEqual([6, 12, 8]) + expect(one.ffAboveGradeIn).toBe(8) + const two = resolveBuildingFoundation({ type: 'raised' }, 2) + expect([two.stemThicknessIn, two.footingWidthIn]).toEqual([8, 15]) + // The stem's top 8 in over grade, the floor framing on it. + expect(two.ffMinIn).toBe(RAISED_FF) + expect(two.ffAboveGradeIn).toBe(RAISED_FF) +}) + +test('a finish floor asked under the least the type allows is held at it, and sizes stay in range', () => { + const raised = resolveBuildingFoundation( + { type: 'raised', ffAboveGradeIn: 12, stemThicknessIn: 1, footingDepthIn: 30 }, + 1, + ) + expect(raised.ffAboveGradeIn).toBe(RAISED_FF) + expect(raised.stemThicknessIn).toBe(4) + expect(raised.footingDepthIn).toBe(30) + expect(resolveBuildingFoundation({ ffAboveGradeIn: 24 }, 1).ffAboveGradeIn).toBe(24) + // A slab edge's taper is held to 6 to 12 in. + expect( + [3, 9, 20].map((edgeTaperIn) => resolveBuildingFoundation({ edgeTaperIn }, 1).edgeTaperIn), + ).toEqual([6, 9, 12]) +}) + +test("a slab edge's 45° taper runs half the edge's depth under the slab, 6 to 12 in, never more than that depth", () => { + // Depth under the slab's underside → the taper, and the plumb inside face under it, inches. + for (const [depth, taper, plumb] of [ + [12, 6, 6], + [20, 10, 10], + [30, 12, 18], + [8, 6, 2], + [4, 4, 0], + ]) { + expect(slabEdgeTaper(depth! * IN) / IN).toBeCloseTo(taper!, 9) + expect((depth! * IN - slabEdgeTaper(depth! * IN)) / IN).toBeCloseTo(plumb!, 9) + } + // One the record sets stands, as far as the depth allows. + expect(slabEdgeTaper(20 * IN, 7 * IN) / IN).toBeCloseTo(7, 9) + expect(slabEdgeTaper(4 * IN, 10 * IN) / IN).toBeCloseTo(4, 9) +}) + +test('the outline runs on the walls’ outside faces, counter-clockwise, every corner of a box turning out', () => { + const [outline] = foundationOutlines(ring(BOX)) + expect(outline?.edges).toHaveLength(4) + expect(outline?.convex).toEqual([true, true, true, true]) + const xs = outline!.edges.map((edge) => edge.a[0]) + const zs = outline!.edges.map((edge) => edge.a[1]) + expect(Math.min(...xs)).toBeCloseTo(-0.051, 9) + expect(Math.max(...xs)).toBeCloseTo(10.051, 9) + expect(Math.min(...zs)).toBeCloseTo(-0.051, 9) + expect(Math.max(...zs)).toBeCloseTo(8.051, 9) +}) + +test('an L-shaped house has one inside corner', () => { + const [outline] = foundationOutlines( + ring([ + [0, 0], + [10, 0], + [10, 4], + [5, 4], + [5, 8], + [0, 8], + ]), + ) + expect(outline?.convex.filter((convex) => !convex)).toHaveLength(1) +}) + +test('switched off, the record leaves the generic foundation as it was', () => { + const generic = genericFoundation(house({ enabled: false })) + expect(ofKind(generic, 'stem').length).toBeGreaterThan(0) + expect(genericFoundation(house({ enabled: false, type: 'raised', ffAboveGradeIn: 18 }))).toEqual( + generic, + ) + expect(designedFoundationReport(house({ enabled: false }), 'building_a')).toBeNull() +}) + +test('with no record the house stands on the default slab, as one switched on does', () => { + expect(genericFoundation(house(null))).toEqual(genericFoundation(house({ enabled: true }))) + expect(ofKind(genericFoundation(house(null)), 'turndown')).toHaveLength(4) + expect(designedFoundationReport(house(null), 'building_a')?.foundation.type).toBe('slab') +}) + +test('a slab on grade: a 4 in slab on its pad 8 in under its top, its edge turned down 12 in under that, 12 in wide, plumb inside from a square bottom, then in at 45 degrees', () => { + const nodes = house({ enabled: true, type: 'slab' }) + const members = genericFoundation(nodes) + expect(ofKind(members, 'footing')).toHaveLength(0) + expect(ofKind(members, 'stem')).toHaveLength(0) + const edges = ofKind(members, 'turndown') + expect(edges).toHaveLength(4) + const [slab] = ofKind(members, 'slab') + expect(slab?.bottomY).toBeCloseTo(0.05 - 4 * IN, 9) + // The floor stands 4 in over the flat ground: the pad cuts it to 8 in under the slab's top. + const pad = 0.05 - 8 * IN + expect(pad).toBeLessThan(FLAT_GROUND_Y) + expect(foundationGradingPads(nodes).map((p) => p.y)).toEqual([pad]) + for (const edge of edges) { + const section = edge.section! + const bottom = Math.min(...section.map(([, y]) => y)) + expect(bottom).toBeCloseTo(pad - 12 * IN, 9) + // The outer face reaches the floor top, a millimetre under it. + expect(Math.max(...section.map(([, y]) => y))).toBeCloseTo(0.049, 9) + const [bottomLeft, bottomRight, plumbTop, slopeTop] = section + expect(bottomLeft).toEqual([0, bottom]) + expect(bottomRight?.[0]).toBeCloseTo(12 * IN, 9) + // 16 in under the slab's underside: plumb 8 in, square to the flat bottom, then 8 in at 45 + // degrees, as far in as it rises. + expect(plumbTop?.[0]).toBeCloseTo(12 * IN, 9) + expect((plumbTop?.[1] ?? 0) - bottom).toBeCloseTo(8 * IN, 9) + expect((slopeTop?.[0] ?? 0) - 12 * IN).toBeCloseTo(8 * IN, 9) + expect((slopeTop?.[1] ?? 0) - (plumbTop?.[1] ?? 0)).toBeCloseTo(8 * IN, 9) + expect(slopeTop?.[1]).toBeCloseTo(0.05 - 4 * IN, 9) + expect(edge.width).toBeCloseTo(20 * IN, 9) + } + expect(foundationRimBottom(nodes, 'wall_g_0')).toBeNull() +}) + +test('a deeper edge grows plumb, not wider; a taper the record sets stands', () => { + /** Each side's taper across, the plumb face under it and the taper's rise, inches. */ + const profile = (record: BuildingFoundationRecord) => + ofKind(genericFoundation(house({ enabled: true, type: 'slab', ...record })), 'turndown').map( + (edge) => { + const section = edge.section! + const [bottom, plumbTop, top] = [section[1]![1], section[2]![1], section[3]![1]] + const across = section[3]![0] + return [ + Math.round(((across - 12 * IN) / IN) * 1e6) / 1e6, + Math.round(((plumbTop - bottom) / IN) * 1e6) / 1e6, + Math.round(((top - plumbTop) / IN) * 1e6) / 1e6, + ] + }, + ) + // 26 in under grade is 30 in under the slab: a 12 in taper on 18 in of plumb face. + for (const side of profile({ edgeDepthIn: 26 })) expect(side).toEqual([12, 18, 12]) + // 16 in under the slab, the taper set to 10 in: 6 in of plumb face under it. + for (const side of profile({ edgeTaperIn: 10 })) expect(side).toEqual([10, 6, 10]) + // 4 in under grade is 8 in under the slab: the least taper, 6 in, on 2 in of plumb face. + for (const side of profile({ edgeDepthIn: 4 })) expect(side).toEqual([6, 2, 6]) +}) + +/** `nodes` with `extra` added to the ground level. */ +function withOnLevel(nodes: Nodes, ...extra: AnyNode[]): Nodes { + const level = nodes['level_g' as AnyNodeId] as LevelNode + return { + ...nodes, + ...keyed(extra), + level_g: { ...level, children: [...level.children, ...extra.map((node) => node.id)] }, + } as Nodes +} + +test('a slab past the walls (a patio in the outline) is flush with the floor: no raised edge where no wall stands', () => { + const patio = FoundationNode.parse({ + id: 'foundation_patio', + parentId: 'level_g', + polygon: [ + [-0.051, -0.051], + [14, -0.051], + [14, 8.051], + [-0.051, 8.051], + ], + }) + const members = genericFoundation(withOnLevel(house({ enabled: true, type: 'slab' }), patio)) + const slabs = ofKind(members, 'slab') + expect(slabs).toHaveLength(1) + // The floor's top (0.05), a hair under: the walking surface where no plate lies over it. + expect(slabs[0]?.topY).toBeCloseTo(0.048, 9) + const top = (edge: FoundationMember) => Math.max(...edge.section!.map(([, y]) => y)) + const east = ofKind(members, 'turndown').filter((edge) => edge.from[0] > 13 && edge.to[0] > 13) + expect(east).toHaveLength(1) + expect(top(east[0]!)).toBeCloseTo(0.048, 9) + // Under the west wall the edge comes up to the floor's top, a millimetre under it. + const west = ofKind(members, 'turndown').filter((edge) => edge.from[0] < 0 && edge.to[0] < 0) + expect(top(west[0]!)).toBeCloseTo(0.049, 9) +}) + +test('under a floor a step down (a garage) the slab drops to its underside, round it at the floor top', () => { + const garage = SlabNode.parse({ + id: 'slab_garage', + parentId: 'level_g', + polygon: [ + [6, 1], + [9, 1], + [9, 4], + [6, 4], + ], + elevation: -0.1, + thickness: 0.05, + }) + const slabs = ofKind( + genericFoundation(withOnLevel(house({ enabled: true, type: 'slab' }), garage)), + 'slab', + ) + expect(slabs).toHaveLength(2) + const [main, sunk] = slabs + expect(main?.topY).toBeCloseTo(0.048, 9) + expect(main?.holes).toHaveLength(1) + // The garage's plate from -0.15 to -0.1: the concrete a millimetre over its underside. + expect(sunk?.topY).toBeCloseTo(-0.149, 9) + expect(sunk?.bottomY).toBeCloseTo(-0.149 - 4 * IN, 9) +}) + +test('the ground is dug out under a floor set down in the slab, and under a slab thicker than its pad allows', () => { + const garage = SlabNode.parse({ + id: 'slab_garage', + parentId: 'level_g', + polygon: [ + [6, 1], + [9, 1], + [9, 4], + [6, 4], + ], + elevation: -0.1, + thickness: 0.05, + }) + const pads = foundationGradingPads(withOnLevel(house({ enabled: true, type: 'slab' }), garage)) + // The house's pad 8 in under its top; the 4 in slab leaves gravel under it, nothing to dig. + expect(pads[0]?.y).toBeCloseTo(0.05 - 8 * IN, 9) + const pits = pads.filter((pad) => pad.kind === 'pit') + expect(pits).toHaveLength(1) + // Under the garage the dirt goes down to its concrete's underside. + expect(pits[0]?.y).toBeCloseTo(-0.149 - 4 * IN, 9) + expect(Math.min(...pits[0]!.outline.map(([x]) => x))).toBeCloseTo(6, 6) + + // A 10 in slab on a pad 8 in under its top: the dirt under it goes down to its underside. + const thick = foundationGradingPads(house({ enabled: true, type: 'slab', slabThicknessIn: 10 })) + expect(thick.filter((pad) => pad.kind === 'pit').map((pad) => pad.y)).toEqual([ + expect.closeTo(0.05 - 10 * IN, 9), + ]) +}) + +test('a raised floor reads its framing off its layers; a record that set only the depth keeps it', () => { + const tenInch = { + ...DEFAULT_RAISED_FLOOR_BUILD_UP, + structure: { system: 'joists', size: '2x10', spacing: 16 * IN }, + } as const + const layered = resolveBuildingFoundation({ type: 'raised', floorBuildUp: tenInch }, 1) + expect(layered.floorFramingDepthIn).toBeCloseTo(0.75 + 0.625 + 9.25 + 1.5, 4) + expect(layered.ffAboveGradeIn).toBeCloseTo(8 + 0.75 + 0.625 + 9.25 + 1.5, 4) + const { flooring: _, ...bare } = DEFAULT_RAISED_FLOOR_BUILD_UP + const flooringOff = resolveBuildingFoundation({ type: 'raised', floorBuildUp: bare }, 1) + expect(flooringOff.floorFramingDepthIn).toBeCloseTo(0.625 + 11.25 + 1.5, 4) + const legacy = resolveBuildingFoundation({ type: 'raised', floorFramingDepthIn: 11.5 }, 1) + expect(legacy.floorFramingDepthIn).toBe(11.5) + expect(legacy.floorBuildUp).toEqual(DEFAULT_RAISED_FLOOR_BUILD_UP) +}) + +test('a raised floor: a 6 in stem wall flush with the walls on a 12 x 8 in footing 12 in under the crawl space dirt, the framing on it', () => { + // Seated: the floor top 22⅛ in over the ground. + const y = FLAT_GROUND_Y + RAISED_FF * IN - 0.05 + const nodes = house({ enabled: true, type: 'raised' }, { y }) + const members = genericFoundation(nodes) + const stems = ofKind(members, 'stem') + const footings = ofKind(members, 'footing') + expect(stems).toHaveLength(4) + expect(footings).toHaveLength(4) + const stemTop = y + 0.05 - FRAMING * IN + // The stem's top 8 in over the ground. + expect(stemTop - FLAT_GROUND_Y).toBeCloseTo(8 * IN, 9) + for (const stem of stems) { + expect(stem.width).toBeCloseTo(6 * IN, 9) + expect(stem.topY).toBeCloseTo(stemTop, 9) + } + for (const footing of footings) { + expect(footing.width).toBeCloseTo(12 * IN, 9) + // The crawl space is dug 18 in under the joists, 8½ in under the ground outside: the footing + // goes its 12 in under that dirt. + expect(footing.bottomY).toBeCloseTo(stemTop + 1.5 * IN - 18 * IN - 12 * IN, 9) + expect(footing.topY - footing.bottomY).toBeCloseTo(8 * IN, 9) + } + // The south stem's outer face on the wall's outer face (z = -0.05, a millimetre out). + const south = stems.find((stem) => Math.abs(stem.from[1] - stem.to[1]) < 1e-9 && stem.from[1] < 1) + expect((south?.from[1] ?? 0) - 3 * IN).toBeCloseTo(-0.051, 9) + // Every exterior wall carries its finish down over the framing to the stem. + for (let index = 0; index < 4; index++) + expect(foundationRimBottom(nodes, `wall_g_${index}`)).toBeCloseTo(stemTop - y, 9) + const report = designedFoundationReport(nodes, 'building_a') + expect(report?.stemTopY).toBeCloseTo(stemTop, 9) + expect(report?.steps).toBe(0) +}) + +test('on a slope the footing steps down with the ground, every step deep enough and level', () => { + const terrain = eastStep() + // Seated over the high side: the floor 22⅛ in over the 1 m grade. + const y = 1 + RAISED_FF * IN - 0.05 + const nodes = house({ enabled: true, type: 'raised' }, { terrain, y }) + const footings = ofKind(genericFoundation(nodes), 'footing') + const site = nodes['site_a' as AnyNodeId] + const bottoms = new Set(footings.map((footing) => Math.round(footing.bottomY * 1000))) + expect(bottoms.size).toBeGreaterThan(1) + for (const footing of footings) { + // Every point along the step is at least 12 in under the grade over it. + for (let k = 0; k <= 20; k++) { + const x = footing.from[0] + ((footing.to[0] - footing.from[0]) * k) / 20 + const z = footing.from[1] + ((footing.to[1] - footing.from[1]) * k) / 20 + expect(footing.bottomY).toBeLessThanOrEqual(groundHeightAt(site, x, z) - 12 * IN + 0.02) + } + } + expect(designedFoundationReport(nodes, 'building_a')?.steps).toBeGreaterThan(0) + + const level = genericFoundation( + house({ enabled: true, type: 'raised', stepped: false }, { terrain, y }), + ) + const lowest = Math.min(...ofKind(level, 'footing').map((footing) => footing.bottomY)) + for (const footing of ofKind(level, 'footing')) expect(footing.bottomY).toBeCloseTo(lowest, 9) +}) + +/** + * A raised house on ground falling `fall` metres west to east across its 10 m, along its north and + * south sides, its floor seated 22⅛ in over the high (west) side: the stem's top 8 in over it. + */ +function fallingHouse(fall: number) { + const field = createTerrainField({ cols: 33, rows: 33, spacing: 1, origin: [-16, -16] }) + const heights = new Int16Array(field.heights) + for (let row = 0; row < field.rows; row += 1) + for (let col = 0; col < field.cols; col += 1) + heights[row * field.cols + col] = Math.round((10 - (field.origin[0] + col)) * fall * 10) + const terrain = encodeTerrainField({ ...field, heights }) + const nodes = house( + { enabled: true, type: 'raised' }, + { terrain, y: fall + RAISED_FF * IN - 0.05 }, + ) + const southOf = (kind: FoundationMember['kind']) => + ofKind(genericFoundation(nodes), kind).filter( + (member) => Math.abs(member.from[1] - member.to[1]) < 1e-9 && member.from[1] < 1, + ) + return { nodes, southOf, gradeAt: (x: number) => (10 - x) * (fall / 10) } +} + +test('a side whose stem stands no more than 36 in out of the ground stays one level run', () => { + // 0.6 m (23.6 in) of fall: the stem stands 31.6 in out at the low end. + const { nodes, southOf } = fallingHouse(0.6) + expect(southOf('footing')).toHaveLength(1) + expect(southOf('stem')[0]?.topY).toBeCloseTo(0.6 + 8 * IN, 6) + expect(southOf('cripple')).toHaveLength(0) + const report = designedFoundationReport(nodes, 'building_a')! + expect(report.steps).toBe(0) + expect(report.cripples).toBe(0) + // Its footing at the deepest need along it, the low end's. + expect(southOf('footing')[0]?.bottomY).toBeLessThanOrEqual(-12 * IN + 1e-6) +}) + +test('past 36 in the footing steps down the slope, the stem drops to 8 in over grade, a cripple wall over it', () => { + // 1.2 m (47 in) of fall: the stem would stand 55 in out at the low end. + const { nodes, southOf, gradeAt } = fallingHouse(1.2) + const footings = southOf('footing') + const report = designedFoundationReport(nodes, 'building_a')! + expect(footings.length).toBeGreaterThan(1) + // Steps are counted along a side, not summed round the house, with the tallest one. + expect(report.steps).toBe(footings.length - 1) + expect(report.tallestStep).toBeCloseTo(8 * IN, 6) + const full = report.stemTopY! + const stems = southOf('stem') + const cripples = southOf('cripple') + expect(cripples.length).toBeGreaterThan(0) + expect(report.cripples).toBeGreaterThanOrEqual(cripples.length) + for (const stem of stems) { + // Its top 8 in at least over the highest ground along it, and in whole steps under the framing. + const high = gradeAt(Math.min(stem.from[0], stem.to[0])) + expect(stem.topY - high).toBeGreaterThanOrEqual(8 * IN - 0.02) + const drop = (full - stem.topY) / (8 * IN) + expect(Math.abs(drop - Math.round(drop))).toBeLessThan(1e-6) + } + for (const cripple of cripples) { + expect(cripple.material).toBe('wood') + expect(cripple.topY).toBeCloseTo(full, 9) + // It stands on a stem, the stem's width. + expect(stems.some((stem) => Math.abs(stem.topY - cripple.bottomY) < 1e-9)).toBe(true) + expect(cripple.width).toBeCloseTo(6 * IN, 9) + } + expect(report.tallestCripple).toBeGreaterThan(0) + // The high end keeps its stem full height, to the floor framing. + expect(Math.max(...stems.map((stem) => stem.topY))).toBeCloseTo(full, 9) + // The south wall carries its siding down over each cripple wall to the stem under it. + const plane = 1.2 + RAISED_FF * IN - 0.05 + const steps = foundationRimSteps(nodes).get('wall_g_0') ?? [] + expect(steps).toHaveLength(cripples.length) + for (const step of steps) { + const cripple = cripples.find( + (member) => + Math.abs(Math.min(member.from[0], member.to[0]) - Math.min(step.a[0], step.b[0])) < 1e-6, + ) + expect(step.bottom).toBeCloseTo(cripple!.bottomY - plane, 9) + } + expect(foundationRimSteps(fallingHouse(0.6).nodes).size).toBe(0) +}) + +test('a raised floor cuts the ground round it to hold its stem 8 in out, and digs its crawl space to 18 in under the joists', () => { + const y = FLAT_GROUND_Y + RAISED_FF * IN - 0.05 + const nodes = house({ enabled: true, type: 'raised' }, { y }) + const report = designedFoundationReport(nodes, 'building_a')! + const stemTop = y + 0.05 - FRAMING * IN + // The joists' underside 1½ in over the stem, the dirt 18 in under that. + expect(report.crawlY).toBeCloseTo(stemTop + 1.5 * IN - 18 * IN, 9) + const pads = foundationGradingPads(nodes) + expect(pads.map((pad) => pad.kind)).toEqual(['cut', 'pit']) + expect(pads[0]?.y).toBeCloseTo(stemTop - 8 * IN, 9) + expect(pads[1]?.y).toBeCloseTo(report.crawlY!, 9) + // The pit stands inside the stem walls: its outline 6 in in from the outline's. + const xs = pads[1]!.outline.map(([x]) => x) + expect(Math.min(...xs)).toBeCloseTo(-0.051 + 6 * IN, 6) +}) + +test('piles: a grade beam all round on piles at every corner and no more than 8 ft apart', () => { + const nodes = house({ enabled: true, type: 'raised', support: 'piles' }, { y: 0.45 }) + const members = genericFoundation(nodes) + expect(ofKind(members, 'footing')).toHaveLength(0) + expect(ofKind(members, 'grade-beam')).toHaveLength(4) + const piles = ofKind(members, 'pile') + // 10 m sides take 5 bays of 2 m, 8 m sides 4 of 2 m: 18 piles, corners shared. + expect(piles).toHaveLength(18) + for (const pile of piles) { + expect(pile.width).toBeCloseTo(12 * IN, 9) + expect(pile.topY - pile.bottomY).toBeCloseTo(120 * IN, 9) + } + expect(designedFoundationReport(nodes, 'building_a')?.piles).toBe(18) +}) + +test('piles: the grade beam goes its own depth down, or deeper where the least under grade asks', () => { + const beamBottom = (record: BuildingFoundationRecord) => + Math.min( + ...ofKind(genericFoundation(house(record, { y: 0.45 })), 'grade-beam').map((m) => m.bottomY), + ) + const record = { enabled: true, type: 'raised', support: 'piles' } as const + // The stem top 0.45 + 0.05 less the framing; a 24 in beam reaches further than 12 in under grade. + const top = 0.5 - FRAMING * IN + expect(beamBottom(record)).toBeCloseTo(top - 24 * IN, 6) + expect(beamBottom({ ...record, footingDepthIn: 36 })).toBeCloseTo(FLAT_GROUND_Y - 36 * IN, 6) +}) + +test('piles: a crawl space dug deeper than the grade beam takes the beam down to its dirt', () => { + const nodes = house( + { enabled: true, type: 'raised', support: 'piles', crawlClearanceIn: 48 }, + { y: 0.45 }, + ) + const crawl = designedFoundationReport(nodes, 'building_a')!.crawlY! + const beams = ofKind(genericFoundation(nodes), 'grade-beam') + // The stem top 0.5 m less the framing; 48 in under the joists is under the 24 in beam's bottom. + expect(crawl).toBeLessThan(0.5 - FRAMING * IN - 24 * IN) + for (const beam of beams) expect(beam.bottomY).toBeLessThanOrEqual(crawl + 1e-9) +}) + +test('the resolved record reads the storeys the building has', () => { + expect(buildingFoundationOf(house({ enabled: true }), 'building_a')?.storeys).toBe(1) +}) diff --git a/packages/core/src/lib/designed-foundation.ts b/packages/core/src/lib/designed-foundation.ts new file mode 100644 index 0000000000..f620eeb1f9 --- /dev/null +++ b/packages/core/src/lib/designed-foundation.ts @@ -0,0 +1,1426 @@ +import type { AnyNode, AnyNodeId, BuildingNode, LevelNode, WallNode } from '../schema' +import { DEFAULT_SLAB_ELEVATION } from '../schema/nodes/slab' +import { levelFloorWorldY, levelPlaneWorldY, levelPlateUndersideWorldY } from '../services/storey' +import { getWallFaceOffsets } from '../systems/wall/wall-frame' +import { + type BuildingFoundation, + FOUNDATION_SILL_IN, + inches, + slabEdgeTaper, +} from './building-foundation' +import { exteriorWallLoops } from './exterior-wall-loops' +import type { GradingPad } from './foundation-grading' +import type { FoundationMember } from './generic-foundation' +import { containsPoint, difference, intersection, type Ring, union } from './polygon-boolean' +import { isSiteFlatwork } from './site-flatwork' +import { levelLocalToWorldXZ } from './terrain-support-query' + +type Nodes = Record +type P = [number, number] + +/** One side of a foundation outline, level-local, the building on its left. */ +type OutlineEdge = { a: P; b: P; wallId: string | null } + +/** + * The outside of the walls of one closed loop, level-local and counter-clockwise (the building on + * the left of every edge): each wall's exterior face, the corners where those faces meet, a jog + * where two walls in line differ in thickness. + */ +export type FoundationOutline = { + edges: OutlineEdge[] + /** Per vertex (the start of `edges[i]`): the outline turns outward there (a corner of the house, not an inside corner). */ + convex: boolean[] +} + +/** What the designed foundation measured and how it stands against its own rules. */ +export type DesignedFoundationReport = { + buildingId: string + levelId: string + foundation: BuildingFoundation + floorTopY: number + highestGradeY: number + lowestGradeY: number + /** The stem wall's (a raised grade beam's, a basement's walls') top; `null` for a slab. */ + stemTopY: number | null + /** The deepest the perimeter goes, the bottom of its lowest step. */ + bottomY: number + /** The most steps along one side. */ + steps: number + /** The tallest of those steps, metres (a short run joined to its deeper neighbour can drop more than one step height). */ + tallestStep: number + piles: number + /** A raised floor's cripple walls: how many runs, and the tallest, metres (stem top up to the floor framing). */ + cripples: number + tallestCripple: number + /** A raised floor's crawl space: the dirt under it, dug out to this, world Y; `null` for a slab. */ + crawlY: number | null + /** The house stands on a basement (`designedBasement`); absent for a slab or a raised floor. */ + basement?: BasementReport +} + +/** + * How a house over a basement stands: the report's `levelId` and `floorTopY` are the basement's, + * its `stemTopY` the basement walls' top, its grades read along their outside faces. + */ +export type BasementReport = { + /** The ground floor over it, whose floor framing bears on the walls' top. */ + groundLevelId: string + /** The level whose walls the outline runs along: the basement's own, or the ground floor's. */ + outlineLevelId: string + /** The basement's walls are its own (drawn on its level), not poured under the house's outline. */ + ownWalls: boolean + /** Where the floor over it bears on the walls: their top, under its sill, world Y. */ + wallTopY: number + /** That floor's top, world Y: its framing and the sill stand between it and `wallTopY`. */ + groundFloorTopY: number + /** The ground dug out inside the walls' outside faces down to this, world Y. */ + excavationY: number +} + +/** Grade is read along the outline this often, metres. */ +const GRADE_STEP = 0.15 +/** The sill a raised floor's joists bear on, over the stem's top: the joists' underside stands this far over it. */ +const SILL = inches(FOUNDATION_SILL_IN) +/** The concrete's outer face stands this far outside the wall's face, so the two never share a plane. */ +const FACE_CLEARANCE = 0.001 + +/** + * Where a wall's exterior face lies against a centreline edge of the loop: the distance from the + * edge, out of the building. A justified wall's body is off its line; this follows the body. + */ +function exteriorFaceDistance(wall: WallNode | undefined, edgeDir: P, outward: P): number { + if (!wall) return 0 + const { a, b } = getWallFaceOffsets(wall) + const chord: P = [wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]] + const along = edgeDir[0] * chord[0] + edgeDir[1] * chord[1] >= 0 ? 1 : -1 + // The wall's own left, at this edge. + const left: P = [-edgeDir[1] * along, edgeDir[0] * along] + const side = left[0] * outward[0] + left[1] * outward[1] >= 0 ? 1 : -1 + return Math.max(a * side, b * side) +} + +const sub = (a: P, b: P): P => [a[0] - b[0], a[1] - b[1]] +const add = (a: P, b: P): P => [a[0] + b[0], a[1] + b[1]] +const scale = (a: P, k: number): P => [a[0] * k, a[1] * k] +const cross = (a: P, b: P) => a[0] * b[1] - a[1] * b[0] +const len = (a: P) => Math.hypot(a[0], a[1]) +const unit = (a: P): P => { + const l = len(a) + return l < 1e-12 ? [0, 0] : [a[0] / l, a[1] / l] +} + +function signedArea(points: readonly P[]): number { + let sum = 0 + for (let i = 0; i < points.length; i++) { + const p = points[i]! + const q = points[(i + 1) % points.length]! + sum += p[0] * q[1] - q[0] * p[1] + } + return sum / 2 +} + +/** The point where the lines through `p` along `u` and through `q` along `v` cross; `null` when they run in line. */ +function intersect(p: P, u: P, q: P, v: P): P | null { + const d = cross(u, v) + if (Math.abs(d) < 1e-6) return null + const t = cross(sub(q, p), v) / d + return add(p, scale(u, t)) +} + +/** + * The exterior faces of each closed loop of `walls`, as one outline per loop. Walls in line merge + * into one side (a partition meeting the outside wall splits it in the wall graph, not here). + */ +export function foundationOutlines(walls: WallNode[]): FoundationOutline[] { + const byId = new Map(walls.map((wall) => [wall.id as string, wall])) + const outlines: FoundationOutline[] = [] + for (const loop of exteriorWallLoops(walls)) { + const centre: OutlineEdge[] = [] + for (const face of loop) + for (let i = 0; i + 1 < face.points.length; i++) { + const a = face.points[i] as P + const b = face.points[i + 1] as P + if (len(sub(b, a)) > 1e-6) centre.push({ a, b, wallId: face.wallId }) + } + if (centre.length < 3) continue + const ccw = signedArea(centre.map((edge) => edge.a)) > 0 + // Each centreline edge moved out to its wall's exterior face. + const faces = centre.map((edge) => { + const u = unit(sub(edge.b, edge.a)) + const outward: P = ccw ? [u[1], -u[0]] : [-u[1], u[0]] + const wall = edge.wallId ? byId.get(edge.wallId) : undefined + const d = exteriorFaceDistance(wall, u, outward) + FACE_CLEARANCE + return { + a: add(edge.a, scale(outward, d)), + b: add(edge.b, scale(outward, d)), + u, + wallId: edge.wallId as string | null, + } + }) + // Sides in line become one. + const merged: typeof faces = [] + for (const face of faces) { + const last = merged[merged.length - 1] + if ( + last && + Math.abs(cross(last.u, face.u)) < 1e-6 && + last.u[0] * face.u[0] + last.u[1] * face.u[1] > 0 && + Math.abs(cross(last.u, sub(face.a, last.a))) < 1e-4 + ) { + last.b = face.b + continue + } + merged.push({ ...face }) + } + if (merged.length > 2) { + const first = merged[0]! + const last = merged[merged.length - 1]! + if ( + Math.abs(cross(last.u, first.u)) < 1e-6 && + last.u[0] * first.u[0] + last.u[1] * first.u[1] > 0 && + Math.abs(cross(last.u, sub(first.a, last.a))) < 1e-4 + ) { + first.a = last.a + merged.pop() + } + } + if (merged.length < 3) continue + // Corners: neighbouring faces meet where their lines cross; faces in line (two thicknesses) jog. + const points: { at: P; wallId: string | null }[] = [] + for (let i = 0; i < merged.length; i++) { + const prev = merged[(i - 1 + merged.length) % merged.length]! + const face = merged[i]! + const corner = intersect(prev.a, prev.u, face.a, face.u) + if (corner && len(sub(corner, face.a)) < 2 && len(sub(corner, prev.b)) < 2) { + points.push({ at: corner, wallId: face.wallId }) + } else { + points.push({ at: prev.b, wallId: null }) + points.push({ at: face.a, wallId: face.wallId }) + } + } + let edges: OutlineEdge[] = points + .map((point, i) => ({ + a: point.at, + b: points[(i + 1) % points.length]!.at, + wallId: point.wallId, + })) + .filter((edge) => len(sub(edge.b, edge.a)) > 1e-6) + if (!ccw) edges = edges.reverse().map((edge) => ({ a: edge.b, b: edge.a, wallId: edge.wallId })) + if (edges.length < 3) continue + const convex = edges.map((edge, i) => { + const prev = edges[(i - 1 + edges.length) % edges.length]! + return cross(unit(sub(prev.b, prev.a)), unit(sub(edge.b, edge.a))) >= -1e-9 + }) + outlines.push({ edges, convex }) + } + return outlines +} + +/** + * A drawn outline (a `foundation` node's polygon) as the designed foundation reads one: wound + * counter-clockwise, repeated corners and corners in line dropped. `null` for fewer than three + * corners or no area. + */ +export function outlineOfPolygon( + polygon: ReadonlyArray, +): FoundationOutline | null { + let points: P[] = [] + for (const [x, z] of polygon) { + const last = points[points.length - 1] + if (!last || len(sub([x, z], last)) > 1e-6) points.push([x, z]) + } + while (points.length > 1 && len(sub(points[0]!, points[points.length - 1]!)) <= 1e-6) points.pop() + // A corner in line with its neighbours is no corner. + points = points.filter((point, i) => { + const prev = points[(i - 1 + points.length) % points.length]! + const next = points[(i + 1) % points.length]! + return Math.abs(cross(unit(sub(point, prev)), unit(sub(next, point)))) > 1e-6 + }) + if (points.length < 3 || Math.abs(signedArea(points)) < 1e-6) return null + if (signedArea(points) < 0) points.reverse() + const edges: OutlineEdge[] = points.map((a, i) => ({ + a, + b: points[(i + 1) % points.length]!, + wallId: null, + })) + const convex = edges.map((edge, i) => { + const prev = edges[(i - 1 + edges.length) % edges.length]! + return cross(unit(sub(prev.b, prev.a)), unit(sub(edge.b, edge.a))) >= -1e-9 + }) + return { edges, convex } +} + +/** A ring as the clipper returns it, closed by repeating its first point: open it again. */ +function openRing(ring: Ring): P[] { + const points = ring.map(([x, z]) => [x, z] as P) + const first = points[0] + const last = points[points.length - 1] + if (first && last && points.length > 1 && first[0] === last[0] && first[1] === last[1]) + points.pop() + return points +} + +/** An outline moved `inset` metres into the building, its corners re-met. */ +function insetOutline(outline: FoundationOutline, inset: number): FoundationOutline { + if (Math.abs(inset) < 1e-9) return outline + const moved = outline.edges.map((edge) => { + const u = unit(sub(edge.b, edge.a)) + const inward: P = [-u[1], u[0]] + return { a: add(edge.a, scale(inward, inset)), u, wallId: edge.wallId } + }) + const corners = moved.map((edge, i) => { + const prev = moved[(i - 1 + moved.length) % moved.length]! + return intersect(prev.a, prev.u, edge.a, edge.u) ?? edge.a + }) + return { + edges: moved.map((edge, i) => ({ + a: corners[i]!, + b: corners[(i + 1) % corners.length]!, + wallId: edge.wallId, + })), + convex: outline.convex, + } +} + +/** The highest and the lowest grade along the outlines, read every {@link GRADE_STEP} metres. */ +function gradeAlong( + outlines: readonly FoundationOutline[], + gradeOf: (p: P) => number, +): { highestGradeY: number; lowestGradeY: number } { + let highestGradeY = Number.NEGATIVE_INFINITY + let lowestGradeY = Number.POSITIVE_INFINITY + for (const outline of outlines) + for (const edge of outline.edges) { + const length = len(sub(edge.b, edge.a)) + const count = Math.max(2, Math.ceil(length / GRADE_STEP) + 1) + for (let k = 0; k < count; k++) { + const g = gradeOf(add(edge.a, scale(sub(edge.b, edge.a), k / (count - 1)))) + highestGradeY = Math.max(highestGradeY, g) + lowestGradeY = Math.min(lowestGradeY, g) + } + } + return { highestGradeY, lowestGradeY } +} + +/** A stretch of a side at one footing level: `s0` to `s1` metres along it, its bottom at `level`. */ +type Piece = { s0: number; s1: number; level: number } +/** A raised floor's stretch: its stem's top at `stemTop`, a cripple wall over it where that is under the floor framing. */ +type RaisedPiece = Piece & { stemTop: number } + +/** + * A side cut into level pieces. Each sample needs its bottom no higher than `required(s)` (the + * grade there, less the depth); pieces sit on steps of `stepHeight` over `datum` (the building's + * deepest need, so flat ground puts every bottom exactly at its depth), and a piece shorter than + * `minRun` joins the deeper neighbour. Unstepped, a side is one piece at `datum`. + */ +function piecesAlong( + length: number, + required: (s: number) => number, + datum: number, + foundation: BuildingFoundation, +): Piece[] { + if (!foundation.stepped) return [{ s0: 0, s1: length, level: datum }] + const step = inches(foundation.stepHeightIn) + const minRun = inches(foundation.minStepRunIn) + const count = Math.max(2, Math.ceil(length / GRADE_STEP) + 1) + const levels: number[] = [] + for (let k = 0; k < count; k++) { + // A sample stands for the run half a spacing either side of it: its level holds at both ends + // too, or a steep bank between samples leaves the footing short of its depth there. + const s = (length * k) / (count - 1) + const half = length / (count - 1) / 2 + const need = Math.min( + required(s), + required(Math.max(0, s - half)), + required(Math.min(length, s + half)), + ) + levels.push(datum + Math.max(0, Math.floor((need - datum) / step + 1e-9)) * step) + } + const pieces: Piece[] = [] + for (let k = 0; k < count; k++) { + const s = (length * k) / (count - 1) + const half = length / (count - 1) / 2 + const last = pieces[pieces.length - 1] + if (last && Math.abs(last.level - levels[k]!) < 1e-9) last.s1 = Math.min(length, s + half) + else + pieces.push({ s0: Math.max(0, s - half), s1: Math.min(length, s + half), level: levels[k]! }) + } + // A step's run is never shorter than the least run: the short piece takes the deeper level of its + // neighbours, which every sample of both still allows. + while (pieces.length > 1) { + let shortest = -1 + for (let k = 0; k < pieces.length; k++) { + const piece = pieces[k]! + if (piece.s1 - piece.s0 >= minRun - 1e-9) continue + if (shortest < 0 || piece.s1 - piece.s0 < pieces[shortest]!.s1 - pieces[shortest]!.s0) + shortest = k + } + if (shortest < 0) break + const piece = pieces[shortest]! + const before = pieces[shortest - 1] + const after = pieces[shortest + 1] + const into = !after || (before && before.level <= after.level) ? shortest - 1 : shortest + 1 + const other = pieces[into]! + other.s0 = Math.min(other.s0, piece.s0) + other.s1 = Math.max(other.s1, piece.s1) + other.level = Math.min(other.level, piece.level) + pieces.splice(shortest, 1) + // Neighbours left at one level join. + for (let k = pieces.length - 1; k > 0; k--) { + const left = pieces[k - 1]! + const right = pieces[k]! + if (Math.abs(left.level - right.level) < 1e-9) { + left.s1 = right.s1 + pieces.splice(k, 1) + } + } + } + return pieces +} + +/** + * A stretch of a wall over a cripple wall: its finish runs further down there, to the stepped-down + * stem's top. `a` to `b` along the stem's line, level-local plan; `bottom` level-local Y. + */ +export type FoundationRimStep = { a: [number, number]; b: [number, number]; bottom: number } + +export type DesignedFoundation = { + members: FoundationMember[] + /** Per exterior wall: the level-local Y its finish runs down to over the floor framing (the stem's top). */ + rimBottoms: Map + /** Per exterior wall: where its finish runs further down, over a cripple wall. */ + rimSteps: Map + /** + * Per wall a slab room shares with the house, on the house's stem: the stem's top (the sill + * line), level-local. Under it the wall's face on the room's side, down to the room's slab, is + * the stem's bare concrete; its finish starts there. + */ + sharedStemTops: Map + /** + * How it grades the ground (`foundation-grading`): a slab's building pads, one per outline, the + * grade held along its edge; a raised floor's cut and crawl space; a basement's dig. + */ + pads: GradingPad[] + report: DesignedFoundationReport +} + +/** How one drawn outline stands apart from the house's floor (a porch slab). */ +export type DesignedOutlineOptions = { + /** Its top this far under the level's floor top, metres. */ + drop?: number + /** No floor plate lies over it: its slab is its own walking surface, up to its top. */ + ownTop?: boolean +} + +/** A slab room's curb: its walls' sill this far over its slab (IRC R317.1: 8 in), metres. */ +export const SLAB_ROOM_CURB = inches(8) + +/** A raised house's room on a slab of its own (a garage, `floor.foundation`). */ +type SlabRoom = { + zoneId: string + /** Its walls' outside faces, level-local: a wall it shares with the house to its far face. */ + region: FoundationOutline + /** Its slab's top, world Y: the room's floor. */ + slabTop: number + /** The ground held under it (`slabRoomTopAboveGradeIn` under its top), world Y. */ + padY: number + /** Its walls: where they stand inside the house, on the house's stem wall (`insideRuns`). */ + shared: WallNode[] +} + +/** Whether a wall runs along a side of `polygon`, within its own half thickness. */ +function wallAlongPolygon(wall: WallNode, polygon: ReadonlyArray) { + const mid = scale(add(wall.start as P, wall.end as P), 0.5) + const dir = unit(sub(wall.end as P, wall.start as P)) + return polygon.some((a, index) => { + const b = polygon[(index + 1) % polygon.length]! + const side = sub(b as P, a as P) + const length = len(side) + if (length < 1e-6 || Math.abs(cross(unit(side), dir)) > 0.05) return false + const t = ((mid[0] - a[0]) * side[0] + (mid[1] - a[1]) * side[1]) / (length * length) + if (t < -0.01 || t > 1.01) return false + const foot = add(a as P, scale(side, Math.max(0, Math.min(1, t)))) + return len(sub(mid, foot)) <= (wall.thickness ?? 0.15) / 2 + 0.05 + }) +} + +/** + * The level's rooms on a slab of their own (`floor.foundation: 'slab'`) in a raised house: each + * one's outline, the outside faces of the walls round it (`foundationOutlines`), its slab at its + * floor and the ground held under it, and the walls it shares with the rest of the house. + */ +function slabRoomsOf( + nodes: Nodes, + level: LevelNode, + planeY: number, + foundation: BuildingFoundation, +): SlabRoom[] { + const levelWalls = level.children + .map((id) => nodes[id as AnyNodeId]) + .filter((node): node is WallNode => node?.type === 'wall' && node.visible !== false) + const rooms: SlabRoom[] = [] + for (const id of level.children) { + const zone = nodes[id as AnyNodeId] + if (zone?.type !== 'zone' || zone.floor?.foundation !== 'slab' || zone.polygon.length < 3) + continue + const bounding = + zone.boundaryWallIds.length > 0 + ? levelWalls.filter((wall) => zone.boundaryWallIds.includes(wall.id)) + : levelWalls.filter((wall) => wallAlongPolygon(wall, zone.polygon)) + const region = foundationOutlines(bounding)[0] + if (!region) continue + const slabTop = planeY + (zone.floor.elevation ?? DEFAULT_SLAB_ELEVATION) + rooms.push({ + zoneId: zone.id, + region, + slabTop, + padY: slabTop - inches(foundation.slabRoomTopAboveGradeIn), + shared: bounding, + }) + } + return rooms +} + +/** + * A side of a raised outline (the stem's centreline, level-local) cut where it runs along a slab + * room: `s0` to `s1` metres along it, the room it runs along or `null` for the raised floor. + */ +function slabSpansOf( + edge: OutlineEdge, + rooms: readonly SlabRoom[], +): Array<{ s0: number; s1: number; room: SlabRoom | null }> { + const span = sub(edge.b, edge.a) + const length = len(span) + if (rooms.length === 0 || length < 1e-9) return [{ s0: 0, s1: length, room: null }] + const u = unit(span) + const inward: P = [-u[1], u[0]] + const count = Math.max(2, Math.ceil(length / 0.05) + 1) + const roomAt = (s: number) => { + const probe = add(add(edge.a, scale(u, s)), scale(inward, 0.02)) + return ( + rooms.find((room) => + containsPoint([{ outer: room.region.edges.map((side) => side.a), holes: [] }], probe), + ) ?? null + ) + } + const spans: Array<{ s0: number; s1: number; room: SlabRoom | null }> = [] + let previous = roomAt(0) + let from = 0 + for (let k = 1; k < count; k++) { + const s = (length * k) / (count - 1) + const room = roomAt(s) + if (room === previous) continue + const cut = s - length / (count - 1) / 2 + spans.push({ s0: from, s1: cut, room: previous }) + from = cut + previous = room + } + spans.push({ s0: from, s1: length, room: previous }) + return spans +} + +/** + * The stretches of a wall's centreline (level-local) standing inside `outline`, not on it: the + * house on both its sides. + */ +function insideRuns(wall: WallNode, outline: FoundationOutline): Array<[P, P]> { + const area = [{ outer: outline.edges.map((edge) => edge.a), holes: [] }] + const start = wall.start as P + const span = sub(wall.end as P, start) + const length = len(span) + if (length < 1e-6) return [] + const u = unit(span) + const side = scale([-u[1], u[0]], (wall.thickness ?? 0.15) / 2 + 0.05) + const count = Math.max(2, Math.ceil(length / 0.05) + 1) + const inside = (s: number) => { + const at = add(start, scale(u, s)) + return containsPoint(area, add(at, side)) && containsPoint(area, sub(at, side)) + } + const runs: Array<[P, P]> = [] + let from: number | null = null + for (let k = 0; k < count; k++) { + const s = (length * k) / (count - 1) + if (inside(s)) from ??= s + else if (from !== null) { + if (s - from > 0.1) runs.push([add(start, scale(u, from)), add(start, scale(u, s))]) + from = null + } + } + if (from !== null && length - from > 0.1) + runs.push([add(start, scale(u, from)), add(start, scale(u, length))]) + return runs +} + +/** + * The walls standing over `from` to `to` along a stem's centreline `edge`: in line with it, within + * their half thickness and the stem's, overlapping the stretch. + */ +function wallsAlong(walls: readonly WallNode[], edge: OutlineEdge, from: number, to: number) { + const u = unit(sub(edge.b, edge.a)) + const along = (p: P) => (p[0] - edge.a[0]) * u[0] + (p[1] - edge.a[1]) * u[1] + const across = (p: P) => Math.abs(cross(u, sub(p, edge.a))) + return walls.filter((wall) => { + const start = wall.start as P + const end = wall.end as P + if (Math.abs(cross(u, unit(sub(end, start)))) > 0.05) return false + if (across(scale(add(start, end), 0.5)) > (wall.thickness ?? 0.15) / 2 + 0.15) return false + const [a, b] = [along(start), along(end)] + return Math.min(to, Math.max(a, b)) - Math.max(from, Math.min(a, b)) > 1e-3 + }) +} + +/** + * The doors in `wall` (a garage's overhead door, a man door), as stretches `s0` to `s1` along the + * stem's centreline `edge`, within `from` to `to`, in order. + */ +function doorSpansAlong( + nodes: Nodes, + wall: WallNode, + edge: OutlineEdge, + from: number, + to: number, +): Array<[number, number]> { + const u = unit(sub(edge.b, edge.a)) + const dir = unit(sub(wall.end as P, wall.start as P)) + const along = (p: P) => (p[0] - edge.a[0]) * u[0] + (p[1] - edge.a[1]) * u[1] + const spans: Array<[number, number]> = [] + for (const node of Object.values(nodes)) { + if (node.type !== 'door' || node.parentId !== wall.id) continue + const centre = node.position[0] + const a = along(add(wall.start as P, scale(dir, centre - node.width / 2))) + const b = along(add(wall.start as P, scale(dir, centre + node.width / 2))) + const s0 = Math.max(from, Math.min(a, b)) + const s1 = Math.min(to, Math.max(a, b)) + if (s1 - s0 > 1e-3) spans.push([s0, s1]) + } + return spans.sort((p, q) => p[0] - q[0]) +} + +/** + * The building's designed foundation (`metadata.foundation`, switched on), under the exterior walls + * of `level`, sized as the record says and set to the ground as it lies: + * + * - Slab on grade: the slab under the outline on its building pad, the finished grade held + * `topAboveGrade` under the slab's top all round (`pads`: low ground filled, high ground cut), its + * edge turned down `edgeDepth` under that grade, one level all round (a slab never steps), + * `edgeWidth` wide at the bottom, the inside face plumb from there (square to the bottom), then + * in at 45° to the slab's underside (`slabEdgeTaper`: half the depth under it, 6 to 12 in). + * - Raised floor: a stem wall flush with the walls' faces from its footing up to the floor framing + * (the floor top less `floorFramingDepth`); the footing centred under it, its bottom `footingDepth` + * under the grade outside and as far under the crawl space's dirt, its top level. A side stays one + * level run while its stem stands no more than `maxStem` out of the ground; past that its footing + * steps down the slope, and where a step's stem would still stand over `maxStem` its top drops in + * whole steps to `topAboveGrade` over the ground there, a wood cripple wall over it up to the floor + * framing (or one level all round, unstepped). The crawl space is dug out to `crawlClearance` + * under the joists, and the ground outside cut down to `topAboveGrade` under the stem's top + * (`pads`). + * - Piles: a grade beam in place of the footing and stem (or the slab's edge), its bottom as deep + * as a footing's, on piles at every corner and no further apart than `pileSpacing`. + * + * Grade is read along the outline where it meets the ground; the deepest need sets the datum the + * steps are counted from, so flat ground leaves every bottom exactly at its depth. The outline is + * the walls' outside faces (`foundationOutlines`), or `drawn` (the building's `foundation` nodes). + */ +export function designedFoundation( + nodes: Nodes, + building: BuildingNode, + level: LevelNode, + walls: WallNode[], + foundation: BuildingFoundation, + gradeAt: (x: number, z: number) => number, + drawn?: FoundationOutline[], + options: DesignedOutlineOptions = {}, +): DesignedFoundation | null { + const outlines = drawn ?? foundationOutlines(walls) + if (outlines.length === 0) return null + const planeY = levelPlaneWorldY(nodes, level.id) ?? building.position?.[1] ?? 0 + const drop = Math.max(0, options.drop ?? 0) + const floorTopY = (levelFloorWorldY(nodes, level.id) ?? planeY) - drop + // A slab's top is the floor's: a hair under the plates that draw the walking surface, and the + // walking surface itself where no plate lies over it (past the walls, or the plates hidden). + const slabTop = floorTopY - 0.002 + const raised = foundation.type === 'raised' + const piles = foundation.support === 'piles' + const world = (p: P): P => levelLocalToWorldXZ(building, p[0], p[1]) + const gradeOf = (p: P) => { + const [x, z] = world(p) + return gradeAt(x, z) + } + + const { highestGradeY, lowestGradeY } = gradeAlong(outlines, gradeOf) + + const members: FoundationMember[] = [] + const rimBottoms = new Map() + const rimSteps = new Map() + const base = { levelId: level.id, material: 'concrete' as const, designed: true as const } + const stemTopY = raised ? floorTopY - inches(foundation.floorFramingDepthIn) : null + // The top of the perimeter's concrete: under the floor framing, or (a slab) the floor top. + const perimeterTop = stemTopY ?? floorTopY - 0.001 + // The level's floors lower than the slab's top (a garage a step down, a sunken room, a recessed + // basin), world XZ: the concrete under them drops to their underside. + const lowFloors: { polygon: P[]; y: number }[] = [] + if (!raised && !options.ownTop) + for (const id of level.children) { + const plate = nodes[id as AnyNodeId] + if (plate?.type !== 'slab' || isSiteFlatwork(plate) || plate.polygon.length < 3) continue + const top = planeY + plate.elevation + if (plate.recessed !== true && top >= slabTop - 1e-4) continue + lowFloors.push({ + polygon: plate.polygon.map(([x, z]) => world([x, z])), + y: Math.min(slabTop, top - plate.thickness + 0.001), + }) + } + // A raised house's rooms on a slab of their own (a garage): poured apart from its raised floor. + const slabRooms = + raised && !piles && !options.drop && !options.ownTop + ? slabRoomsOf(nodes, level, planeY, foundation) + : [] + /** The concrete's top at a level-local point: under a lower floor there, else the slab's. */ + const surfaceAt = (p: P) => { + const at = world(p) + let top = slabTop + for (const floor of lowFloors) + if (containsPoint([{ outer: floor.polygon, holes: [] }], at)) top = Math.min(top, floor.y) + return top + } + /** + * The wall standing on a side of the outline: its own (traced from the walls), or one whose + * outside face runs along a drawn side. + */ + const wallOn = (edge: OutlineEdge): WallNode | undefined => { + if (edge.wallId) return walls.find((candidate) => candidate.id === edge.wallId) + const mid = scale(add(edge.a, edge.b), 0.5) + const u = unit(sub(edge.b, edge.a)) + return walls.find((wall) => { + const span = sub(wall.end as P, wall.start as P) + const length = len(span) + if (length < 1e-6 || Math.abs(cross(unit(span), u)) > 0.05) return false + const offset = sub(mid, wall.start as P) + const t = (offset[0] * span[0] + offset[1] * span[1]) / (length * length) + if (t < 0 || t > 1) return false + return ( + len(sub(mid, add(wall.start as P, scale(span, t)))) <= (wall.thickness ?? 0.15) / 2 + 0.03 + ) + }) + } + const depthUnderGrade = inches( + piles ? foundation.footingDepthIn : raised ? foundation.footingDepthIn : foundation.edgeDepthIn, + ) + // A slab stands on its building pad: the finished grade held `topAboveGrade` under its top all + // round (low ground filled, high ground cut), so its edge or grade beam is one level all round. + const padY = raised ? null : floorTopY - inches(foundation.topAboveGradeIn) + // A raised floor's crawl space, dug out under the joists (they bear on a sill on the stem). The + // dug-out dirt is the ground inside: the footing goes as far under it as under the grade outside + // (IRC R403.1.4), so where the dig is the lower its bottom drops to stay that deep. + const crawlY = stemTopY === null ? null : stemTopY + SILL - inches(foundation.crawlClearanceIn) + const needOf = (grade: number) => + (crawlY === null ? grade : Math.min(grade, crawlY)) - depthUnderGrade + const datum = padY !== null ? padY - depthUnderGrade : needOf(lowestGradeY) + let bottomY = Number.POSITIVE_INFINITY + let steps = 0 + let tallestStep = 0 + let cripples = 0 + let tallestCripple = 0 + const pileAt: P[] = [] + const pads: GradingPad[] = [] + // The walls a slab room shares with the house, each on one stem however many rooms it bounds. + const sharedStems = new Set() + const sharedStemTops = new Map() + + /** Level pieces along one side of `outline` (possibly inset), bottoms no higher than its grade less the depth. */ + const piecesOf = (edge: OutlineEdge, depth: number) => { + const span = sub(edge.b, edge.a) + const length = len(span) + if (padY !== null) + return { length, pieces: [{ s0: 0, s1: length, level: padY - depth }], u: unit(span) } + const pieces = piecesAlong( + length, + (s) => gradeOf(add(edge.a, scale(span, s / length))) - depth, + lowestGradeY - depth, + foundation, + ) + countSteps(pieces) + return { length, pieces, u: unit(span) } + } + + const countSteps = (pieces: Piece[]) => { + steps = Math.max(steps, pieces.length - 1) + for (let k = 1; k < pieces.length; k++) + tallestStep = Math.max(tallestStep, Math.abs(pieces[k]!.level - pieces[k - 1]!.level)) + } + + /** + * A raised floor's side (the stem's centreline) in level pieces, each with its stem's top: one + * level run, its footing at the deepest need along it, while the stem stands no more than + * `maxStem` out of the ground; past that the footing steps down the slope (`piecesAlong`), and + * where a piece's stem would still stand over `maxStem` its top drops in whole steps to + * `topAboveGrade` over the piece's highest ground. + */ + const raisedPiecesOf = (edge: OutlineEdge): { pieces: RaisedPiece[]; u: P } => { + const span = sub(edge.b, edge.a) + const length = len(span) + const u = unit(span) + const full = stemTopY! + const gradeAlong = (s: number) => gradeOf(add(edge.a, scale(span, s / length))) + const count = Math.max(2, Math.ceil(length / GRADE_STEP) + 1) + const samples = Array.from({ length: count }, (_, k) => { + const s = (length * k) / (count - 1) + return { s, grade: gradeAlong(s) } + }) + const lowest = Math.min(...samples.map((sample) => sample.grade)) + if (!foundation.stepped) + return { pieces: [{ s0: 0, s1: length, level: datum, stemTop: full }], u } + const step = inches(foundation.stepHeightIn) + const maxStem = inches(foundation.maxStemIn) + if (full - lowest <= maxStem + 1e-9) { + // One level run: its bottom on the step at or under the side's deepest need. + const need = needOf(lowest) + const level = datum + Math.max(0, Math.floor((need - datum) / step + 1e-9)) * step + return { pieces: [{ s0: 0, s1: length, level, stemTop: full }], u } + } + const pieces = piecesAlong(length, (s) => needOf(gradeAlong(s)), datum, foundation) + countSteps(pieces) + const top = inches(foundation.topAboveGradeIn) + return { + pieces: pieces.map((piece) => { + const over = samples.filter( + (sample) => sample.s >= piece.s0 - 1e-9 && sample.s <= piece.s1 + 1e-9, + ) + const grades = + over.length > 0 + ? over.map((sample) => sample.grade) + : [gradeAlong((piece.s0 + piece.s1) / 2)] + if (full - Math.min(...grades) <= maxStem + 1e-9) return { ...piece, stemTop: full } + const drop = + Math.max(0, Math.floor((full - (Math.max(...grades) + top)) / step + 1e-9)) * step + return { ...piece, stemTop: full - drop } + }), + u, + } + } + + /** The world run of `s0..s1` along `edge`, carried `before` and `after` metres past its ends. */ + const runOf = ( + edge: OutlineEdge, + u: P, + s0: number, + s1: number, + before: number, + after: number, + ) => ({ + from: world(add(edge.a, scale(u, s0 - before))), + to: world(add(edge.a, scale(u, s1 + after))), + }) + + for (const outline of outlines) { + const count = outline.edges.length + // A side is carried past an inside corner (where its neighbour's band would leave a notch), by + // its own band's width; at a corner of the house it stops on the corner. + const reach = (i: number, width: number) => ({ + before: outline.convex[i] ? 0 : width, + after: outline.convex[(i + 1) % count] ? 0 : width, + }) + + if (piles) { + const width = inches(foundation.gradeBeamWidthIn) + const centre = insetOutline(outline, width / 2) + const beamTop = stemTopY ?? floorTopY - 0.001 + const beamDepth = inches(foundation.gradeBeamDepthIn) + const spacing = inches(foundation.pileSpacingIn) + centre.edges.forEach((edge, i) => { + const { length, pieces, u } = piecesOf(edge, depthUnderGrade) + const ends = { before: width / 2, after: width / 2 } + for (const [k, piece] of pieces.entries()) { + // The piles carry it, so it needs no depth under the crawl space's dirt; it still goes + // down to that dirt, or the dig inside would undercut it. + const bottom = Math.min( + beamTop - beamDepth, + piece.level, + crawlY ?? Number.POSITIVE_INFINITY, + ) + bottomY = Math.min(bottomY, bottom) + members.push({ + ...base, + kind: 'grade-beam', + ...runOf( + edge, + u, + piece.s0, + piece.s1, + k === 0 ? ends.before : 0, + k === pieces.length - 1 ? ends.after : 0, + ), + width, + bottomY: bottom, + topY: beamTop, + }) + } + const bays = Math.max(1, Math.ceil(length / spacing - 1e-9)) + for (let k = 0; k < bays; k++) { + const s = (length * k) / bays + const at = add(edge.a, scale(u, s)) + if (pileAt.some((other) => len(sub(other, at)) < width)) continue + pileAt.push(at) + const piece = + pieces.find((candidate) => s <= candidate.s1 + 1e-9) ?? pieces[pieces.length - 1]! + const top = Math.min(beamTop - beamDepth, piece.level) + const bottom = top - inches(foundation.pileLengthIn) + bottomY = Math.min(bottomY, bottom) + const [x, z] = world(at) + members.push({ + ...base, + kind: 'pile', + from: [x, z], + to: [x, z], + width: inches(foundation.pileDiameterIn), + bottomY: bottom, + topY: top, + }) + } + }) + } else if (raised) { + const stemWidth = inches(foundation.stemThicknessIn) + const footingWidth = Math.max(stemWidth, inches(foundation.footingWidthIn)) + const footingThickness = inches(foundation.footingThicknessIn) + const centre = insetOutline(outline, stemWidth / 2) + const outlineArea = [{ outer: outline.edges.map((edge) => world(edge.a)), holes: [] }] + /** + * A slab room's side, `s0` to `s1` along `edge` (the stem's centreline): a curb standing + * `SLAB_ROOM_CURB` over the room's slab, on a footing its depth under the ground the room's + * slab holds; at each door in the wall over it the curb is cut down to the slab, which runs + * through to the outside face. The wall's finish runs down to the curb's top. + */ + const pourCurb = (edge: OutlineEdge, s0: number, s1: number, room: SlabRoom) => { + const length = len(sub(edge.b, edge.a)) + const u = unit(sub(edge.b, edge.a)) + const inward: P = [-u[1], u[0]] + const first = s0 < 1e-9 + const last = s1 > length - 1e-9 + const footingBottom = room.padY - depthUnderGrade + const footingTop = footingBottom + footingThickness + const curbTop = room.slabTop + SLAB_ROOM_CURB + bottomY = Math.min(bottomY, footingBottom) + members.push({ + ...base, + kind: 'footing', + ...runOf(edge, u, s0, s1, first ? footingWidth / 2 : 0, last ? footingWidth / 2 : 0), + width: footingWidth, + bottomY: footingBottom, + topY: footingTop, + }) + // Every wall standing on this stretch (walls in line share one side of the outline). + const over = wallsAlong(walls, edge, s0, s1) + const doors = over + .flatMap((wall) => doorSpansAlong(nodes, wall, edge, s0, s1)) + .sort((p, q) => p[0] - q[0]) + let from = s0 + for (const [d0, d1] of [...doors, [s1, s1] as [number, number]]) { + if (d0 - from > 1e-3) + members.push({ + ...base, + kind: 'stem', + ...runOf( + edge, + u, + from, + d0, + from === s0 && first ? stemWidth / 2 : 0, + d0 === s1 && last ? stemWidth / 2 : 0, + ), + width: stemWidth, + bottomY: footingTop, + topY: curbTop, + }) + if (d1 - d0 > 1e-3) { + // The door's gap: the slab runs out through it, at its own top, down to the footing. + const at = (s: number, across: number) => + world(add(add(edge.a, scale(u, s)), scale(inward, across))) + const gap = [ + at(d0, -stemWidth / 2), + at(d1, -stemWidth / 2), + at(d1, stemWidth / 2), + at(d0, stemWidth / 2), + ] + members.push({ + ...base, + kind: 'slab', + from: gap[0]!, + to: gap[0]!, + width: 0, + bottomY: footingTop, + topY: room.slabTop - 0.002, + outline: gap, + }) + } + from = Math.max(from, d1) + } + for (const wall of over) { + const steps = rimSteps.get(wall.id) ?? [] + steps.push({ + a: add(edge.a, scale(u, s0 - (first ? stemWidth / 2 : 0))), + b: add(edge.a, scale(u, s1 + (last ? stemWidth / 2 : 0))), + bottom: curbTop - planeY, + }) + rimSteps.set(wall.id, steps) + } + } + centre.edges.forEach((edge) => { + const length = len(sub(edge.b, edge.a)) + for (const span of slabSpansOf(edge, slabRooms)) { + if (span.room) { + pourCurb(edge, span.s0, span.s1, span.room) + continue + } + // A side partly along a slab room: its raised stretch stands as a side of its own. + const whole = span.s0 < 1e-9 && span.s1 > length - 1e-9 + const along = unit(sub(edge.b, edge.a)) + const part = whole + ? edge + : { + a: add(edge.a, scale(along, span.s0)), + b: add(edge.a, scale(along, span.s1)), + wallId: edge.wallId, + } + const { pieces: own } = raisedPiecesOf(part) + const u = along + const pieces = own.map((piece) => ({ + ...piece, + s0: piece.s0 + span.s0, + s1: piece.s1 + span.s0, + })) + for (const [k, piece] of pieces.entries()) { + const first = k === 0 && span.s0 < 1e-9 + const last = k === pieces.length - 1 && span.s1 > length - 1e-9 + const footingTop = Math.min(piece.level + footingThickness, piece.stemTop - 0.01) + const footingBottom = Math.min(piece.level, footingTop - footingThickness) + bottomY = Math.min(bottomY, footingBottom) + members.push({ + ...base, + kind: 'footing', + ...runOf( + edge, + u, + piece.s0, + piece.s1, + first ? footingWidth / 2 : 0, + last ? footingWidth / 2 : 0, + ), + width: footingWidth, + bottomY: footingBottom, + topY: footingTop, + }) + members.push({ + ...base, + kind: 'stem', + ...runOf( + edge, + u, + piece.s0, + piece.s1, + first ? stemWidth / 2 : 0, + last ? stemWidth / 2 : 0, + ), + width: stemWidth, + bottomY: footingTop, + topY: piece.stemTop, + }) + const cripple = stemTopY! - piece.stemTop + if (cripple > 1e-4) { + cripples += 1 + tallestCripple = Math.max(tallestCripple, cripple) + // The wall over it carries its finish down over the cripple wall to the stem. + const wall = wallOn(edge) + if (wall) { + const steps = rimSteps.get(wall.id) ?? [] + steps.push({ + a: add(edge.a, scale(u, piece.s0 - (first ? stemWidth / 2 : 0))), + b: add(edge.a, scale(u, piece.s1 + (last ? stemWidth / 2 : 0))), + bottom: piece.stemTop - planeY, + }) + rimSteps.set(wall.id, steps) + } + members.push({ + ...base, + material: 'wood', + kind: 'cripple', + ...runOf( + edge, + u, + piece.s0, + piece.s1, + first ? stemWidth / 2 : 0, + last ? stemWidth / 2 : 0, + ), + width: stemWidth, + bottomY: piece.stemTop, + topY: stemTopY!, + }) + } + } + } + }) + // A slab room's slab between its curbs, and the walls it shares with the house on the + // house's stem, their full height standing over the room's slab. + for (const room of slabRooms) { + if (!room.region.edges.some((edge) => containsPoint(outlineArea, world(edge.a)))) continue + const ring = insetOutline(room.region, stemWidth).edges.map((edge) => world(edge.a)) + members.push({ + ...base, + kind: 'slab', + from: ring[0]!, + to: ring[0]!, + width: 0, + bottomY: room.slabTop - inches(foundation.slabThicknessIn), + topY: room.slabTop - 0.002, + outline: ring, + }) + for (const wall of room.shared) { + if (sharedStems.has(wall.id)) continue + sharedStems.add(wall.id) + const footingBottom = Math.min(crawlY ?? room.padY, room.padY) - depthUnderGrade + const footingTop = footingBottom + footingThickness + // Only where the wall stands inside the house: a stretch of it on the outline (the + // house's wall running on past the room) is on the outline's own stem. + for (const [start, end] of insideRuns(wall, outline)) { + const u = unit(sub(end, start)) + bottomY = Math.min(bottomY, footingBottom) + sharedStemTops.set(wall.id, stemTopY! - planeY) + members.push( + { + ...base, + kind: 'footing', + from: world(add(start, scale(u, -footingWidth / 2))), + to: world(add(end, scale(u, footingWidth / 2))), + width: footingWidth, + bottomY: footingBottom, + topY: footingTop, + }, + { + ...base, + kind: 'stem', + from: world(add(start, scale(u, -stemWidth / 2))), + to: world(add(end, scale(u, stemWidth / 2))), + width: stemWidth, + bottomY: footingTop, + topY: stemTopY!, + }, + ) + } + } + } + } else { + // The slab's edge, in section: out at the outline from its top down to the step's bottom, + // along it the edge's width, the inside face plumb back up, then in at 45° to the slab's + // underside (a deeper edge grows plumb, not wider). Its top is the slab's beside it; where a + // wall stands on the side, the floor top under the wall (a curb, over a floor a step down). + const edgeWidth = inches(foundation.edgeWidthIn) + const slabUnderside = floorTopY - inches(foundation.slabThicknessIn) + const taperAsSet = foundation.edgeTaperIn === null ? null : inches(foundation.edgeTaperIn) + outline.edges.forEach((edge, i) => { + const wall = wallOn(edge) + const lip = wall ? Math.max(0.05, (wall.thickness ?? 0.15) - FACE_CLEARANCE * 2) : 0 + const inward: P = [-unit(sub(edge.b, edge.a))[1], unit(sub(edge.b, edge.a))[0]] + const { pieces, u } = piecesOf(edge, depthUnderGrade) + for (const [k, piece] of pieces.entries()) { + const mid = add(edge.a, scale(u, (piece.s0 + piece.s1) / 2)) + const insideTop = surfaceAt(add(mid, scale(inward, lip + 0.05))) + const outsideTop = wall ? Math.max(perimeterTop, insideTop) : insideTop + const bottom = Math.min(piece.level, slabUnderside - 0.01, insideTop - 0.01) + bottomY = Math.min(bottomY, bottom) + const rise = Math.min(slabUnderside, insideTop) - bottom + const taper = slabEdgeTaper(rise, taperAsSet) + const top = edgeWidth + taper + const lipWidth = Math.min(lip, top) + const band = { before: 0, after: 0 } + const corners = reach(i, top) + if (k === 0) band.before = corners.before + if (k === pieces.length - 1) band.after = corners.after + const section: Array<[number, number]> = [ + [0, bottom], + [edgeWidth, bottom], + ...(rise - taper > 1e-6 + ? ([[edgeWidth, bottom + rise - taper]] as Array<[number, number]>) + : []), + [top, bottom + rise], + ...(insideTop > bottom + rise + 1e-6 + ? ([[top, insideTop]] as Array<[number, number]>) + : []), + ...(outsideTop > insideTop + 1e-6 + ? ([ + [lipWidth, insideTop], + [lipWidth, outsideTop], + ] as Array<[number, number]>) + : []), + [0, outsideTop], + ] + members.push({ + ...base, + kind: 'turndown', + ...runOf(edge, u, piece.s0, piece.s1, band.before, band.after), + width: top, + bottomY: bottom, + topY: outsideTop, + section, + }) + } + }) + } + if (!raised) { + const thickness = inches(foundation.slabThicknessIn) + const slab = (polygon: P[], holes: P[][], top: number, bottom: number) => { + if (polygon.length < 3) return + members.push({ + ...base, + kind: 'slab', + from: polygon[0]!, + to: polygon[0]!, + width: 0, + bottomY: bottom, + topY: top, + outline: polygon, + ...(holes.length > 0 ? { holes } : {}), + }) + } + // A hair inside the edge's outer face, which stands on the same line. + const ring = insetOutline(outline, 0.01).edges.map((edge) => world(edge.a)) + if (lowFloors.length === 0) slab(ring, [], slabTop, floorTopY - thickness) + else { + // At the floor's top round the lower floors, under each of them at its underside. + const low = union(lowFloors.map((floor) => floor.polygon)) + for (const piece of difference(ring, low)) + slab(openRing(piece.outer), piece.holes.map(openRing), slabTop, floorTopY - thickness) + for (const floor of lowFloors) + for (const piece of intersection(ring, floor.polygon)) + slab(openRing(piece.outer), piece.holes.map(openRing), floor.y, floor.y - thickness) + } + } + if (padY !== null) { + pads.push({ outline: outline.edges.map((edge) => world(edge.a)), y: padY }) + // The ground stays under the concrete: dug out under a slab thicker than its pad leaves room + // for, and under each floor set down in it (a garage a step down) to that floor's underside. + const thickness = inches(foundation.slabThicknessIn) + const under = [ + { polygon: outline.edges.map((edge) => world(edge.a)), y: floorTopY - thickness }, + ...lowFloors.map((floor) => ({ polygon: floor.polygon, y: floor.y - thickness })), + ] + for (const { polygon, y } of under) + if (y < padY - 1e-4) pads.push({ outline: polygon, y, kind: 'pit' }) + } + if (stemTopY !== null && crawlY !== null) { + // Round a raised floor the ground is cut down to hold the stem's top `topAboveGrade` over it + // (never filled: the floor stands over the slope); inside its perimeter the crawl is dug out. + // A slab room in it is graded as a slab is, the ground held its exposure under its top, and + // no crawl space is dug under it. + const perimeter = inches(piles ? foundation.gradeBeamWidthIn : foundation.stemThicknessIn) + const area = [{ outer: outline.edges.map((edge) => world(edge.a)), holes: [] }] + const rooms = slabRooms + .map((room) => ({ room, ring: room.region.edges.map((edge) => world(edge.a)) })) + .filter(({ ring }) => ring.some((point) => containsPoint(area, point))) + const without = (ring: P[]): P[][] => + rooms.length === 0 + ? [ring] + : difference(ring, union(rooms.map((entry) => entry.ring))).map((piece) => + openRing(piece.outer), + ) + for (const ring of without(area[0]!.outer)) + pads.push({ outline: ring, y: stemTopY - inches(foundation.topAboveGradeIn), kind: 'cut' }) + for (const ring of without( + insetOutline(outline, perimeter).edges.map((edge) => world(edge.a)), + )) + pads.push({ outline: ring, y: crawlY, kind: 'pit' }) + for (const { room, ring } of rooms) pads.push({ outline: ring, y: room.padY }) + } + } + if (stemTopY !== null) for (const wall of walls) rimBottoms.set(wall.id, stemTopY - planeY) + + return { + members, + rimBottoms, + rimSteps, + sharedStemTops, + pads, + report: { + buildingId: building.id, + levelId: level.id, + foundation, + floorTopY, + highestGradeY, + lowestGradeY, + stemTopY, + bottomY: Number.isFinite(bottomY) ? bottomY : datum, + steps, + tallestStep, + piles: pileAt.length, + cripples, + tallestCripple, + crawlY, + }, + } +} + +/** A house over a basement, as {@link designedBasement} cuts it into the ground. */ +export type DesignedBasementOptions = { + /** The basement: the building's lowest level, under its ground floor. */ + basement: LevelNode + /** The ground floor over it, whose floor framing bears on the basement walls. */ + ground: LevelNode + /** The level the outlines were read from: the basement's with its own walls, else the ground floor's. */ + outlineLevelId: string + /** + * The basement's own walls, where they close an outline: they are its foundation walls, standing + * on their (generic) `footings`, its floor its own plates. Absent, the walls are poured. + */ + own?: { footings: FoundationMember[] } +} + +/** + * Where the floor over a basement bears on its walls, world Y: under the joists of the ground + * floor's plates (flooring, subfloor and joists; a ceiling hangs under them, inside the basement) + * by the sill. A ground floor with no plate bears on them under its plane. + */ +export function basementWallTopY(nodes: Nodes, ground: LevelNode): number { + const plane = levelPlaneWorldY(nodes, ground.id) ?? 0 + let underJoists = Number.POSITIVE_INFINITY + for (const id of ground.children) { + const plate = nodes[id as AnyNodeId] + if (plate?.type !== 'slab' || plate.plateRole !== 'base' || plate.recessed === true) continue + if (isSiteFlatwork(plate) || plate.polygon.length < 3) continue + const ceiling = plate.buildUp?.ceiling?.thickness ?? 0 + underJoists = Math.min(underJoists, plane + plate.elevation - plate.thickness + ceiling) + } + return (Number.isFinite(underJoists) ? underJoists : plane) - SILL +} + +/** + * A house over a basement (`basement`, under its `ground` floor), cut into the ground: nothing is + * filled or cut round it. Inside its outlines, the basement walls' outside faces, the ground is dug + * out down to the slab's underside and cut plumb along them (`pads`: an `excavation` per outline), + * so no earth stands in the basement or through its walls and slab; outside them the natural ground + * meets the walls where it lies, a downhill wall standing further out of it (a daylight basement). + * + * - With walls of its own (drawn on its level, closing an outline), those are the foundation walls, + * standing on their footing as the generic foundation lays it (`own.footings`), the basement's + * floor its own plates; the outlines run along their outside faces. + * - Without, its walls are poured under the house's outlines (the ground floor walls' outside + * faces, or the house's drawn outline): `basementWallThickness` of concrete flush with them, from + * a footing (`footingWidth` × `footingThickness`, centred under the wall, its top at the slab's + * underside) up to the floor framing over the basement (`basementWallTopY`), and the slab + * (`slabThickness`) between them, its top the basement's floor. + * + * The house is seated with the walls' top `basementTopAboveGrade` over the highest grade along the + * outlines (`buildBuildingFoundationChanges`), the basement as deep under it as its storey goes. + */ +export function designedBasement( + nodes: Nodes, + building: BuildingNode, + foundation: BuildingFoundation, + gradeAt: (x: number, z: number) => number, + outlines: FoundationOutline[], + { basement, ground, outlineLevelId, own }: DesignedBasementOptions, +): DesignedFoundation | null { + if (outlines.length === 0) return null + const world = (p: P): P => levelLocalToWorldXZ(building, p[0], p[1]) + const plane = levelPlaneWorldY(nodes, basement.id) ?? building.position?.[1] ?? 0 + const floorTopY = levelFloorWorldY(nodes, basement.id) ?? plane + const wallTopY = basementWallTopY(nodes, ground) + const { highestGradeY, lowestGradeY } = gradeAlong(outlines, (p) => { + const [x, z] = world(p) + return gradeAt(x, z) + }) + const members: FoundationMember[] = [] + let excavationY: number + let bottomY: number + if (own) { + members.push(...own.footings) + // Its floor is its own plates: dug out to their underside. + excavationY = levelPlateUndersideWorldY(nodes, basement.id) ?? plane + bottomY = Math.min(excavationY, ...own.footings.map((member) => member.bottomY)) + } else { + const base = { levelId: basement.id, material: 'concrete' as const, designed: true as const } + const wallWidth = inches(foundation.basementWallThicknessIn) + const footingWidth = Math.max(wallWidth, inches(foundation.footingWidthIn)) + const slabBottom = floorTopY - inches(foundation.slabThicknessIn) + const footingBottom = slabBottom - inches(foundation.footingThicknessIn) + excavationY = slabBottom + bottomY = footingBottom + for (const outline of outlines) { + // Along the walls' centreline, each run carried half its width past both ends so the + // corners close. + for (const edge of insetOutline(outline, wallWidth / 2).edges) { + const span = sub(edge.b, edge.a) + const length = len(span) + if (length < 1e-6) continue + const u = unit(span) + const run = (reach: number) => ({ + from: world(add(edge.a, scale(u, -reach))), + to: world(add(edge.a, scale(u, length + reach))), + }) + members.push({ + ...base, + kind: 'footing', + ...run(footingWidth / 2), + width: footingWidth, + bottomY: footingBottom, + topY: slabBottom, + }) + if (wallTopY > slabBottom + 1e-4) + members.push({ + ...base, + kind: 'stem', + ...run(wallWidth / 2), + width: wallWidth, + bottomY: slabBottom, + topY: wallTopY, + }) + } + // The slab between the walls, a hair under the plates that draw the basement's floor. + const ring = insetOutline(outline, wallWidth).edges.map((edge) => world(edge.a)) + if (ring.length >= 3) + members.push({ + ...base, + kind: 'slab', + from: ring[0]!, + to: ring[0]!, + width: 0, + bottomY: slabBottom, + topY: floorTopY - 0.002, + outline: ring, + }) + } + } + return { + members, + rimBottoms: new Map(), + rimSteps: new Map(), + sharedStemTops: new Map(), + pads: outlines.map((outline) => ({ + outline: outline.edges.map((edge) => world(edge.a)), + y: excavationY, + kind: 'excavation' as const, + })), + report: { + buildingId: building.id, + levelId: basement.id, + foundation, + floorTopY, + highestGradeY, + lowestGradeY, + stemTopY: wallTopY, + bottomY, + steps: 0, + tallestStep: 0, + piles: 0, + cripples: 0, + tallestCripple: 0, + crawlY: null, + basement: { + groundLevelId: ground.id, + outlineLevelId, + ownWalls: own !== undefined, + wallTopY, + groundFloorTopY: levelFloorWorldY(nodes, ground.id) ?? wallTopY + SILL, + excavationY, + }, + }, + } +} diff --git a/packages/core/src/lib/exterior-wall-loops.ts b/packages/core/src/lib/exterior-wall-loops.ts index b90e717e7b..e1bb679890 100644 --- a/packages/core/src/lib/exterior-wall-loops.ts +++ b/packages/core/src/lib/exterior-wall-loops.ts @@ -1,4 +1,5 @@ import type { WallNode } from '../schema' +import { isRailingWall } from '../schema/nodes/railing-wall' import { getWallCurveFrameAt, isCurvedWall } from '../systems/wall/wall-curve' import { dedupeSequentialPoints, @@ -16,7 +17,9 @@ import { distanceToSegment } from './room-topology-index' * The outside of a storey's walls: the unbounded faces of the planar wall * graph, walked the other way round from rooms. Facades fill these loops and * the wall tool selects them. Kept apart from the room kernel (`room-graph`), - * which only ever wants the bounded faces. + * which only ever wants the bounded faces. A railing closes rooms but holds no + * outside back (a deck's guard), so the loops are traced without railings: the + * outline of a house with a deck is its solid walls'. */ type WallFace = { @@ -403,7 +406,10 @@ export function enclosedWallIds( } function outerFaces(walls: WallNode[], joinTolerance: number) { - const loops = exteriorFaces(walls, Math.max(WALL_ENDPOINT_TOLERANCE, joinTolerance)) + const loops = exteriorFaces( + walls.filter((wall) => !isRailingWall(wall)), + Math.max(WALL_ENDPOINT_TOLERANCE, joinTolerance), + ) return loops.filter( (loop, index) => !loops.some((other, otherIndex) => { diff --git a/packages/core/src/lib/floor-build-up.test.ts b/packages/core/src/lib/floor-build-up.test.ts new file mode 100644 index 0000000000..47a4fb95a0 --- /dev/null +++ b/packages/core/src/lib/floor-build-up.test.ts @@ -0,0 +1,71 @@ +import { expect, test } from 'bun:test' +import { DEFAULT_UPPER_FLOOR_THICKNESS, FloorBuildUp, SlabNode } from '../schema' +import { + DEFAULT_FLOOR_BUILD_UP, + DEFAULT_RAISED_FLOOR_BUILD_UP, + FLOOR_BUILD_UP_PRESETS, + floorBuildUpMatches, + floorBuildUpOf, + floorBuildUpPatch, + floorBuildUpThickness, + floorLayerDepths, +} from './build-up' + +const IN = 0.0254 + +test('the framed floor: ¾ in hardwood on ⅝ in plywood, 2x12 joists 16 in on centre, ½ in drywall, 13⅛ in', () => { + expect(floorLayerDepths(DEFAULT_FLOOR_BUILD_UP)).toEqual({ + flooring: expect.closeTo(0.75 * IN, 9), + sheathing: expect.closeTo(0.625 * IN, 9), + joists: expect.closeTo(11.25 * IN, 9), + ceiling: expect.closeTo(0.5 * IN, 9), + }) + expect(DEFAULT_FLOOR_BUILD_UP.structure).toEqual({ + system: 'joists', + size: '2x12', + spacing: 16 * IN, + }) + expect(floorBuildUpThickness(DEFAULT_FLOOR_BUILD_UP)).toBeCloseTo( + DEFAULT_UPPER_FLOOR_THICKNESS, + 6, + ) + // Over a crawl space nothing hangs under the joists. + expect(DEFAULT_RAISED_FLOOR_BUILD_UP.ceiling).toBeUndefined() + expect(floorBuildUpThickness(DEFAULT_RAISED_FLOOR_BUILD_UP)).toBeCloseTo(12.625 * IN, 6) +}) + +test('every preset parses, and its thickness is its layers', () => { + for (const preset of FLOOR_BUILD_UP_PRESETS) { + expect(FloorBuildUp.parse(preset.buildUp)).toEqual(preset.buildUp) + expect(preset.buildUp.presetId).toBe(preset.id) + } + const ijoist = FLOOR_BUILD_UP_PRESETS.find( + (preset) => preset.buildUp.structure.system === 'i-joists', + )! + expect(floorLayerDepths(ijoist.buildUp).joists).toBeCloseTo(11.875 * IN, 9) +}) + +test('joists take a lumber size and I-joists a depth', () => { + expect( + FloorBuildUp.safeParse({ structure: { system: 'joists', size: '11-7/8', spacing: 0.4 } }) + .success, + ).toBe(false) + expect( + FloorBuildUp.safeParse({ structure: { system: 'i-joists', size: '2x10', spacing: 0.4 } }) + .success, + ).toBe(false) +}) + +test('a layer taken out is off; a named layer keeps its name; the plate patch carries both together', () => { + const { ceiling: _, ...exposed } = DEFAULT_FLOOR_BUILD_UP + const named = { ...exposed, flooring: { ...exposed.flooring!, name: 'Brazilian cherry' } } + const patch = floorBuildUpPatch(named) + expect(patch.thickness).toBeCloseTo(12.625 * IN, 6) + const plate = SlabNode.parse({ polygon: [], ...patch }) + expect(floorBuildUpOf(plate).flooring?.name).toBe('Brazilian cherry') + expect(floorBuildUpMatches(plate)).toBe(true) + // A plate without layers reads the default; its own 14 in thickness no longer matches it. + const older = SlabNode.parse({ polygon: [], thickness: 14 * IN }) + expect(floorBuildUpOf(older)).toBe(DEFAULT_FLOOR_BUILD_UP) + expect(floorBuildUpMatches(older)).toBe(false) +}) diff --git a/packages/core/src/lib/floor-foundation-datum.ts b/packages/core/src/lib/floor-foundation-datum.ts index 988a6c31df..fe38256b0e 100644 --- a/packages/core/src/lib/floor-foundation-datum.ts +++ b/packages/core/src/lib/floor-foundation-datum.ts @@ -1,11 +1,20 @@ -import type { AnyNode, SlabNode } from '../schema' +import type { AnyNode, BuildingNode, SlabNode } from '../schema' import { DEFAULT_SLAB_ELEVATION, MIN_SLAB_THICKNESS } from '../schema/nodes/slab' +import { BELOW_GRADE_TOLERANCE, getAuthoredLevelElevations } from '../services/storey' import { wallOverlapsSlabFootprint } from '../systems/slab/slab-support' import { getWallArcData } from '../systems/wall/wall-curve' import { wallOpeningBand } from './floor-opening-footprints' import { area, containsPoint, difference, intersection, union } from './polygon-boolean' import { automaticRoomBaseElevations } from './room-floor-feasibility' -import { isLevelAtSiteDatum } from './terrain-support-query' +import { heightAt } from './terrain-field' +import { persistedTerrainFieldOf } from './terrain-source-persisted' +import { + isLevelAtSiteDatum, + isSiteDatum, + levelLocalToWorldXZ, + SITE_DATUM_EPSILON, + SITE_DATUM_Y, +} from './terrain-support-query' const heights = new WeakMap< object, @@ -97,11 +106,74 @@ export function floorPlateAtGroundContact( ): boolean { return ( !!plate.parentId && - isLevelAtSiteDatum(nodes as Record, plate.parentId) && + isGroundStorey(nodes, plate.parentId) && floorFootprintSupportClass(nodes, plate) === 'ground-bearing' ) } +const groundStoreys = new WeakMap>() + +/** + * The storey a building stands on the ground with: the one at the site datum, or the base of the + * stack of a building placed off it. Seating the house on its foundation (or "Set as ground + * floor") moves the building up, or down onto ground lower than the datum, and its ground floor + * still bears on what is under it, not on walls; a storey lifted by its own base elevation stands + * on nothing, and one moved down under the ground is a basement. Authored elevations only: the + * displaced ones are built from this answer. + */ +function isGroundStorey(nodes: Readonly>, levelId: string): boolean { + let ids = groundStoreys.get(nodes) + if (!ids) { + ids = new Set() + for (const [id, elevation] of getAuthoredLevelElevations(nodes)) { + const building = elevation.buildingId ? nodes[elevation.buildingId] : undefined + if ( + isLevelAtSiteDatum(nodes as Record, id) || + (building?.type === 'building' && + isSiteDatum(elevation.baseY) && + (building.position[1] > SITE_DATUM_Y + SITE_DATUM_EPSILON || + !buriedUnderGround(nodes, building, id))) + ) + ids.add(id) + } + groundStoreys.set(nodes, ids) + } + return ids.has(levelId) +} + +/** + * The ground stands over the floor tops of the base of `building`'s stack, past the below-grade + * tolerance, all across its floor plates. The tops, not the bare base: a ground floor raised on its + * own foundation in a house moved down onto lower ground still stands on that ground. + */ +function buriedUnderGround( + nodes: Readonly>, + building: BuildingNode, + levelId: string, +): boolean { + const planeY = building.position[1] + const level = nodes[levelId] + const plates = + level?.type === 'level' + ? level.children.flatMap((childId) => { + const plate = nodes[childId] + return plate?.type === 'slab' && plate.plateRole === 'base' ? [plate] : [] + }) + : [] + const corners = plates.length + ? plates.flatMap((plate) => + plate.polygon.map(([x, z]) => [x, z, plate.floorHeight ?? plate.elevation] as const), + ) + : [[0, 0, 0] as const] + const site = building.parentId ? nodes[building.parentId] : undefined + const field = site?.type === 'site' ? persistedTerrainFieldOf(site) : null + return corners.every(([x, z, top]) => { + if (!field) return SITE_DATUM_Y - (planeY + top) > BELOW_GRADE_TOLERANCE + const [worldX, worldZ] = levelLocalToWorldXZ(building, x, z) + return heightAt(field, worldX, worldZ) - (planeY + top) > BELOW_GRADE_TOLERANCE + }) +} + export function floorPlateHoldsUnderside( nodes: Readonly>, plate: SlabNode, diff --git a/packages/core/src/lib/floor-plates.ts b/packages/core/src/lib/floor-plates.ts index 0e26bb864d..d79100f3ba 100644 --- a/packages/core/src/lib/floor-plates.ts +++ b/packages/core/src/lib/floor-plates.ts @@ -4,6 +4,7 @@ import type { SurfaceHoleMetadata } from '../schema/nodes/surface-hole-metadata' import type { ZoneNode } from '../schema/nodes/zone' import { stairArrivalOpening } from '../systems/stair/stair-footprint' import { omitUndefined } from '../utils/omit-undefined' +import { deckBoardSettings, deckBoardThickness } from './deck-boards' import { automaticFloorHeight, floorPlateHoldsUnderside, @@ -16,8 +17,10 @@ import { exposedIntervals, plateFootprint, roomClearPolygon, + roomRailingFootprints, roomWallFootprints, } from './level-footprints' +import { isOutdoorRoomType, OUTDOOR_ROOM_DEFAULTS } from './outdoor-room' import { area, difference, intersection, type Polygon, union } from './polygon-boolean' import type { TopologyRoom } from './room-topology-index' import { autoRoomVerticalPlacements } from './room-vertical-placement' @@ -427,13 +430,25 @@ export function buildFloorPlates({ const componentOf = (room: PlateRoom) => components.find((component) => component.members.includes(room)) const baseOf = (room: PlateRoom) => componentOf(room)?.elevation ?? DEFAULT_SLAB_ELEVATION - const elevationOf = (room: PlateRoom) => intent.get(room.zone.id) ?? baseOf(room) + // A room stepped down from the floor it shares (a porch, `floor.drop`) follows that floor. + const elevationOf = (room: PlateRoom) => + intent.get(room.zone.id) ?? baseOf(room) - (room.zone.floor?.drop ?? 0) const thresholds = floorOpeningFootprints(grounded, elevationOf, nodes ?? {}) const interiors = new Map( grounded.map((room) => [ room.zone.id, union([ - Math.abs(elevationOf(room) - baseOf(room)) >= 0.001 - 1e-9 ? roomClearPolygon(room) : [], + Math.abs(elevationOf(room) - baseOf(room)) >= 0.001 - 1e-9 + ? // A room on a slab of its own (a garage) takes its walls' footprints with it: the + // house's floor stops at them, never running under its walls or through its doors. + room.zone.floor?.foundation === 'slab' + ? union([...roomClearPolygon(room), ...roomWallFootprints([room])]) + : // A porch or deck a step down takes its railings: they stand on its own floor, and + // the house's floor stops at the house wall rather than ringing it under them. + isOutdoorRoomType(room.zone.roomType) + ? union([...roomClearPolygon(room), ...roomRailingFootprints(room)]) + : roomClearPolygon(room) + : [], ...(thresholds.get(room.zone.id) ?? []), ]), ]), @@ -894,7 +909,7 @@ export function buildFloorPlates({ })) } if (plate.plateRole === 'base') delete (plate as Partial).fillToTerrain - return omitUndefined(plate) + return omitUndefined(withOutdoorFloorTag(plate, group.members)) }) const plateIds = new Set(plates.map((plate) => plate.id)) const retired = existing.filter((slab) => !used.has(slab.id) && !plateIds.has(slab.id)) @@ -911,6 +926,40 @@ export function buildFloorPlates({ return { plates, retired, remap } } +// The framing plugin's slab kinds an outdoor room's plate is tagged with (`OutdoorRoomDefaults`). +const OUTDOOR_FLOOR_TAGS = new Set(['porch-slab', 'deck']) + +/** + * A plate standing alone under one outdoor room (a porch, deck or patio stepped down from the + * house) is tagged for the framing plugin, as a drawn porch slab or deck is: `metadata.floor` + * (and, for a deck, its boards' thickness in `metadata.decking`, `lib/deck-boards`). Any other + * plate loses a tag it was given that way. + */ +function withOutdoorFloorTag(plate: SlabNode, members: readonly PlateRoom[]): SlabNode { + const type = members.length === 1 ? members[0]!.zone.roomType : undefined + const { + floor: tag, + decking: _decking, + ...rest + } = (plate.metadata ?? {}) as Record + const outdoor = plate.plateRole !== 'base' && isOutdoorRoomType(type) + if (!outdoor) { + if (!(typeof tag === 'string' && OUTDOOR_FLOOR_TAGS.has(tag))) return plate + return { ...plate, metadata: rest } + } + const floorTag = OUTDOOR_ROOM_DEFAULTS[type].floorTag + return { + ...plate, + metadata: { + ...rest, + floor: floorTag, + ...(floorTag === 'deck' + ? { decking: deckBoardThickness(deckBoardSettings(members[0]!.zone)) } + : {}), + }, + } +} + const warnedLevels = new Set() export function warnPlateFailure(levelId: string, ids: string[], error: unknown) { if (warnedLevels.has(levelId)) return diff --git a/packages/core/src/lib/footing.test.ts b/packages/core/src/lib/footing.test.ts new file mode 100644 index 0000000000..6b42527bff --- /dev/null +++ b/packages/core/src/lib/footing.test.ts @@ -0,0 +1,210 @@ +import { expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FootingNode, + LevelNode, + SiteNode, + SlabNode, +} from '../schema' +import type { BuildingFoundationRecord } from './building-foundation' +import { footingDesign, footingLevelOf, footingMembers } from './footing' +import type { FoundationMember } from './generic-foundation' +import { FLAT_GROUND_Y } from './site-ground-height' +import { encodeTerrainField } from './terrain-codec' +import { createTerrainField } from './terrain-field' + +const IN = 0.0254 +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes +const ofKind = (members: FoundationMember[], kind: FoundationMember['kind']) => + members.filter((member) => member.kind === kind) + +const STRIP = FootingNode.parse({ + id: 'footing_strip', + parentId: 'level_g', + shape: 'strip', + start: [2, 4], + end: [8, 4], +}) +const PAD = FootingNode.parse({ + id: 'footing_pad', + parentId: 'level_g', + shape: 'pad', + center: [5, 4], + width: 24 * IN, + length: 30 * IN, + thickness: 10 * IN, +}) + +/** A storey whose floor top stands 0.05 m over its plane, the building lifted `y` over the site. */ +function house( + record: BuildingFoundationRecord | null, + footings: FootingNode[], + y = 0, + terrain?: string, +) { + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'], ...(terrain ? { terrain } : {}) }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_g'], + position: [0, y, 0], + ...(record ? { metadata: { foundation: record } } : {}), + }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: ['slab_g', ...footings.map((f) => f.id)], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + elevation: 0.05, + thickness: 0.05, + }), + ...footings, + ]) +} + +test('on a slab a footing thickens the slab: under the record’s slab, or the floor plate as built', () => { + // Switched on, a generator's or no record (the default slab), the record's 4 in slab under the + // floor top; one the panel switched off (it keeps the type it wrote), the plate's own underside. + for (const [record, top] of [ + [{ enabled: true, type: 'slab' as const }, 0.05 - 4 * IN], + [{ type: 'slab' as const, ffAboveGradeIn: 8 }, 0.05 - 4 * IN], + [null, 0.05 - 4 * IN], + [{ enabled: false, type: 'slab' as const, ffAboveGradeIn: 8 }, 0], + ] as const) { + const design = footingDesign(house(record, [STRIP]), STRIP)! + expect(design.foundation.type).toBe('slab') + expect(design.topY).toBeCloseTo(top, 6) + expect(design.bottomY).toBeCloseTo(top - 8 * IN, 6) + expect(design.members.map((m) => m.kind)).toEqual(['footing']) + expect(design.members[0]!.width).toBeCloseTo(12 * IN, 6) + expect(design.members[0]!.from).toEqual([2, 4]) + expect(design.members[0]!.to).toEqual([8, 4]) + } +}) + +test('with the record switched off, a footing under a floor raised on its own foundation carries a stem to its underside', () => { + for (const record of [ + { enabled: false }, + { enabled: false, type: 'slab' as const, ffAboveGradeIn: 8 }, + ]) { + const nodes = house(record, [STRIP, PAD]) + const plate = nodes['slab_g' as AnyNodeId] as SlabNode + // The plate 0.6 m up on its foundation, its top 0.65 m over the plane. + nodes[plate.id] = { ...plate, elevation: 0.65, foundation: { type: 'solid' } } as SlabNode + const strip = footingDesign(nodes, STRIP)! + expect(strip.foundation.type).toBe('raised') + expect(strip.bottomY).toBeCloseTo(FLAT_GROUND_Y - 12 * IN, 6) + expect(ofKind(strip.members, 'stem')[0]!.topY).toBeCloseTo(0.6, 6) + expect(ofKind(footingDesign(nodes, PAD)!.members, 'stem')[0]!.topY).toBeCloseTo(0.6, 6) + } +}) + +test('on a slope a footing under the slab goes down to the ground, or its own depth under it', () => { + // The ground 1 m under the site's datum: the slab stands on fill over it. + const field = createTerrainField({ cols: 33, rows: 33, spacing: 1, origin: [-16, -16] }) + const terrain = encodeTerrainField({ + ...field, + heights: new Int16Array(field.heights).fill(-100), + }) + const nodes = house({ enabled: true, type: 'slab' }, [STRIP], 0, terrain) + const design = footingDesign(nodes, STRIP)! + expect(design.gradeY).toBeCloseTo(-1, 6) + expect(design.topY).toBeCloseTo(0.05 - 4 * IN, 6) + expect(design.bottomY).toBeCloseTo(-1, 6) + const deep = FootingNode.parse({ ...STRIP, depth: 0.3 }) + expect(footingDesign(house({ type: 'slab' }, [deep], 0, terrain), deep)!.bottomY).toBeCloseTo( + -1.3, + 6, + ) +}) + +test('under a raised floor a strip sits its depth under the crawl space dirt and carries a stem to the stem top', () => { + const floorTop = 0.65 + // ¾ in flooring, ⅝ in sheathing, 2x12 joists and the 1½ in sill. + const stemTop = floorTop - 14.125 * IN + // The crawl space dug 18 in under the joists, lower than the ground outside here. + const crawl = stemTop + 1.5 * IN - 18 * IN + const nodes = house({ type: 'raised' }, [STRIP, PAD], 0.6) + const strip = footingDesign(nodes, STRIP)! + // The ground it goes down from is the crawl space's dirt, dug lower than the ground outside here. + expect(crawl).toBeLessThan(FLAT_GROUND_Y) + expect(strip.gradeY).toBeCloseTo(crawl, 6) + expect(strip.bottomY).toBeCloseTo(crawl - 12 * IN, 6) + expect(strip.topY).toBeCloseTo(crawl - 12 * IN + 8 * IN, 6) + const [stem] = ofKind(strip.members, 'stem') + expect(stem!.width).toBeCloseTo(6 * IN, 6) + expect(stem!.bottomY).toBeCloseTo(strip.topY, 6) + expect(stem!.topY).toBeCloseTo(stemTop, 6) + + // A pad carries a square pier, the stem's thickness on a side. + const pad = footingDesign(nodes, PAD)! + const [pier] = ofKind(pad.members, 'stem') + expect(pier!.width).toBeCloseTo(6 * IN, 6) + expect(Math.hypot(pier!.to[0] - pier!.from[0], pier!.to[1] - pier!.from[1])).toBeCloseTo( + 6 * IN, + 6, + ) + expect(pier!.topY).toBeCloseTo(stemTop, 6) +}) + +test("a footing's own depth wins over the building's", () => { + const deep = FootingNode.parse({ ...STRIP, depth: 0.6 }) + const design = footingDesign(house({ type: 'raised' }, [deep], 0.6), deep)! + expect(design.depth).toBe(0.6) + // Its depth under the crawl space's dirt, 18 in under the joists. + const crawl = 0.65 - 14.125 * IN + 1.5 * IN - 18 * IN + expect(design.bottomY).toBeCloseTo(Math.min(FLAT_GROUND_Y, crawl) - 0.6, 6) +}) + +test('on piles a strip is a grade beam with a pile at each end and none further apart than the spacing', () => { + const nodes = house({ type: 'raised', support: 'piles' }, [STRIP, PAD], 0.6) + const strip = footingDesign(nodes, STRIP)! + const [beam] = ofKind(strip.members, 'grade-beam') + expect(beam!.width).toBeCloseTo(12 * IN, 6) + const piles = ofKind(strip.members, 'pile') + // 6 m at 8 ft: three bays. + expect(piles.map((p) => p.from[0])).toEqual([2, 4, 6, 8]) + for (const pile of piles) { + expect(pile.topY).toBeCloseTo(beam!.bottomY, 6) + expect(pile.topY - pile.bottomY).toBeCloseTo(120 * IN, 6) + } + + // A pad is a cap on one pile. + const pad = footingDesign(nodes, PAD)! + expect(pad.members.map((m) => m.kind)).toEqual(['footing', 'pile']) + expect(pad.members[1]!.from).toEqual([5, 4]) +}) + +test('a footing on no building pours nothing', () => { + const loose = FootingNode.parse({ ...STRIP, parentId: 'level_x' }) + expect(footingMembers(keyed([loose]), loose)).toEqual([]) +}) + +test("footings drawn from any storey go on the building's lowest", () => { + const nodes = house(null, []) + const upper = LevelNode.parse({ id: 'level_1', parentId: 'building_a', level: 1 }) + const building = nodes['building_a' as AnyNodeId] as BuildingNode + const scene = keyed([ + ...Object.values(nodes), + { ...building, children: ['level_g', 'level_1'] } as BuildingNode, + upper, + ]) + expect(footingLevelOf(scene, 'level_1')?.id).toBe('level_g') + expect(footingLevelOf(scene, 'level_g')?.id).toBe('level_g') + expect(footingLevelOf(scene, 'level_x')).toBeNull() +}) diff --git a/packages/core/src/lib/footing.ts b/packages/core/src/lib/footing.ts new file mode 100644 index 0000000000..03b8208bc3 --- /dev/null +++ b/packages/core/src/lib/footing.ts @@ -0,0 +1,329 @@ +import type { AnyNode, AnyNodeId, BuildingNode, FootingNode, LevelNode, SlabNode } from '../schema' +import { getLevelElevations, levelFloorWorldY, levelPlaneWorldY } from '../services/storey' +import { + type BuildingFoundation, + buildingFoundationOf, + buildingFoundationRecordOf, + FOUNDATION_SILL_IN, + inches, +} from './building-foundation' +import { groundFloorConstruction } from './floor-foundation-datum' +import { type FoundationMember, siteOf } from './generic-foundation' +import { containsPoint } from './polygon-boolean' +import { groundHeightAt } from './site-ground-height' +import { levelLocalToWorldXZ } from './terrain-support-query' + +type Nodes = Record +type P = [number, number] + +/** Grade is read along a strip this often, metres. */ +const GRADE_STEP = 0.15 +/** A stem or pier shorter than this is the floor framing sitting on the footing: nothing to draw. */ +const MIN_STEM_HEIGHT = 1e-3 + +/** A footing's plan, level-local: a strip's centreline, or a pad's centre, sides and turn. */ +export type FootingPlan = + | { shape: 'strip'; start: P; end: P; width: number } + | { shape: 'pad'; center: P; width: number; length: number; rotation: number } + +export function footingPlan(footing: FootingNode): FootingPlan | null { + if (footing.shape === 'pad') { + if (!footing.center) return null + return { + shape: 'pad', + center: footing.center, + width: footing.width, + length: footing.length ?? footing.width, + rotation: footing.rotation ?? 0, + } + } + if (!(footing.start && footing.end)) return null + return { shape: 'strip', start: footing.start, end: footing.end, width: footing.width } +} + +/** A pad's local x and z directions in the plan, turned by `rotation` about Y as three.js turns them. */ +function padAxes(rotation: number): { x: P; z: P } { + const cos = Math.cos(rotation) + const sin = Math.sin(rotation) + return { x: [cos, -sin], z: [sin, cos] } +} + +/** The footing's outline, level-local, four corners. */ +export function footingOutline(plan: FootingPlan): P[] { + if (plan.shape === 'pad') { + const { x, z } = padAxes(plan.rotation) + const [hx, hz] = [plan.width / 2, plan.length / 2] + return [ + [-1, -1], + [1, -1], + [1, 1], + [-1, 1], + ].map(([sx, sz]) => [ + plan.center[0] + x[0] * hx * sx! + z[0] * hz * sz!, + plan.center[1] + x[1] * hx * sx! + z[1] * hz * sz!, + ]) + } + const dx = plan.end[0] - plan.start[0] + const dz = plan.end[1] - plan.start[1] + const length = Math.hypot(dx, dz) || 1 + const [nx, nz] = [(-dz / length) * (plan.width / 2), (dx / length) * (plan.width / 2)] + return [ + [plan.start[0] + nx, plan.start[1] + nz], + [plan.end[0] + nx, plan.end[1] + nz], + [plan.end[0] - nx, plan.end[1] - nz], + [plan.start[0] - nx, plan.start[1] - nz], + ] +} + +/** How a footing stands, derived from the building's foundation record and the ground under it. */ +export type FootingDesign = { + levelId: string + /** The level's frame in the world: `world = origin + Ry(yaw) · level-local`. */ + levelFrame: { origin: [number, number, number]; yaw: number } + foundation: BuildingFoundation + /** The concrete it pours, world (the {@link FoundationMember} frame). */ + members: FoundationMember[] + /** + * The ground its depth is measured from, world Y: the lowest grade under it, or under the + * designed raised floor the crawl space's dug-out dirt where that is lower. + */ + gradeY: number + floorTopY: number + /** + * The footing's own body (the thickened slab, the footing, a strip's grade beam or a pad's pile + * cap): its top and bottom, world Y. A stem, pier or pile is in `members`, not here. + */ + topY: number + bottomY: number + /** The body's width across: the node's, or the record's grade beam for a strip on piles. */ + width: number + /** Its bottom under the grade: the node's, else the building's footing depth (under a slab, none: down to the grade). */ + depth: number +} + +const memo = new WeakMap>() + +/** + * A footing as the building's foundation pours it: as the record says (the default slab where there + * is none), else, where the record is switched off, as the floor over it is built (a floor plate + * raised on its own foundation is a raised floor, its stem top the plate's underside; any other a + * slab, its underside the plate's), the record's defaults for the rest: + * + * - Slab on grade: a thickened slab, its top at the slab body's underside, `thickness` under it and + * down at least to the lowest grade beneath it (the node's own depth under it, where it stores + * one), so on a slope it bears on the ground, not on the fill under the slab. + * - Raised floor on footings: the bottom `depth` under the lowest grade beneath it, the top + * `thickness` over that; a strip carries a concrete stem (the record's stem thickness) up to the + * stem top under the floor framing, a pad a square pier of that side. + * - Piles: a strip is a grade beam of the record's sizes on piles no further apart than the + * record's spacing, one at each end; a pad is a pile cap on one pile. + * + * `null` for a footing on no level of a building, or with no plan. + */ +export function footingDesign(nodes: Nodes, footing: FootingNode): FootingDesign | null { + // A footing the scene does not hold as passed (a draft, a live drag) is designed afresh. + const stored = nodes[footing.id] === footing + let byId = memo.get(nodes) + if (stored && byId?.has(footing.id)) return byId.get(footing.id) ?? null + const design = designFooting(nodes, footing) + if (stored) { + if (!byId) { + byId = new Map() + memo.set(nodes, byId) + } + byId.set(footing.id, design) + } + return design +} + +/** The concrete a footing pours, world; empty where it stands on no building. */ +export function footingMembers(nodes: Nodes, footing: FootingNode): FoundationMember[] { + return footingDesign(nodes, footing)?.members ?? [] +} + +function designFooting(nodes: Nodes, footing: FootingNode): FootingDesign | null { + const plan = footingPlan(footing) + const levelId = footing.parentId + if (!(plan && levelId)) return null + const buildingId = getLevelElevations(nodes).get(levelId)?.buildingId + const building = buildingId ? nodes[buildingId as AnyNodeId] : undefined + if (building?.type !== 'building') return null + const resolved = buildingFoundationOf(nodes, building.id) + const planeY = levelPlaneWorldY(nodes, levelId) + if (!resolved || planeY === null) return null + // The designed foundation decides it (the default slab where there is no record, a generator's + // type, the panel's). One the panel switched off keeps its type but follows the floor again. + const decides = buildingFoundationRecordOf(building)?.enabled !== false + const floor = decides ? null : plateOver(nodes, levelId, plan) + const builtRaised = !!floor && groundFloorConstruction(nodes, floor).foundationHeight > 1e-3 + const foundation: BuildingFoundation = floor + ? { ...resolved, type: builtRaised ? 'raised' : 'slab', support: 'footing' } + : resolved + const floorUnderside = floor ? planeY + floor.elevation - floor.thickness : null + const floorTopY = levelFloorWorldY(nodes, levelId) ?? planeY + const site = siteOf(nodes, building) + const world = (p: P): P => levelLocalToWorldXZ(building as BuildingNode, p[0], p[1]) + const gradeOf = (p: P) => { + const [x, z] = world(p) + return groundHeightAt(site, x, z) + } + const samples: P[] = plan.shape === 'pad' ? [plan.center, ...footingOutline(plan)] : [] + if (plan.shape === 'strip') { + const count = + Math.max( + 2, + Math.ceil( + Math.hypot(plan.end[0] - plan.start[0], plan.end[1] - plan.start[1]) / GRADE_STEP, + ), + ) + 1 + for (let k = 0; k < count; k++) { + const t = k / (count - 1) + samples.push([ + plan.start[0] + (plan.end[0] - plan.start[0]) * t, + plan.start[1] + (plan.end[1] - plan.start[1]) * t, + ]) + } + } + const gradeY = Math.min(...samples.map(gradeOf)) + + const raised = foundation.type === 'raised' + const piles = foundation.support === 'piles' + const thickness = footing.thickness + const depth = footing.depth ?? (raised || piles ? inches(foundation.footingDepthIn) : 0) + const stemTopY = floorUnderside ?? floorTopY - inches(foundation.floorFramingDepthIn) + const slabUnderside = floorUnderside ?? floorTopY - inches(foundation.slabThicknessIn) + // Under the designed raised floor the ground is the crawl space's dug-out dirt where that is the + // lower: a footing goes its depth under that, as the perimeter's does. + const groundY = + decides && raised + ? Math.min( + gradeY, + stemTopY + inches(FOUNDATION_SILL_IN) - inches(foundation.crawlClearanceIn), + ) + : gradeY + const base = { levelId, material: 'concrete' as const } + + // The body as a run in the world plan: a strip end to end, a pad along its local z. + const bodyRun = (along = plan.shape === 'pad' ? plan.length : 0) => { + if (plan.shape === 'strip') return { from: world(plan.start), to: world(plan.end) } + const { z } = padAxes(plan.rotation) + const half = along / 2 + return { + from: world([plan.center[0] - z[0] * half, plan.center[1] - z[1] * half]), + to: world([plan.center[0] + z[0] * half, plan.center[1] + z[1] * half]), + } + } + const pile = (at: P, topY: number): FoundationMember => { + const [x, z] = world(at) + return { + ...base, + kind: 'pile', + from: [x, z], + to: [x, z], + width: inches(foundation.pileDiameterIn), + bottomY: topY - inches(foundation.pileLengthIn), + topY, + } + } + + const members: FoundationMember[] = [] + let width = plan.width + let topY: number + let bottomY: number + if (piles) { + topY = raised ? stemTopY : slabUnderside + if (plan.shape === 'strip') { + width = inches(foundation.gradeBeamWidthIn) + bottomY = Math.min(topY - inches(foundation.gradeBeamDepthIn), groundY - depth) + members.push({ ...base, kind: 'grade-beam', ...bodyRun(), width, bottomY, topY }) + const span: P = [plan.end[0] - plan.start[0], plan.end[1] - plan.start[1]] + const bays = Math.max( + 1, + Math.ceil(Math.hypot(span[0], span[1]) / inches(foundation.pileSpacingIn) - 1e-9), + ) + for (let k = 0; k <= bays; k++) + members.push( + pile( + [plan.start[0] + (span[0] * k) / bays, plan.start[1] + (span[1] * k) / bays], + bottomY, + ), + ) + } else { + bottomY = Math.min(topY - thickness, groundY - depth) + members.push({ ...base, kind: 'footing', ...bodyRun(), width, bottomY, topY }) + members.push(pile(plan.center, bottomY)) + } + } else if (raised) { + topY = Math.min(groundY - depth + thickness, stemTopY - 0.01) + bottomY = Math.min(groundY - depth, topY - thickness) + members.push({ ...base, kind: 'footing', ...bodyRun(), width, bottomY, topY }) + if (stemTopY - topY > MIN_STEM_HEIGHT) { + const stem = inches(foundation.stemThicknessIn) + members.push({ + ...base, + kind: 'stem', + ...bodyRun(stem), + width: stem, + bottomY: topY, + topY: stemTopY, + }) + } + } else { + topY = slabUnderside + bottomY = Math.min(topY - thickness, gradeY - depth) + members.push({ ...base, kind: 'footing', ...bodyRun(), width, bottomY, topY }) + } + + const levelFrame = { + origin: [building.position[0], planeY, building.position[2]] as [number, number, number], + yaw: building.rotation?.[1] ?? 0, + } + return { + levelId, + levelFrame, + foundation, + members, + gradeY: groundY, + floorTopY, + topY, + bottomY, + width, + depth, + } +} + +/** The level's floor plate over a footing's centre (a strip's middle). */ +function plateOver(nodes: Nodes, levelId: string, plan: FootingPlan): SlabNode | null { + const at: P = + plan.shape === 'pad' + ? plan.center + : [(plan.start[0] + plan.end[0]) / 2, (plan.start[1] + plan.end[1]) / 2] + const level = nodes[levelId as AnyNodeId] + if (level?.type !== 'level') return null + for (const id of level.children) { + const plate = nodes[id as AnyNodeId] + if ( + plate?.type === 'slab' && + plate.plateRole === 'base' && + containsPoint([{ outer: plate.polygon, holes: plate.holes ?? [] }], at) + ) + return plate + } + return null +} + +/** IRC R403.1: a footing at least 12 in wide and 6 in thick. */ +export const FOOTING_MINIMUM = { width: inches(12), thickness: inches(6) } + +/** The level footings drawn from `levelId` go on: its building's lowest level. */ +export function footingLevelOf(nodes: Nodes, levelId: string): LevelNode | null { + const elevations = getLevelElevations(nodes) + const buildingId = elevations.get(levelId)?.buildingId + if (!buildingId) return null + let lowest: LevelNode | null = null + for (const node of Object.values(nodes)) { + if (node.type !== 'level' || elevations.get(node.id)?.buildingId !== buildingId) continue + if (!lowest || node.level < lowest.level) lowest = node + } + return lowest +} diff --git a/packages/core/src/lib/foundation-construction.test.ts b/packages/core/src/lib/foundation-construction.test.ts index 15e9891e0f..fdcf975949 100644 --- a/packages/core/src/lib/foundation-construction.test.ts +++ b/packages/core/src/lib/foundation-construction.test.ts @@ -302,22 +302,30 @@ describe('what reads the construction', () => { expect(wall.thickness).toBe(0.354) }) - test('the generic foundation draws the stem in the matching material and width', () => { + test('the generic foundation pours the stem, as wide as the construction says', () => { + // The designed foundation switched off: the generic one is what stands under the house. + const generic = (site: SitePatch | null) => { + const nodes = house(site) + const building = nodes['building_a' as AnyNodeId]! + return { + ...nodes, + building_a: { ...building, metadata: { foundation: { enabled: false } } }, + } as Nodes + } const stems = (site: SitePatch | null) => - genericFoundation(house(site)).filter((member) => member.kind === 'stem') + genericFoundation(generic(site)).filter((member) => member.kind === 'stem') // the basement's own walls are none, so the ground floor ring is the outline: stems only where the floor is above the footing const us = stems({ address: { state: 'TX' } }) const eu = stems({ dossier: dossier('Paris, France') }) const unknown = stems({}) expect(unknown.length).toBeGreaterThan(0) - for (const run of unknown) expect(run.material).toBe('cmu') - for (const run of us) expect(run.material).toBe('cmu') - for (const run of eu) expect(run.material).toBe('concrete') + // Poured everywhere: block is for basement walls, not a crawl space's stem. + for (const run of [...unknown, ...us, ...eu]) expect(run.material).toBe('concrete') expect([us.length, eu.length]).toEqual([unknown.length, unknown.length]) for (const run of us) expect(run.width).toBeCloseTo(FOUNDATION_CONSTRUCTIONS.cmu.stemThickness, 9) for (const run of eu) expect(run.width).toBeCloseTo(0.25, 9) - const footings = genericFoundation(house({ dossier: dossier('Paris, France') })).filter( + const footings = genericFoundation(generic({ dossier: dossier('Paris, France') })).filter( (member) => member.kind === 'footing', ) expect(footings.length).toBeGreaterThan(0) @@ -326,9 +334,15 @@ describe('what reads the construction', () => { test('the ground seat reads the construction: a floor 0.15 m over grade is seated in Paris, a floor 0.3 m over it is not', () => { const seated = (nodes: Nodes, offset: number) => { + // The designed foundation off: on, its own rule seats the floor (here, over a basement). const raised = { ...nodes, - building_a: { ...nodes.building_a, groundLevelId: 'level_main', position: [0, offset, 0] }, + building_a: { + ...nodes.building_a, + groundLevelId: 'level_main', + position: [0, offset, 0], + metadata: { foundation: { enabled: false } }, + }, } as Nodes return groundSeat(raised, 'building_a') } diff --git a/packages/core/src/lib/foundation-grading.test.ts b/packages/core/src/lib/foundation-grading.test.ts new file mode 100644 index 0000000000..e99d7ed388 --- /dev/null +++ b/packages/core/src/lib/foundation-grading.test.ts @@ -0,0 +1,192 @@ +import { expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + LevelNode, + SiteNode, + SlabNode, + WallNode, +} from '../schema' +import { + FOUNDATION_GRADING, + finishedGradeAt, + type GradingPad, + gradedTerrainFieldOf, + gradeTerrainField, +} from './foundation-grading' +import { + designedFoundationReport, + foundationGradingPads, + genericFoundation, +} from './generic-foundation' +import { encodeTerrainField } from './terrain-codec' +import { createTerrainField, heightAt } from './terrain-field' + +const IN = 0.0254 +const FT = 12 * IN +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes + +const PAD: GradingPad = { + outline: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + y: 0, +} + +test('under and along a pad the grade is the pad’s; it falls 6 in over the first 10 ft', () => { + expect(finishedGradeAt([PAD], 5, 4, -3)).toBe(0) + expect(finishedGradeAt([PAD], 10, 4, -3)).toBe(0) + expect(finishedGradeAt([PAD], 10 + 5 * FT, 4, -3)).toBeCloseTo(-3 * IN, 9) + expect(finishedGradeAt([PAD], 10 + 10 * FT, 4, 5)).toBeCloseTo(-6 * IN, 9) +}) + +test('past the apron a bank of 2:1 meets the natural ground: fill below it, cut above it, untouched past it', () => { + const toe = -FOUNDATION_GRADING.fall + const at = (run: number, natural: number) => + finishedGradeAt([PAD], 10 + FOUNDATION_GRADING.apron + run, 4, natural) + // 2 m past the toe the bank stands 1 m off it: lower ground is filled up to it, higher cut down. + expect(at(2, toe - 3)).toBeCloseTo(toe - 1, 9) + expect(at(2, toe + 3)).toBeCloseTo(toe + 1, 9) + // Ground the bank already reaches stays as it is. + expect(at(2, toe + 0.4)).toBeCloseTo(toe + 0.4, 9) + expect(finishedGradeAt([], 50, 50, 1.23)).toBe(1.23) +}) + +/** A 10 × 8 m slab house on ground falling 1 m in 10 m along x, its floor top `floor` over the plane. */ +function slopedHouse(floor = 0.05) { + const field = createTerrainField({ cols: 81, rows: 81, spacing: 0.5, origin: [-15, -15] }) + for (let row = 0; row < field.rows; row++) + for (let col = 0; col < field.cols; col++) { + const x = field.origin[0] + col * field.spacing + field.heights[row * field.cols + col] = Math.round((-0.1 * x) / field.step) + } + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const walls = corners.map((start, index) => + WallNode.parse({ + id: `wall_${index}`, + parentId: 'level_g', + start, + end: corners[(index + 1) % 4], + thickness: 0.1, + }), + ) + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'], terrain: encodeTerrainField(field) }), + BuildingNode.parse({ id: 'building_a', parentId: 'site_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((w) => w.id), 'slab_g'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: corners, + elevation: floor, + thickness: 0.05, + }), + ...walls, + ]) +} + +test('on a slope the ground round a slab is held 8 in under its top: filled on the low side, cut on the high', () => { + const nodes = slopedHouse() + const site = nodes['site_a' as AnyNodeId] + const graded = gradedTerrainFieldOf(nodes, site as never)! + const report = designedFoundationReport(nodes, 'building_a')! + const pad = report.floorTopY - 8 * IN + expect(foundationGradingPads(nodes).map((p) => p.y)).toEqual([pad]) + // Natural: 0 at the high side (x 0), -1 m at the low side (x 10). + expect(report.highestGradeY).toBeCloseTo(0.0051, 2) + // Along both sides the finished grade is the pad: cut where the ground stood higher, filled + // where it fell away. + expect(heightAt(graded, -0.06, 4)).toBeCloseTo(pad, 1) + expect(heightAt(graded, 10.06, 4)).toBeCloseTo(pad, 1) + // The low side's edge stands its 8 in out of the fill, no more. + for (const edge of genericFoundation(nodes).filter((m) => m.kind === 'turndown')) { + const top = Math.max(...edge.section!.map(([, y]) => y)) + expect(top - pad).toBeCloseTo(8 * IN - 0.001, 3) + } + // Far off the ground is as it was. + expect(heightAt(graded, -14, 4)).toBeCloseTo(1.4, 2) +}) + +test('a porch slab beside the house stands down in the ground graded round it, 1.5 in under the house', () => { + const house = slopedHouse() + const porch = SlabNode.parse({ + id: 'slab_porch', + parentId: 'level_g', + plateRole: 'sunken', + polygon: [ + [0, 8], + [10, 8], + [10, 10], + [0, 10], + ], + elevation: 0.05 - 1.5 * IN, + thickness: 0.05, + metadata: { floor: 'porch-slab' }, + }) + const level = house['level_g' as AnyNodeId] as LevelNode + const nodes = { + ...house, + [level.id]: { ...level, children: [...level.children, porch.id] }, + [porch.id]: porch, + } as Nodes + const report = designedFoundationReport(nodes, 'building_a')! + const pad = report.floorTopY - 8 * IN + const pads = foundationGradingPads(nodes) + // The house's pad, and the porch's at the same grade: its top 6.5 in over it. + expect(pads.map((p) => p.y)).toEqual([pad, expect.closeTo(pad, 9)] as never) + expect(pads[1]!.outline).toEqual(porch.polygon) + const graded = gradedTerrainFieldOf(nodes, nodes['site_a' as AnyNodeId] as never)! + expect(heightAt(graded, 5, 10.06)).toBeCloseTo(pad, 1) + // An ordinary plate a step down inside the house grades nothing of its own. + const sunken = { ...porch, metadata: {} } + expect(foundationGradingPads({ ...nodes, [porch.id]: sunken } as Nodes)).toHaveLength(1) +}) + +test('round a raised floor the ground is only cut, and its crawl space dug out inside its stem walls', () => { + const cut: GradingPad = { ...PAD, kind: 'cut' } + const pit: GradingPad = { + outline: [ + [1, 1], + [9, 1], + [9, 7], + [1, 7], + ], + y: -0.5, + kind: 'pit', + } + // Higher ground is cut to the grade, lower ground left as it lies, along the apron and the bank. + expect(finishedGradeAt([cut], 10 + 5 * FT, 4, 2)).toBeCloseTo(-3 * IN, 9) + expect(finishedGradeAt([cut], 10 + 5 * FT, 4, -2)).toBe(-2) + expect(finishedGradeAt([cut], 10 + FOUNDATION_GRADING.apron + 2, 4, -3)).toBe(-3) + // Inside the stem walls the dirt is dug down to the pit, never raised to it. + expect(finishedGradeAt([cut, pit], 5, 4, 0.3)).toBe(-0.5) + expect(finishedGradeAt([cut, pit], 5, 4, -0.8)).toBe(-0.8) + // Between the pit and the outline (the stem walls) only the cut. + expect(finishedGradeAt([cut, pit], 0.5, 4, 0.3)).toBe(0) + // A pit alone grades nothing outside it. + expect(finishedGradeAt([pit], 12, 4, 0.3)).toBe(0.3) +}) + +test('the same field and pads grade once; no pads leave the field itself', () => { + const field = createTerrainField({ cols: 9, rows: 9, spacing: 1 }) + expect(gradeTerrainField(field, [])).toBe(field) + const once = gradeTerrainField(field, [PAD]) + expect(gradeTerrainField(field, [PAD])).toBe(once) + expect(once).not.toBe(field) +}) diff --git a/packages/core/src/lib/foundation-grading.ts b/packages/core/src/lib/foundation-grading.ts new file mode 100644 index 0000000000..3c4516f278 --- /dev/null +++ b/packages/core/src/lib/foundation-grading.ts @@ -0,0 +1,170 @@ +import type { AnyNode, AnyNodeId, SiteNode } from '../schema' +import { inches } from './building-foundation' +import { foundationGradingPads } from './generic-foundation' +import { quantize, type TerrainField } from './terrain-field' +import { terrainFieldOf } from './terrain-source' + +type P = [number, number] + +/** + * How a foundation grades the ground round it: + * + * - `pad` (a slab's building pad, the default): the finished grade held along its edge, its top + * less the exposure the record asks (8 in by default, IRC R317.1), whatever the ground did + * before. Low ground is filled up to it, high ground cut down to it. + * - `cut` (round a raised floor's stem wall): ground standing over that grade is cut down to it; + * lower ground stays as it lies, the floor standing over the slope. + * - `pit` (a raised floor's crawl space): the ground inside the outline dug down to `y`; nothing + * outside it. + * - `excavation` (a basement): the ground inside the outline, the basement walls' outside faces, + * dug out down to `y`, the underside of its slab, and cut plumb along the outline; the ground + * outside meets the walls as it lies, neither filled nor cut. The terrain's surface is drawn + * open over it (graded into the drawn field, the ground would slope down to the walls across a + * whole sample of the field), so the renderer grades without it (`isExcavation`). + */ +export type GradingPad = { + /** The foundation's edge (a pit's: the stem walls' inside faces), world XZ, closed. */ + outline: P[] + /** The finished grade along the edge (a pit's and an excavation's floor), world Y. */ + y: number + kind?: 'pad' | 'cut' | 'pit' | 'excavation' +} + +/** A basement's dig, which the ground's surface is drawn open over rather than graded down into. */ +export const isExcavation = (pad: GradingPad): boolean => pad.kind === 'excavation' + +/** + * How the finished grade leaves a pad: it falls 6 in within the first 10 ft (IRC R401.3, water + * drains away from the foundation), then meets the natural ground on a bank no steeper than 2:1, + * filled where the ground is lower, cut where it is higher. + */ +export const FOUNDATION_GRADING = { + apron: inches(120), + fall: inches(6), + /** Rise per run of the bank back to the natural ground. */ + bank: 0.5, +} as const + +/** Distance from `p` to the closed outline: 0 inside it. */ +function distanceToOutline(outline: readonly P[], [x, z]: readonly [number, number]): number { + let inside = false + let nearest = Number.POSITIVE_INFINITY + for (let i = 0, j = outline.length - 1; i < outline.length; j = i++) { + const [ax, az] = outline[i]! + const [bx, bz] = outline[j]! + if (az > z !== bz > z && x < ((bx - ax) * (z - az)) / (bz - az) + ax) inside = !inside + const dx = bx - ax + const dz = bz - az + const length2 = dx * dx + dz * dz + const t = length2 > 0 ? Math.max(0, Math.min(1, ((x - ax) * dx + (z - az) * dz) / length2)) : 0 + nearest = Math.min(nearest, Math.hypot(x - (ax + dx * t), z - (az + dz * t))) + } + return inside ? 0 : nearest +} + +/** + * The finished grade at a world point, the natural ground `natural` there graded to the nearest + * pad: the pad's grade under and along it, falling over the apron, then a bank of fill or cut back + * to the natural ground (a `cut` pad's bank only cuts). Past the bank the ground is as it was. A + * crawl space (`pit`) or a basement (`excavation`) the point lies in digs it down further. + */ +export function finishedGradeAt( + pads: readonly GradingPad[], + x: number, + z: number, + natural: number, +): number { + let pad: GradingPad | null = null + let distance = Number.POSITIVE_INFINITY + let pit = Number.POSITIVE_INFINITY + for (const candidate of pads) { + const d = distanceToOutline(candidate.outline, [x, z]) + if (candidate.kind === 'pit' || candidate.kind === 'excavation') { + if (d === 0) pit = Math.min(pit, candidate.y) + continue + } + if (d < distance) { + distance = d + pad = candidate + } + } + return Math.min(pit, pad ? padGradeAt(pad, distance, natural) : natural) +} + +/** The grade `distance` off `pad` (a `pad` or a `cut`), over ground lying at `natural`. */ +function padGradeAt(pad: GradingPad, distance: number, natural: number): number { + const { apron, fall, bank } = FOUNDATION_GRADING + const cutOnly = pad.kind === 'cut' + if (distance <= apron) { + const grade = pad.y - (fall * distance) / apron + return cutOnly ? Math.min(natural, grade) : grade + } + const toe = pad.y - fall + const reach = (distance - apron) * bank + return Math.min(toe + reach, cutOnly ? natural : Math.max(toe - reach, natural)) +} + +const graded = new WeakMap() + +/** + * The terrain as the pads grade it (`finishedGradeAt` at every sample they reach), a new field; + * the field itself where no pad is given. Worked out once per field and set of pads. + */ +export function gradeTerrainField(field: TerrainField, pads: readonly GradingPad[]): TerrainField { + if (pads.length === 0) return field + const key = JSON.stringify(pads) + const hit = graded.get(field) + if (hit?.key === key) return hit.field + let low = Number.POSITIVE_INFINITY + let high = Number.NEGATIVE_INFINITY + for (const value of field.heights) { + low = Math.min(low, value) + high = Math.max(high, value) + } + const heights = new Int16Array(field.heights) + const { apron, fall, bank } = FOUNDATION_GRADING + for (const pad of pads) { + // A bank reaches no further than the field's whole relief allows; a dig has none. + const relief = Math.max(Math.abs(high * field.step - pad.y), Math.abs(low * field.step - pad.y)) + const dig = pad.kind === 'pit' || pad.kind === 'excavation' + const grow = dig ? field.spacing : apron + (relief + fall) / bank + field.spacing + const xs = pad.outline.map((p) => p[0]) + const zs = pad.outline.map((p) => p[1]) + const col0 = Math.max(0, Math.floor((Math.min(...xs) - grow - field.origin[0]) / field.spacing)) + const col1 = Math.min( + field.cols - 1, + Math.ceil((Math.max(...xs) + grow - field.origin[0]) / field.spacing), + ) + const row0 = Math.max(0, Math.floor((Math.min(...zs) - grow - field.origin[1]) / field.spacing)) + const row1 = Math.min( + field.rows - 1, + Math.ceil((Math.max(...zs) + grow - field.origin[1]) / field.spacing), + ) + for (let row = row0; row <= row1; row++) + for (let col = col0; col <= col1; col++) { + const index = row * field.cols + col + const x = field.origin[0] + col * field.spacing + const z = field.origin[1] + row * field.spacing + const natural = (field.heights[index] ?? 0) * field.step + heights[index] = quantize(field, finishedGradeAt(pads, x, z, natural)) + } + } + const result: TerrainField = { ...field, heights } + graded.set(field, { key, field: result }) + return result +} + +/** + * The site's ground as built: its terrain (`terrainFieldOf`, a stroke in flight included) graded to + * the buildings' foundations (`foundationGradingPads`: slab pads, the cut round a raised floor, its + * crawl space, a basement's dig). What the ground is picked and walked on (the renderer draws it + * open over a basement instead of dug); the foundation itself is designed against the natural + * ground. `null` for flat ground. + */ +export function gradedTerrainFieldOf( + nodes: Record, + site: (Pick & { id?: string }) | null | undefined, +): TerrainField | null { + const field = terrainFieldOf(site) + return field ? gradeTerrainField(field, foundationGradingPads(nodes)) : null +} diff --git a/packages/core/src/lib/foundation-view.test.ts b/packages/core/src/lib/foundation-view.test.ts new file mode 100644 index 0000000000..53be446092 --- /dev/null +++ b/packages/core/src/lib/foundation-view.test.ts @@ -0,0 +1,295 @@ +import { afterEach, expect, test } from 'bun:test' +import { nodeRegistry, registerNode } from '../registry' +import { + type AnyNode, + type AnyNodeId, + BaseNode, + BuildingNode, + FootingNode, + FoundationNode, + LevelNode, + nodeType, + objectId, + SiteNode, + SlabNode, + WallNode, +} from '../schema' +import type { BuildingFoundationRecord } from './building-foundation' +import { + buildingFoundationNodes, + type FoundationPlanShape, + foundationBadge, + foundationPlan, + foundationViewHiddenIds, + foundationViewOf, + traceFoundationOutlines, +} from './foundation-view' +import { designedFoundationReport, genericFoundation } from './generic-foundation' + +afterEach(() => nodeRegistry._reset()) + +const IN = 0.0254 +type Nodes = Record +type P = [number, number] +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes + +const BOX: P[] = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] + +/** + * A one-storey house on a flat site standing on `record`; `below` adds an empty level under the + * ground floor (as a person adds one before drawing in it), `place` moves and turns the building, + * `outline` draws its foundation outline on the ground floor, `walls: false` leaves the walls out. + */ +function house( + record: BuildingFoundationRecord | null, + { + below = false, + place, + outline, + walls: withWalls = true, + }: { + below?: boolean + place?: { position: [number, number, number]; yaw: number } + outline?: P[] + walls?: boolean + } = {}, +) { + const walls = withWalls + ? BOX.map((start, index) => + WallNode.parse({ + id: `wall_g_${index}`, + parentId: 'level_g', + start, + end: BOX[(index + 1) % BOX.length], + thickness: 0.1, + }), + ) + : [] + const drawn = outline + ? [FoundationNode.parse({ id: 'foundation_a', parentId: 'level_g', polygon: outline })] + : [] + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: below ? ['level_b', 'level_g'] : ['level_g'], + ...(place ? { position: place.position, rotation: [0, place.yaw, 0] } : {}), + ...(record ? { metadata: { foundation: record } } : {}), + }), + ...(below + ? [LevelNode.parse({ id: 'level_b', parentId: 'building_a', level: -1, children: [] })] + : []), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((w) => w.id), 'slab_g', ...drawn.map((d) => d.id)], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: BOX, + elevation: 0.05, + thickness: 0.05, + }), + ...walls, + ...drawn, + ]) +} + +const bounds = (points: P[]) => ({ + minX: Math.min(...points.map((p) => p[0])), + maxX: Math.max(...points.map((p) => p[0])), + minZ: Math.min(...points.map((p) => p[1])), + maxZ: Math.max(...points.map((p) => p[1])), +}) +const ofKind = (shapes: FoundationPlanShape[], kind: FoundationPlanShape['kind']) => + shapes.filter((shape) => shape.kind === kind) +const ringsOf = (shape: FoundationPlanShape | undefined) => + shape && 'rings' in shape ? shape.rings : [] + +test('the badge names the foundation: the default slab with no record, Off only switched off', () => { + expect(foundationBadge(house(null), 'building_a')).toBe('Slab') + // A generator's record names its type and never sets the switch. + expect(foundationBadge(house({ type: 'raised' }), 'building_a')).toBe('Raised') + expect(foundationBadge(house({ enabled: false, type: 'slab' }), 'building_a')).toBe('Off') + expect(foundationBadge(house({ enabled: true, type: 'slab' }), 'building_a')).toBe('Slab') + for (const type of ['slab', 'raised'] as const) + expect(foundationBadge(house({ type, support: 'piles' }), 'building_a')).toBe('Piles') + expect(foundationBadge(house(null), 'level_g')).toBe('Off') +}) + +test('the view selects the level footings go on, and ghosts the walls the foundation runs under', () => { + for (const record of [null, { enabled: false }]) { + expect(foundationViewOf(house(record), 'building_a')).toMatchObject({ + levelId: 'level_g', + wallLevelId: 'level_g', + }) + // An empty level under the ground floor is where footings are drawn; the walls stay the ground floor's. + expect(foundationViewOf(house(record, { below: true }), 'building_a')).toMatchObject({ + levelId: 'level_b', + wallLevelId: 'level_g', + }) + } + expect(foundationViewOf(house(null), 'nothing')).toEqual({ + levelId: null, + wallLevelId: null, + badge: 'Off', + }) +}) + +test('a slab plan: the slab outlined, its turned-down edge dashed where it ends inside, one ring with open corners', () => { + const shapes = foundationPlan(house(null), 'building_a') + const [slab] = ofKind(shapes, 'slab') + expect(slab).toMatchObject({ hidden: false, filled: true }) + expect(ringsOf(slab)).toHaveLength(1) + const outer = bounds(ringsOf(slab)[0]!) + // Under the whole house, to within the walls' outside faces (0.05 m out of the centrelines). + for (const [value, centreline] of [ + [outer.minX, 0], + [outer.maxX, 10], + [outer.minZ, 0], + [outer.maxZ, 8], + ] as const) + expect(Math.abs(value - centreline)).toBeLessThanOrEqual(0.05 + 1e-6) + // The four sides' edges merge into one band: only its inside line shows, one closed rectangle, + // nothing drawn across the corners. + const [edge] = ofKind(shapes, 'turndown') + expect(edge).toMatchObject({ hidden: true, filled: false }) + expect(ringsOf(edge)).toHaveLength(1) + const inside = ringsOf(edge)[0]! + expect(inside).toHaveLength(4) + const inner = bounds(inside) + expect(inner.minX).toBeGreaterThan(outer.minX + 12 * IN - 1e-6) + expect(inner.maxX).toBeLessThan(outer.maxX - 12 * IN + 1e-6) +}) + +test('a moved and turned building draws the same plan in its own frame', () => { + const still = foundationPlan(house(null), 'building_a') + const moved = foundationPlan( + house(null, { place: { position: [7, 0, -3], yaw: 0.6 } }), + 'building_a', + ) + expect(moved.length).toBe(still.length) + const a = bounds(ringsOf(ofKind(still, 'slab')[0])[0]!) + const b = bounds(ringsOf(ofKind(moved, 'slab')[0])[0]!) + for (const key of ['minX', 'maxX', 'minZ', 'maxZ'] as const) expect(b[key]).toBeCloseTo(a[key], 6) +}) + +test('piles are dashed circles of the record’s diameter, under a solid grade beam', () => { + const shapes = foundationPlan( + house({ type: 'raised', support: 'piles', pileDiameterIn: 16 }), + 'building_a', + ) + const piles = ofKind(shapes, 'pile') + expect(piles.length).toBeGreaterThan(4) + for (const pile of piles) { + expect(pile.hidden).toBe(true) + expect('radius' in pile && pile.radius).toBeCloseTo(8 * IN, 6) + } + const [beam] = ofKind(shapes, 'grade-beam') + // Over a crawl space the beam is cut: its outside and inside, one ring each. + expect(beam).toMatchObject({ hidden: false, filled: true }) + expect(ringsOf(beam)).toHaveLength(2) + expect(shapes.at(-1)?.kind).toBe('pile') +}) + +test('a raised floor: the poured stem wall solid, its footing dashed on both sides of it', () => { + const shapes = foundationPlan(house({ type: 'raised' }), 'building_a') + const [stem] = ofKind(shapes, 'stem') + const [footing] = ofKind(shapes, 'footing') + expect(stem).toMatchObject({ hidden: false, filled: true }) + expect(footing).toMatchObject({ hidden: true, filled: false }) + expect(ringsOf(stem)).toHaveLength(2) + expect(ringsOf(footing)).toHaveLength(2) + const stemOut = bounds(ringsOf(stem)[0]!) + const footingOut = bounds(ringsOf(footing)[0]!) + expect(footingOut.minX).toBeLessThan(stemOut.minX) + for (const member of genericFoundation(house({ type: 'raised' }))) + expect(member.material).toBe('concrete') +}) + +test('switched off, the plan is the generic footing dashed round the poured stem wall', () => { + const shapes = foundationPlan(house({ enabled: false }), 'building_a') + expect(ofKind(shapes, 'stem')[0]).toMatchObject({ hidden: false, filled: true }) + expect(ofKind(shapes, 'footing')[0]).toMatchObject({ hidden: true, filled: false }) + expect(ofKind(shapes, 'slab')).toEqual([]) + expect(foundationPlan(house(null), 'level_g')).toEqual([]) +}) + +test('a drawn outline replaces the walls’ and needs none; tracing the walls gives their outside faces', () => { + const traced = traceFoundationOutlines(house(null), 'building_a') + expect(traced).toHaveLength(1) + const faces = bounds(traced[0]!) + expect(faces.minX).toBeCloseTo(-0.051, 3) + expect(faces.maxX).toBeCloseTo(10.051, 3) + expect(traceFoundationOutlines(house(null, { walls: false }), 'building_a')).toEqual([]) + + // Stretched 2 m out on the right, past the walls (a porch slab). + const stretched: P[] = [ + [-0.05, -0.05], + [12, -0.05], + [12, 8.05], + [-0.05, 8.05], + ] + const nodes = house(null, { outline: stretched }) + expect(buildingFoundationNodes(nodes, 'building_a').map((node) => node.id)).toEqual([ + 'foundation_a', + ]) + const slab = bounds(ringsOf(ofKind(foundationPlan(nodes, 'building_a'), 'slab')[0])[0]!) + expect(slab.maxX).toBeCloseTo(12 - 0.01, 3) + // Drawn in either direction it is the same foundation. + const reversed = house(null, { outline: [...stretched].reverse() }) + expect(genericFoundation(reversed)).toEqual(genericFoundation(nodes)) + // With no walls at all the drawn outline still stands, on its own level. + const bare = house(null, { outline: stretched, walls: false }) + expect(designedFoundationReport(bare, 'building_a')?.levelId).toBe('level_g') + expect(ofKind(foundationPlan(bare, 'building_a'), 'turndown')).toHaveLength(1) + // Switched off, the generic foundation follows the walls again. + const off = house({ enabled: false }, { outline: stretched }) + expect(genericFoundation(off)).toEqual(genericFoundation(house({ enabled: false }))) +}) + +test('3D hides the house on every level but its footings, its outline and a plugin drawing the foundation', () => { + const Framer = BaseNode.extend({ id: objectId('framer'), type: nodeType('test:framer') }) + registerNode({ + kind: 'test:framer', + schemaVersion: 1, + schema: Framer, + category: 'utility', + defaults: () => ({}), + capabilities: { drawsFoundation: () => true }, + }) + const base = house(null, { below: true, outline: BOX }) + const footing = FootingNode.parse({ + id: 'footing_a', + parentId: 'level_b', + start: [2, 4], + end: [8, 4], + }) + const framer = Framer.parse({ id: 'framer_g', parentId: 'level_g' }) as AnyNode + const ground = base['level_g' as AnyNodeId] + const lower = base['level_b' as AnyNodeId] + if (ground?.type !== 'level' || lower?.type !== 'level') throw new Error('fixture') + const nodes = { + ...base, + level_g: { ...ground, children: [...ground.children, framer.id] }, + level_b: { ...lower, children: [footing.id] }, + footing_a: footing, + framer_g: framer, + } as Nodes + const hidden = foundationViewHiddenIds(nodes, 'building_a') + expect([...hidden].sort()).toEqual( + ['slab_g', 'wall_g_0', 'wall_g_1', 'wall_g_2', 'wall_g_3'].sort(), + ) + // Worked out once per scene state. + expect(foundationViewHiddenIds(nodes, 'building_a')).toBe(hidden) + expect(foundationViewHiddenIds(nodes, 'level_g')).toEqual([]) +}) diff --git a/packages/core/src/lib/foundation-view.ts b/packages/core/src/lib/foundation-view.ts new file mode 100644 index 0000000000..fb88de68a7 --- /dev/null +++ b/packages/core/src/lib/foundation-view.ts @@ -0,0 +1,259 @@ +import { nodeRegistry } from '../registry' +import type { AnyNode, AnyNodeId, BuildingNode, FoundationNode } from '../schema' +import { getLevelElevations } from '../services/storey' +import { buildingFoundationOf, isBuildingFoundationOn } from './building-foundation' +import { foundationOutlines } from './designed-foundation' +import { footingLevelOf } from './footing' +import { + designedFoundationReport, + enclosedFoundationWalls, + type FoundationMember, + genericFoundation, +} from './generic-foundation' +import { type Ring, union } from './polygon-boolean' + +type Nodes = Record +type P = [number, number] + +/** What the level list's Foundation row says the building stands on. */ +export type FoundationBadge = 'Slab' | 'Raised' | 'Piles' | 'Basement' | 'Off' + +/** + * The building's foundation as the editor's foundation view shows it: a view of the record and of + * what the engine draws, never a stored level. + */ +export type FoundationView = { + /** + * The level the view selects: the one footings are drawn on (the building's lowest level), so + * the plan draws them and the Footing tool draws there without changing level. + */ + levelId: string | null + /** The level whose walls the foundation runs under: the plan ghosts them over it. */ + wallLevelId: string | null + badge: FoundationBadge +} + +/** + * Slab, Raised or Piles while the building's designed foundation is on (a building with no record + * stands on the default slab), Basement while it stands on one; Off where its record is switched + * off (the generic footing the engine derives from the walls is no foundation anyone chose). + */ +export function foundationBadge(nodes: Nodes, buildingId: string): FoundationBadge { + const building = nodes[buildingId as AnyNodeId] + if (building?.type !== 'building' || !isBuildingFoundationOn(building)) return 'Off' + const foundation = buildingFoundationOf(nodes, buildingId) + if (!foundation) return 'Off' + if (designedFoundationReport(nodes, buildingId)?.basement) return 'Basement' + if (foundation.support === 'piles') return 'Piles' + return foundation.type === 'raised' ? 'Raised' : 'Slab' +} + +/** The concrete the engine draws under the building (`genericFoundation`): designed, or generic. */ +export function buildingFoundationMembers(nodes: Nodes, buildingId: string): FoundationMember[] { + const elevations = getLevelElevations(nodes) + return genericFoundation(nodes).filter( + (member) => elevations.get(member.levelId)?.buildingId === buildingId, + ) +} + +export function foundationViewOf(nodes: Nodes, buildingId: string): FoundationView { + const building = nodes[buildingId as AnyNodeId] + const badge = foundationBadge(nodes, buildingId) + if (building?.type !== 'building') return { levelId: null, wallLevelId: null, badge } + const report = designedFoundationReport(nodes, buildingId) + const wallLevelId = + report?.basement?.outlineLevelId ?? + report?.levelId ?? + buildingFoundationMembers(nodes, buildingId)[0]?.levelId ?? + null + const anyLevel = + wallLevelId ?? building.children.find((id) => nodes[id as AnyNodeId]?.type === 'level') ?? null + const levelId = anyLevel ? (footingLevelOf(nodes, anyLevel)?.id ?? anyLevel) : null + return { levelId, wallLevelId: wallLevelId ?? levelId, badge } +} + +/** The building's drawn foundation outlines (`foundation` nodes on its levels). */ +export function buildingFoundationNodes(nodes: Nodes, buildingId: string): FoundationNode[] { + const elevations = getLevelElevations(nodes) + return Object.values(nodes).filter( + (node): node is FoundationNode => + node.type === 'foundation' && + !!node.parentId && + elevations.get(node.parentId)?.buildingId === buildingId, + ) +} + +/** + * The outlines a foundation traced from the building's walls has: the outside faces of each closed + * loop of exterior walls on its lowest enclosed level, level-local, counter-clockwise. Empty where + * the walls close no loop. + */ +export function traceFoundationOutlines(nodes: Nodes, buildingId: string): P[][] { + const enclosed = enclosedFoundationWalls(nodes, buildingId) + if (!enclosed) return [] + return foundationOutlines(enclosed.walls).map((outline) => + outline.edges.map((edge) => [edge.a[0], edge.a[1]] as P), + ) +} + +const hiddenMemo = new WeakMap>() + +/** + * What the foundation view hides of the building in 3D: everything on its levels (walls, floors, + * roofs, ceilings, stairs, rooms, furniture, a plugin's nodes) but the footings, the drawn + * foundation outlines, and a node that draws its level's foundation itself (a framing plugin's), + * with what is under it. The levels stay: their groups carry the footings. + */ +export function foundationViewHiddenIds(nodes: Nodes, buildingId: string): readonly string[] { + let byBuilding = hiddenMemo.get(nodes) + const hit = byBuilding?.get(buildingId) + if (hit) return hit + const building = nodes[buildingId as AnyNodeId] + const out: string[] = [] + if (building?.type === 'building') { + const stack: string[] = [] + for (const id of building.children) { + const level = nodes[id as AnyNodeId] + if (level?.type === 'level') stack.push(...[...level.children].reverse()) + } + const seen = new Set() + while (stack.length > 0) { + const id = stack.pop() as string + if (seen.has(id)) continue + seen.add(id) + const node = nodes[id as AnyNodeId] + if (!node || node.type === 'footing' || node.type === 'foundation') continue + if (nodeRegistry.get(node.type)?.capabilities?.drawsFoundation?.(node) === true) continue + out.push(id) + const children = (node as { children?: unknown }).children + if (Array.isArray(children)) + for (let index = children.length - 1; index >= 0; index--) + if (typeof children[index] === 'string') stack.push(children[index] as string) + } + } + if (!byBuilding) { + byBuilding = new Map() + hiddenMemo.set(nodes, byBuilding) + } + byBuilding.set(buildingId, out) + return out +} + +/** + * One kind of the foundation's concrete in the plan, building-local metres (the plan's frame): its + * pieces merged, so no seams or overlaps show at the corners. `hidden` lines lie under concrete + * above them (a slab's turned-down edge, a footing under its stem, a pile under its beam) and are + * drawn dashed; `filled` concrete is what the plan cuts (the slab, a stem wall, a grade beam over + * a crawl space). Piles are circles. + */ +export type FoundationPlanShape = + | { + kind: Exclude + /** Closed rings, drawn as one shape (holes even-odd). */ + rings: P[][] + hidden: boolean + filled: boolean + } + | { kind: 'pile'; center: P; radius: number; hidden: true; filled: false } + +/** Bottom to top as a plan draws them: the slab under its edge, the stems over their footings, piles on top. */ +const PLAN_ORDER: Exclude[] = [ + 'slab', + 'turndown', + 'footing', + 'grade-beam', + 'stem', +] + +/** World plan to the building's own frame: the inverse of `levelLocalToWorldXZ`. */ +function toBuildingLocal(building: BuildingNode, [x, z]: P): P { + const angle = building.rotation?.[1] ?? 0 + const [px, , pz] = building.position ?? [0, 0, 0] + const [dx, dz] = [x - px, z - pz] + if (angle === 0) return [dx, dz] + const cos = Math.cos(angle) + const sin = Math.sin(angle) + return [cos * dx - sin * dz, sin * dx + cos * dz] +} + +/** A run's footprint, world: `width` across its line, or its `section`'s reach across. */ +function runFootprint(member: FoundationMember): P[] | null { + const dx = member.to[0] - member.from[0] + const dz = member.to[1] - member.from[1] + const length = Math.hypot(dx, dz) + if (length < 1e-9) return null + // To the left of the run, as the section's `across` is measured. + const [nx, nz] = [-dz / length, dx / length] + const across = member.section?.map(([offset]) => offset) ?? [-member.width / 2, member.width / 2] + const [low, high] = [Math.min(...across), Math.max(...across)] + const at = ([x, z]: P, offset: number): P => [x + nx * offset, z + nz * offset] + return [at(member.from, low), at(member.to, low), at(member.to, high), at(member.from, high)] +} + +/** A ring as the clipper returns it, closed by repeating its first point: open it again. */ +function openRing(ring: Ring): P[] { + const points = ring.map(([x, z]) => [x, z] as P) + const first = points[0] + const last = points[points.length - 1] + if (first && last && points.length > 1 && first[0] === last[0] && first[1] === last[1]) + points.pop() + return points +} + +/** + * The foundation plan of the building: the concrete the engine draws under it, seen from above, in + * the building's frame, bottom to top, each kind's pieces merged into one shape. Over a slab the + * slab's outline is drawn and what it covers (its turned-down edge, a grade beam) shows only where + * it ends inside it, dashed; without one, stems and grade beams are drawn whole and footings dashed + * round them, as they are round a slab poured between stems (a garage's, a basement's). + */ +export function foundationPlan(nodes: Nodes, buildingId: string): FoundationPlanShape[] { + const building = nodes[buildingId as AnyNodeId] + if (building?.type !== 'building') return [] + const local = (point: P) => toBuildingLocal(building, point) + const footprints = new Map() + const piles: FoundationPlanShape[] = [] + for (const member of buildingFoundationMembers(nodes, buildingId)) { + if (member.kind === 'pile') { + piles.push({ + kind: 'pile', + center: local(member.from), + radius: member.width / 2, + hidden: true, + filled: false, + }) + continue + } + const world = member.kind === 'slab' ? member.outline : runFootprint(member) + if (!world || world.length < 3) continue + const list = footprints.get(member.kind) ?? [] + list.push(world.map(local)) + footprints.set(member.kind, list) + } + // A slab between walls standing on stems (a garage's off a raised house, a basement's) covers + // none of them: they stay whole, its door gaps showing as breaks in the curbs. + const slabbed = footprints.has('slab') && !footprints.has('stem') + const shapes: FoundationPlanShape[] = [] + for (const kind of PLAN_ORDER) { + const rings = footprints.get(kind) + if (!rings) continue + const merged = union(rings) + if (kind === 'slab') { + shapes.push({ + kind, + rings: merged.map((polygon) => openRing(polygon.outer)), + hidden: false, + filled: true, + }) + } else if (slabbed) { + // Under the slab its outside face is the slab's edge: only where it ends inside shows. + const inner = merged.flatMap((polygon) => polygon.holes.map(openRing)) + if (inner.length > 0) shapes.push({ kind, rings: inner, hidden: true, filled: false }) + } else { + const all = merged.flatMap((polygon) => [polygon.outer, ...polygon.holes].map(openRing)) + const hidden = kind === 'footing' || kind === 'turndown' + shapes.push({ kind, rings: all, hidden, filled: !hidden }) + } + } + return [...shapes, ...piles] +} diff --git a/packages/core/src/lib/framing-post.test.ts b/packages/core/src/lib/framing-post.test.ts new file mode 100644 index 0000000000..01388b1d5a --- /dev/null +++ b/packages/core/src/lib/framing-post.test.ts @@ -0,0 +1,152 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BeamNode, + BuildingNode, + ColumnNode, + LevelNode, + SiteNode, +} from '../schema' +import { + createFramingPost, + FRAMING_POST_MIN_HEIGHT, + framingPostBasePatch, + framingPostFields, + framingPostHeightLimit, + framingPostSize, + framingPostTopAt, + isFramingPost, +} from './framing-post' + +const IN = 0.0254 +const FT = 12 * IN +type Nodes = Record +const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes + +function storey(beams: BeamNode[]) { + return keyed([ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ id: 'building_a', parentId: 'site_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + height: 2.7, + children: beams.map((b) => b.id), + }), + ...beams, + ]) +} + +describe('framing posts', () => { + test('a post is a plain square column of its dressed size, pressure-treated, marked a post', () => { + const post = createFramingPost({ position: [3, 4], size: '4x4', height: 2.4 }) + expect(post.type).toBe('column') + expect(post).toMatchObject({ + role: 'post', + postBase: 'none', + style: 'plain', + crossSection: 'square', + position: [3, 0, 4], + height: 2.4, + baseStyle: 'none', + capitalStyle: 'none', + shaftStartScale: 1, + shaftEndScale: 1, + shaftCornerRadius: 0, + edgeSoftness: 0, + slots: { shaft: 'library:wood-woodplank19' }, + }) + expect(post.width).toBeCloseTo(3.5 * IN) + expect(post.depth).toBeCloseTo(3.5 * IN) + expect(isFramingPost(post)).toBe(true) + expect(framingPostSize(post)).toBe('4x4') + // ornamental columns are none of this + const column = ColumnNode.parse({}) + expect(isFramingPost(column)).toBe(false) + expect(column.role).toBeUndefined() + expect(column.postBase).toBeUndefined() + }) + + test('its height comes from its top and foot, never under the least a tool makes', () => { + expect( + createFramingPost({ position: [0, 0], baseElevation: 0.05, topElevation: 2.5 }).height, + ).toBeCloseTo(2.45) + // a deck post on the ground carries the grade as its own y + const deck = createFramingPost({ + position: [0, -0.4, 0], + topElevation: 1.6, + supportSlabId: 'ground', + levelId: 'level_g', + }) + expect(deck).toMatchObject({ + position: [0, -0.4, 0], + supportSlabId: 'ground', + parentId: 'level_g', + }) + expect(deck.height).toBeCloseTo(2) + expect(createFramingPost({ position: [0, 0], height: 0.01 }).height).toBe( + FRAMING_POST_MIN_HEIGHT, + ) + expect(createFramingPost({ position: [0, 0] }).height).toBeCloseTo(8 * FT) + }) + + test('a base is the column base block: a steel post base or a concrete pier', () => { + const pier = createFramingPost({ position: [0, 0], size: '6x6', base: 'pier', height: 2.4 }) + expect(pier).toMatchObject({ + postBase: 'pier', + baseStyle: 'simple-square', + slots: { shaft: 'library:wood-woodplank19', base: 'library:concrete-raw' }, + }) + expect(pier.baseHeight).toBeCloseTo(8 * IN) + expect(pier.width * pier.baseWidthScale).toBeCloseTo(11.5 * IN) + const stood = framingPostBasePatch(pier, 'post-base') + expect(stood).toMatchObject({ postBase: 'post-base', slots: { base: 'library:metal-steel' } }) + expect(stood.baseHeight).toBeCloseTo(1.5 * IN) + // taking the base off keeps the shaft's wood and drops the base's paint + expect(framingPostBasePatch(pier, 'none')).toEqual({ + postBase: 'none', + baseStyle: 'none', + baseHeight: 0, + baseWidthScale: 1, + baseDepthScale: 1, + slots: { shaft: 'library:wood-woodplank19' }, + }) + expect(framingPostFields('8x8').width).toBeCloseTo(7.25 * IN) + }) + + test('a post stands up to the underside of the beam over it, else the ceiling plane', () => { + const header = BeamNode.parse({ + id: 'beam_h', + parentId: 'level_g', + start: [0, 0], + end: [4, 0], + width: 5.5 * IN, + depth: 7.5 * IN, + }) + const nodes = storey([header]) + expect(framingPostTopAt(nodes, 'level_g', [4, 0], { baseElevation: 0.05 })).toEqual({ + top: 2.7 - 7.5 * IN, + beamId: 'beam_h', + }) + expect(framingPostTopAt(nodes, 'level_g', [8, 0])).toEqual({ top: 2.7, beamId: null }) + // a beam under the post's foot is not what it carries + const girder = { ...header, id: 'beam_g' as BeamNode['id'], topElevation: 0 } + const low = storey([girder]) + expect(framingPostTopAt(low, 'level_g', [2, 0], { baseElevation: 0.05 }).beamId).toBeNull() + }) + + test('a post taller than its size may stand on a deck is flagged', () => { + expect( + framingPostHeightLimit(createFramingPost({ position: [0, 0], size: '4x4', height: 9 * FT })), + ).toMatchObject({ + size: '4x4', + limit: expect.closeTo(8 * FT, 6), + }) + expect( + framingPostHeightLimit(createFramingPost({ position: [0, 0], size: '6x6', height: 9 * FT })), + ).toBeNull() + expect(framingPostHeightLimit(ColumnNode.parse({ height: 9 }))).toBeNull() + }) +}) diff --git a/packages/core/src/lib/framing-post.ts b/packages/core/src/lib/framing-post.ts new file mode 100644 index 0000000000..75ad113dfc --- /dev/null +++ b/packages/core/src/lib/framing-post.ts @@ -0,0 +1,250 @@ +import type { AnyNode, AnyNodeId } from '../schema' +import { ColumnNode, type ColumnPostBase } from '../schema/nodes/column' +import { getWallPlaneTop } from '../services/storey' +import { beamBottomElevation, beamsOverPoint } from './beam' + +type Nodes = Record + +const INCH = 0.0254 +const FOOT = 12 * INCH +/** The shortest post the tools make: under that a beam is sitting on the floor. */ +export const FRAMING_POST_MIN_HEIGHT = 0.3 +/** A post's height where nothing says otherwise: a porch's eight-foot post. */ +const DEFAULT_POST_HEIGHT = 8 * FOOT + +/** The square framing posts, by their actual dressed side: 3-1/2, 5-1/2 and 7-1/4 in. */ +export const FRAMING_POST_SIZES = { + '4x4': 3.5 * INCH, + '6x6': 5.5 * INCH, + '8x8': 7.25 * INCH, +} as const + +export type FramingPostSize = keyof typeof FRAMING_POST_SIZES + +export const DEFAULT_FRAMING_POST_SIZE: FramingPostSize = '6x6' + +/** + * The tallest each post may stand on a deck (IRC Table R507.4; the table also turns on the + * species and the area the post carries, so this is the check, not the design). + */ +export const FRAMING_POST_MAX_HEIGHT: Record = { + '4x4': 8 * FOOT, + '6x6': 11 * FOOT, + '8x8': 14 * FOOT, +} + +/** A framing post's wood: pressure-treated, as a post on a slab or the ground must be. */ +export const FRAMING_POST_MATERIAL_REF = 'library:wood-woodplank19' + +/** + * The bases a post stands on, drawn as the column's base block: a steel post base holding the + * wood an inch and a half off the concrete, or a concrete pier 8 in tall and 3 in wider than the + * post all round. + */ +const POST_BASES: Record< + Exclude, + { height: number; margin: number; material: string } +> = { + 'post-base': { height: 1.5 * INCH, margin: 0.25 * INCH, material: 'library:metal-steel' }, + pier: { height: 8 * INCH, margin: 3 * INCH, material: 'library:concrete-raw' }, +} + +export const FRAMING_POST_BASE_LABELS: Record = { + none: 'None', + 'post-base': 'Post base', + pier: 'Concrete pier', +} + +/** Whether a node is a framing post: a column marked `role: 'post'`. */ +export function isFramingPost(node: AnyNode | null | undefined): node is ColumnNode { + return node?.type === 'column' && node.role === 'post' +} + +/** The post size a column's section is, or null when it is none of them (or not square). */ +export function framingPostSize( + column: Pick, +): FramingPostSize | null { + for (const [size, side] of Object.entries(FRAMING_POST_SIZES) as [FramingPostSize, number][]) { + if (Math.abs(column.width - side) < INCH / 16 && Math.abs(column.depth - side) < INCH / 16) + return size + } + return null +} + +/** + * The column fields that set a post's base, the base block and its material together: a post with + * none starts straight off the floor. + */ +export function framingPostBasePatch( + post: Pick & { slots?: Record }, + base: ColumnPostBase, +): Partial { + const { base: _base, ...slots } = post.slots ?? {} + // The base scales stay at 1 with no base: the plan's footprint reads them whatever the style. + if (base === 'none') + return { + postBase: 'none', + baseStyle: 'none', + baseHeight: 0, + baseWidthScale: 1, + baseDepthScale: 1, + slots, + } + const { height, margin, material } = POST_BASES[base] + return { + postBase: base, + baseStyle: 'simple-square', + baseHeight: height, + baseWidthScale: Math.min(3, (post.width + margin * 2) / post.width), + baseDepthScale: Math.min(3, (post.depth + margin * 2) / post.depth), + slots: { ...slots, base: material }, + } +} + +/** + * The column fields that make a framing post: a plain square sawn post of `size`, sharp-cornered, + * its shaft the whole section with no capital, pressure-treated, on `base`. + */ +export function framingPostFields( + size: FramingPostSize = DEFAULT_FRAMING_POST_SIZE, + base: ColumnPostBase = 'none', +): Partial { + const side = FRAMING_POST_SIZES[size] + return { + role: 'post', + style: 'plain', + crossSection: 'square', + supportStyle: 'vertical', + width: side, + depth: side, + radius: side / 2, + edgeSoftness: 0, + shaftProfile: 'straight', + shaftTaper: 0, + shaftBulge: 0, + shaftStartScale: 1, + shaftEndScale: 1, + shaftSegmentCount: 1, + shaftTwistStep: 0, + shaftCornerRadius: 0, + shaftDetail: 'none', + capitalStyle: 'none', + capitalHeight: 0, + // No capital, but the plan's footprint reads its scales: the post's own size. + capitalWidthScale: 1, + capitalDepthScale: 1, + baseRoundBandCount: 0, + ringCount: 0, + fluteCount: 0, + spiralRibCount: 0, + panelCount: 0, + latheRingCount: 0, + carvingLevel: 0, + lowerBandEnabled: false, + dentilCount: 0, + beadCount: 0, + ...framingPostBasePatch( + { width: side, depth: side, slots: { shaft: FRAMING_POST_MATERIAL_REF } }, + base, + ), + } +} + +/** What `createFramingPost` builds a post from. */ +export type FramingPostInput = { + /** + * Where it stands: a level-local plan point `[x, z]`, or the column's `[x, y, z]` with `y` its + * foot over what it stands on (a deck post on the ground carries the grade there). + */ + position: readonly [number, number] | readonly [number, number, number] + size?: FramingPostSize + base?: ColumnPostBase + /** Its height, foot to top. Else `topElevation - baseElevation`, else eight feet. */ + height?: number + /** Its foot, level-local: the top of the slab or the ground it stands on. */ + baseElevation?: number + /** Its top, level-local: the underside of the beam it carries. */ + topElevation?: number + /** The level it goes on: its `parentId`. */ + levelId?: string + /** What it stands on: a slab id, or `'ground'` (`GROUND_SUPPORT_ID`). */ + supportSlabId?: string + rotation?: number + name?: string + metadata?: Record +} + +/** + * A framing post, as a column node ready for `createNode` on its level: what the Post tool places, + * and what code that frames a porch or a deck makes under its beams. + */ +export function createFramingPost(input: FramingPostInput): ColumnNode { + const size = input.size ?? DEFAULT_FRAMING_POST_SIZE + const position: [number, number, number] = + input.position.length === 3 + ? [input.position[0], input.position[1], input.position[2]] + : [input.position[0], 0, input.position[1]] + const height = + input.height ?? + (input.topElevation !== undefined + ? input.topElevation - (input.baseElevation ?? 0) - position[1] + : DEFAULT_POST_HEIGHT) + return ColumnNode.parse({ + name: input.name ?? 'Post', + ...framingPostFields(size, input.base ?? 'none'), + position, + rotation: input.rotation ?? 0, + height: Math.max(FRAMING_POST_MIN_HEIGHT, height), + ...(input.levelId ? { parentId: input.levelId } : {}), + ...(input.supportSlabId ? { supportSlabId: input.supportSlabId } : {}), + ...(input.metadata ? { metadata: input.metadata } : {}), + }) +} + +/** What a post at a plan point stands up to: the beam over it, else the ceiling plane. */ +export type FramingPostTop = { + /** Level-local Y of the post's top. */ + top: number + /** The beam it carries, when one is over it. */ + beamId: string | null +} + +/** + * Where the top of a post standing at a plan point of a level goes: the underside of the lowest + * beam over it that clears its foot (`baseElevation`), else the ceiling plane there (the top of + * the level's walls), as a post under a ceiling with no beam yet stands to it. + */ +export function framingPostTopAt( + nodes: Nodes, + levelId: string, + point: readonly [number, number], + options: { baseElevation?: number; side?: number } = {}, +): FramingPostTop { + const foot = options.baseElevation ?? 0 + for (const beam of beamsOverPoint(nodes, levelId, point)) { + const bottom = beamBottomElevation(nodes, beam) + if (bottom >= foot + FRAMING_POST_MIN_HEIGHT) return { top: bottom, beamId: beam.id } + } + const half = Math.max(options.side ?? FRAMING_POST_SIZES['6x6'], 0.02) / 2 + return { + top: getWallPlaneTop( + { start: [point[0] - half, point[1]], end: [point[0] + half, point[1]], thickness: half * 2 }, + levelId, + nodes, + ), + beamId: null, + } +} + +/** + * A post taller than its size may stand (`FRAMING_POST_MAX_HEIGHT`): the size and the limit, for + * the panel to warn with; null for a post within it or of no post size. + */ +export function framingPostHeightLimit( + post: Pick, +): { size: FramingPostSize; limit: number } | null { + const size = framingPostSize(post) + if (!size) return null + const limit = FRAMING_POST_MAX_HEIGHT[size] + return post.height > limit + 1e-6 ? { size, limit } : null +} diff --git a/packages/core/src/lib/generic-foundation.test.ts b/packages/core/src/lib/generic-foundation.test.ts index de09e2d88a..5adbab6223 100644 --- a/packages/core/src/lib/generic-foundation.test.ts +++ b/packages/core/src/lib/generic-foundation.test.ts @@ -10,6 +10,7 @@ import { LevelNode, nodeType, objectId, + railingWallDefaults, SiteNode, SlabNode, WallNode, @@ -28,6 +29,10 @@ const { footingThickness, footingBelowGrade, stemThickness } = FOUNDATION_STANDA type Nodes = Record const keyed = (nodes: AnyNode[]) => Object.fromEntries(nodes.map((n) => [n.id, n])) as Nodes +// The designed foundation is on unless switched off: these houses switch it off, to stand on the +// generic one under test. +const GENERIC = { metadata: { foundation: { enabled: false } } } + /** A 10 × 8 m ring of walls on `level`, 0.1 m thick unless told. */ function ring(level: string, extra: Partial = {}, thickness = 0.1) { const corners: Array<[number, number]> = [ @@ -60,7 +65,7 @@ function flatHouse({ slab, wallExtra, thickness, terrain }: House = {}) { const walls = ring('level_g', wallExtra, thickness) return keyed([ SiteNode.parse({ id: 'site_a', children: ['building_a'], ...(terrain ? { terrain } : {}) }), - BuildingNode.parse({ id: 'building_a', parentId: 'site_a', children: ['level_g'] }), + BuildingNode.parse({ ...GENERIC, id: 'building_a', parentId: 'site_a', children: ['level_g'] }), LevelNode.parse({ id: 'level_g', parentId: 'building_a', @@ -104,7 +109,7 @@ function houseOverBasement(basementWalls = 4) { elevation: 0.2, }) return keyed([ - BuildingNode.parse({ id: 'building_a', children: ['level_b', 'level_g'] }), + BuildingNode.parse({ ...GENERIC, id: 'building_a', children: ['level_b', 'level_g'] }), LevelNode.parse({ id: 'level_b', parentId: 'building_a', @@ -131,7 +136,7 @@ function twoStoreys(groundWalls = 4) { const ground = ring('level_g').slice(0, groundWalls) const upper = ring('level_u') return keyed([ - BuildingNode.parse({ id: 'building_a', children: ['level_g', 'level_u'] }), + BuildingNode.parse({ ...GENERIC, id: 'building_a', children: ['level_g', 'level_u'] }), LevelNode.parse({ id: 'level_g', parentId: 'building_a', @@ -251,6 +256,7 @@ function seatedBasement(slab: Partial = {}) { return keyed([ SiteNode.parse({ id: 'site_a', children: ['building_a'] }), BuildingNode.parse({ + ...GENERIC, id: 'building_a', parentId: 'site_a', position: [0, -2.697, 0], @@ -439,7 +445,7 @@ test('a plugin that steps aside from a picture gives the generic foundation back test('a level with no walls draws nothing, and a hidden wall is not carried', () => { const none = keyed([ - BuildingNode.parse({ id: 'building_a', children: ['level_g'] }), + BuildingNode.parse({ ...GENERIC, id: 'building_a', children: ['level_g'] }), LevelNode.parse({ id: 'level_g', parentId: 'building_a', level: 0, children: [] }), ]) expect(genericFoundation(none)).toEqual([]) @@ -461,4 +467,57 @@ test('the answer is worked out once per scene state', () => { expect(genericFoundation(nodes)).toBe(genericFoundation(nodes)) }) +/** `house` with a 3 × 4 m deck off its east wall, fenced by three railings. */ +function withRailedDeck(house: Nodes): Nodes { + const corners: Array<[number, number]> = [ + [10, 2], + [13, 2], + [13, 6], + [10, 6], + ] + const railings = corners.slice(1).map((end, index) => + WallNode.parse({ + id: `wall_deck_${index}`, + parentId: 'level_g', + start: corners[index], + end, + ...railingWallDefaults(), + }), + ) + const level = house['level_g' as AnyNodeId] as LevelNode + return keyed([ + ...Object.values(house), + { ...level, children: [...level.children, ...railings.map((wall) => wall.id)] }, + ...railings, + ]) +} + +/** The furthest east any member reaches: its run's ends and its slab outline. */ +const eastmost = (members: FoundationMember[]) => + Math.max( + ...members.flatMap((member) => [ + member.from[0], + member.to[0], + ...(member.outline ?? []).map(([x]) => x), + ]), + ) + +test('a railing carries no foundation: a deck it fences adds none, a ring of railings stands on none', () => { + const generic = genericFoundation(withRailedDeck(flatHouse())) + expect(ofKind(generic, 'footing')).toHaveLength(4) + expect(ofKind(generic, 'stem')).toHaveLength(4) + expect(eastmost(generic)).toBeLessThan(10.5) + + // The designed foundation (on unless switched off) follows the same outline. + const house = flatHouse() + const building = house['building_a' as AnyNodeId] as BuildingNode + const designed = genericFoundation( + withRailedDeck({ ...house, building_a: { ...building, metadata: {} } } as Nodes), + ) + expect(designed.length).toBeGreaterThan(0) + expect(eastmost(designed)).toBeLessThan(10.5) + + expect(genericFoundation(flatHouse({ wallExtra: railingWallDefaults() }))).toEqual([]) +}) + afterEach(() => nodeRegistry._reset()) diff --git a/packages/core/src/lib/generic-foundation.ts b/packages/core/src/lib/generic-foundation.ts index 866383acfa..652e8796c3 100644 --- a/packages/core/src/lib/generic-foundation.ts +++ b/packages/core/src/lib/generic-foundation.ts @@ -1,14 +1,41 @@ import { isCaptureSessionOn } from '../events/capture-sessions' import { nodeRegistry } from '../registry' -import type { AnyNode, AnyNodeId, BuildingNode, LevelNode, SlabNode, WallNode } from '../schema' -import { getLevelElevations, levelPlateUndersideWorldY } from '../services/storey' +import type { + AnyNode, + AnyNodeId, + BuildingNode, + FoundationNode, + LevelNode, + SlabNode, + WallNode, +} from '../schema' +import { isRailingWall } from '../schema/nodes/railing-wall' +import { + getLevelElevations, + levelFloorWorldY, + levelPlaneWorldY, + levelPlateUndersideWorldY, +} from '../services/storey' import { getWallCenterlineSegments } from '../systems/wall/wall-curve' import { getWallThickness } from '../systems/wall/wall-footprint' import { getWallBodyCenterOffset } from '../systems/wall/wall-frame' +import { buildingFoundationOf, inches, isBuildingFoundationOn } from './building-foundation' +import { + type DesignedFoundationReport, + designedBasement, + designedFoundation, + type FoundationOutline, + type FoundationRimStep, + foundationOutlines, + outlineOfPolygon, +} from './designed-foundation' import { exteriorWallLoops } from './exterior-wall-loops' import { type FoundationConstructionId, foundationReadingOf } from './foundation-construction' +import type { GradingPad } from './foundation-grading' import { FOUNDATION_STANDARD } from './foundation-standard' import { levelGradeStanding } from './grade-standing' +import { groundLevelOf } from './ground-level' +import { containsPoint } from './polygon-boolean' import { groundHeightAt } from './site-ground-height' import { levelLocalToWorldXZ } from './terrain-support-query' @@ -20,20 +47,37 @@ type Nodes = Record * `topY`. A `footing` is the concrete pour under the wall, a `stem` the foundation wall on it, * built of `material`: the footing is always concrete, the stem is what the building's * foundation construction is (block or cast concrete, by region). + * + * A designed foundation (the building's record switched on, `designedFoundation`) adds what a box + * does not draw: a slab's `turndown` edge, drawn as its `section`; a `grade-beam`; a `pile`, a + * cylinder `width` across at `from`; the `slab` itself, its plan `outline` from `bottomY` to `topY`. */ export type FoundationMember = { - kind: 'footing' | 'stem' - material: 'concrete' | 'cmu' + /** `cripple`: a raised floor's wood cripple wall, from a stepped-down stem's top up to the floor framing. */ + kind: 'footing' | 'stem' | 'cripple' | 'turndown' | 'grade-beam' | 'pile' | 'slab' + material: 'concrete' | 'cmu' | 'wood' levelId: string from: [number, number] to: [number, number] width: number bottomY: number topY: number + /** + * The run's cross-section in place of the `width` box: `[across, y]` points of a closed outline, + * `across` metres to the left of `from` → `to` (into the building), `y` world. + */ + section?: Array<[number, number]> + /** `slab`: the plan outline, world XZ, and the holes in it (where the slab drops under a lower floor). */ + outline?: Array<[number, number]> + holes?: Array> + /** Drawn from the building's foundation record: no plugin takes it over. */ + designed?: true } +// A stem wall under a raised floor is poured, wherever the house stands: block is for basement +// walls, not the short course a crawl space stands on. const STEM_MATERIAL: Record = { - cmu: 'cmu', + cmu: 'concrete', 'cast-concrete': 'concrete', } @@ -42,18 +86,32 @@ const GRADE_SAMPLES = 7 /** A stem shorter than this is the floor sitting on the footing: nothing to draw. */ const MIN_STEM_HEIGHT = 1e-3 +type FoundationScene = { + capturing: boolean + members: FoundationMember[] + rimBottoms: Map + rimSteps: Map + sharedStemTops: Map + reports: Map + pads: GradingPad[] +} + // Per scene and per capture session: a plugin's `drawsFoundation` may step aside from a picture // (the pictures show the finished house), a state no node holds. -const memo = new WeakMap() +const memo = new WeakMap() /** - * The foundation a building stands on where the scene stores none: a continuous footing and a - * foundation wall under the exterior walls of each building's lowest level with a closed outline, + * The foundation each building stands on. Unless its record is switched off, that is its designed + * foundation (`designedFoundation`, a slab on grade where there is no record), under its + * foundation outlines where it has some (`foundation` nodes), else under its exterior walls; over a + * basement, the basement's walls cut into the ground (`designedBasement`, which stands walls of + * the basement's own on the generic footing). Switched off, it is the generic one: a continuous + * footing and a poured foundation wall under the exterior walls of each building's lowest level + * with a closed outline, * derived from those walls, never stored, so nothing in a saved scene changes. The numbers are * {@link FOUNDATION_STANDARD}: a 16 × 8 in footing whose bottom sits 12 in under the lowest grade * (a generic depth: core has no frost data), and a wall from the footing up to the underside of the - * lowest floor, of the building's foundation construction (`foundationReadingOf`): 8 in block by - * default, cast concrete in Europe. + * lowest floor, as thick as the building's foundation construction says (`foundationReadingOf`). * * - A level below grade (all of it) has no second wall: its exterior walls are the foundation * walls, so only the footing is drawn, under the underside of its floor. @@ -65,16 +123,358 @@ const memo = new WeakMap { + return foundationScene(nodes).rimBottoms +} + +/** + * Where each wall's finish runs further down than its {@link foundationRimBottom}: over the + * cripple walls a raised floor stands on down a slope, to the stepped-down stem's top. By wall id. + */ +export function foundationRimSteps( + nodes: Nodes, +): ReadonlyMap { + return foundationScene(nodes).rimSteps +} + +/** + * Where each wall a slab room (a garage) shares with a raised house stands on the house's stem: the + * stem's top, the sill line, level-local. The wall's face on the room's side is the stem's bare + * concrete under it, down to the room's slab. By wall id. + */ +export function foundationSharedStemTops(nodes: Nodes): ReadonlyMap { + return foundationScene(nodes).sharedStemTops +} + +/** What the building's designed foundation measured (a basement's, `report.basement`); `null` where its record is off or it has no closed outline. */ +export function designedFoundationReport( + nodes: Nodes, + buildingId: string, +): DesignedFoundationReport | null { + return foundationScene(nodes).reports.get(buildingId) ?? null +} + +function foundationScene(nodes: Nodes): FoundationScene { const capturing = isCaptureSessionOn() const hit = memo.get(nodes) - if (hit && hit.capturing === capturing) return hit.members - const members: FoundationMember[] = [] + if (hit && hit.capturing === capturing) return hit + const scene: FoundationScene = { + capturing, + members: [], + rimBottoms: new Map(), + rimSteps: new Map(), + sharedStemTops: new Map(), + reports: new Map(), + pads: [], + } for (const node of Object.values(nodes)) { - if (node.type === 'building' && node.visible !== false) - members.push(...buildingFoundation(nodes, node)) + if (node.type !== 'building' || node.visible === false) continue + const designed = designedFoundationOf(nodes, node) + if (designed) { + scene.members.push(...designed.members) + for (const [wallId, bottom] of designed.rimBottoms) { + const wall = nodes[wallId as AnyNodeId] + if (wall?.type === 'wall' && !wall.underpinning) scene.rimBottoms.set(wallId, bottom) + } + for (const [wallId, steps] of designed.rimSteps) { + const wall = nodes[wallId as AnyNodeId] + if (wall?.type === 'wall' && !wall.underpinning) scene.rimSteps.set(wallId, steps) + } + for (const [wallId, top] of designed.sharedStemTops) scene.sharedStemTops.set(wallId, top) + scene.reports.set(node.id, designed.report) + scene.pads.push(...designed.pads, ...outdoorSlabPads(nodes, node)) + continue + } + scene.members.push(...buildingFoundation(nodes, node)) } - memo.set(nodes, { capturing, members }) - return members + memo.set(nodes, scene) + return scene +} + +/** + * How the buildings' designed foundations grade the ground (`designedFoundation`): each slab's + * pad, filled or cut to round it; the cut round a raised floor's stem wall and its crawl space dug + * out; a basement's dig, the ground round it left as it lies (`designedBasement`). The site's + * ground is graded to them (`gradeTerrainField`). + */ +export function foundationGradingPads(nodes: Nodes): readonly GradingPad[] { + return foundationScene(nodes).pads +} + +/** The least a porch or patio slab stands over the ground graded round it. */ +const OUTDOOR_SLAB_MIN_EXPOSURE = inches(4) + +/** + * How a porch or patio slab beside the house grades the ground: a plate the structure kernel tags + * `metadata.floor: 'porch-slab'` (`OutdoorRoomDefaults.floorTag`) on the storey standing on the + * ground, held as far under its top as the house's pad is under the house's floor (`topAboveGrade` + * less the slab's drop, 4 in at least). The slab then stands down in the ground the house is graded + * to, its edge running into it (the slab draws its edge down to that ground), never over it. Beside + * a raised floor the ground standing over that grade is only cut. + */ +function outdoorSlabPads(nodes: Nodes, building: BuildingNode): GradingPad[] { + const foundation = buildingFoundationOf(nodes, building.id) + const ground = groundLevelOf(nodes, building.id)?.level + if (!foundation || !ground) return [] + const planeY = levelPlaneWorldY(nodes, ground.id) ?? building.position?.[1] ?? 0 + const floorTop = levelFloorWorldY(nodes, ground.id) ?? planeY + const pads: GradingPad[] = [] + for (const id of ground.children) { + const plate = nodes[id as AnyNodeId] + if (plate?.type !== 'slab' || plate.plateRole === 'base' || plate.visible === false) continue + if ((plate.metadata as Record | undefined)?.floor !== 'porch-slab') continue + if (plate.polygon.length < 3) continue + const top = planeY + plate.elevation + const exposure = Math.max( + OUTDOOR_SLAB_MIN_EXPOSURE, + inches(foundation.topAboveGradeIn) - Math.max(0, floorTop - top), + ) + pads.push({ + outline: plate.polygon.map(([x, z]) => levelLocalToWorldXZ(building, x, z)), + y: top - exposure, + ...(foundation.type === 'raised' ? { kind: 'cut' as const } : {}), + }) + } + return pads +} + +/** + * The designed foundation, where the building's record is on. Over a basement it is the + * basement's (`designedBasementOf`); else a ground floor the ground stands over is still designed, + * so the record can seat it. It is the building's own: a plugin that draws a level's foundation + * does not take it over. Its outline is the building's `foundation` nodes where it has some (then + * it needs no walls), else its exterior walls'; an outline that pours a construction of its own or + * stands a drop under the floor (a porch slab) is poured apart, and the house's own outlines + * report. + */ +function designedFoundationOf(nodes: Nodes, building: BuildingNode) { + if (!isBuildingFoundationOn(building)) return null + const under = basementUnder(nodes, building) + if (under) return designedBasementOf(nodes, building, under) + const enclosed = lowestEnclosedLevel(nodes, building) + const drawn = drawnOutlinesOf(nodes, building) + const level = enclosed?.level ?? drawn?.level + if (!level) return null + const foundation = buildingFoundationOf(nodes, building.id) + if (!foundation) return null + const site = siteOf(nodes, building) + const gradeAt = (x: number, z: number) => groundHeightAt(site, x, z) + const walls = enclosed?.walls ?? [] + if (!drawn) return designedFoundation(nodes, building, level, walls, foundation, gradeAt) + + // The house's own outline is the walls' until an outline of the house's replaces it; a porch + // slab or an outline of another construction pours beside it. + let main: ReturnType = drawn.groups.some(isHouseGroup) + ? null + : enclosed + ? designedFoundation(nodes, building, level, walls, foundation, gradeAt) + : null + const members: FoundationMember[] = [...(main?.members ?? [])] + const pads: GradingPad[] = [...(main?.pads ?? [])] + let report: DesignedFoundationReport | null = main?.report ?? null + for (const group of drawn.groups) { + const own = + group.construction === 'building' ? foundation : { ...foundation, type: group.construction } + const designed = designedFoundation( + nodes, + building, + level, + walls, + own, + gradeAt, + group.outlines, + { + drop: group.drop, + ownTop: !group.covered, + }, + ) + if (!designed) continue + members.push(...designed.members) + pads.push(...designed.pads) + if (isHouseGroup(group) && !main) main = designed + report ??= designed.report + } + if (!report) return null + return { + members, + pads, + rimBottoms: main?.rimBottoms ?? new Map(), + rimSteps: main?.rimSteps ?? new Map(), + sharedStemTops: main?.sharedStemTops ?? new Map(), + report: main?.report ?? report, + } +} + +/** + * The building's basement: its lowest level, where that lies under its ground floor (a lower floor + * index, as the level panel's "+" under the lowest adds one, walls or none), and the ground floor + * over it. `null` for a building that stands on its ground floor. + */ +function basementUnder( + nodes: Nodes, + building: BuildingNode, +): { basement: LevelNode; ground: LevelNode } | null { + const ground = groundLevelOf(nodes, building.id) + if (!ground) return null + const elevations = getLevelElevations(nodes) + let lowest: LevelNode | null = null + for (const node of Object.values(nodes)) { + if (node.type !== 'level' || elevations.get(node.id)?.buildingId !== building.id) continue + if (!lowest || node.level < lowest.level) lowest = node + } + return lowest && lowest.level < ground.level.level + ? { basement: lowest, ground: ground.level } + : null +} + +/** + * A house over a basement, cut into the ground (`designedBasement`): no pad round it, the ground + * dug out inside the basement walls' outside faces. Walls of the basement's own that close an + * outline are its foundation walls, on the generic footing (`buildingFoundation`); without them + * its walls are poured under the house's outline (its drawn one, else the ground floor walls' + * outside faces). An outline of another construction or a drop under the floor (a porch slab) + * still pours beside it, the ground under it dug as it digs, never filled or cut round it. + */ +function designedBasementOf( + nodes: Nodes, + building: BuildingNode, + { basement, ground }: { basement: LevelNode; ground: LevelNode }, +) { + const foundation = buildingFoundationOf(nodes, building.id) + if (!foundation) return null + const site = siteOf(nodes, building) + const gradeAt = (x: number, z: number) => groundHeightAt(site, x, z) + const walls = basement.children.some((id) => isSolidWall(nodes[id])) + ? exteriorWalls(nodes, basement) + : [] + const outlines = walls.length > 0 ? foundationOutlines(walls) : [] + if (outlines.length > 0) + return designedBasement(nodes, building, foundation, gradeAt, outlines, { + basement, + ground, + outlineLevelId: basement.id, + own: { footings: buildingFoundation(nodes, building) }, + }) + + const enclosed = lowestEnclosedLevel(nodes, building) + const drawn = drawnOutlinesOf(nodes, building) + const house = drawn?.groups.filter(isHouseGroup).flatMap((group) => group.outlines) ?? [] + const designed = designedBasement( + nodes, + building, + foundation, + gradeAt, + house.length > 0 ? house : enclosed ? foundationOutlines(enclosed.walls) : [], + { + basement, + ground, + outlineLevelId: (house.length > 0 ? drawn?.level.id : enclosed?.level.id) ?? ground.id, + }, + ) + if (!designed) return null + for (const group of drawn?.groups ?? []) { + if (!drawn || isHouseGroup(group)) continue + const porch = designedFoundation( + nodes, + building, + drawn.level, + enclosed?.walls ?? [], + group.construction === 'building' ? foundation : { ...foundation, type: group.construction }, + gradeAt, + group.outlines, + { drop: group.drop, ownTop: !group.covered }, + ) + if (!porch) continue + designed.members.push(...porch.members) + designed.pads.push(...porch.pads.filter((pad) => pad.kind === 'pit')) + } + return designed +} + +type OutlineGroup = { + construction: FoundationNode['construction'] + drop: number + /** A floor plate of the house lies over it: the plate is its walking surface. */ + covered: boolean + outlines: FoundationOutline[] +} + +/** Outlines of the house's own foundation (as its record, at its floor), which replace the walls'. */ +function isHouseGroup(group: Pick): boolean { + return group.construction === 'building' && group.drop === 0 +} + +/** A drawn outline of the house's own foundation: it replaces the outline traced from the walls. */ +export function isHouseOutline(node: Pick): boolean { + return isHouseGroup({ construction: node.construction ?? 'building', drop: node.drop ?? 0 }) +} + +/** + * The building's drawn foundation outlines (`foundation` nodes), with the level the lowest stands + * on, grouped by what they pour: their construction, their drop, and whether a floor plate of the + * house covers them. + */ +function drawnOutlinesOf( + nodes: Nodes, + building: BuildingNode, +): { level: LevelNode; groups: OutlineGroup[] } | null { + const elevations = getLevelElevations(nodes) + let level: LevelNode | null = null + const groups: OutlineGroup[] = [] + const plates = Object.values(nodes).filter( + (node): node is SlabNode => + node.type === 'slab' && + node.plateRole === 'base' && + !!node.parentId && + elevations.get(node.parentId)?.buildingId === building.id, + ) + for (const node of Object.values(nodes)) { + if (node.type !== 'foundation' || node.visible === false || !node.parentId) continue + const host = nodes[node.parentId as AnyNodeId] + if (host?.type !== 'level' || elevations.get(host.id)?.buildingId !== building.id) continue + const outline = outlineOfPolygon(node.polygon) + if (!outline) continue + const centre = outlineCentre(outline) + const covered = plates.some((plate) => + containsPoint([{ outer: plate.polygon, holes: plate.holes ?? [] }], centre), + ) + const construction = node.construction ?? 'building' + const drop = node.drop ?? 0 + const group = groups.find( + (candidate) => + candidate.construction === construction && + candidate.drop === drop && + candidate.covered === covered, + ) + if (group) group.outlines.push(outline) + else groups.push({ construction, drop, covered, outlines: [outline] }) + if (!level || host.level < level.level) level = host + } + return level ? { level, groups } : null +} + +/** A point well inside an outline: its corners' mean (an outline is a footprint, rarely hollow). */ +function outlineCentre(outline: FoundationOutline): [number, number] { + let x = 0 + let z = 0 + for (const edge of outline.edges) { + x += edge.a[0] + z += edge.a[1] + } + return [x / outline.edges.length, z / outline.edges.length] } function buildingFoundation(nodes: Nodes, building: BuildingNode): FoundationMember[] { @@ -86,7 +486,8 @@ function buildingFoundation(nodes: Nodes, building: BuildingNode): FoundationMem const gradeAt = (x: number, z: number) => groundHeightAt(site, x, z) const lowestGrade = Math.min(...walls.map((wall) => lowestGradeAlong(building, wall, gradeAt))) const floors = floorsOf(nodes, level) - const wallBaseY = building.position[1] + (getLevelElevations(nodes).get(level.id)?.baseY ?? 0) + const wallBaseY = + (building.position?.[1] ?? 0) + (getLevelElevations(nodes).get(level.id)?.baseY ?? 0) // The footing never pokes above the wall's base into the room, nor above the floor's underside. const floorUnderside = levelPlateUndersideWorldY(nodes, level.id) ?? wallBaseY const { construction } = foundationReadingOf(nodes, building.id) @@ -159,7 +560,7 @@ function lowestEnclosedLevel( ) .sort((a, b) => a.level - b.level) for (const level of levels) { - if (!level.children.some((id) => nodes[id]?.type === 'wall')) continue + if (!level.children.some((id) => isSolidWall(nodes[id]))) continue const walls = exteriorWalls(nodes, level) if (walls.length > 0) return { level, walls } if (levelGradeStanding(nodes, level.id) === 'at-grade') return null @@ -167,6 +568,15 @@ function lowestEnclosedLevel( return null } +/** The building's lowest level whose exterior walls close an outline, with those walls (`lowestEnclosedLevel`). */ +export function enclosedFoundationWalls( + nodes: Nodes, + buildingId: string, +): { level: LevelNode; walls: WallNode[] } | null { + const building = nodes[buildingId as AnyNodeId] + return building?.type === 'building' ? lowestEnclosedLevel(nodes, building) : null +} + function levelDrawsFoundation(nodes: Nodes, level: LevelNode): boolean { return level.children.some((id) => { const child = nodes[id] @@ -176,6 +586,12 @@ function levelDrawsFoundation(nodes: Nodes, level: LevelNode): boolean { }) } +/** A wall a foundation goes under: a railing stands on its floor as a guard, on no footing. */ +function isSolidWall(node: AnyNode | undefined): boolean { + return node?.type === 'wall' && !isRailingWall(node) +} + +/** A level's walls on its outline (`exteriorWallLoops`, which a railing never closes). */ function exteriorWalls(nodes: Nodes, level: LevelNode): WallNode[] { const walls = level.children .map((id) => nodes[id]) @@ -191,7 +607,7 @@ function floorsOf(nodes: Nodes, level: LevelNode): SlabNode[] { .filter((node): node is SlabNode => node?.type === 'slab' && node.recessed !== true) } -function siteOf(nodes: Nodes, building: BuildingNode): AnyNode | null { +export function siteOf(nodes: Nodes, building: BuildingNode): AnyNode | null { const parent = building.parentId ? nodes[building.parentId as AnyNodeId] : undefined return parent?.type === 'site' ? parent diff --git a/packages/core/src/lib/ground-seat.ts b/packages/core/src/lib/ground-seat.ts index e8fe7f0eb8..6c17ede6f5 100644 --- a/packages/core/src/lib/ground-seat.ts +++ b/packages/core/src/lib/ground-seat.ts @@ -5,6 +5,7 @@ import { type FoundationReading, foundationReadingOf, } from './foundation-construction' +import { designedFoundationReport } from './generic-foundation' import { footprintSamples, terrainFieldOverLevel } from './grade-standing' import { groundLevelOf } from './ground-level' import { heightAt } from './terrain-field' @@ -42,6 +43,14 @@ export function gradeUnderLevel(nodes: Record, level: LevelN return sum / samples.length } +/** + * What seats the ground floor: the building's foundation construction (`aboveGrade` over the grade + * under the floor), or its designed foundation's own rule over the highest grade along it: a + * slab's, a raised floor's or a pile-borne floor's top `ffAboveGradeIn`, a basement's walls' top + * `basementTopAboveGradeIn`. + */ +export type GroundSeatRule = 'construction' | 'slab' | 'raised' | 'piles' | 'basement' + /** Where a building's ground level stands against grade. */ export type GroundSeat = { buildingId: string @@ -54,22 +63,33 @@ export type GroundSeat = { gradeElevation: number /** How far the floor datum stands over grade: negative when it is sunk. */ offset: number - /** How far the building's foundation construction stands the ground floor over grade, metres. */ + /** + * The `offset` at which the ground floor is seated, metres: as far as the building's foundation + * construction stands it over grade, or where its designed foundation's rule holds (`rule`). + */ aboveGrade: number /** The construction, the region and style it was read from. */ foundation: FoundationReading + rule: GroundSeatRule + /** What the rule asks, inches: the construction's seat, or the record's height over grade. */ + askedIn: number /** * The floor datum stands between grade and the foundation's top (`aboveGrade`, or the block's * 8 in where that is higher), within {@link GROUND_SEAT_TOLERANCE}: what a scene seated before * the 8 in rule, one seated by it and one seated by its own region's construction all satisfy, - * so a check has nothing to say about any of them. + * so a check has nothing to say about any of them. On a designed foundation, what its rule + * measures stands between the highest grade and its seat. */ seated: boolean - /** The floor datum stands at `aboveGrade` over grade, within the tolerance. */ + /** The ground floor stands at its seat (`aboveGrade`), within the tolerance. */ atFoundationTop: boolean } -/** `null` for an id that is no building, or a building with no level at or over the ground. */ +/** + * `null` for an id that is no building, or a building with no level at or over the ground. Where + * the building's designed foundation stands under its ground floor (`designedFoundationReport`), + * the seat is that foundation's own (`designedSeatOf`); else the construction's. + */ export function groundSeat( nodes: Record, buildingId: string, @@ -80,19 +100,70 @@ export function groundSeat( const gradeElevation = gradeUnderLevel(nodes, ground.level) const offset = datum.floorElevation - gradeElevation const foundation = foundationReadingOf(nodes, buildingId) - const aboveGrade = foundation.construction.seatAboveGrade - return { + const base = { buildingId, levelId: ground.level.id, explicit: ground.explicit, floorElevation: datum.floorElevation, gradeElevation, offset, - aboveGrade, foundation, + } + const designed = designedSeatOf(nodes, buildingId, ground.level.id) + if (designed) { + // The offset at which the designed seat holds, so a move by `aboveGrade - offset` reaches it. + const miss = designed.over - designed.asked + return { + ...base, + aboveGrade: offset - miss, + rule: designed.rule, + askedIn: designed.asked / INCH, + seated: designed.over >= -GROUND_SEAT_TOLERANCE && miss <= GROUND_SEAT_TOLERANCE, + atFoundationTop: Math.abs(miss) <= GROUND_SEAT_TOLERANCE, + } + } + const aboveGrade = foundation.construction.seatAboveGrade + return { + ...base, + aboveGrade, + rule: 'construction', + askedIn: aboveGrade / INCH, seated: offset >= -GROUND_SEAT_TOLERANCE && offset <= Math.max(aboveGrade, GROUND_FLOOR_ABOVE_GRADE) + GROUND_SEAT_TOLERANCE, atFoundationTop: Math.abs(offset - aboveGrade) <= GROUND_SEAT_TOLERANCE, } } + +const INCH = 0.0254 + +/** + * How the building's designed foundation seats its ground floor, where one stands under it: what + * it measures over the highest grade along the foundation (`over`: a slab's, a raised floor's or a + * pile-borne floor's top; over a basement, the basement walls' top) and what the record asks of it + * (`asked`: `ffAboveGradeIn`, or `basementTopAboveGradeIn`), metres. `null` where none is designed, + * or it stands under another level than the ground floor. + */ +function designedSeatOf( + nodes: Record, + buildingId: string, + groundLevelId: string, +): { rule: Exclude; over: number; asked: number } | null { + const report = designedFoundationReport(nodes, buildingId) + if (!report) return null + const { foundation } = report + if (report.basement) + return report.basement.groundLevelId === groundLevelId + ? { + rule: 'basement', + over: report.basement.wallTopY - report.highestGradeY, + asked: foundation.basementTopAboveGradeIn * INCH, + } + : null + if (report.levelId !== groundLevelId) return null + return { + rule: foundation.support === 'piles' ? 'piles' : foundation.type, + over: report.floorTopY - report.highestGradeY, + asked: foundation.ffAboveGradeIn * INCH, + } +} diff --git a/packages/core/src/lib/level-footprints.ts b/packages/core/src/lib/level-footprints.ts index d5dfaa665c..c64d3d226a 100644 --- a/packages/core/src/lib/level-footprints.ts +++ b/packages/core/src/lib/level-footprints.ts @@ -1,4 +1,5 @@ import type { WallNode } from '../schema' +import { isRailingWall } from '../schema/nodes/railing-wall' import { getWallSurfacePolygon, isCurvedWall } from '../systems/wall/wall-curve' import { calculateLevelMiters, getWallPlanFootprint } from '../systems/wall/wall-footprint' import { getWallFaceLine } from '../systems/wall/wall-frame' @@ -195,6 +196,15 @@ export function roomWallFootprints(rooms: readonly TopologyRoom[]): MultiPolygon return union(boundaryFootprints(rooms).map(({ polygon }) => polygon)) } +/** The footprints of the railings round `room` (a deck's guard): no house wall among them. */ +export function roomRailingFootprints(room: TopologyRoom): MultiPolygon { + return union( + boundaryFootprints([room]) + .filter(({ id }) => isRailingWall(room.context.walls.get(id))) + .map(({ polygon }) => polygon), + ) +} + export function roomClearPolygon(room: TopologyRoom): MultiPolygon { const cached = clearCache.get(room) if (cached) return cached diff --git a/packages/core/src/lib/opening-casing.test.ts b/packages/core/src/lib/opening-casing.test.ts new file mode 100644 index 0000000000..1aba96fbe6 --- /dev/null +++ b/packages/core/src/lib/opening-casing.test.ts @@ -0,0 +1,164 @@ +import { describe, expect, test } from 'bun:test' +import { type AnyNode, DoorNode, RoofSegmentNode, WallNode, WindowNode } from '../schema' +import { + DEFAULT_OPENING_CASING_WIDTH, + OPENING_CASING_PROUD, + type OpeningCasingBoard, + openingCasingBoards, + openingCasingFaces, + resolveOpeningCasingHost, +} from './opening-casing' + +// Casing once sat inside the doors and windows. Now the cutout is the unit's +// own size and the casing is boards on the finished wall faces around it, on +// both faces unless one is turned off, 2 in wide. + +const INCH = 0.0254 +const host = { thickness: 0.2 } + +const window = (values: Record = {}) => + WindowNode.parse({ id: 'window_c', position: [2, 1.2, 0], width: 1.2, height: 1, ...values }) +const door = (values: Record = {}) => + DoorNode.parse({ id: 'door_c', position: [2, 1.05, 0], width: 0.9, height: 2.1, ...values }) + +const parts = (boards: OpeningCasingBoard[], face: 'a' | 'b') => + boards + .filter((board) => board.face === face) + .map((board) => board.part) + .sort() + +/** A board's extent along one axis of the host frame. */ +const span = (board: OpeningCasingBoard, axis: 0 | 1 | 2) => [ + board.center[axis] - board.size[axis] / 2, + board.center[axis] + board.size[axis] / 2, +] + +describe('opening casing schema', () => { + test('a door or window saved before casing loads cased on both faces, 2 in wide', () => { + for (const legacy of [ + WindowNode.parse({ id: 'window_old', width: 1.2, height: 1 }), + DoorNode.parse({ id: 'door_old', width: 0.9, height: 2.1 }), + ]) { + expect(legacy.casingFaceA).toBe(true) + expect(legacy.casingFaceB).toBe(true) + expect(legacy.casingWidth).toBeCloseTo(2 * INCH, 12) + } + expect(DEFAULT_OPENING_CASING_WIDTH).toBeCloseTo(2 * INCH, 12) + expect(OPENING_CASING_PROUD).toBeCloseTo(0.75 * INCH, 12) + }) + + test('a casing face turned off stays off', () => { + const parsed = WindowNode.parse({ id: 'window_off', casingFaceA: false, casingWidth: 0.09 }) + expect(parsed.casingFaceA).toBe(false) + expect(parsed.casingFaceB).toBe(true) + expect(parsed.casingWidth).toBe(0.09) + }) +}) + +describe('opening casing boards', () => { + test('a window is a picture frame on each face; a door has no board at the floor', () => { + const windowBoards = openingCasingBoards(window({ sill: false }), host) + expect(parts(windowBoards, 'a')).toEqual(['bottom', 'head', 'leg', 'leg']) + expect(parts(windowBoards, 'b')).toEqual(['bottom', 'head', 'leg', 'leg']) + const doorBoards = openingCasingBoards(door(), host) + expect(parts(doorBoards, 'a')).toEqual(['head', 'leg', 'leg']) + expect(parts(doorBoards, 'b')).toEqual(['head', 'leg', 'leg']) + }) + + test('every board stands on a finished face, outside the opening', () => { + for (const opening of [window(), door()]) { + for (const board of openingCasingBoards(opening, host)) { + const [z0, z1] = span(board, 2) + if (board.face === 'a') { + expect(z0).toBeCloseTo(0.1, 12) + expect(z1).toBeCloseTo(0.1 + OPENING_CASING_PROUD, 12) + } else { + expect(z1).toBeCloseTo(-0.1, 12) + expect(z0).toBeCloseTo(-0.1 - OPENING_CASING_PROUD, 12) + } + const [x0, x1] = span(board, 0) + const [y0, y1] = span(board, 1) + const beside = x0 >= opening.width / 2 - 1e-12 || x1 <= -opening.width / 2 + 1e-12 + const aboveOrBelow = y0 >= opening.height / 2 - 1e-12 || y1 <= -opening.height / 2 + 1e-12 + expect(beside || aboveOrBelow).toBe(true) + } + } + }) + + test('the boards are as wide as the casing, and butt: head and bottom run over the legs', () => { + const boards = openingCasingBoards(window({ casingWidth: 3 * INCH }), host) + const head = boards.find((board) => board.part === 'head')! + const leg = boards.find((board) => board.part === 'leg')! + expect(head.size[1]).toBeCloseTo(3 * INCH, 12) + expect(head.size[0]).toBeCloseTo(1.2 + 6 * INCH, 12) + expect(leg.size[0]).toBeCloseTo(3 * INCH, 12) + expect(leg.size[1]).toBeCloseTo(1, 12) + }) + + test('a face turned off has no boards', () => { + const boards = openingCasingBoards(door({ casingFaceB: false }), host) + expect(boards.length).toBeGreaterThan(0) + expect(boards.every((board) => board.face === 'a')).toBe(true) + expect(openingCasingBoards(door({ casingFaceA: false, casingFaceB: false }), host)).toEqual([]) + }) + + test('an opening off the wall centre still cases the wall faces', () => { + const faces = openingCasingFaces(window({ position: [2, 1.2, 0.05] }), host) + expect(faces).toEqual([ + { face: 'a', plane: 0.05, out: 1 }, + { face: 'b', plane: -0.15000000000000002, out: -1 }, + ]) + }) + + test('a sill takes the bottom board under it, on the window’s own front only', () => { + const drop = 0.03 + for (const [rotationY, front] of [ + [0, 'a'], + [Math.PI, 'b'], + ] as const) { + const boards = openingCasingBoards( + window({ sill: true, sillThickness: drop, rotation: [0, rotationY, 0] }), + host, + ) + const bottomTop = (face: 'a' | 'b') => + span(boards.find((board) => board.face === face && board.part === 'bottom')!, 1)[1] + const back = front === 'a' ? 'b' : 'a' + expect(bottomTop(front)).toBeCloseTo(-0.5 - drop, 12) + expect(bottomTop(back)).toBeCloseTo(-0.5, 12) + expect(parts(boards, front)).toEqual(['bottom', 'foot', 'foot', 'head', 'leg', 'leg']) + } + }) + + test('a corner window has no leg at its corner, and its head stops there', () => { + const boards = openingCasingBoards(window(), { ...host, cornerEnd: 'end' }) + const legs = boards.filter((board) => board.face === 'a' && board.part === 'leg') + expect(legs).toHaveLength(1) + expect(legs[0]!.center[0]).toBeLessThan(0) + const head = boards.find((board) => board.face === 'a' && board.part === 'head')! + expect(span(head, 0)[1]).toBeCloseTo(0.6, 12) + }) +}) + +describe('opening casing host', () => { + const wall = WallNode.parse({ id: 'wall_c', start: [0, 0], end: [4, 0], thickness: 0.15 }) + + test('a wall cases on its own thickness; a curtain wall or a scripted unit takes none', () => { + const nodes = { [wall.id]: wall } as Record + expect(resolveOpeningCasingHost(window({ parentId: wall.id }), nodes)).toEqual({ + thickness: 0.15, + }) + const curtain = { ...wall, wallType: 'curtain' as const } + expect(resolveOpeningCasingHost(window({ parentId: wall.id }), { [wall.id]: curtain })).toBe( + null, + ) + expect(resolveOpeningCasingHost(window(), nodes)).toBe(null) + }) + + test('a gable-end opening cases on the roof segment’s wall', () => { + const segment = RoofSegmentNode.parse({ id: 'rseg_c', wallThickness: 0.18 }) + const opening = window({ roofSegmentId: segment.id, roofFace: 'front', parentId: segment.id }) + expect( + resolveOpeningCasingHost(opening, { [segment.id]: segment } as Record), + ).toEqual({ thickness: 0.18 }) + }) +}) diff --git a/packages/core/src/lib/opening-casing.ts b/packages/core/src/lib/opening-casing.ts new file mode 100644 index 0000000000..6a1e442dab --- /dev/null +++ b/packages/core/src/lib/opening-casing.ts @@ -0,0 +1,149 @@ +import type { AnyNode, DoorNode, WallFace, WindowNode } from '../schema' +import { DEFAULT_OPENING_CASING_WIDTH, OPENING_CASING_PROUD } from '../schema/nodes/opening-types' +import { DEFAULT_WALL_THICKNESS, getWallThickness } from '../systems/wall/wall-footprint' +import { cornerPair } from './corner-pair' + +/** + * Door and window casing: flat boards on the finished faces of the host wall + * around the opening, over the drywall or siding. The wall cutout is the unit's + * own width × height and the casing never reaches into it: each board starts at + * the cutout's edge and runs outward, `OPENING_CASING_PROUD` off the face. A + * window gets a picture frame (head, two legs, bottom), a door a head and two + * legs, butted at the corners: the head and bottom run the full width over the + * legs' ends. + * + * Everything here is in the host frame, centred on the opening: x along the + * host toward its end, y up, z across it toward face `a`. The 3D builders turn + * that into the unit's own frame (a unit facing the back is turned half round, + * see `openingHostFacing`); the plan draws it as it is. + */ + +export { DEFAULT_OPENING_CASING_WIDTH, OPENING_CASING_PROUD } + +type CasedOpening = DoorNode | WindowNode +/** From and to along one axis of the host frame (m). */ +type Span = readonly [number, number] + +export type OpeningCasingHost = { + /** The host's total thickness with its finishes: each face is half of it off the mid-plane (m). */ + thickness: number + /** The host end a corner window runs to: no leg there; the head and bottom stop at the opening. */ + cornerEnd?: 'start' | 'end' +} + +export type OpeningCasingFace = { + face: WallFace + /** Across the host, from the opening's centre plane to the finished face (m). */ + plane: number + /** The way the face looks across the host: +1 for `a`, -1 for `b`. */ + out: 1 | -1 +} + +export type OpeningCasingBoard = { + face: WallFace + /** + * `leg` boards stand beside the opening, the part a plan cut shows. A `foot` carries a leg on + * down past a window's sill to the bottom board, which sits under the sill on that face. + */ + part: 'head' | 'leg' | 'foot' | 'bottom' + /** Centre, from the opening's centre in the host frame (m). */ + center: [number, number, number] + /** Along the host, up, across it (m). */ + size: [number, number, number] +} + +/** The face width of an opening's casing boards; a save from before casing has the default. */ +export function openingCasingWidth(opening: Pick): number { + const width = opening.casingWidth + return typeof width === 'number' && width > 0 ? width : DEFAULT_OPENING_CASING_WIDTH +} + +/** + * +1 when the unit faces the host's front (its own +z is face `a`), -1 when it is turned half + * round to face the back, which mirrors its x and z against the host's. + */ +export function openingHostFacing(opening: Pick): 1 | -1 { + return Math.cos(opening.rotation?.[1] ?? 0) >= 0 ? 1 : -1 +} + +/** + * The host an opening is cased on: its wall, or the roof-segment face it is cut into (a gable + * end, whose face `a` is the outside). Null where casing has nowhere to go: a curtain wall, a + * railing (its openings are gaps in the guard), a dormer face, a unit built from a script (its + * script draws its own trim). + */ +export function resolveOpeningCasingHost( + opening: CasedOpening, + nodes: Readonly>, + host: AnyNode | undefined = nodes[opening.roofSegmentId ?? opening.parentId ?? ''], +): OpeningCasingHost | null { + if (opening.source) return null + if (opening.roofSegmentId) { + return host?.type === 'roof-segment' + ? { thickness: host.wallThickness ?? DEFAULT_WALL_THICKNESS } + : null + } + if (host?.type !== 'wall' || host.wallType === 'curtain' || host.wallType === 'railing') + return null + const pair = opening.type === 'window' ? cornerPair(nodes, opening) : null + return { thickness: getWallThickness(host), ...(pair ? { cornerEnd: pair.end } : {}) } +} + +/** The host faces an opening is cased on: both unless turned off (a save from before has both). */ +export function openingCasingFaces( + opening: Pick, + host: OpeningCasingHost, +): OpeningCasingFace[] { + const offset = opening.position[2] ?? 0 + const half = host.thickness / 2 + const faces: OpeningCasingFace[] = [] + if (opening.casingFaceA !== false) faces.push({ face: 'a', plane: half - offset, out: 1 }) + if (opening.casingFaceB !== false) faces.push({ face: 'b', plane: -half - offset, out: -1 }) + return faces +} + +/** How far a window's sill takes the bottom board down on `face`: its thickness, on its front. */ +function sillDrop(opening: CasedOpening, face: WallFace): number { + if (opening.type !== 'window' || !opening.sill || opening.openingKind === 'opening') return 0 + const front: WallFace = openingHostFacing(opening) === 1 ? 'a' : 'b' + return face === front ? Math.max(opening.sillThickness ?? 0, 0) : 0 +} + +/** The casing boards of a rectangular opening on each face it is cased on, in the host frame. */ +export function openingCasingBoards( + opening: CasedOpening, + host: OpeningCasingHost, +): OpeningCasingBoard[] { + const width = openingCasingWidth(opening) + const proud = OPENING_CASING_PROUD + const halfWidth = opening.width / 2 + const halfHeight = opening.height / 2 + const corner = host.cornerEnd === 'start' ? -1 : host.cornerEnd === 'end' ? 1 : 0 + const left = -halfWidth - (corner === -1 ? 0 : width) + const right = halfWidth + (corner === 1 ? 0 : width) + const legs = ([-1, 1] as const) + .filter((side) => side !== corner) + .map( + (side): Span => + side < 0 ? [-halfWidth - width, -halfWidth] : [halfWidth, halfWidth + width], + ) + const boards: OpeningCasingBoard[] = [] + for (const { face, plane, out } of openingCasingFaces(opening, host)) { + const z = plane + (out * proud) / 2 + const board = (part: OpeningCasingBoard['part'], [x0, x1]: Span, [y0, y1]: Span) => + boards.push({ + face, + part, + center: [(x0 + x1) / 2, (y0 + y1) / 2, z], + size: [x1 - x0, y1 - y0, proud], + }) + board('head', [left, right], [halfHeight, halfHeight + width]) + for (const along of legs) board('leg', along, [-halfHeight, halfHeight]) + if (opening.type !== 'window') continue + const drop = sillDrop(opening, face) + if (drop > 1e-6) + for (const along of legs) board('foot', along, [-halfHeight - drop, -halfHeight]) + board('bottom', [left, right], [-halfHeight - drop - width, -halfHeight - drop]) + } + return boards +} diff --git a/packages/core/src/lib/outdoor-room.test.ts b/packages/core/src/lib/outdoor-room.test.ts new file mode 100644 index 0000000000..0a7791a10d --- /dev/null +++ b/packages/core/src/lib/outdoor-room.test.ts @@ -0,0 +1,554 @@ +import { describe, expect, test } from 'bun:test' +import { setZoneIntent } from '../commands/structure/set-zone-intent' +import { applyToScratch, structureChangeBatch } from '../commands/structure/shared' +import { enclosed } from '../commands/structure/structure.test' +import { type AnyNode, type AnyNodeId, type SlabNode, WallNode, type ZoneNode } from '../schema' +import type { BeamNode } from '../schema/nodes/beam' +import type { ColumnNode } from '../schema/nodes/column' +import { railingWallDefaults } from '../schema/nodes/railing-wall' +import { derivedDeletionIntent } from '../store/derived-node-guard' +import { getWallThickness } from '../systems/wall/wall-footprint' +import { planSeparatorInsertion } from '../systems/wall/wall-topology' +import { beamBottomElevation } from './beam' +import { FRAMING_POST_SIZES } from './framing-post' +import { DECK_FLOOR_DROP, DECKING_THICKNESS, PORCH_FLOOR_DROP } from './outdoor-room' +import { PORCH_POST_SETBACK } from './porch-framing' +import { porchPartOf } from './porch-framing-sync' +import { reconcileLevelStructure } from './structure-kernel' +import { reconcileSceneStructure } from './structure-reconcile' + +const LEVEL = 'level_test' + +/** Settle the level's structure the way the live sync does after an edit. */ +function settle(before: Record, after: Record) { + let i = 0 + const derived = reconcileLevelStructure({ + levelId: LEVEL, + nodes: after, + previousNodes: before, + mintId: (kind) => { + let id: string + do id = `${kind}_derived${++i}` + while (after[id]) + return id + }, + }) + return applyToScratch(after, structureChangeBatch(derived.patches)) +} + +/** The kitchen (an 8 × 4 room) with invisible walls closing off a porch on its z = 4 side. */ +function withPorch() { + const { nodes } = enclosed() + let next: Record = nodes + let n = 0 + for (const [start, end] of [ + [ + [2, 4], + [2, 6], + ], + [ + [2, 6], + [6, 6], + ], + [ + [6, 6], + [6, 4], + ], + ] as Array<[[number, number], [number, number]]>) { + const plan = planSeparatorInsertion(next as Record, { + levelId: LEVEL as AnyNodeId, + start, + end, + joinRadius: 0.05, + mintId: () => `separator_test${++n}`, + }) + expect(plan).not.toBeNull() + next = applyToScratch(next, plan!.changes) + } + return settle(nodes, next) +} + +const zones = (nodes: Record) => + Object.values(nodes).filter((node): node is ZoneNode => node.type === 'zone') +const plates = (nodes: Record) => + Object.values(nodes).filter((node): node is SlabNode => node.type === 'slab') + +describe('a room closed off outside the house by invisible walls', () => { + test('is a porch: concrete, a step down, covered', () => { + const nodes = withPorch() + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + expect(porch).toBeDefined() + expect(porch.name).toBe('Porch') + expect(porch.floor?.drop).toBeCloseTo(PORCH_FLOOR_DROP, 9) + expect(porch.hasCeiling).toBeUndefined() + expect(zones(nodes).find((zone) => zone.name === 'Kitchen')?.roomType).toBeUndefined() + }) + + test('its floor plate steps down from the house and is a porch slab', () => { + const nodes = withPorch() + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + const base = plates(nodes).find((plate) => plate.plateRole === 'base')! + const own = plates(nodes).find((plate) => plate.zoneIds?.includes(porch.id) && plate !== base)! + expect(own.elevation).toBeCloseTo(base.elevation - PORCH_FLOOR_DROP, 6) + expect(own.metadata?.floor).toBe('porch-slab') + }) + + test('the house wall it faces is an outside wall there, even under its ceiling', () => { + const nodes = withPorch() + const wall = Object.values(nodes).find( + (node): node is WallNode => node.type === 'wall' && node.start[1] === 4 && node.end[1] === 4, + )! + expect([wall.frontSide, wall.backSide].sort()).toEqual(['exterior', 'interior']) + }) + + test('invisible walls never cut the wall they meet', () => { + const nodes = withPorch() + const walls = Object.values(nodes).filter((node) => node.type === 'wall') + expect(walls).toHaveLength(4) + }) +}) + +/** The kitchen with a room off its z = 4 side: railings on `railed` sides, invisible walls on the rest. */ +function withRailings(railed: ReadonlySet) { + const { nodes } = enclosed() + let next: Record = nodes + const sides: Array<[[number, number], [number, number]]> = [ + [ + [2, 4], + [2, 6], + ], + [ + [2, 6], + [6, 6], + ], + [ + [6, 6], + [6, 4], + ], + ] + sides.forEach(([start, end], index) => { + if (railed.has(index)) { + const railing = WallNode.parse({ + ...railingWallDefaults(), + id: `wall_railing${index}`, + parentId: LEVEL, + start, + end, + }) + next = { ...next, [railing.id]: railing } + return + } + const plan = planSeparatorInsertion(next as Record, { + levelId: LEVEL as AnyNodeId, + start, + end, + joinRadius: 0.05, + mintId: () => `separator_rail${index}`, + }) + next = applyToScratch(next, plan!.changes) + }) + return settle(nodes, next) +} + +describe('a room fenced off outside the house by railings', () => { + test('is a deck: boards, 1 in down, no ceiling, framed', () => { + const nodes = withRailings(new Set([0, 1, 2])) + const deck = zones(nodes).find((zone) => zone.roomType === 'deck')! + expect(deck).toBeDefined() + expect(deck.name).toBe('Deck') + expect(deck.hasCeiling).toBe(false) + expect(deck.floor?.drop).toBeCloseTo(DECK_FLOOR_DROP, 9) + expect( + plates(nodes).find((slab) => slab.zoneIds?.includes(deck.id) && slab.plateRole !== 'base') + ?.metadata?.floor, + ).toBe('deck') + expect( + Object.values(nodes).some((node) => node.type === 'ceiling' && node.zoneId === deck.id), + ).toBe(false) + }) + + test("its railings stand on its own floor: the house's floor stops at the house wall", () => { + const nodes = withRailings(new Set([0, 1, 2])) + const deck = zones(nodes).find((zone) => zone.roomType === 'deck')! + const base = plates(nodes).find((plate) => plate.plateRole === 'base')! + const own = plates(nodes).find( + (plate) => plate.plateRole !== 'base' && plate.zoneIds?.includes(deck.id), + )! + const houseWall = Object.values(nodes).find( + (node): node is WallNode => + node.type === 'wall' && + node.wallType !== 'railing' && + node.start[1] === 4 && + node.end[1] === 4, + )! + const railing = railingWallDefaults().thickness + const bounds = (polygon: Array<[number, number]>) => ({ + minX: Math.min(...polygon.map(([x]) => x)), + maxX: Math.max(...polygon.map(([x]) => x)), + minZ: Math.min(...polygon.map(([, z]) => z)), + maxZ: Math.max(...polygon.map(([, z]) => z)), + }) + // No ring of house floor under the railings, and no hole cut for the deck in it. + const face = 4 + getWallThickness(houseWall) / 2 + expect(bounds(base.polygon).maxZ).toBeCloseTo(face, 3) + expect(base.holes).toEqual([]) + // The deck's floor runs from the house wall's face out to the railings' outside faces. + const deckBounds = bounds(own.polygon) + expect(deckBounds.minZ).toBeCloseTo(face, 3) + expect(deckBounds.maxZ).toBeCloseTo(6 + railing / 2, 3) + expect(deckBounds.minX).toBeCloseTo(2 - railing / 2, 3) + expect(deckBounds.maxX).toBeCloseTo(6 + railing / 2, 3) + }) + + test('closed with invisible walls as well, it is a porch, posts and beam along its railings too', () => { + const nodes = withRailings(new Set([0, 2])) + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + expect(porch).toBeDefined() + expect( + Object.values(nodes).filter( + (node) => node.type === 'beam' && porchPartOf(node)?.porchOf === porch.id, + ), + ).toHaveLength(3) + }) +}) + +describe('room type', () => { + test('a porch made a deck steps 1 in down, takes boards, loses its ceiling and its name', () => { + const nodes = withPorch() + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + const plan = setZoneIntent(nodes, { zoneId: porch.id, patch: { roomType: 'deck' } }) + expect(plan.conflicts).toBeUndefined() + const changed = applyToScratch(nodes, structureChangeBatch(plan.changes)) + const after = settle(nodes, changed) + const deck = after[porch.id] as ZoneNode + expect(deck.roomType).toBe('deck') + expect(deck.name).toBe('Deck') + expect(deck.hasCeiling).toBe(false) + expect(deck.floor?.drop).toBeCloseTo(DECK_FLOOR_DROP, 9) + expect(deck.floor?.finish).toBe('library:wood-woodfine11') + const plate = plates(after).find( + (slab) => slab.zoneIds?.includes(deck.id) && slab.plateRole !== 'base', + )! + expect(plate.metadata?.floor).toBe('deck') + expect(plate.metadata?.decking).toBeCloseTo(DECKING_THICKNESS, 9) + }) + + test('back to an ordinary room drops what the type brought, keeping a picked finish', () => { + const nodes = withPorch() + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + const picked = applyToScratch( + nodes, + structureChangeBatch( + setZoneIntent(nodes, { + zoneId: porch.id, + patch: { floor: { finish: 'library:tile-floor1' } }, + }).changes, + ), + ) + const plan = setZoneIntent(picked, { zoneId: porch.id, patch: { roomType: null } }) + const room = applyToScratch(picked, structureChangeBatch(plan.changes))[porch.id] as ZoneNode + expect(room.roomType).toBeUndefined() + expect(room.floor?.drop).toBeUndefined() + expect(room.floor?.finish).toBe('library:tile-floor1') + expect(room.hasCeiling).toBeUndefined() + }) +}) + +describe('planSeparatorInsertion', () => { + test('joins a wall it ends on at its reference line and breaks across a wall it crosses', () => { + const { nodes } = enclosed() + // From inside the kitchen, through its z = 4 wall, to 2 m outside. + const plan = planSeparatorInsertion(nodes as Record, { + levelId: LEVEL as AnyNodeId, + start: [3, 0.03], + end: [3, 6], + joinRadius: 0.05, + })! + expect(plan.resolvedStart).toEqual([3, 0]) + expect(plan.separators.map((separator) => [separator.start, separator.end])).toEqual([ + [ + [3, 0], + [3, 4], + ], + [ + [3, 4], + [3, 6], + ], + ]) + expect(plan.changes.update).toEqual([]) + expect(plan.changes.delete).toEqual([]) + }) + + test('drawn over an invisible wall or along a wall, adds only what is not there', () => { + const nodes = withPorch() as Record + const draw = (start: [number, number], end: [number, number]) => + planSeparatorInsertion(nodes, { levelId: LEVEL as AnyNodeId, start, end, joinRadius: 0.05 }) + expect(draw([2, 6], [6, 6])).toBeNull() + expect(draw([6, 6], [2, 6])).toBeNull() + expect(draw([1, 0], [3, 0])).toBeNull() + expect( + draw([0, 6], [6, 6])!.separators.map((separator) => [separator.start, separator.end]), + ).toEqual([ + [ + [0, 6], + [2, 6], + ], + ]) + }) +}) + +const porchOf = (nodes: Record) => + zones(nodes).find((zone) => zone.roomType === 'porch')! +const posts = (nodes: Record, zoneId: string) => + Object.values(nodes).filter( + (node): node is ColumnNode => node.type === 'column' && porchPartOf(node)?.porchOf === zoneId, + ) +const beams = (nodes: Record, zoneId: string) => + Object.values(nodes).filter( + (node): node is BeamNode => node.type === 'beam' && porchPartOf(node)?.porchOf === zoneId, + ) +const edit = (nodes: Record, id: string, data: Partial) => + ({ ...nodes, [id]: { ...nodes[id], ...data } }) as Record +const intent = ( + nodes: Record, + zoneId: string, + patch: Parameters[1]['patch'], +) => + settle( + nodes, + applyToScratch(nodes, structureChangeBatch(setZoneIntent(nodes, { zoneId, patch }).changes)), + ) +const inset = FRAMING_POST_SIZES['6x6'] / 2 + PORCH_POST_SETBACK + +describe('a covered porch frames itself', () => { + test('posts on its outside corners and between, a beam along each open side', () => { + const nodes = withPorch() + const porch = porchOf(nodes) + const standing = posts(nodes, porch.id) + // The 4 m front: a post at each corner and one between; the 2 m sides need none between. + expect(standing.map((post) => [post.position[0], post.position[2]]).sort()).toEqual( + [ + [2 + inset, 6 - inset], + [4, 6 - inset], + [6 - inset, 6 - inset], + ].map(([x, z]) => [expect.closeTo(x!, 9), expect.closeTo(z!, 9)]) as never, + ) + expect(beams(nodes, porch.id)).toHaveLength(3) + expect( + standing.every((post) => post.role === 'post' && post.width === FRAMING_POST_SIZES['6x6']), + ).toBe(true) + }) + + test('the posts stand on the porch slab, up to the underside of the beam', () => { + const nodes = withPorch() + const porch = porchOf(nodes) + const plate = plates(nodes).find( + (slab) => slab.zoneIds?.includes(porch.id) && slab.plateRole !== 'base', + )! + const front = beams(nodes, porch.id).find((beam) => beam.start[1] === beam.end[1])! + for (const post of posts(nodes, porch.id)) { + expect(post.supportSlabId).toBe(plate.id) + expect(plate.elevation + post.height).toBeCloseTo( + beamBottomElevation(nodes as never, front), + 6, + ) + } + }) + + test('moves with the porch', () => { + const before = withPorch() + const porch = porchOf(before) + let moved: Record = before + for (const node of Object.values(before)) + if (node.type === 'separator') + moved = edit(moved, node.id, { + start: [node.start[0], node.start[1] === 6 ? 6.5 : node.start[1]], + end: [node.end[0], node.end[1] === 6 ? 6.5 : node.end[1]], + } as never) + const after = settle(before, moved) + expect(porchOf(after).id).toBe(porch.id) + expect(posts(after, porch.id).map((post) => post.position[2])).toEqual( + [6.5 - inset, 6.5 - inset, 6.5 - inset].map((z) => expect.closeTo(z, 9)) as never, + ) + expect(new Set(posts(after, porch.id).map((post) => post.id))).toEqual( + new Set(posts(before, porch.id).map((post) => post.id)), + ) + }) + + test('a post moved by hand hands the framing over; Automatic again rebuilds it', () => { + const before = withPorch() + const porch = porchOf(before) + const middle = posts(before, porch.id).find((post) => Math.abs(post.position[0] - 4) < 1e-6)! + const moved = settle( + before, + edit(before, middle.id, { position: [4.5, 0, middle.position[2]] } as never), + ) + expect((moved[porch.id] as ZoneNode).porchFraming?.auto).toBe(false) + expect((moved[middle.id] as ColumnNode).position[0]).toBe(4.5) + const rebuilt = intent(moved, porch.id, { porchFraming: { auto: true } }) + expect((rebuilt[porch.id] as ZoneNode).porchFraming).toBeUndefined() + expect((rebuilt[middle.id] as ColumnNode).position[0]).toBeCloseTo(4, 9) + }) + + test('a post deleted by hand stays deleted', () => { + const before = withPorch() + const porch = porchOf(before) + const middle = posts(before, porch.id).find((post) => Math.abs(post.position[0] - 4) < 1e-6)! + const { [middle.id]: _gone, ...rest } = before + const after = settle(before, rest) + expect((after[porch.id] as ZoneNode).porchFraming?.auto).toBe(false) + expect(posts(after, porch.id)).toHaveLength(2) + }) + + test('closer post spacing and a bigger beam rebuild it', () => { + const before = withPorch() + const porch = porchOf(before) + const after = intent(before, porch.id, { porchFraming: { postSpacing: 1, beamSize: '6x10' } }) + expect(posts(after, porch.id).length).toBeGreaterThan(posts(before, porch.id).length) + expect(beams(after, porch.id).every((beam) => beam.size === '6x10')).toBe(true) + }) + + test('a deck or an uncovered porch has none', () => { + const before = withPorch() + const porch = porchOf(before) + const deck = intent(before, porch.id, { roomType: 'deck' }) + expect(posts(deck, porch.id)).toHaveLength(0) + expect(beams(deck, porch.id)).toHaveLength(0) + const open = intent(before, porch.id, { hasCeiling: false }) + expect(posts(open, porch.id)).toHaveLength(0) + }) +}) + +describe('a deck or patio carries a roof on posts once switched on', () => { + const deckOf = (nodes: Record) => + zones(nodes).find((zone) => zone.roomType === 'deck')! + + test('a deck fenced by railings has none until its posts and beam are on', () => { + const before = withRailings(new Set([0, 1, 2])) + const deck = deckOf(before) + expect(posts(before, deck.id)).toHaveLength(0) + const on = intent(before, deck.id, { porchFraming: { auto: true } }) + expect((on[deck.id] as ZoneNode).porchFraming?.auto).toBe(true) + // The 4 m railing: a post at each outside corner and one between; a beam on each railing. + expect(posts(on, deck.id)).toHaveLength(3) + expect(beams(on, deck.id)).toHaveLength(3) + const plate = plates(on).find( + (slab) => slab.zoneIds?.includes(deck.id) && slab.plateRole !== 'base', + )! + expect(posts(on, deck.id).every((post) => post.supportSlabId === plate.id)).toBe(true) + }) + + test('a size chosen rebuilds them automatically; Off takes them all, edited or not', () => { + const before = withRailings(new Set([0, 1, 2])) + const deck = deckOf(before) + const on = intent(before, deck.id, { porchFraming: { auto: true } }) + const middle = posts(on, deck.id).find((post) => Math.abs(post.position[0] - 4) < 1e-6)! + const moved = settle( + on, + edit(on, middle.id, { position: [4.5, 0, middle.position[2]] } as never), + ) + expect((moved[deck.id] as ZoneNode).porchFraming?.auto).toBe(false) + const resized = intent(moved, deck.id, { porchFraming: { postSize: '4x4' } }) + expect((resized[deck.id] as ZoneNode).porchFraming?.auto).toBe(true) + expect(posts(resized, deck.id).every((post) => post.width === FRAMING_POST_SIZES['4x4'])).toBe( + true, + ) + const handEdited = settle( + resized, + edit(resized, middle.id, { position: [4.5, 0, middle.position[2]] } as never), + ) + const off = intent(handEdited, deck.id, { porchFraming: { auto: null } }) + expect((off[deck.id] as ZoneNode).porchFraming?.auto).toBeUndefined() + expect( + Object.values(off).filter((node) => node.type === 'column' || node.type === 'beam'), + ).toHaveLength(0) + }) + + test('a porch keeps Automatic as its default, stored only when off', () => { + const before = withPorch() + const porch = porchOf(before) + const resized = intent(before, porch.id, { porchFraming: { postSize: '8x8' } }) + expect((resized[porch.id] as ZoneNode).porchFraming?.auto).toBeUndefined() + expect(posts(resized, porch.id).every((post) => post.width === FRAMING_POST_SIZES['8x8'])).toBe( + true, + ) + }) +}) + +describe('deleting a floor plate', () => { + /** The porch off the kitchen's z = 4 side and a deck fenced with railings off its z = 0 side, settled. */ + const withPorchAndDeck = () => { + const porch = withPorch() + const railed: Record = { ...porch } + for (const [index, [start, end]] of ( + [ + [ + [2, 0], + [2, -2], + ], + [ + [2, -2], + [6, -2], + ], + [ + [6, -2], + [6, 0], + ], + ] as Array<[[number, number], [number, number]]> + ).entries()) { + const railing = WallNode.parse({ + ...railingWallDefaults(), + id: `wall_deckrail${index}`, + parentId: LEVEL, + start, + end, + }) + railed[railing.id] = railing + } + return settle(porch, railed) + } + const sceneSync = (before: Record, after: Record) => { + let i = 0 + return reconcileSceneStructure({ + nodes: after, + previousNodes: before, + mintId: (kind) => `${kind}_sync${++i}`, + }).nodes as Record + } + + test("the house's shared plate is not a porch's or a deck's floor: they keep theirs", () => { + const nodes = withPorchAndDeck() + const base = plates(nodes).find((plate) => plate.plateRole === 'base')! + const outdoor = zones(nodes).filter((zone) => zone.roomType) + expect(outdoor.map((zone) => zone.roomType).sort()).toEqual(['deck', 'porch']) + expect(outdoor.every((zone) => base.zoneIds?.includes(zone.id))).toBe(true) + // The store's delete intent and the reconciler's both switch off the kitchen's floor alone. + expect(derivedDeletionIntent(nodes as never, [base.id as AnyNodeId])).toEqual([ + { id: zones(nodes).find((zone) => zone.name === 'Kitchen')!.id, data: { hasFloor: false } }, + ] as never) + const { [base.id]: _gone, ...rest } = nodes + const after = sceneSync(nodes, rest) + for (const zone of outdoor) { + expect((after[zone.id] as ZoneNode).hasFloor).toBeUndefined() + expect( + plates(after).some( + (plate) => plate.plateRole !== 'base' && plate.zoneIds?.includes(zone.id), + ), + ).toBe(true) + } + expect( + (after[zones(nodes).find((zone) => zone.name === 'Kitchen')!.id] as ZoneNode).hasFloor, + ).toBe(false) + }) + + test("a porch's own plate deleted takes the porch's floor only", () => { + const nodes = withPorchAndDeck() + const porch = zones(nodes).find((zone) => zone.roomType === 'porch')! + const own = plates(nodes).find( + (plate) => plate.plateRole !== 'base' && plate.zoneIds?.includes(porch.id), + )! + expect(derivedDeletionIntent(nodes as never, [own.id as AnyNodeId])).toEqual([ + { id: porch.id, data: { hasFloor: false } }, + ] as never) + }) +}) diff --git a/packages/core/src/lib/outdoor-room.ts b/packages/core/src/lib/outdoor-room.ts new file mode 100644 index 0000000000..6bf36c0adb --- /dev/null +++ b/packages/core/src/lib/outdoor-room.ts @@ -0,0 +1,90 @@ +import type { ZoneNode } from '../schema' +import { DEFAULT_DECK_BOARDS, deckBoardThickness } from './deck-boards' + +/** + * Rooms outside the house, the outdoor room types: a porch (a covered floor a step + * down from the house), a deck (a framed floor of boards) and a patio (an uncovered slab). A room + * of one of these types is outdoors whatever covers it, so the house walls it faces are exterior + * walls even under a porch ceiling, and its floor steps down from the house's (`floor.drop`). + * + * A room closed off outside the house by invisible walls starts as a porch: its floor a concrete + * slab 1.5 in under the house's and its ceiling at the house's, both adjustable in its panel. + */ + +const IN = 0.0254 + +export const OUTDOOR_ROOM_TYPES = ['porch', 'deck', 'patio'] as const +export type OutdoorRoomType = (typeof OUTDOOR_ROOM_TYPES)[number] + +/** A porch or patio slab steps down from the house's finish floor by 1.5 in at most. */ +export const PORCH_FLOOR_DROP = 1.5 * IN +/** Deck boards ride 1 in under the threshold so water sheds away from the door. */ +export const DECK_FLOOR_DROP = 1 * IN +/** + * The deck boards on a deck's floor unless its room says otherwise (5/4x6, 1 in: `lib/deck-boards`); + * the framing hangs under them. + */ +export const DECKING_THICKNESS = deckBoardThickness(DEFAULT_DECK_BOARDS) + +export type OutdoorRoomDefaults = { + /** The name a room of the type is given while it has a default name. */ + name: string + drop: number + /** The floor finish it starts with. */ + finish: string + /** Whether it has a ceiling (a covered porch). */ + covered: boolean + /** How the framing plugin reads its floor plate (`metadata.floor`). */ + floorTag: 'porch-slab' | 'deck' +} + +export const OUTDOOR_ROOM_DEFAULTS: Record = { + porch: { + name: 'Porch', + drop: PORCH_FLOOR_DROP, + finish: 'library:concrete-raw', + covered: true, + floorTag: 'porch-slab', + }, + deck: { + name: 'Deck', + drop: DECK_FLOOR_DROP, + finish: 'library:wood-woodfine11', + covered: false, + floorTag: 'deck', + }, + patio: { + name: 'Patio', + drop: PORCH_FLOOR_DROP, + finish: 'library:concrete-raw', + covered: false, + floorTag: 'porch-slab', + }, +} + +export function isOutdoorRoomType(value: unknown): value is OutdoorRoomType { + return typeof value === 'string' && (OUTDOOR_ROOM_TYPES as readonly string[]).includes(value) +} + +/** Whether a room is one of the outdoor room types. */ +export function isOutdoorRoom(zone: Pick): boolean { + return isOutdoorRoomType(zone.roomType) +} + +/** "Porch", "Porch 2", …: the next free name for a room of `type` among `names`. */ +export function nextOutdoorRoomName(type: OutdoorRoomType, names: Iterable): string { + const base = OUTDOOR_ROOM_DEFAULTS[type].name + const taken = new Set(names) + if (!taken.has(base)) return base + for (let n = 2; ; n++) if (!taken.has(`${base} ${n}`)) return `${base} ${n}` +} + +/** Whether `name` is a type's default name ("Deck", "Deck 3"), so a type change may rename it. */ +export function isOutdoorRoomDefaultName(name: string | undefined): boolean { + return ( + typeof name === 'string' && + OUTDOOR_ROOM_TYPES.some((type) => + new RegExp(`^${OUTDOOR_ROOM_DEFAULTS[type].name}( \\d+)?$`).test(name), + ) + ) +} diff --git a/packages/core/src/lib/plate-surface.ts b/packages/core/src/lib/plate-surface.ts index 5a483a38a5..9de778f530 100644 --- a/packages/core/src/lib/plate-surface.ts +++ b/packages/core/src/lib/plate-surface.ts @@ -50,6 +50,9 @@ import { roomFloorPlate } from './room-floor-plate' * needs and never runs per frame. */ +/** What a room on a slab of its own (`floor.foundation: 'slab'`, a garage) shows with no finish of its own. */ +export const SLAB_ROOM_FLOOR_FINISH = 'library:concrete-raw' + /** A zone floor finish: a `MaterialRef` string, or an inline legacy material. */ export type PlateFinish = string | Record @@ -224,6 +227,7 @@ function zoneSignature(zones: readonly ZoneNode[]): string { zone.polygon, zone.holes, zone.floor?.finish ?? null, + zone.floor?.foundation ?? null, zone.floor?.script !== undefined, zone.floorStepFinish, zone.floorStepOverrides ?? null, @@ -483,7 +487,9 @@ function partitionTop( } for (const zone of zones) { const clear = clearByZone.get(zone.id) - const finish = zone.floor?.finish + // A room on a slab of its own (a garage) is bare concrete until a finish is chosen for it. + const finish = + zone.floor?.finish ?? (zone.floor?.foundation === 'slab' ? SLAB_ROOM_FLOOR_FINISH : undefined) // A scripted floor is the room's own cell, so a hit on it names the room its script belongs to. if (!clear || (finish === undefined && !zone.floor?.script && !plate.plateRole)) continue candidates.push({ role: roomFinishRole(zone.id), finish, polygons: clear }) diff --git a/packages/core/src/lib/porch-framing-sync.ts b/packages/core/src/lib/porch-framing-sync.ts new file mode 100644 index 0000000000..9444d10d28 --- /dev/null +++ b/packages/core/src/lib/porch-framing-sync.ts @@ -0,0 +1,272 @@ +import type { AnyNode, AnyNodeId, SlabNode, ZoneNode } from '../schema' +import type { BeamNode } from '../schema/nodes/beam' +import type { ColumnNode } from '../schema/nodes/column' +import { beamBottomElevation, createBeam } from './beam' +import { + createFramingPost, + FRAMING_POST_MIN_HEIGHT, + framingPostFields, + framingPostSize, +} from './framing-post' +import { type PorchFramingPlan, planPorchFraming, porchFramingSettings } from './porch-framing' + +/** + * A porch's posts and beams (`lib/porch-framing`), kept by the structure kernel as it keeps a + * room's floor and ceiling: made when a covered porch has an open side, moved with the porch, + * rebuilt when its framing settings change, removed when it stops being a covered porch. A deck + * or patio gets the same once they are switched on, to carry a roof over it. They are ordinary + * post and beam nodes. Moving, resizing or deleting one hands the room's framing to the user + * (`porchFraming.auto: false`): framing edited by hand stops rebuilding; turning it back on + * rebuilds it. + */ + +type Nodes = Readonly> +type PorchPart = ColumnNode | BeamNode + +/** What the kernel stamps on a porch part: the porch, which part, and what it made it as. */ +export type PorchPartMetadata = { porchOf: string; porchPart: string; porchSeed: string } + +/** The porch a post or beam was made for, when the kernel made it. */ +export function porchPartOf(node: AnyNode | null | undefined): PorchPartMetadata | null { + if (node?.type !== 'column' && node?.type !== 'beam') return null + const metadata = node.metadata as Partial | undefined + return typeof metadata?.porchOf === 'string' && typeof metadata.porchPart === 'string' + ? (metadata as PorchPartMetadata) + : null +} + +const round = (value: number) => Math.round(value * 1e6) / 1e6 + +/** A part's shape, as it was made: anything else is a hand edit. */ +function seedOf(part: PorchPart): string { + if (part.type === 'column') + return JSON.stringify( + [part.position[0], part.position[2], part.width, part.depth, part.height, part.rotation].map( + round, + ), + ) + return JSON.stringify([ + ...[...part.start, ...part.end, part.width, part.depth].map(round), + part.topElevation === undefined ? null : round(part.topElevation), + part.placement, + ]) +} + +/** + * A porch part's id and shape, for the structure sync to notice a hand edit or a delete (and run + * the kernel, which hands the porch's framing over); null for any other node. + */ +export function porchPartSignature(node: AnyNode | null | undefined): string | null { + return porchPartOf(node) ? JSON.stringify([node!.id, seedOf(node as PorchPart)]) : null +} + +function edited(part: PorchPart): boolean { + return seedOf(part) !== porchPartOf(part)?.porchSeed +} + +/** + * A room the kernel frames, still closed and its framing automatic: a covered porch (automatic + * unless switched off), or a deck or patio whose posts and beam are switched on (`auto: true`, to + * carry a roof over it; off by default). + */ +function framedPorch(zone: ZoneNode | undefined): boolean { + if (!zone || zone.enclosureStatus === 'open') return false + if (zone.roomType === 'porch') + return zone.hasCeiling !== false && zone.porchFraming?.auto !== false + return coverOptional(zone) && zone.porchFraming?.auto === true +} + +/** A deck or patio: its posts and beam are off until switched on. */ +function coverOptional(zone: Pick): boolean { + return zone.roomType === 'deck' || zone.roomType === 'patio' +} + +/** The floor plate a porch's posts stand on: its own step-down plate, else the one it shares. */ +function porchPlate(zone: ZoneNode, plates: readonly SlabNode[]): SlabNode | undefined { + const holding = plates.filter((plate) => plate.zoneIds?.includes(zone.id)) + return holding.find((plate) => plate.plateRole !== 'base') ?? holding[0] +} + +const yawOf = (start: readonly [number, number], end: readonly [number, number]) => + Math.atan2(-(end[1] - start[1]), end[0] - start[0]) + +function withPorch(node: PorchPart, zone: ZoneNode, key: string): PorchPart { + const metadata: PorchPartMetadata = { porchOf: zone.id, porchPart: key, porchSeed: '' } + const tagged = { ...node, metadata: { ...node.metadata, ...metadata } } as PorchPart + ;(tagged.metadata as PorchPartMetadata).porchSeed = seedOf(tagged) + return tagged +} + +/** The porch's planned parts as nodes, reusing the ids (and the touches) of the ones it has. */ +function buildParts( + zone: ZoneNode, + plan: PorchFramingPlan, + owned: readonly PorchPart[], + plate: SlabNode | undefined, + levelId: string, + nodes: Nodes, + mintId: (kind: 'column' | 'beam') => string, +): PorchPart[] { + const settings = porchFramingSettings(zone) + const byKey = new Map(owned.map((part) => [porchPartOf(part)!.porchPart, part])) + const beams = new Map() + for (const planned of plan.beams) { + const existing = byKey.get(planned.key) + const fresh = createBeam({ + start: planned.start, + end: planned.end, + preset: settings.beamSize, + levelId, + supportSlabId: plate?.id, + name: `${zone.name} beam`, + }) + const beam: BeamNode = + existing?.type === 'beam' + ? { + ...existing, + start: fresh.start, + end: fresh.end, + width: fresh.width, + depth: fresh.depth, + size: fresh.size, + plies: fresh.plies, + material: fresh.material, + placement: 'dropped', + topElevation: undefined, + supportSlabId: plate?.id, + } + : { ...fresh, id: mintId('beam') as BeamNode['id'] } + beams.set(planned.key, withPorch(beam, zone, planned.key) as BeamNode) + } + const foot = plate?.elevation ?? 0 + const posts = plan.posts.map((planned) => { + // Up to the underside of the beam it carries (the lower of two at a corner). + const bottoms = planned.beams.flatMap((key) => { + const beam = beams.get(key) + return beam ? [beamBottomElevation(nodes as never, beam)] : [] + }) + const height = Math.max(FRAMING_POST_MIN_HEIGHT, Math.min(...bottoms) - foot) + const run = plan.beams.find((beam) => beam.key === planned.beams.at(-1)) + const rotation = run ? yawOf(run.start, run.end) : 0 + const existing = byKey.get(planned.key) + const position: [number, number, number] = [planned.position[0], 0, planned.position[1]] + const post: ColumnNode = + existing?.type === 'column' + ? { + ...existing, + ...(framingPostSize(existing) === settings.postSize + ? {} + : framingPostFields(settings.postSize, existing.postBase ?? 'post-base')), + position, + rotation, + height, + supportSlabId: plate?.id, + } + : { + ...createFramingPost({ + position: planned.position, + size: settings.postSize, + base: 'post-base', + height, + levelId, + supportSlabId: plate?.id, + rotation, + name: `${zone.name} post`, + }), + id: mintId('column') as ColumnNode['id'], + } + return withPorch(post, zone, planned.key) + }) + return [...beams.values(), ...posts] +} + +/** + * The kernel's porch framing pass over one level: which posts and beams to put and delete, and + * the porches whose framing a hand edit has handed to the user. `zones` are the level's rooms as + * the pass leaves them, `plates` its floor plates, `current` a node as the pass has it so far; + * `previousNodes` (the scene before the edit) tells a deleted part from one never made. + */ +export function reconcilePorchFraming({ + levelId, + nodes, + previousNodes, + zones, + plates, + current, + mintId, +}: { + levelId: string + nodes: Nodes + previousNodes?: Nodes + zones: readonly ZoneNode[] + plates: readonly SlabNode[] + current: (id: string) => AnyNode | undefined + mintId: (kind: 'column' | 'beam') => string +}): { put: AnyNode[]; delete: AnyNodeId[] } { + const put: AnyNode[] = [] + const remove: AnyNodeId[] = [] + const owned = new Map() + for (const node of Object.values(nodes)) { + const part = porchPartOf(node) + if (!part || node.parentId !== levelId) continue + const latest = (current(node.id) ?? node) as PorchPart + owned.set(part.porchOf, [...(owned.get(part.porchOf) ?? []), latest]) + } + const zonesById = new Map(zones.map((zone) => [zone.id, zone])) + // A porch gone, or no longer a covered one: what it made goes with it; a part edited by hand + // stays, the user's own. A deck's or patio's switched off goes whole, edited or not. + for (const [zoneId, parts] of owned) { + const zone = zonesById.get(zoneId as ZoneNode['id']) + if (framedPorch(zone) || zone?.porchFraming?.auto === false) continue + const switchedOff = !!zone && coverOptional(zone) && zone.porchFraming?.auto === undefined + for (const part of parts) { + if (switchedOff || !edited(part)) remove.push(part.id) + else { + const { + porchOf: _porchOf, + porchPart: _porchPart, + porchSeed: _seed, + ...metadata + } = part.metadata as PorchPartMetadata & Record + put.push({ ...part, metadata } as AnyNode) + } + } + } + for (const zone of zones) { + if (!framedPorch(zone)) continue + const parts = owned.get(zone.id) ?? [] + const before = previousNodes?.[zone.id] + const settingsChanged = + before?.type !== 'zone' || + before.roomType !== zone.roomType || + JSON.stringify(before.porchFraming ?? null) !== JSON.stringify(zone.porchFraming ?? null) + const plan = planPorchFraming(zone, nodes) + if (!settingsChanged) { + // A part of the plan gone since the edit before: deleted by hand. (One the plan dropped + // went with an earlier pass.) + const planned = new Set([...plan.posts, ...plan.beams].map((part) => part.key)) + const deleted = Object.values(previousNodes ?? {}).some((node) => { + const part = porchPartOf(node) + return part?.porchOf === zone.id && planned.has(part.porchPart) && !nodes[node.id] + }) + if (deleted || parts.some(edited)) { + put.push({ ...zone, porchFraming: { ...zone.porchFraming, auto: false } }) + continue + } + } + const built = buildParts( + zone, + plan, + // Settings changed: what a hand made goes too; the porch is rebuilt from its settings. + parts, + porchPlate(zone, plates), + levelId, + nodes, + mintId, + ) + const kept = new Set(built.map((part) => part.id)) + for (const part of parts) if (!kept.has(part.id)) remove.push(part.id) + put.push(...built) + } + return { put, delete: remove } +} diff --git a/packages/core/src/lib/porch-framing.test.ts b/packages/core/src/lib/porch-framing.test.ts new file mode 100644 index 0000000000..149416b353 --- /dev/null +++ b/packages/core/src/lib/porch-framing.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, test } from 'bun:test' +import { type AnyNode, WallNode } from '../schema' +import { FRAMING_POST_SIZES } from './framing-post' +import { PORCH_POST_SETBACK, planPorchFraming } from './porch-framing' + +const LEVEL = 'level_porch' +const wall = (id: string, start: [number, number], end: [number, number]) => + WallNode.parse({ id: `wall_${id}`, parentId: LEVEL, start, end, thickness: 0.15 }) +const nodes = (...walls: WallNode[]): Record => + Object.fromEntries(walls.map((node) => [node.id, node])) + +// A porch in a corner of the house: house walls along z = 0 and x = 0, open on the other two sides. +const corner = { + parentId: LEVEL, + polygon: [ + [0, 0], + [3, 0], + [3, 2.5], + [0, 2.5], + ] as Array<[number, number]>, +} +const house = nodes(wall('south', [0, 0], [6, 0]), wall('west', [0, 6], [0, 0])) +const inset = FRAMING_POST_SIZES['6x6'] / 2 + PORCH_POST_SETBACK + +describe('planPorchFraming', () => { + test('a porch in a corner of the house gets its one corner post', () => { + const { posts } = planPorchFraming(corner, house) + const cornerPost = posts.find((post) => post.key.startsWith('corner:'))! + expect(cornerPost.position[0]).toBeCloseTo(3 - inset, 9) + expect(cornerPost.position[1]).toBeCloseTo(2.5 - inset, 9) + expect(posts.filter((post) => post.key.startsWith('corner:'))).toHaveLength(1) + }) + + test('a side longer than the spacing gets posts between, evenly; none against the house', () => { + const { posts } = planPorchFraming(corner, house) + // The 3 m side reaches 2.8 m from the west wall's face to the corner post: two bays at 8 ft. + const between = posts.filter((post) => post.key.startsWith('side:')) + expect(between).toHaveLength(1) + const start = 0.075 + const end = 3 - inset + expect(between[0]!.position[0]).toBeCloseTo((start + end) / 2, 9) + expect(between[0]!.position[1]).toBeCloseTo(2.5 - inset, 9) + expect(posts.every((post) => post.position[0] > 0.07 && post.position[1] > 0.07)).toBe(true) + }) + + test('a beam runs along each open side, from the house wall face to the corner post', () => { + const { beams } = planPorchFraming(corner, house) + expect(beams).toHaveLength(2) + const east = beams.find((beam) => Math.abs(beam.start[0] - beam.end[0]) < 1e-9)! + expect(east.start[0]).toBeCloseTo(3 - inset, 9) + expect(Math.min(east.start[1], east.end[1])).toBeCloseTo(0.075, 9) + // It reaches the corner post's outside face. + expect(Math.max(east.start[1], east.end[1])).toBeCloseTo( + 2.5 - inset + FRAMING_POST_SIZES['6x6'] / 2, + 9, + ) + }) + + test('closer post spacing adds posts; a walled-in room has none', () => { + const tight = planPorchFraming({ ...corner, porchFraming: { postSpacing: 1 } }, house) + expect(tight.posts.length).toBeGreaterThan(planPorchFraming(corner, house).posts.length) + const closed = planPorchFraming( + corner, + nodes( + wall('south', [0, 0], [3, 0]), + wall('east', [3, 0], [3, 2.5]), + wall('north', [3, 2.5], [0, 2.5]), + wall('west', [0, 2.5], [0, 0]), + ), + ) + expect(closed).toEqual({ posts: [], beams: [] }) + }) + + test('a railing side still carries posts and the beam', () => { + const railing = { + ...wall('rail', [3, 0], [3, 2.5]), + wallType: 'railing', + } as unknown as WallNode + const { beams } = planPorchFraming(corner, nodes(...Object.values(house), railing) as never) + expect(beams).toHaveLength(2) + }) +}) diff --git a/packages/core/src/lib/porch-framing.ts b/packages/core/src/lib/porch-framing.ts new file mode 100644 index 0000000000..6263007f59 --- /dev/null +++ b/packages/core/src/lib/porch-framing.ts @@ -0,0 +1,200 @@ +import type { AnyNode, WallNode, ZoneNode } from '../schema' +import { DEFAULT_BEAM_SIZE_PRESET_ID } from '../schema/nodes/beam' +import { beamSizePreset } from './beam' +import { DEFAULT_FRAMING_POST_SIZE, FRAMING_POST_SIZES, type FramingPostSize } from './framing-post' + +/** + * Where a porch's posts and beam go (a porch room, `lib/outdoor-room`): a post on each outside + * corner, where two open sides meet (a porch in a corner of the house gets its one corner post), + * more along each open side at most `postSpacing` apart, evenly; none where a side meets the house, + * whose wall carries the beam's end. The beam runs along each open side on the posts, dropped + * under the porch ceiling. Pure geometry: the structure kernel turns it into post and beam nodes. + * + * A side is open when no wall stands on it (an invisible wall) or the wall on it is a railing. + */ + +const IN = 0.0254 +const FT = 0.3048 + +/** Post faces sit this far in from the slab edge. */ +export const PORCH_POST_SETBACK = 1.5 * IN +/** Posts no more than 8 ft apart (the porch generator's and DCA 6's limit). */ +export const PORCH_POST_SPACING = 8 * FT + +export type PorchFramingSettings = { + /** A framing post size (`FRAMING_POST_SIZES`). */ + postSize: FramingPostSize + postSpacing: number + /** A stock beam size id (`BEAM_SIZE_PRESETS`). */ + beamSize: string +} + +export const DEFAULT_PORCH_FRAMING: PorchFramingSettings = { + postSize: DEFAULT_FRAMING_POST_SIZE, + postSpacing: PORCH_POST_SPACING, + beamSize: DEFAULT_BEAM_SIZE_PRESET_ID, +} + +const isPostSize = (size: string | undefined): size is FramingPostSize => + !!size && Object.hasOwn(FRAMING_POST_SIZES, size) + +/** A porch's framing settings: its own, else the defaults (an unknown size is the default). */ +export function porchFramingSettings(zone: Pick): PorchFramingSettings { + const own = zone.porchFraming + return { + postSize: isPostSize(own?.postSize) ? own.postSize : DEFAULT_PORCH_FRAMING.postSize, + postSpacing: own?.postSpacing ?? DEFAULT_PORCH_FRAMING.postSpacing, + beamSize: + own?.beamSize && beamSizePreset(own.beamSize) + ? beamSizePreset(own.beamSize)!.id + : DEFAULT_PORCH_FRAMING.beamSize, + } +} + +type P = [number, number] + +export type PorchPostPlan = { + /** Stable within the porch: which corner or side, and which bay. */ + key: string + position: P + /** Actual post width (square). */ + size: number + /** The beams (their keys) it stands under: two at a corner, one along a side. */ + beams: string[] +} + +export type PorchBeamPlan = { + key: string + start: P + end: P + width: number + depth: number +} + +export type PorchFramingPlan = { posts: PorchPostPlan[]; beams: PorchBeamPlan[] } + +const sub = (a: P, b: P): P => [a[0] - b[0], a[1] - b[1]] +const add = (a: P, b: P): P => [a[0] + b[0], a[1] + b[1]] +const scale = (a: P, s: number): P => [a[0] * s, a[1] * s] +const len = (a: P) => Math.hypot(a[0], a[1]) +const unit = (a: P): P => { + const l = len(a) + return l > 1e-12 ? [a[0] / l, a[1] / l] : [0, 0] +} + +/** The wall standing along `a` → `b` (its reference line on the segment), if any. */ +function wallAlong(a: P, b: P, walls: readonly WallNode[]): WallNode | undefined { + const dir = unit(sub(b, a)) + const mid = scale(add(a, b), 0.5) + return walls.find((wall) => { + if ((wall.curveOffset ?? 0) !== 0) return false + const w = sub(wall.end as P, wall.start as P) + const length = len(w) + if (length < 1e-6) return false + const u = scale(w, 1 / length) + if (Math.abs(u[0] * dir[1] - u[1] * dir[0]) > 1e-3) return false + const rel = sub(mid, wall.start as P) + const across = Math.abs(rel[0] * u[1] - rel[1] * u[0]) + const along = rel[0] * u[0] + rel[1] * u[1] + return across < 1e-3 && along > -1e-3 && along < length + 1e-3 + }) +} + +/** Whether a wall is a railing, which a porch's posts and beam still stand along. */ +function isRailing(wall: WallNode): boolean { + return (wall as WallNode & { wallType?: string }).wallType === 'railing' +} + +/** + * The posts and beams for porch `zone` among `nodes` (its level's walls decide which sides are + * open). Empty when no side is open. + */ +export function planPorchFraming( + zone: Pick, + nodes: Readonly>, +): PorchFramingPlan { + const settings = porchFramingSettings(zone) + const postSize = FRAMING_POST_SIZES[settings.postSize] + const beam = beamSizePreset(settings.beamSize)! + const spacing = Math.max(0.6, settings.postSpacing) + const walls = Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === zone.parentId, + ) + const polygon = zone.polygon as P[] + const n = polygon.length + if (n < 3) return { posts: [], beams: [] } + // Inward normals: the polygon's winding decides which side of each edge is inside. + let area = 0 + for (let i = 0; i < n; i++) { + const a = polygon[i]! + const b = polygon[(i + 1) % n]! + area += a[0] * b[1] - b[0] * a[1] + } + const turn = area > 0 ? 1 : -1 + const edges = polygon.map((a, i) => { + const b = polygon[(i + 1) % n]! + const wall = wallAlong(a, b, walls) + const dir = unit(sub(b, a)) + const inward: P = [-dir[1] * turn, dir[0] * turn] + return { a, b, dir, inward, open: !wall || isRailing(wall), wall } + }) + const inset = postSize / 2 + PORCH_POST_SETBACK + const posts: PorchPostPlan[] = [] + const beams: PorchBeamPlan[] = [] + // The post line of an edge: the edge moved in by the inset. + const onLine = (edge: (typeof edges)[number], p: P): P => add(p, scale(edge.inward, inset)) + // The outside corner where open edge i-1 meets open edge i: the two post lines' crossing. + const corner = (i: number): P => { + const prev = edges[(i - 1 + n) % n]! + const next = edges[i]! + const p = onLine(prev, prev.b) + const q = onLine(next, next.a) + const denom = prev.dir[0] * next.dir[1] - prev.dir[1] * next.dir[0] + if (Math.abs(denom) < 1e-9) return q + const t = ((q[0] - p[0]) * next.dir[1] - (q[1] - p[1]) * next.dir[0]) / denom + return add(p, scale(prev.dir, t)) + } + edges.forEach((edge, i) => { + if (!edge.open) return + const prev = edges[(i - 1 + n) % n]! + const next = edges[(i + 1) % n]! + const startCorner = prev.open + const endCorner = next.open + // A side meeting the house starts at the wall's face, where the beam bears. + const startFace = startCorner ? 0 : (prev.wall?.thickness ?? 0.15) / 2 + const endFace = endCorner ? 0 : (next.wall?.thickness ?? 0.15) / 2 + const start = startCorner ? corner(i) : add(onLine(edge, edge.a), scale(edge.dir, startFace)) + const end = endCorner + ? corner((i + 1) % n) + : sub(onLine(edge, edge.b), scale(edge.dir, endFace)) + if (startCorner) + posts.push({ + key: `corner:${i}`, + position: start, + size: postSize, + beams: [`beam:${(i - 1 + n) % n}`, `beam:${i}`], + }) + const run = sub(end, start) + const length = len(run) + if (length < postSize) return + const bays = Math.max(1, Math.ceil(length / spacing - 1e-9)) + for (let k = 1; k < bays; k++) + posts.push({ + key: `side:${i}:${k}`, + position: add(start, scale(run, k / bays)), + size: postSize, + beams: [`beam:${i}`], + }) + // The beam reaches the corner posts' outside faces, so two sides' beams meet over a corner. + const reachStart = startCorner ? postSize / 2 : 0 + const reachEnd = endCorner ? postSize / 2 : 0 + beams.push({ + key: `beam:${i}`, + start: sub(start, scale(edge.dir, reachStart)), + end: add(end, scale(edge.dir, reachEnd)), + width: beam.width, + depth: beam.depth, + }) + }) + return { posts, beams } +} diff --git a/packages/core/src/lib/railing-rooms.test.ts b/packages/core/src/lib/railing-rooms.test.ts new file mode 100644 index 0000000000..9be7b14a9a --- /dev/null +++ b/packages/core/src/lib/railing-rooms.test.ts @@ -0,0 +1,128 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + generateId, + LevelNode, + railingWallDefaults, + WallNode, + type ZoneNode, +} from '../schema' +import { railingFencedRoomIds } from './railing-rooms' +import type { BoundarySpan } from './room-topology-index' +import { reconcileSceneStructure } from './structure-reconcile' + +type Point = [number, number] +type Nodes = Record +const LEVEL = 'level_railing' + +const span = (roomId: string, boundaryId: string, face: 'a' | 'b', t0 = 0, t1 = 1) => + ({ roomId, boundaryId, kind: 'wall', face, t0, t1 }) satisfies BoundarySpan + +/** Walls drawn by hand (a railing where `railing` is set), reconciled into rooms. */ +function drawn(edges: { start: Point; end: Point; railing?: boolean }[]): Nodes { + const level = LevelNode.parse({ id: LEVEL, children: [] }) + const nodes: Nodes = {} + for (const { start, end, railing } of edges) { + const wall = WallNode.parse({ + id: generateId('wall'), + parentId: LEVEL, + start, + end, + thickness: 0.2, + ...(railing ? railingWallDefaults() : {}), + }) + nodes[wall.id] = wall + level.children.push(wall.id) + } + nodes[level.id] = level + return reconcileSceneStructure({ nodes, mintId: (kind) => generateId(kind) }).nodes as Nodes +} + +const ring = (corners: Point[], railing = false) => + corners.map((start, index) => ({ start, end: corners[(index + 1) % corners.length]!, railing })) + +const wallOn = (nodes: Nodes, a: Point, b: Point) => + Object.values(nodes).find( + (node): node is WallNode => + node.type === 'wall' && + [node.start, node.end].every((p) => + [a, b].some((q) => Math.hypot(p[0] - q[0], p[1] - q[1]) < 1e-6), + ), + )! + +/** The wall's side toward a point: front is the +normal, perp(end - start) = (-dz, dx). */ +function sideToward(wall: WallNode, [x, z]: Point) { + const [mx, mz] = [(wall.start[0] + wall.end[0]) / 2, (wall.start[1] + wall.end[1]) / 2] + const [nx, nz] = [-(wall.end[1] - wall.start[1]), wall.end[0] - wall.start[0]] + return (x - mx) * nx + (z - mz) * nz > 0 ? wall.frontSide : wall.backSide +} + +describe('rooms a railing fences', () => { + test('a room is fenced where nothing stands behind some stretch of its railing', () => { + const railings = new Set(['wall_rail']) + expect(railingFencedRoomIds([span('deck', 'wall_rail', 'a')], railings)).toEqual( + new Set(['deck']), + ) + // A room behind the whole railing (round a stair well) leaves both inside… + expect( + railingFencedRoomIds( + [span('hall', 'wall_rail', 'a'), span('well', 'wall_rail', 'b')], + railings, + ), + ).toEqual(new Set()) + // …a room behind only part of it does not. + expect( + railingFencedRoomIds( + [span('deck', 'wall_rail', 'a'), span('nook', 'wall_rail', 'b', 0, 0.5)], + railings, + ), + ).toEqual(new Set(['deck'])) + // A solid wall fences nothing. + expect(railingFencedRoomIds([span('hall', 'wall_solid', 'a')], railings)).toEqual(new Set()) + }) + + test("a deck fenced by railings off the house is outside: the house wall's deck face is exterior", () => { + const nodes = drawn([ + ...ring([ + [0, 0], + [6, 0], + [6, 5], + [0, 5], + ]), + { start: [1, 5], end: [1, 7], railing: true }, + { start: [1, 7], end: [4, 7], railing: true }, + { start: [4, 7], end: [4, 5], railing: true }, + ]) + const zones = Object.values(nodes).filter((node): node is ZoneNode => node.type === 'zone') + expect(zones).toHaveLength(2) + const house = wallOn(nodes, [6, 5], [0, 5]) + expect(sideToward(house, [2.5, 6])).toBe('exterior') + expect(sideToward(house, [3, 2])).toBe('interior') + const guard = wallOn(nodes, [1, 7], [4, 7]) + expect([guard.frontSide, guard.backSide]).toEqual(['exterior', 'exterior']) + }) + + test('a railing round a stair well inside a room leaves the room inside', () => { + const nodes = drawn([ + ...ring([ + [0, 0], + [8, 0], + [8, 6], + [0, 6], + ]), + ...ring( + [ + [3, 2], + [5, 2], + [5, 4], + [3, 4], + ], + true, + ), + ]) + const outer = wallOn(nodes, [0, 0], [8, 0]) + expect(sideToward(outer, [4, 1])).toBe('interior') + const guard = wallOn(nodes, [3, 2], [5, 2]) + expect([guard.frontSide, guard.backSide]).toEqual(['interior', 'interior']) + }) +}) diff --git a/packages/core/src/lib/railing-rooms.ts b/packages/core/src/lib/railing-rooms.ts new file mode 100644 index 0000000000..83d163169b --- /dev/null +++ b/packages/core/src/lib/railing-rooms.ts @@ -0,0 +1,47 @@ +import type { WallNode } from '../schema' +import { isRailingWall } from '../schema/nodes/railing-wall' +import type { BoundarySpan } from './room-topology-index' + +/** Span overlap below this (a fraction of the boundary) is a shared endpoint, not a shared edge. */ +const SPAN_OVERLAP = 1e-6 + +/** The ids of the railing walls among `walls`. */ +export function railingWallIds(walls: readonly WallNode[]): Set { + return new Set(walls.flatMap((wall) => (isRailingWall(wall) ? [wall.id] : []))) +} + +/** + * The rooms a railing fences off from the outside: a railing on the room's edge with no room + * standing behind some stretch of it (a deck's or a porch's guard on the storey's outline). A + * railing is a guard, not an enclosure, so such a room is outside whatever covers it, and a wall + * it shares with the house faces outdoors. A railing with rooms on both faces (round a stair well, + * along a loft) leaves them inside. + */ +export function railingFencedRoomIds( + spans: readonly BoundarySpan[], + railingIds: ReadonlySet, +): Set { + const railings = spans.filter((span) => span.kind === 'wall' && railingIds.has(span.boundaryId)) + const fenced = new Set() + for (const span of railings) { + const behind = railings + .filter( + (other) => + other.boundaryId === span.boundaryId && + other.face !== span.face && + Math.min(span.t1, other.t1) - Math.max(span.t0, other.t0) > SPAN_OVERLAP, + ) + .sort((a, b) => a.t0 - b.t0) + let reach = span.t0 + let open = false + for (const other of behind) { + if (other.t0 > reach + SPAN_OVERLAP) { + open = true + break + } + reach = Math.max(reach, other.t1) + } + if (open || reach < span.t1 - SPAN_OVERLAP) fenced.add(span.roomId) + } + return fenced +} diff --git a/packages/core/src/lib/roof-eave.test.ts b/packages/core/src/lib/roof-eave.test.ts new file mode 100644 index 0000000000..137e5eb6b9 --- /dev/null +++ b/packages/core/src/lib/roof-eave.test.ts @@ -0,0 +1,186 @@ +import { describe, expect, test } from 'bun:test' +import { RoofSegmentNode } from '../schema/nodes/roof-segment' +import { LUMBER_DEPTH, ROOF_BUILD_UP_PRESETS, roofBuildUpPatch } from './build-up' +import { + eaveContains, + FASCIA_DROP, + FASCIA_THICKNESS, + fasciaStockFor, + LEVEL_CUT_RUN, + roofEaveProfile, + roofSegmentEaveInput, + roofSegmentEaveProfile, + SOFFIT_THICKNESS, +} from './roof-eave' + +const IN = 0.0254 +const SHEET = (7 / 16) * IN + +/** 2x10 rafters at 6/12 under a 7/16 in sheet, 0.6 m out from the plate line. */ +const input = (cut: 'plumb' | 'square' | 'plumb-level') => ({ + tan: 0.5, + reach: 0.6, + wallOut: 0.075, + tailDepth: LUMBER_DEPTH['2x10'], + tailLift: 0, + deckDepth: LUMBER_DEPTH['2x10'] + SHEET, + cut, +}) + +describe('an eave cut by its rule', () => { + test('plumb: the tails cut plumb at the reach, a plumb 1x over their face from the sheathing to 3/4 in under them', () => { + const eave = roofEaveProfile(input('plumb')) + const cos = 1 / Math.hypot(1, 0.5) + const top = (LUMBER_DEPTH['2x10'] + SHEET) / cos - 0.6 * 0.5 + expect(eave.end).toEqual([ + [0.6, top], + [0.6, -0.3], + ]) + expect(eave.low).toBeCloseTo(-0.3, 9) + const [backTop, frontTop, frontBottom, backBottom] = eave.fascia.corners + expect(backTop).toEqual([0.6, top]) + expect(frontTop[0]).toBeCloseTo(0.6 + FASCIA_THICKNESS, 9) + expect(backBottom[0]).toBeCloseTo(0.6, 9) + // 10.34 in of plumb tail and 0.49 in of sheet: a whole 1x12 drops 0.42 in under it + expect(eave.fascia).toMatchObject({ square: false, stock: '1x12', ripped: false }) + expect(eave.fascia.height).toBeCloseTo(11.25 * IN, 9) + expect(frontBottom[1]).toBeCloseTo(top - 11.25 * IN, 9) + expect(-0.3 - frontBottom[1]).toBeGreaterThan(0.25 * IN) + // a 2x6's 6.64 in takes a whole 1x8, 0.61 in under it + const six = roofEaveProfile({ + ...input('plumb'), + tailDepth: LUMBER_DEPTH['2x6'], + deckDepth: LUMBER_DEPTH['2x6'] + SHEET, + }) + expect(six.fascia).toMatchObject({ stock: '1x8', ripped: false }) + // where no whole 1x drops 1/4 to 3/4 in under the tails, one ripped to drop 3/4 in + const four = roofEaveProfile({ + ...input('plumb'), + tailDepth: LUMBER_DEPTH['2x4'], + deckDepth: LUMBER_DEPTH['2x4'] + SHEET, + }) + expect(four.fascia.corners[2][1]).toBeCloseTo(-0.3 - FASCIA_DROP, 9) + expect(four.fascia).toMatchObject({ stock: '1x6', ripped: true }) + expect(eave.soffit).toBeNull() + }) + + test('square: the tails cut square to the slope through the sheathing edge, the fascia square on them', () => { + const eave = roofEaveProfile(input('square')) + const cos = 1 / Math.hypot(1, 0.5) + const sin = 0.5 * cos + const depth = LUMBER_DEPTH['2x10'] + SHEET + const [corner, foot] = eave.end as [[number, number], [number, number]] + expect(foot[0]).toBeCloseTo(0.6 - depth * sin, 9) + expect(foot[1]).toBeCloseTo(corner[1] - depth * cos, 9) + // on the tails' underside + expect(foot[1]).toBeCloseTo(eave.under(foot[0]), 9) + const [backTop, frontTop, frontBottom, backBottom] = eave.fascia.corners + expect(eave.fascia.square).toBe(true) + expect(eave.fascia.height).toBeCloseTo(depth + FASCIA_DROP, 9) + // its top face in the sheathing's plane, its back on the cut + expect(frontTop[1]).toBeCloseTo(eave.top(frontTop[0]), 9) + expect(backBottom[0] - backTop[0]).toBeCloseTo(-eave.fascia.height * sin, 9) + expect(frontBottom[0] - backBottom[0]).toBeCloseTo(FASCIA_THICKNESS * cos, 9) + // nothing of the tails out past the cut + expect(eaveContains(eave, [0.6, eave.under(0.6) + 0.01])).toBe(false) + expect(eaveContains(eave, [foot[0] - 0.001, foot[1] + 0.002])).toBe(true) + }) + + test('plumb and level: a level cut under the plumb cut, a soffit nailed up to it from the wall out to the fascia', () => { + const eave = roofEaveProfile(input('plumb-level')) + const [, plumbFoot, inner] = eave.end as [[number, number], [number, number], [number, number]] + expect(plumbFoot[0]).toBeCloseTo(0.6, 9) + expect(plumbFoot[1]).toBeCloseTo(-0.3 + LEVEL_CUT_RUN * 0.5, 9) + expect(inner).toEqual([0.6 - LEVEL_CUT_RUN, plumbFoot[1]]) + // the level cut meets the tails' underside + expect(inner[1]).toBeCloseTo(eave.under(inner[0]), 9) + expect(eave.soffit).toEqual({ + y: plumbFoot[1], + thickness: SOFFIT_THICKNESS, + from: 0.075, + to: 0.6, + }) + expect(eave.fascia.corners[2][1]).toBeCloseTo(plumbFoot[1] - FASCIA_DROP, 9) + // the box down to the soffit's underside is the eave's, nothing under it + expect(eaveContains(eave, [0.3, plumbFoot[1] - SOFFIT_THICKNESS + 0.001])).toBe(true) + expect(eaveContains(eave, [0.3, plumbFoot[1] - SOFFIT_THICKNESS - 0.002])).toBe(false) + // a steep 2x4's level cut takes at most half its plumb depth + const steep = roofEaveProfile({ + ...input('plumb-level'), + tan: 1, + tailDepth: LUMBER_DEPTH['2x4'], + }) + const [, foot] = steep.end as [unknown, [number, number]] + expect(foot[1] - steep.under(0.6)).toBeCloseTo((LUMBER_DEPTH['2x4'] * Math.SQRT2) / 2, 9) + }) + + test('a truss tail rides its bottom chord: its underside a chord over the plate, the fascia to 3/4 in under it', () => { + const chord = LUMBER_DEPTH['2x4'] + const eave = roofEaveProfile({ + ...input('plumb'), + tailDepth: chord, + tailLift: chord, + deckDepth: 2 * chord + SHEET, + }) + expect(eave.under(0.6)).toBeCloseTo(chord - 0.3, 9) + const drop = chord - 0.3 - eave.fascia.corners[2][1] + expect(drop).toBeGreaterThanOrEqual(0.25 * IN - 1e-9) + expect(drop).toBeLessThanOrEqual(FASCIA_DROP + 1e-9) + }) + + test('a flat eave and a rising edge are plumb, whatever the plane asks', () => { + expect(roofEaveProfile({ ...input('square'), tan: 0 }).cut).toBe('plumb') + expect(roofEaveProfile({ ...input('plumb-level'), tan: -0.5 }).cut).toBe('plumb') + }) + + test('the 1x a board is cut from: the narrowest as wide, ripped; past a 1x12 a ripped width', () => { + expect(fasciaStockFor(5.5 * IN)).toEqual({ stock: '1x6', ripped: false }) + expect(fasciaStockFor(6 * IN)).toEqual({ stock: '1x8', ripped: true }) + expect(fasciaStockFor(11.25 * IN)).toEqual({ stock: '1x12', ripped: false }) + expect(fasciaStockFor(12 * IN)).toEqual({ stock: null, ripped: true }) + }) +}) + +describe("a roof plane's eave", () => { + const segment = (patch: Partial = {}) => + RoofSegmentNode.parse({ + roofType: 'gable', + width: 8, + depth: 6, + pitch: 26.565, + wallThickness: 0.15, + overhang: 0.6, + ...patch, + }) + + test('its layers name its tails; a truss rides its bottom chord; without layers, rafters under a sheet', () => { + const preset = ROOF_BUILD_UP_PRESETS[0]!.buildUp + const trusses = roofSegmentEaveInput(segment(roofBuildUpPatch(preset))) + expect(trusses.tailDepth).toBeCloseTo(LUMBER_DEPTH['2x4'], 9) + expect(trusses.tailLift).toBeCloseTo(LUMBER_DEPTH['2x4'], 9) + expect(trusses.deckDepth).toBeCloseTo(2 * LUMBER_DEPTH['2x4'] + SHEET, 9) + const bare = roofSegmentEaveInput(segment({ deckThickness: 0.1 })) + expect(bare.tailDepth).toBeCloseTo(0.1 - SHEET, 9) + expect(bare.tailLift).toBe(0) + // the deck's edge, out past the wall's outer face by the overhang along the slope + expect(bare.reach).toBeCloseTo(0.075 + 0.6 * Math.cos(Math.atan(0.5)), 4) + }) + + test('absent, the cut is plumb; a saved plane loads as it was drawn', () => { + expect(segment().eaveCut).toBeUndefined() + expect(roofSegmentEaveProfile(segment()).cut).toBe('plumb') + expect(roofSegmentEaveProfile(segment({ eaveCut: 'square' })).cut).toBe('square') + // a shed's high edge stays plumb + expect( + roofSegmentEaveProfile(segment({ roofType: 'shed', eaveCut: 'square' }), 'high').cut, + ).toBe('plumb') + }) + + test("a flat roof's boards sized on purpose rise over its membrane", () => { + const flat = roofSegmentEaveProfile( + segment({ roofType: 'flat', pitch: 0, fasciaHeight: 0.3, shingleThickness: 0.02 }), + ) + expect(flat.fascia.corners[0][1]).toBeCloseTo(flat.top(flat.reach) + 0.02, 9) + expect(flat.fascia.height).toBe(0.3) + }) +}) diff --git a/packages/core/src/lib/roof-eave.ts b/packages/core/src/lib/roof-eave.ts new file mode 100644 index 0000000000..5097a50b0d --- /dev/null +++ b/packages/core/src/lib/roof-eave.ts @@ -0,0 +1,301 @@ +import { + getRoofSegmentDeckReach, + getSegmentSlopeFrame, + type RoofSegmentNode, +} from '../schema/nodes/roof-segment' +import type { RoofEaveCut } from '../schema/nodes/roof-types' +import { LUMBER_DEPTH } from './build-up' + +const inches = (value: number) => value * 0.0254 + +/** A 1x board's actual thickness: every fascia and rake board is nominal 1x. */ +export const FASCIA_THICKNESS = inches(0.75) +/** How far a fascia or rake board drops below the tails' lowest point, past a soffit's edge. */ +export const FASCIA_DROP = inches(0.75) +/** The least it drops where a 1x then needs no ripping. */ +const FASCIA_DROP_MIN = inches(0.25) +/** The run of a plumb-and-level tail's level cut: a 2x4's width for the soffit's edge to nail to. */ +export const LEVEL_CUT_RUN = inches(3.5) +/** A boxed eave's soffit: a 1/2 in panel nailed up to the level cuts. */ +export const SOFFIT_THICKNESS = inches(0.5) +/** The sheathing a plane without layers is taken to carry over its tails: Bones' 7/16 in. */ +const BARE_SHEATHING = inches(7 / 16) + +export type FasciaStock = '1x6' | '1x8' | '1x10' | '1x12' + +/** The 1x boards a fascia or rake board is cut from, narrowest first, at their actual widths. */ +export const FASCIA_STOCK: readonly { size: FasciaStock; width: number }[] = [ + { size: '1x6', width: inches(5.5) }, + { size: '1x8', width: inches(7.25) }, + { size: '1x10', width: inches(9.25) }, + { size: '1x12', width: inches(11.25) }, +] + +/** + * The 1x a board `width` wide is cut from: the narrowest stock as wide, ripped down to it when + * wider. Past a 1x12 no 1x is wide enough: a ripped width of wider stock (`stock` null). + */ +export function fasciaStockFor(width: number): { stock: FasciaStock | null; ripped: boolean } { + const stock = FASCIA_STOCK.find((candidate) => candidate.width >= width - 1e-4) + if (!stock) return { stock: null, ripped: true } + return { stock: stock.size, ripped: stock.width - width > 1e-3 } +} + +/** + * How wide a board covering `cover` is, `scale` its width per unit of the cover's measure (a rake + * board's, its plumb depth's cosine): `FASCIA_DROP` past it, or a 1x whole where one drops between + * `least` and that, so the board is stock, not ripped. + */ +function boardWidth(cover: number, scale = 1, least = FASCIA_DROP_MIN): number { + const whole = FASCIA_STOCK.find( + ({ width }) => + width >= (cover + least) * scale - 1e-6 && width <= (cover + FASCIA_DROP) * scale + 1e-6, + ) + return whole ? whole.width : (cover + FASCIA_DROP) * scale +} + +/** A point of an eave's section: `s` out from the plate line in plan, `y` up from the plate's plane there. */ +export type EavePoint = readonly [number, number] + +/** The numbers an eave is cut from (`roofEaveProfile`), the roof shell's and Bones' alike. */ +export type RoofEaveInput = { + /** The slope's tangent, falling outward; 0 for a flat roof, negative where it rises (a shed's high edge). */ + tan: number + /** Out from the plate line (the wall's centre line), in plan, to the overhang's end: where the tails are cut. */ + reach: number + /** Out from the plate line to the wall's outer face. */ + wallOut: number + /** The tails' depth square to the slope: a rafter's, or a truss's top chord. */ + tailDepth: number + /** + * How far the tails' underside stands over the plate's plane, plumb: 0 for a rafter, its bottom + * chord's depth for a truss's top chord, which rides it at the heel. + */ + tailLift: number + /** The deck's depth square to the slope, from the plate's plane to the sheathing's top. */ + deckDepth: number + /** Absent, plumb. */ + cut?: RoofEaveCut + /** The fascia's own height (its width as a board) and thickness, where the plane sets them. */ + fasciaHeight?: number + fasciaThickness?: number + /** How far the fascia's top stands over the sheathing's: a flat roof's coping band over its membrane. */ + fasciaRise?: number +} + +/** + * One eave's section, the rule the roof shell's eave edge, fascia and soffit and Bones' rafter tails + * and fascia are all cut by, so the frame never hangs below the shell nor pokes out of it. + */ +export type RoofEaveProfile = { + /** The cut drawn: a flat eave, or an edge rising outward, is plumb whatever the plane asks. */ + cut: RoofEaveCut + tan: number + cos: number + sin: number + reach: number + wallOut: number + /** The sheathing's top at `s`. */ + top: (s: number) => number + /** The tails' underside at `s`. */ + under: (s: number) => number + /** + * The tails' end, from its top corner on the sheathing's top down: a plumb or square cut's two + * ends, or a plumb cut's and then its level cut's inner end. + */ + end: readonly EavePoint[] + /** The tails' lowest point's height. */ + low: number + fascia: { + /** Its section: back top, front top, front bottom, back bottom. */ + corners: readonly [EavePoint, EavePoint, EavePoint, EavePoint] + /** Its width as a board: plumb, its height; square to the slope, its depth along its face. */ + height: number + thickness: number + /** Square to the slope (a `square` cut's), else plumb. */ + square: boolean + stock: FasciaStock | null + ripped: boolean + } + /** A boxed eave's soffit: its top (on the level cuts) and thickness, from the wall's outer face out to the fascia's back. */ + soffit: { y: number; thickness: number; from: number; to: number } | null + /** + * The rake board over a barge rafter on this slope: plumb, its top on the sheathing's top, as deep + * plumb (`height`) as the fascia is at the corner, or over a boxed eave as the barge with the + * fascia's drop; `width` is square to its edges, as a board. + */ + rake: { height: number; width: number; stock: FasciaStock | null; ripped: boolean } +} + +/** + * An eave cut to its plane's rule. The tails reach `reach` out from the plate line along the + * sheathing's top, the deck's full depth under it: + * - plumb: the tails cut plumb there; a plumb 1x fascia covers their face, its top flush with the + * sheathing's top (under the drip edge), its bottom about `FASCIA_DROP` below their lowest point; + * - square: the tails cut square to the slope through the sheathing's outer edge, the fascia square + * to the slope on them, its top face in the sheathing's plane; + * - plumb-level: the plumb cut, and under it a level cut `LEVEL_CUT_RUN` long (at most half the tail's + * plumb depth deep) that a soffit nails up to, run level from the fascia's back to the wall. + * The fascia is as deep as that takes (a whole 1x where one drops at least 1/4 in under the tails, + * else one ripped to it), unless the plane sets its own. + */ +export function roofEaveProfile(input: RoofEaveInput): RoofEaveProfile { + const tan = Number.isFinite(input.tan) ? input.tan : 0 + const cos = 1 / Math.hypot(1, tan) + const sin = tan * cos + const { reach, wallOut, tailLift } = input + const deck = Math.max(0, input.deckDepth) + const top = (s: number) => deck / cos - s * tan + const under = (s: number) => tailLift - s * tan + const thickness = input.fasciaThickness ?? FASCIA_THICKNESS + const cut: RoofEaveCut = tan > 1e-6 ? (input.cut ?? 'plumb') : 'plumb' + const corner: EavePoint = [reach, top(reach)] + let end: EavePoint[] + let low: number + let height: number + let corners: [EavePoint, EavePoint, EavePoint, EavePoint] + let soffit: RoofEaveProfile['soffit'] = null + if (cut === 'square') { + // the sheathing's top to the tails' underside, square to the slope + const depth = deck - tailLift * cos + height = input.fasciaHeight ?? boardWidth(depth) + const down = (d: number): EavePoint => [reach - d * sin, corner[1] - d * cos] + const out = (point: EavePoint): EavePoint => [ + point[0] + thickness * cos, + point[1] - thickness * sin, + ] + const foot = down(depth) + end = [corner, foot] + low = foot[1] + const bottom = down(height) + corners = [corner, out(corner), out(bottom), bottom] + } else { + let foot = under(reach) + if (cut === 'plumb-level') { + const run = Math.min(LEVEL_CUT_RUN, input.tailDepth / cos / (2 * tan)) + foot = under(reach) + run * tan + end = [corner, [reach, foot], [reach - run, foot]] + soffit = { y: foot, thickness: SOFFIT_THICKNESS, from: wallOut, to: reach } + } else end = [corner, [reach, foot]] + low = foot + const topY = corner[1] + (input.fasciaRise ?? 0) + // past a boxed eave's soffit, the fascia drops its whole 3/4 in under the level cuts + height = + input.fasciaHeight ?? + boardWidth( + topY - foot, + 1, + cut === 'plumb-level' ? SOFFIT_THICKNESS + FASCIA_DROP_MIN : FASCIA_DROP_MIN, + ) + corners = [ + [reach, topY], + [reach + thickness, topY], + [reach + thickness, topY - height], + [reach, topY - height], + ] + } + // A rake board meets the eave's fascia flush at the corner: as deep at its foot (plumb, or square + // along the cut), so neither hangs past the other. Over a boxed eave it covers the barge whole, + // held level with the fascia's bottom at the corner. + const rakeHeight = + input.fasciaHeight ?? + (cut === 'plumb' + ? height + : cut === 'square' + ? height / cos + : boardWidth(deck / cos - tailLift, cos) / cos) + return { + cut, + tan, + cos, + sin, + reach, + wallOut, + top, + under, + end, + low, + fascia: { + corners, + height, + thickness, + square: cut === 'square', + ...fasciaStockFor(height), + }, + soffit, + rake: { height: rakeHeight, width: rakeHeight * cos, ...fasciaStockFor(rakeHeight * cos) }, + } +} + +/** + * Whether a point of an eave's section, out past the wall's outer face, lies in the shell's eave + * (`tolerance` either way): under the sheathing's top, inside the tails' end cut, and over their + * underside or a boxed eave's soffit. + */ +export function eaveContains(profile: RoofEaveProfile, [s, y]: EavePoint, tolerance = 0): boolean { + const { reach, cut, cos, sin, top, under, soffit } = profile + if (y > top(s) + tolerance || s > reach + tolerance) return false + const [cs, cy] = profile.end[0] as EavePoint + if (cut === 'square' && (s - cs) * cos - (y - cy) * sin > tolerance) return false + // a boxed eave stands on its soffit: under it the deck is cut away + const floor = soffit ? soffit.y - soffit.thickness : under(s) + return y >= floor - tolerance +} + +type EaveSegment = Pick< + RoofSegmentNode, + 'roofType' | 'pitch' | 'width' | 'depth' | 'wallThickness' | 'overhang' | 'deckThickness' +> & + Partial< + Pick< + RoofSegmentNode, + | 'gableOverhang' + | 'buildUp' + | 'eaveCut' + | 'fasciaHeight' + | 'fasciaDepth' + | 'shingleThickness' + | 'barrelRise' + | 'barrelSegments' + > + > + +/** + * The numbers a roof plane's eaves are cut from (`roofEaveProfile`): its reach and wall, its layers' + * structure and deck, its eave cut and fascia. Its rafters sit on the plate; a truss's top chord on + * its bottom chord at the heel. A plane without layers is taken as rafters under a 7/16 in sheet, + * as deep as its deck. `high`: a shed's high edge, rising outward, cut plumb. + */ +export function roofSegmentEaveInput( + node: EaveSegment, + edge: 'eave' | 'high' = 'eave', +): RoofEaveInput { + const { tanTheta } = getSegmentSlopeFrame(node) + const deck = Math.max(0, node.deckThickness) + const structure = node.buildUp?.structure + const chord = structure ? LUMBER_DEPTH[structure.size] : 0 + const tailDepth = structure ? chord : Math.max(0, deck - Math.min(BARE_SHEATHING, deck / 2)) + return { + tan: edge === 'high' ? -tanTheta : tanTheta, + reach: getRoofSegmentDeckReach(node).z, + wallOut: Math.max(0, node.wallThickness) / 2, + tailDepth, + tailLift: structure?.system === 'trusses' ? chord : 0, + deckDepth: deck, + cut: edge === 'high' ? 'plumb' : (node.eaveCut ?? 'plumb'), + ...(node.fasciaHeight !== undefined ? { fasciaHeight: node.fasciaHeight } : {}), + ...(node.fasciaDepth !== undefined ? { fasciaThickness: node.fasciaDepth } : {}), + // a flat roof's boards sized on purpose rise over its membrane's edge: a coping band + ...(node.roofType === 'flat' && node.fasciaHeight !== undefined + ? { fasciaRise: Math.max(0, node.shingleThickness ?? 0) } + : {}), + } +} + +/** A roof plane's eave (`roofSegmentEaveInput`, `roofEaveProfile`). */ +export function roofSegmentEaveProfile( + node: EaveSegment, + edge: 'eave' | 'high' = 'eave', +): RoofEaveProfile { + return roofEaveProfile(roofSegmentEaveInput(node, edge)) +} diff --git a/packages/core/src/lib/slab-room-plates.test.ts b/packages/core/src/lib/slab-room-plates.test.ts new file mode 100644 index 0000000000..e53c787e02 --- /dev/null +++ b/packages/core/src/lib/slab-room-plates.test.ts @@ -0,0 +1,212 @@ +import { expect, test } from 'bun:test' +import { applySceneChanges } from '../agent-operations/apply-changes' +import { buildBuildingFoundationChanges } from '../building/foundation' +import { planWallOpening } from '../building/wall-openings' +import { setZoneIntent } from '../commands/structure/set-zone-intent' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + type DoorNode, + LevelNode, + SiteNode, + type SlabNode, + type WallNode, + type ZoneNode, +} from '../schema' +import { getWallCurveLength } from '../systems/wall/wall-curve' +import { planWallInsertion } from '../systems/wall/wall-topology' +import { foundationOutlines } from './designed-foundation' +import { wallOpeningBand } from './floor-opening-footprints' +import { area, difference, intersection, type Ring, union } from './polygon-boolean' +import { reconcileSceneStructure } from './structure-reconcile' + +type Nodes = Record +type Point = [number, number] + +const LEVEL = 'level_g' as AnyNodeId +let minted = 0 +const mintId = (kind: string) => `${kind}_p${minted++}` + +/** + * One edit as the editor makes it: its changes written, then the level's rooms, ceilings and floor + * plates reconciled against the scene before it (`applyStructureReconciliation`). + */ +function commit(nodes: Nodes, change: (nodes: Nodes) => Nodes): Nodes { + const next = change(nodes) + return reconcileSceneStructure({ nodes: next, previousNodes: nodes, mintId }).nodes as Nodes +} + +/** A wall drawn with the wall tool: inserted (joining and splitting the walls it meets), reconciled. */ +function draw(nodes: Nodes, start: Point, end: Point): Nodes { + return commit(nodes, (scene) => { + const result = planWallInsertion(scene, { + levelId: LEVEL, + start, + end, + joinRadius: 0.05, + wallDefaults: { thickness: 0.18 }, + }) + if (!result.ok) throw Error(result.reason) + return applySceneChanges(scene, result.plan.changes) as Nodes + }) +} + +/** A door placed on the wall under `at` with the door tool's rule (`planWallOpening`). */ +function placeDoor(nodes: Nodes, at: Point, width: number, height: number): Nodes { + const wall = wallAt(nodes, at) + const length = getWallCurveLength(wall) + const t = + ((at[0] - wall.start[0]) * (wall.end[0] - wall.start[0]) + + (at[1] - wall.start[1]) * (wall.end[1] - wall.start[1])) / + (length * length) + return commit(nodes, (scene) => { + const { node } = planWallOpening(scene, { kind: 'door', wallId: wall.id, t, width, height }) + return applySceneChanges(scene, { create: [{ node, parentId: wall.id }] }) as Nodes + }) +} + +const walls = (nodes: Nodes) => + Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === LEVEL, + ) + +/** The wall whose centreline passes through `at`. */ +function wallAt(nodes: Nodes, at: Point): WallNode { + return walls(nodes).find((wall) => { + const [ax, az] = wall.start + const [bx, bz] = wall.end + const cross = (bx - ax) * (at[1] - az) - (bz - az) * (at[0] - ax) + const dot = (at[0] - ax) * (bx - ax) + (at[1] - az) * (bz - az) + return Math.abs(cross) < 1e-6 && dot > 0 && dot < (bx - ax) ** 2 + (bz - az) ** 2 + })! +} + +const zoneAt = (nodes: Nodes, at: Point) => + Object.values(nodes).find( + (node): node is ZoneNode => + node.type === 'zone' && + node.parentId === LEVEL && + area(intersection(node.polygon as Ring, square(at, 0.01))) > 0, + )! + +const square = ([x, z]: Point, half: number): Ring => [ + [x - half, z - half], + [x + half, z - half], + [x + half, z + half], + [x - half, z + half], +] + +/** + * A raised house 12.5 × 9 m, drawn wall by wall, seated on its raised floor; then a garage 6 × 6 m + * drawn off its east side, its north wall teeing into the house's east wall, named Garage, with an + * overhead door and a man door to the outside and a door into the house through the wall they + * share — each step as the editor takes it, the plates reconciled after every one. + */ +function drawnRaisedHouseWithGarage(): Nodes { + let nodes = Object.fromEntries( + [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: [LEVEL], + metadata: { foundation: { enabled: true, type: 'slab' } }, + }), + LevelNode.parse({ id: LEVEL, parentId: 'building_a', level: 0, children: [] }), + ].map((node) => [node.id, node]), + ) as Nodes + const house: Point[] = [ + [0, 0], + [12.5, 0], + [12.5, 9], + [0, 9], + ] + house.forEach((corner, index) => { + nodes = draw(nodes, corner, house[(index + 1) % house.length]!) + }) + nodes = commit( + nodes, + (scene) => + applySceneChanges(scene, { + update: buildBuildingFoundationChanges({ + nodes: scene, + buildingId: 'building_a', + patch: { type: 'raised' }, + })!.update, + }) as Nodes, + ) + nodes = draw(nodes, [12.5, 0], [18.5, 0]) + nodes = draw(nodes, [18.5, 0], [18.5, 6]) + nodes = draw(nodes, [18.5, 6], [12.5, 6]) + const garage = zoneAt(nodes, [15.5, 3]) + nodes = commit(nodes, (scene) => { + const plan = setZoneIntent(scene, { zoneId: garage.id, patch: { name: 'Garage' } }) + expect(plan.conflicts ?? []).toEqual([]) + return applySceneChanges(scene, { + update: plan.changes.flatMap((change) => + change.op === 'update' ? [{ id: change.id, data: change.data }] : [], + ), + }) as Nodes + }) + nodes = placeDoor(nodes, [18.5, 3], 4.8, 2.1) + nodes = placeDoor(nodes, [15, 0], 0.9, 2.1) + nodes = placeDoor(nodes, [12.5, 2], 0.9, 2.1) + return nodes +} + +test('a raised house’s floor plates stay inside its walls’ outer faces and never run through a door, beside a garage on its slab', () => { + const nodes = drawnRaisedHouseWithGarage() + const garage = zoneAt(nodes, [15.5, 3]) + const room = zoneAt(nodes, [6, 4.5]) + expect(garage.name).toBe('Garage') + expect(garage.floor).toMatchObject({ foundation: 'slab' }) + expect(garage.floor?.elevation).toBeLessThan(-0.3) + // The garage's north wall split the house's east wall where it met it. + expect(wallAt(nodes, [12.5, 3]).end[1]).toBeCloseTo(6, 6) + const plates = Object.values(nodes).filter( + (node): node is SlabNode => node.type === 'slab' && node.parentId === LEVEL, + ) + // The house's floor, and the garage's slab a plate of its own, set down to its floor. + const housePlates = plates.filter((plate) => plate.zoneIds?.includes(room.id)) + expect(housePlates.length).toBeGreaterThan(0) + const garagePlate = plates.find((plate) => !plate.zoneIds?.includes(room.id))! + expect(garagePlate.zoneIds).toEqual([garage.id]) + expect(garagePlate.elevation).toBeCloseTo(garage.floor!.elevation!, 6) + + const all = walls(nodes) + // The walls' outer faces: the outline the foundation is poured to. + const outline = union( + foundationOutlines(all).map((loop) => loop.edges.map((edge) => edge.a) as Ring), + ) + const doors = Object.values(nodes).filter((node): node is DoorNode => node.type === 'door') + expect(doors).toHaveLength(3) + for (const plate of housePlates) { + const shape = { outer: plate.polygon as Ring, holes: (plate.holes ?? []) as Ring[] } + // Inside the walls' outer faces, nowhere past them. + expect(area(difference(shape, outline))).toBeLessThan(1e-6) + // Never into the garage, past the faces of the walls round it. + expect(area(intersection(shape, garage.polygon as Ring))).toBeLessThan(1e-6) + // Never into a doorway or through it: across each door's span, nothing of the house's floor + // past its room. + for (const door of doors) { + const wall = nodes[door.parentId as AnyNodeId] as WallNode + const length = getWallCurveLength(wall) + const band = wallOpeningBand( + wall, + (door.position[0] - door.width / 2) / length, + (door.position[0] + door.width / 2) / length, + ) + const through = difference(intersection(shape, band), room.polygon as Ring) + expect(area(through)).toBeLessThan(1e-6) + } + } + // The garage's doors open from its slab; the door into the house from the house's floor. + const sill = (at: Point) => { + const door = doors.find((candidate) => candidate.parentId === wallAt(nodes, at).id)! + return door.position[1] - door.height / 2 + } + expect(sill([18.5, 3])).toBeCloseTo(garage.floor!.elevation!, 6) + expect(sill([15, 0])).toBeCloseTo(garage.floor!.elevation!, 6) + expect(sill([12.5, 2])).toBeCloseTo(0, 9) +}) diff --git a/packages/core/src/lib/slab-room.test.ts b/packages/core/src/lib/slab-room.test.ts new file mode 100644 index 0000000000..693c1b9421 --- /dev/null +++ b/packages/core/src/lib/slab-room.test.ts @@ -0,0 +1,326 @@ +import { expect, test } from 'bun:test' +import { buildBuildingFoundationChanges } from '../building/foundation' +import { clampDoorToWall, planWallOpening } from '../building/wall-openings' +import { setZoneIntent } from '../commands/structure/set-zone-intent' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + DoorNode, + LevelNode, + SiteNode, + SlabNode, + WallNode, + ZoneNode, +} from '../schema' +import { levelPlaneWorldY } from '../services/storey' +import { SLAB_ROOM_CURB } from './designed-foundation' +import { + designedFoundationReport, + foundationGradingPads, + foundationRimSteps, + genericFoundation, +} from './generic-foundation' +import { SLAB_ROOM_FLOOR_FINISH } from './plate-surface' +import { doorSillFloorOf, isGarageName, roomFoundationOf, slabRoomElevation } from './slab-room' + +const IN = 0.0254 +type Nodes = Record + +/** + * A raised house 10 × 8 m, a garage 6 × 6 m off its east side sharing the wall x 10: an overhead + * door in the garage's east wall, a man door in its south wall, a door into the house in the + * shared wall. + */ +function raisedHouseWithGarage(): Nodes { + const wall = (id: string, start: [number, number], end: [number, number]) => + WallNode.parse({ id, parentId: 'level_g', start, end, thickness: 0.15 }) + const walls = [ + wall('wall_s', [0, 0], [10, 0]), + wall('wall_gs', [10, 0], [16, 0]), + wall('wall_ge', [16, 0], [16, 6]), + wall('wall_gn', [16, 6], [10, 6]), + wall('wall_e', [10, 6], [10, 8]), + wall('wall_n', [10, 8], [0, 8]), + wall('wall_w', [0, 8], [0, 0]), + wall('wall_shared', [10, 0], [10, 6]), + ] + const door = (id: string, parentId: string, along: number, width: number) => + DoorNode.parse({ id, parentId, wallId: parentId, position: [along, 1.05, 0], width }) + const doors = [ + door('door_overhead', 'wall_ge', 3, 4.8), + door('door_man', 'wall_gs', 2, 0.9), + door('door_house', 'wall_shared', 3, 0.9), + ] + const nodes: AnyNode[] = [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_g'], + metadata: { foundation: { enabled: true, type: 'raised' } }, + }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((w) => w.id), 'slab_g', 'zone_house', 'zone_garage'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: [ + [0, 0], + [16, 0], + [16, 6], + [10, 6], + [10, 8], + [0, 8], + ], + elevation: 0.05, + thickness: 0.05, + }), + ZoneNode.parse({ + id: 'zone_house', + parentId: 'level_g', + autoFromWalls: true, + boundaryWallIds: ['wall_s', 'wall_shared', 'wall_e', 'wall_n', 'wall_w'], + name: 'Living', + spaceRole: 'room', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + }), + ZoneNode.parse({ + id: 'zone_garage', + parentId: 'level_g', + autoFromWalls: true, + boundaryWallIds: ['wall_gs', 'wall_ge', 'wall_gn', 'wall_shared'], + name: 'Room', + spaceRole: 'room', + polygon: [ + [10, 0], + [16, 0], + [16, 6], + [10, 6], + ], + }), + ...walls.map((w) => ({ + ...w, + children: doors.filter((d) => d.parentId === w.id).map((d) => d.id), + })), + ...doors, + ] + return Object.fromEntries(nodes.map((node) => [node.id, node])) as Nodes +} + +function apply(nodes: Nodes, update: Array<{ id: string; data: unknown }>): Nodes { + const next = { ...nodes } + for (const { id, data } of update) + next[id as AnyNodeId] = { ...next[id as AnyNodeId], ...(data as object) } as AnyNode + return next +} + +/** The house seated on its raised floor, then `name` given to the east room through its panel. */ +function seatedAndNamed(name: string): Nodes { + let nodes = raisedHouseWithGarage() + nodes = apply( + nodes, + buildBuildingFoundationChanges({ nodes, buildingId: 'building_a', patch: {} })!.update, + ) + const plan = setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { name } }) + expect(plan.conflicts ?? []).toEqual([]) + return apply(nodes, plan.changes as Array<{ id: string; data: unknown }>) +} + +const zone = (nodes: Nodes, id = 'zone_garage') => nodes[id as AnyNodeId] as ZoneNode + +test('a garage is "garage" as a word, whatever its case', () => { + expect(['Garage', '2-car garage', 'GARAGE 2', 'Garage/Shop'].map(isGarageName)).toEqual([ + true, + true, + true, + true, + ]) + expect(['Garages', 'Garageband', 'Living', ''].map(isGarageName)).toEqual([ + false, + false, + false, + false, + ]) +}) + +test('named a garage, a room of a raised house comes down onto a slab 4 in over grade; a new name leaves it there', () => { + const nodes = seatedAndNamed('2-car garage') + expect(zone(nodes).floor?.foundation).toBe('slab') + expect(roomFoundationOf(nodes, zone(nodes))).toBe('slab') + expect(roomFoundationOf(nodes, zone(nodes, 'zone_house'))).toBe('raised') + const report = designedFoundationReport(nodes, 'building_a')! + const plane = levelPlaneWorldY(nodes, 'level_g')! + const top = plane + zone(nodes).floor!.elevation! + expect(top - report.highestGradeY).toBeCloseTo(4 * IN, 3) + expect(slabRoomElevation(nodes, zone(nodes))).toBeCloseTo(zone(nodes).floor!.elevation!, 4) + // Renamed away from Garage it stays on its slab: the choice is kept. + const renamed = apply( + nodes, + setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { name: 'Workshop' } }).changes as never, + ) + expect(zone(renamed).floor?.foundation).toBe('slab') +}) + +test('a room chosen raised stays raised when named a garage; chosen slab, it comes down, and back up raised', () => { + let nodes = seatedAndNamed('Room 2') + expect(zone(nodes).floor?.foundation).toBeUndefined() + const choose = (foundation: 'raised' | 'slab') => { + const plan = setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { floor: { foundation } } }) + expect(plan.conflicts ?? []).toEqual([]) + nodes = apply(nodes, plan.changes as never) + } + choose('raised') + nodes = apply( + nodes, + setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { name: 'Garage' } }).changes as never, + ) + expect(zone(nodes).floor).toMatchObject({ foundation: 'raised' }) + expect(zone(nodes).floor?.elevation).toBeUndefined() + choose('slab') + expect(zone(nodes).floor?.elevation).toBeLessThan(-0.3) + choose('raised') + expect(zone(nodes).floor?.foundation).toBe('raised') + expect(zone(nodes).floor?.elevation).toBeUndefined() +}) + +test('a garage off a raised house: its slab on grade, its walls on an 8 in curb cut at its doors, the shared wall on the house’s stem', () => { + const nodes = seatedAndNamed('Garage') + const report = designedFoundationReport(nodes, 'building_a')! + const plane = levelPlaneWorldY(nodes, 'level_g')! + const slabTop = plane + zone(nodes).floor!.elevation! + const curbTop = slabTop + SLAB_ROOM_CURB + const members = genericFoundation(nodes) + const stems = members.filter((member) => member.kind === 'stem') + // The house's stems stand at its stem top; the garage's curbs 8 in over its slab. + const curbs = stems.filter((member) => Math.abs(member.topY - curbTop) < 1e-6) + const raised = stems.filter((member) => Math.abs(member.topY - report.stemTopY!) < 1e-6) + expect(curbs.length).toBeGreaterThan(0) + expect(curbs.length + raised.length).toBe(stems.length) + expect(curbTop - slabTop).toBeCloseTo(8 * IN, 9) + // The overhead door's 4.8 m and the man door's 0.9 m are cut out of the curbs: along x 16 the + // curb stands only at its ends, and the slab runs out through both openings. + const along = (x: number) => + curbs.filter((curb) => Math.abs(curb.from[0] - x) < 0.2 && Math.abs(curb.to[0] - x) < 0.2) + const east = along(16 - 0.0762) + const covered = east.reduce((sum, curb) => sum + Math.abs(curb.to[1] - curb.from[1]), 0) + expect(covered).toBeCloseTo(6 + 0.15 - 4.8, 1) + const gaps = members.filter( + (member) => member.kind === 'slab' && Math.abs(member.topY - (slabTop - 0.002)) < 1e-6, + ) + // The slab between the curbs, and one gap for each of the two garage doors. + expect(gaps).toHaveLength(3) + // The shared wall stands on the house's stem, whole: its door into the house cuts nothing. + const shared = raised.filter( + (member) => Math.abs(member.from[0] - 10) < 1e-6 && Math.abs(member.to[0] - 10) < 1e-6, + ) + expect(shared).toHaveLength(1) + expect(Math.abs(shared[0]!.to[1] - shared[0]!.from[1])).toBeCloseTo(6 + 0.1524, 3) + // The garage walls' finish runs down to the curb's top. + for (const id of ['wall_gs', 'wall_ge', 'wall_gn']) + expect(foundationRimSteps(nodes).get(id)?.[0]?.bottom).toBeCloseTo(curbTop - plane, 6) + // Graded: the ground held 4 in under the garage's slab; the house cut round, its crawl dug out + // under the house alone. + const pads = foundationGradingPads(nodes) + const pad = pads.find((entry) => entry.kind === undefined)! + expect(pad.y).toBeCloseTo(slabTop - 4 * IN, 6) + const pit = pads.find((entry) => entry.kind === 'pit')! + expect(Math.max(...pit.outline.map(([x]) => x))).toBeLessThan(10) + expect(pads.filter((entry) => entry.kind === 'cut')).toHaveLength(1) +}) + +test('seating the raised house again keeps the garage on grade; a slab house takes it back onto its floor', () => { + let nodes = seatedAndNamed('Garage') + const before = zone(nodes).floor!.elevation! + nodes = apply( + nodes, + buildBuildingFoundationChanges({ + nodes, + buildingId: 'building_a', + patch: { ffAboveGradeIn: 30 }, + })!.update, + ) + expect(zone(nodes).floor!.elevation!).toBeCloseTo(before - (30 - 22.125) * IN, 4) + nodes = apply( + nodes, + buildBuildingFoundationChanges({ nodes, buildingId: 'building_a', patch: { type: 'slab' } })! + .update, + ) + expect(zone(nodes).floor?.foundation).toBe('slab') + expect(zone(nodes).floor?.elevation).toBeUndefined() +}) + +test('a slab room’s doors open from its slab; the door into the house keeps its sill at the floor', () => { + const nodes = seatedAndNamed('Garage') + const elevation = zone(nodes).floor!.elevation! + const sill = (id: string) => { + const door = nodes[id as AnyNodeId] as DoorNode + return door.position[1] - door.height / 2 + } + expect(sill('door_overhead')).toBeCloseTo(elevation, 4) + expect(sill('door_man')).toBeCloseTo(elevation, 4) + expect(sill('door_house')).toBeCloseTo(0, 6) + // Back on the raised floor they come back up with it. + const back = apply( + nodes, + setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { floor: { foundation: 'raised' } } }) + .changes as never, + ) + const door = back['door_overhead' as AnyNodeId] as DoorNode + expect(door.position[1] - door.height / 2).toBeCloseTo(0, 6) +}) + +test('a door placed or moved onto a garage’s wall to the outside stands on its slab; on the shared wall or a house wall, on the house floor', () => { + const nodes = seatedAndNamed('Garage') + const elevation = zone(nodes).floor!.elevation! + const wall = (id: string) => nodes[id as AnyNodeId] as WallNode + // The rule the door tools place and move a door by (`clampDoorToWall`, given the scene). + const sill = (id: string, height = 2.1) => + clampDoorToWall(wall(id), 1.5, 0.9, height, nodes).clampedY - height / 2 + for (const id of ['wall_gs', 'wall_ge', 'wall_gn']) { + expect(doorSillFloorOf(nodes, wall(id))).toBeCloseTo(elevation, 6) + expect(sill(id)).toBeCloseTo(elevation, 6) + } + expect(sill('wall_ge', 2.4)).toBeCloseTo(elevation, 6) + // Moved back onto a house wall, or into the wall the garage shares with the house: the floor. + for (const id of ['wall_s', 'wall_e', 'wall_n', 'wall_w', 'wall_shared']) + expect(sill(id)).toBeCloseTo(0, 9) + // An agent's add_door seats it the same way, unclamped. + const added = planWallOpening(nodes, { kind: 'door', wallId: 'wall_gn', t: 0.5 }) + const door = added.node as DoorNode + expect(door.position[1] - door.height / 2).toBeCloseTo(elevation, 6) + expect(added.clamped).toBe(false) + const shared = planWallOpening(nodes, { kind: 'door', wallId: 'wall_shared', t: 0.2 }) + expect((shared.node as DoorNode).position[1]).toBeCloseTo(2.1 / 2, 9) + // A garage chosen back onto the raised floor: its doors open from the floor again. + const back = apply( + nodes, + setZoneIntent(nodes, { zoneId: 'zone_garage', patch: { floor: { foundation: 'raised' } } }) + .changes as never, + ) + expect(doorSillFloorOf(back, back['wall_ge' as AnyNodeId] as WallNode)).toBe(0) + // A slab house's garage is on the house's floor: nothing to step down to. + const slab = apply( + nodes, + buildBuildingFoundationChanges({ nodes, buildingId: 'building_a', patch: { type: 'slab' } })! + .update, + ) + expect(zone(slab).floor?.foundation).toBe('slab') + expect(doorSillFloorOf(slab, slab['wall_ge' as AnyNodeId] as WallNode)).toBe(0) +}) + +test('a slab room is bare concrete until a finish is chosen for it', () => { + const nodes = seatedAndNamed('Garage') + expect(zone(nodes).floor?.finish).toBeUndefined() + expect(SLAB_ROOM_FLOOR_FINISH).toBe('library:concrete-raw') +}) diff --git a/packages/core/src/lib/slab-room.ts b/packages/core/src/lib/slab-room.ts new file mode 100644 index 0000000000..78f9f4382d --- /dev/null +++ b/packages/core/src/lib/slab-room.ts @@ -0,0 +1,193 @@ +import type { AnyNode, AnyNodeId, BuildingNode, LevelNode, WallNode, ZoneNode } from '../schema' +import { getLevelElevations, levelPlaneWorldY } from '../services/storey' +import { + type BuildingFoundationType, + buildingFoundationOf, + inches, + isBuildingFoundationOn, +} from './building-foundation' +import { exteriorWallLoops } from './exterior-wall-loops' +import { siteOf } from './generic-foundation' +import { groundHeightAt } from './site-ground-height' +import { levelLocalToWorldXZ } from './terrain-support-query' + +type Nodes = Record + +/** Grade is read along a room's outline this often, metres. */ +const GRADE_STEP = 0.15 + +/** A room named a garage: "garage" as a word, whatever its case ("Garage", "2-car garage"). */ +export function isGarageName(name: string | null | undefined): boolean { + return typeof name === 'string' && /\bgarage\b/i.test(name) +} + +/** The building a room stands in, when it is one with its foundation record on. */ +function buildingOfRoom(nodes: Nodes, zone: Pick): BuildingNode | null { + const buildingId = zone.parentId + ? getLevelElevations(nodes).get(zone.parentId as AnyNodeId)?.buildingId + : undefined + const building = buildingId ? nodes[buildingId as AnyNodeId] : undefined + return building?.type === 'building' && isBuildingFoundationOn(building) ? building : null +} + +/** + * What a room's floor stands on: its own choice (`floor.foundation`, made in its panel or by its + * name), else its building's foundation type. `null` for a room in no building with its foundation + * record on. + */ +export function roomFoundationOf( + nodes: Nodes, + zone: Pick, +): BuildingFoundationType | null { + const building = buildingOfRoom(nodes, zone) + if (!building) return null + return zone.floor?.foundation ?? buildingFoundationOf(nodes, building.id)?.type ?? null +} + +/** + * The floor elevation (level-local) a room on a slab of its own takes in a raised house (a garage): + * its top `slabRoomTopAboveGradeIn` over the highest grade along the room, the slab on grade while + * the house's floor stands over its crawl space. `null` where the room's building is no raised + * house on its designed foundation, or the room has no outline. + */ +export function slabRoomElevation( + nodes: Nodes, + zone: Pick, +): number | null { + const building = buildingOfRoom(nodes, zone) + const foundation = building ? buildingFoundationOf(nodes, building.id) : null + const plane = zone.parentId ? levelPlaneWorldY(nodes, zone.parentId) : null + if (!(building && foundation?.type === 'raised' && plane !== null) || zone.polygon.length < 3) + return null + const site = siteOf(nodes, building) + let highest = Number.NEGATIVE_INFINITY + zone.polygon.forEach(([ax, az], index) => { + const [bx, bz] = zone.polygon[(index + 1) % zone.polygon.length]! + const count = Math.max(2, Math.ceil(Math.hypot(bx - ax, bz - az) / GRADE_STEP) + 1) + for (let k = 0; k < count; k++) { + const t = k / (count - 1) + const [x, z] = levelLocalToWorldXZ(building, ax + (bx - ax) * t, az + (bz - az) * t) + highest = Math.max(highest, groundHeightAt(site, x, z)) + } + }) + const top = highest + inches(foundation.slabRoomTopAboveGradeIn) + return Math.round((top - plane) * 1e4) / 1e4 +} + +/** The visible walls standing on a level. */ +function levelWallsOf(nodes: Nodes, level: LevelNode): WallNode[] { + return level.children + .map((id) => nodes[id as AnyNodeId]) + .filter((node): node is WallNode => node?.type === 'wall' && node.visible !== false) +} + +// The last outside walls found per level, kept while its walls stand as they were: a door tool +// asks for every pointer move over a wall. +const outsideMemo = new Map }>() + +/** The ids of a level's walls on its outline (`exteriorWallLoops`): the outside on one face. */ +function outsideWallIdsOf(levelId: string, walls: readonly WallNode[]): ReadonlySet { + const hit = outsideMemo.get(levelId) + if (hit && hit.walls.length === walls.length && hit.walls.every((wall, k) => wall === walls[k])) + return hit.ids + const ids = new Set( + exteriorWallLoops([...walls]).flatMap((loop) => loop.map((face) => face.wallId as string)), + ) + outsideMemo.set(levelId, { walls, ids }) + return ids +} + +/** + * A room's walls to the outside, by id: its bounding walls (`boundaryWallIds`, else the walls + * along its outline) on the level's outline. A wall it shares with the house is not one. + */ +function outsideWallsOfRoom( + zone: Pick, + walls: readonly WallNode[], + outside: ReadonlySet, +): Set { + return new Set( + walls + .filter((wall) => + zone.boundaryWallIds?.length + ? zone.boundaryWallIds.includes(wall.id) + : wallOnPolygon(wall, zone.polygon), + ) + .filter((wall) => outside.has(wall.id)) + .map((wall) => wall.id as string), + ) +} + +/** + * The doors a room on a slab of its own opens to the outside through (in its walls on the house's + * outline: a garage's overhead door, a man door), their sills moved with its floor from `from` to + * `to` (level-local floor heights, 0 for the house's floor), so each one opens from the slab. A + * door in a wall it shares with the house keeps its sill at the house floor, a step down from it. + */ +export function slabRoomDoorChanges( + nodes: Nodes, + zone: Pick, + from: number, + to: number, +): Array<{ id: AnyNodeId; data: Partial }> { + const shift = to - from + const level = zone.parentId ? nodes[zone.parentId as AnyNodeId] : undefined + if (Math.abs(shift) < 1e-6 || level?.type !== 'level') return [] + const walls = levelWallsOf(nodes, level) + const bounding = outsideWallsOfRoom(zone, walls, outsideWallIdsOf(level.id, walls)) + return Object.values(nodes).flatMap((node) => + node.type === 'door' && node.parentId && bounding.has(node.parentId) + ? [ + { + id: node.id as AnyNodeId, + data: { + position: [ + node.position[0], + Math.round((node.position[1] + shift) * 1e4) / 1e4, + node.position[2], + ], + } as Partial, + }, + ] + : [], + ) +} + +/** + * The floor (level-local height) a door set in `wall` opens from: where the wall is one of a slab + * room's walls to the outside (a garage's, its overhead door or a man door), that room's floor on + * its slab, as `slabRoomDoorChanges` moves the doors it already has; else the house's floor, 0. A + * door in the wall a slab room shares with the house opens from the house's floor, a step up from + * the room. The door tools seat a door on it as they place or move one (`clampDoorToWall`). + */ +export function doorSillFloorOf(nodes: Nodes, wall: Pick): number { + const level = wall.parentId ? nodes[wall.parentId as AnyNodeId] : undefined + if (level?.type !== 'level') return 0 + const rooms = level.children + .map((id) => nodes[id as AnyNodeId]) + .filter( + (node): node is ZoneNode => + node?.type === 'zone' && + node.floor?.foundation === 'slab' && + Math.abs(node.floor.elevation ?? 0) > 1e-6, + ) + if (rooms.length === 0) return 0 + const walls = levelWallsOf(nodes, level) + const outside = outsideWallIdsOf(level.id, walls) + if (!outside.has(wall.id)) return 0 + const room = rooms.find((zone) => outsideWallsOfRoom(zone, walls, outside).has(wall.id)) + return room?.floor?.elevation ?? 0 +} + +/** Whether a wall's centreline runs along a side of `polygon`, within its half thickness. */ +function wallOnPolygon(wall: WallNode, polygon: ReadonlyArray) { + const [mx, mz] = [(wall.start[0] + wall.end[0]) / 2, (wall.start[1] + wall.end[1]) / 2] + return polygon.some(([ax, az], index) => { + const [bx, bz] = polygon[(index + 1) % polygon.length]! + const length2 = (bx - ax) ** 2 + (bz - az) ** 2 + if (length2 < 1e-12) return false + const t = Math.max(0, Math.min(1, ((mx - ax) * (bx - ax) + (mz - az) * (bz - az)) / length2)) + const gap = Math.hypot(mx - (ax + (bx - ax) * t), mz - (az + (bz - az) * t)) + return gap <= (wall.thickness ?? 0.15) / 2 + 0.05 + }) +} diff --git a/packages/core/src/lib/space-detection.test.ts b/packages/core/src/lib/space-detection.test.ts index 1570d3f582..40d238f840 100644 --- a/packages/core/src/lib/space-detection.test.ts +++ b/packages/core/src/lib/space-detection.test.ts @@ -1,10 +1,17 @@ import { describe, expect, test } from 'bun:test' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' +import { AGENT_OPERATIONS, applyAgentOutcome } from '../agent-operations' +import { createZone } from '../commands/structure/create-zone' +import { structureChangeBatch } from '../commands/structure/shared' import { findOpenWallEnds, SeparatorNode } from '../index' -import { BuildingNode, CeilingNode, LevelNode, SlabNode, WallNode } from '../schema' +import { BuildingNode, CeilingNode, generateId, LevelNode, SlabNode, WallNode } from '../schema' +import { DEFAULT_UPPER_FLOOR_THICKNESS } from '../schema/nodes/slab' import type { AnyNode, AnyNodeId } from '../schema/types' +import { resolveCeilingHeight } from '../services/level-height' +import { getWallPlaneTop } from '../services/storey' import { + runAsSceneCommitAuthor, runWithSceneCommitNodeIds, type SceneCommit, subscribeSceneCommits, @@ -2225,3 +2232,152 @@ describe('wallClosesRoom', () => { expect(wallClosesRoom([...bigRoom, bayLeft, bayBottom, bayRight], bayRight)).toBe(true) }) }) + +describe('a new storey floor', () => { + test('a floor over rooms below is framed, 13⅛ in thick, lifts its own storey, keeps the ceiling below, and is one undo step with the room', () => { + const building = BuildingNode.parse({ children: [] }) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 2.819 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 2.819 }) + building.children = [ground.id, upper.id] + const previousSceneState = useScene.getState() + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground, [upper.id]: upper }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + collections: {}, + materials: {}, + installedPlugins: [], + readOnly: false, + } as never) + clearSceneHistory() + const stopDetection = initSpaceDetectionSync(useScene, createEditorStoreStub(), { + newUpperFloorThickness: DEFAULT_UPPER_FLOOR_THICKNESS, + }) + const nodes = () => useScene.getState().nodes as Record + const plateOn = (levelId: string) => + Object.values(nodes()).find( + (node): node is SlabNode => + node.type === 'slab' && node.plateRole === 'base' && node.parentId === levelId, + ) + const ceilingOn = (levelId: string) => + Object.values(nodes()).find( + (node): node is CeilingNode => node.type === 'ceiling' && node.parentId === levelId, + )! + const addRoom = (levelId: string, x = 0, width = 8) => + useScene.getState().applyNodeChanges( + structureChangeBatch( + createZone(nodes(), { + levelId, + polygon: [ + [x, 0], + [x + width, 0], + [x + width, 5], + [x, 5], + ], + enclose: true, + mintId: generateId, + }).changes, + ), + ) + try { + addRoom(ground.id, 0, 30) + expect(plateOn(ground.id)?.thickness).toBe(0.05) + const ceilingBelow = resolveCeilingHeight(ceilingOn(ground.id), nodes()) + clearSceneHistory() + addRoom(upper.id) + expect(plateOn(upper.id)).toMatchObject({ + thickness: DEFAULT_UPPER_FLOOR_THICKNESS, + elevation: DEFAULT_UPPER_FLOOR_THICKNESS, + }) + const wall = Object.values(nodes()).find( + (node): node is WallNode => node.type === 'wall' && node.parentId === upper.id, + )! + expect(getWallPlaneTop(wall, upper.id, nodes())).toBeCloseTo( + 2.819 + DEFAULT_UPPER_FLOOR_THICKNESS - 0.05, + 6, + ) + expect(resolveCeilingHeight(ceilingOn(ground.id), nodes())).toBeCloseTo(ceilingBelow, 6) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + // A second room upstairs gets the same floor. + addRoom(upper.id, 20) + const upstairs = Object.values(nodes()).filter( + (node): node is SlabNode => + node.type === 'slab' && node.plateRole === 'base' && node.parentId === upper.id, + ) + expect(upstairs.map((plate) => [plate.thickness, plate.elevation])).toEqual([ + [DEFAULT_UPPER_FLOOR_THICKNESS, DEFAULT_UPPER_FLOOR_THICKNESS], + [DEFAULT_UPPER_FLOOR_THICKNESS, DEFAULT_UPPER_FLOOR_THICKNESS], + ]) + useScene.temporal.getState().undo() + useScene.temporal.getState().undo() + expect(plateOn(upper.id)).toBeUndefined() + } finally { + stopDetection() + useScene.setState(previousSceneState, true) + clearSceneHistory() + } + }) + + test('rooms an agent builds, or a host without the editor opt-in, keep the 0.05 m floor', () => { + for (const host of ['agent', 'headless'] as const) { + const building = BuildingNode.parse({ children: [] }) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 2.819 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 2.819 }) + building.children = [ground.id, upper.id] + const previousSceneState = useScene.getState() + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground, [upper.id]: upper }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + collections: {}, + materials: {}, + installedPlugins: [], + readOnly: false, + } as never) + clearSceneHistory() + const stopDetection = initSpaceDetectionSync( + useScene, + createEditorStoreStub(), + host === 'agent' ? { newUpperFloorThickness: DEFAULT_UPPER_FLOOR_THICKNESS } : {}, + ) + const nodes = () => useScene.getState().nodes as Record + const build = (levelId: string) => + applyAgentOutcome( + AGENT_OPERATIONS.create_room!( + nodes(), + { + levelId, + name: 'Room', + polygon: [ + [0, 0], + [8, 0], + [8, 5], + [0, 5], + ], + } as never, + { activeLevelId: levelId }, + ), + { + applyChanges: (changes) => useScene.getState().applyNodeChanges(changes as never), + reconcile: () => {}, + getNodes: nodes, + } as never, + ) + try { + runAsSceneCommitAuthor('agent', () => { + build(ground.id) + build(upper.id) + }) + const plate = Object.values(nodes()).find( + (node): node is SlabNode => + node.type === 'slab' && node.plateRole === 'base' && node.parentId === upper.id, + ) + expect(plate).toMatchObject({ thickness: 0.05, elevation: 0.05 }) + } finally { + stopDetection() + useScene.setState(previousSceneState, true) + clearSceneHistory() + } + } + }) +}) diff --git a/packages/core/src/lib/space-detection.ts b/packages/core/src/lib/space-detection.ts index fc6710f76f..b2487f4608 100644 --- a/packages/core/src/lib/space-detection.ts +++ b/packages/core/src/lib/space-detection.ts @@ -1,14 +1,19 @@ +import { setFloorFoundation } from '../commands/structure/set-floor-foundation' +import { structureChangeBatch } from '../commands/structure/shared' import { type AnyNode, type AnyNodeId, + type SlabNode, type WallNode, ZoneNode, type ZoneNode as ZoneNodeType, } from '../schema' import { generateId } from '../schema/base' +import { SlabNode as SlabSchema } from '../schema/nodes/slab' import { findLevelAboveId, getLevelBelow, getLevelElevations } from '../services/storey' import { isSettlingDocument } from '../store/document-settling' import { + activeSceneCommitAuthor, activeSceneCommitNodeIds, getSceneHistoryPauseDepth, isRestoringSceneHistory, @@ -23,8 +28,12 @@ import { sceneEnvironmentOf, } from '../store/scene-environment' import { getClampedWallCurveOffset } from '../systems/wall/wall-curve' +import { floorFootprintSupportClass } from './floor-foundation-datum' import { changedLevelConstructionDisplacements } from './floor-foundation-stack' import { createFloorOpeningIndex, floorOpeningTargets } from './floor-opening-intent' +import { area } from './polygon-boolean' +import { porchPartOf, porchPartSignature } from './porch-framing-sync' +import { railingFencedRoomIds, railingWallIds } from './railing-rooms' import { type ExtractedRoom, extractRooms, @@ -69,6 +78,12 @@ export type SpaceTopologyReconcileEvent = { export type SpaceDetectionSyncOptions = { mintId?: StructureIdFactory onTopologyReconcile?: (event: SpaceTopologyReconcileEvent) => void + /** + * The editor's opt-in for floors a person draws: a new floor over rooms below is born this thick + * (`DEFAULT_UPPER_FLOOR_THICKNESS`). Off by default, and never for writes an agent authors, so + * programmatic builds keep the elevations they asked for. + */ + newUpperFloorThickness?: number } type WallSideUpdate = { @@ -145,6 +160,9 @@ function zoneGeometrySignature(zone: ZoneNodeType) { zone.wallMaterial, zone.wallOverrides, zone.seed, + // A porch's type and framing settings rebuild its posts and beam. + zone.roomType, + zone.porchFraming, // A drawn zone's outline is intent (it may be adopted); a room's outline is // derived, and an edit to it must be re-derived here as the hosted path does. // The kernel's own write is not a change: the baseline is taken after it. @@ -164,11 +182,19 @@ function levelStructureSnapshots(nodes: Record) { const zonesByLevel = new Map() const slabElevationsByLevel = new Map() const coveringUndersidesByLevel = new Map() + const porchPartsByLevel = new Map() const floorOpenings: Extract[] = [] for (const node of Object.values(nodes)) { if (!(node && typeof node === 'object' && 'parentId' in node && node.parentId)) continue const levelId = (node as any).parentId as string + // A porch's own post or beam moved, resized or deleted by hand hands its framing over. + const porchPart = porchPartSignature(node) + if (porchPart) { + const parts = porchPartsByLevel.get(levelId) ?? [] + parts.push(porchPart) + porchPartsByLevel.set(levelId, parts) + } if (node.type === 'stair' && nodes[node.deckSlabId]?.support === 'open') { const target = nodes[node.deckSlabId].parentId const arrivals = arrivalsByLevel.get(target) ?? [] @@ -271,6 +297,7 @@ function levelStructureSnapshots(nodes: Record) { ...zonesByLevel.keys(), ...separatorsByLevel.keys(), ...openingsByLevel.keys(), + ...porchPartsByLevel.keys(), ]) for (const levelId of levelIds) { const walls = wallsByLevel.get(levelId) ?? [] @@ -284,7 +311,7 @@ function levelStructureSnapshots(nodes: Record) { : '' snapshots.set( levelId, - `${(arrivalsByLevel.get(levelId) ?? []).sort().join('|')}#${(separatorsByLevel.get(levelId) ?? []).sort().join('|')}#${storeyKey}#${levelWallSnapshot(walls, nodes, levelId)}##${zones.map(zoneGeometrySignature).sort().join('||')}##${slabKey}##${aboveSlabKey}##${(ceilingIdsByLevel.get(levelId) ?? []).sort().join('|')}##${(openingsByLevel.get(levelId) ?? []).sort().join('|')}`, + `${(arrivalsByLevel.get(levelId) ?? []).sort().join('|')}#${(separatorsByLevel.get(levelId) ?? []).sort().join('|')}#${storeyKey}#${levelWallSnapshot(walls, nodes, levelId)}##${zones.map(zoneGeometrySignature).sort().join('||')}##${slabKey}##${aboveSlabKey}##${(ceilingIdsByLevel.get(levelId) ?? []).sort().join('|')}##${(openingsByLevel.get(levelId) ?? []).sort().join('|')}##${(porchPartsByLevel.get(levelId) ?? []).sort().join('|')}`, ) } @@ -418,6 +445,10 @@ function fallbackLevelIdsForCandidates( } else if (node.type === 'door' || node.type === 'window') { const wall = nodes[node.parentId] if (wall?.parentId) levelIds.add(wall.parentId) + } else if (porchPartOf(node)) { + // A porch's own post or beam edited by hand: the kernel hands its framing over. + if (porchPartSignature(before[id]) !== porchPartSignature(current[id]) && node.parentId) + levelIds.add(node.parentId) } else if (node.type === 'slab') { const old = before[id], next = current[id] @@ -459,12 +490,12 @@ function detectSpacesFromWalls(levelId: string, boundaries: BoundaryNode[]) { const rooms = extractRooms(boundaries) const walls = boundaries.filter((boundary): boundary is WallNode => boundary.type === 'wall') const roomPolygons = rooms.map((room) => room.polygon) + const spans = rooms.flatMap((room) => room.spans) + // A room a railing fences off from the outside (a deck's guard) is outside. + const fenced = railingFencedRoomIds(spans, railingWallIds(walls)) const wallUpdates: WallSideUpdate[] = walls.map((wall) => ({ wallId: wall.id, - ...classifyWallSides( - wall, - rooms.flatMap((room) => room.spans), - ), + ...classifyWallSides(wall, spans, fenced), })) return { @@ -526,8 +557,88 @@ function syncSceneStructure( sceneStore: any, previousNodes: SceneNodes, mintId: StructureIdFactory, + newUpperFloorThickness?: number, +) { + const patches = applyStructureReconciliation(sceneStore, { levelIds, previousNodes, mintId }) + if (newUpperFloorThickness !== undefined && activeSceneCommitAuthor() !== 'agent') + thickenNewUpperFloors(patches, sceneStore, previousNodes, mintId, newUpperFloorThickness) +} + +const SCHEMA_SLAB_THICKNESS = SlabSchema.shape.thickness.parse(undefined) + +const isBasePlate = (node: AnyNode | undefined): node is SlabNode => + node?.type === 'slab' && node.plateRole === 'base' + +/** + * A storey's floor over rooms below is born a framed floor, not the 0.05 m + * schema default: a plate the reconciler just created, standing on rooms below, + * takes the editor's opt-in thickness (`newUpperFloorThickness`) on a level that + * had no floor yet, and + * the thickness of that level's floor on one that has (so a second room upstairs + * matches the first, and an older 0.05 m floor stays as it was). It is written + * as the Floor thickness control writes it — underside held on the walls below, + * so this storey and the ones above ride up and the ceilings below stay — inside + * the edit that made the room, so one undo takes both back. Loaded, remote and + * undone scenes never reach here, so a saved floor keeps its thickness. + */ +function thickenNewUpperFloors( + patches: ReturnType, + sceneStore: any, + previousNodes: SceneNodes, + mintId: StructureIdFactory, + firstFloorThickness: number, ) { - applyStructureReconciliation(sceneStore, { levelIds, previousNodes, mintId }) + for (const patch of patches) { + if (patch.op !== 'create' || !isBasePlate(patch.node as AnyNode)) continue + const nodes = sceneStore.getState().nodes as SceneNodes + const plate = nodes[patch.node.id as AnyNodeId] + if (!isBasePlate(plate) || !plate.parentId) continue + // A plate that took its construction from an earlier one keeps it. + if (Math.abs(plate.thickness - SCHEMA_SLAB_THICKNESS) > 1e-6) continue + if (floorFootprintSupportClass(nodes, plate) !== 'supported') continue + const thickness = newFloorThickness(nodes, previousNodes, plate, firstFloorThickness) + if (thickness === null || Math.abs(thickness - plate.thickness) < 1e-6) continue + const plan = setFloorFoundation(nodes, { slabId: plate.id, patch: { thickness } }) + const { applyNodeChanges } = sceneStore.getState() + if (plan.conflicts?.length || !plan.changes.length || typeof applyNodeChanges !== 'function') + continue + applyNodeChanges(structureChangeBatch(plan.changes)) + const level = nodes[plate.parentId as AnyNodeId] + const building = level?.parentId ? nodes[level.parentId as AnyNodeId] : undefined + applyStructureReconciliation(sceneStore, { + levelIds: + building?.type === 'building' + ? (building.children as string[]).filter((id) => nodes[id as AnyNodeId]?.type === 'level') + : [plate.parentId], + previousNodes: nodes, + mintId, + }) + } +} + +/** The thickness a new supported floor plate takes on its level, or null to leave it. */ +function newFloorThickness( + nodes: SceneNodes, + previousNodes: SceneNodes, + plate: SlabNode, + firstFloorThickness: number, +): number | null { + const before = Object.values(previousNodes).filter( + (node): node is SlabNode => isBasePlate(node) && node.parentId === plate.parentId, + ) + if (!before.length) return firstFloorThickness + const sibling = before + .map((node) => nodes[node.id as AnyNodeId]) + .filter( + (node): node is SlabNode => + isBasePlate(node) && floorFootprintSupportClass(nodes, node) === 'supported', + ) + .sort( + (a, b) => + area([{ outer: b.polygon, holes: b.holes ?? [] }]) - + area([{ outer: a.polygon, holes: a.holes ?? [] }]) || a.id.localeCompare(b.id), + )[0] + return sibling ? sibling.thickness : null } function publishSpaces(levelIds: string[], nodes: SceneNodes, editorStore: any) { @@ -668,7 +779,13 @@ export function initSpaceDetectionSync( pauseSceneHistory(sceneStore) try { const levels = [...new Set([...changedWalls.keys(), ...fallbackLevels])] - syncSceneStructure(levels, sceneStore, previousNodes, mintId) + syncSceneStructure( + levels, + sceneStore, + previousNodes, + mintId, + options.newUpperFloorThickness, + ) publishSpaces(levels, sceneStore.getState().nodes, editorStore) for (const [levelId, wallIds] of changedWalls) { const topologyDelta = topologyIndex.applyWallDelta(levelId, wallIds, previousNodes, nodes) @@ -721,7 +838,13 @@ export function initSpaceDetectionSync( isProcessing = true pauseSceneHistory(sceneStore) try { - syncSceneStructure([...levelsToUpdate], sceneStore, previousNodes, mintId) + syncSceneStructure( + [...levelsToUpdate], + sceneStore, + previousNodes, + mintId, + options.newUpperFloorThickness, + ) publishSpaces([...levelsToUpdate], sceneStore.getState().nodes, editorStore) } finally { resumeSceneHistory(sceneStore) @@ -749,7 +872,7 @@ export function initSpaceDetectionSync( isProcessing = true pauseSceneHistory(sceneStore) try { - syncSceneStructure(levels, sceneStore, previousNodes, mintId) + syncSceneStructure(levels, sceneStore, previousNodes, mintId, options.newUpperFloorThickness) publishSpaces(levels, sceneStore.getState().nodes, editorStore) for (const id of levels) topologyIndex.rebuildLevel(id, sceneStore.getState().nodes) previousNodes = sceneStore.getState().nodes diff --git a/packages/core/src/lib/structure-kernel.ts b/packages/core/src/lib/structure-kernel.ts index fc2c96324f..ddd6350d1f 100644 --- a/packages/core/src/lib/structure-kernel.ts +++ b/packages/core/src/lib/structure-kernel.ts @@ -19,6 +19,7 @@ import { buildFloorPlates, warnPlateFailure } from './floor-plates' import { floorRoomFaces } from './floor-room-faces' import { type FloorStepOverride, remapFloorStepOverrideKeys } from './floor-step-finish' import { newZone } from './new-zone' +import { isOutdoorRoom, nextOutdoorRoomName, OUTDOOR_ROOM_DEFAULTS } from './outdoor-room' import { replacementPlateFor } from './plate-reference' import { area, @@ -30,6 +31,8 @@ import { union, } from './polygon-boolean' import { polygonInteriorPoint } from './polygon-label' +import { reconcilePorchFraming } from './porch-framing-sync' +import { railingFencedRoomIds, railingWallIds } from './railing-rooms' import { pointFromTuple } from './room-graph' import { roomNameAllocator } from './room-name' import type { BoundarySpan } from './room-topology-index' @@ -117,18 +120,50 @@ function equal(a: unknown, b: unknown) { const SPAN_OVERLAP = 1e-6 /** - * The rooms that are outside: without a ceiling, and left without crossing a wall, through a - * separator no room stands behind or into another such room. A porch made a room for its floor - * left its wall with no outside: the front door faced the hall. A room that only lost its - * ceiling is walled in and a kitchen open to a terrace keeps its ceiling, so both stay inside; so - * does a courtyard walled on every side, a limit of this rule. + * The rooms closed off outside the house, and what they are by default: open to the outside across + * invisible walls or railings nothing stands behind, along at least as much of their edge as walls + * close (a porch on the front, or in a corner of the house). Closed with invisible walls, a porch; + * fenced with railings alone, a deck. A room walled on three sides and open on the fourth (a + * kitchen open to a terrace, a recessed porch) is not one of them. `lengthOf` is a wall's or a + * separator's length; `railingIds` the railing walls. */ -export function outdoorRoomIds( - rooms: readonly { spans: readonly BoundarySpan[]; hasCeiling: boolean }[], -): Set { - const separatorSpans = rooms.flatMap((room) => - room.spans.filter((span) => span.kind === 'separator'), - ) +export function outsideRoomTypes( + rooms: readonly { spans: readonly BoundarySpan[] }[], + lengthOf: (boundaryId: string) => number, + railingIds: ReadonlySet = new Set(), +): Map { + const railing = (span: BoundarySpan) => span.kind === 'wall' && railingIds.has(span.boundaryId) + const { exposed } = openLineExposure(rooms, (span) => span.kind === 'separator' || railing(span)) + const spanLength = (span: BoundarySpan) => (span.t1 - span.t0) * lengthOf(span.boundaryId) + const types = new Map() + for (const room of rooms) { + let open = 0 + let walled = 0 + let invisible = false + for (const span of room.spans) { + if (span.kind === 'wall' && !railing(span)) walled += spanLength(span) + else if (exposed(span)) { + open += spanLength(span) + invisible ||= span.kind === 'separator' + } + } + if (room.spans[0] && open > 0 && open >= walled - 1e-6) + types.set(room.spans[0].roomId, invisible ? 'porch' : 'deck') + } + return types +} + +/** Each separator span's spans across it, and whether any stretch of it has none. */ +function separatorExposure(rooms: readonly { spans: readonly BoundarySpan[] }[]) { + return openLineExposure(rooms, (span) => span.kind === 'separator') +} + +/** Each open line's span (`isOpen`): the spans across it, and whether any stretch of it has none. */ +function openLineExposure( + rooms: readonly { spans: readonly BoundarySpan[] }[], + isOpen: (span: BoundarySpan) => boolean, +) { + const separatorSpans = rooms.flatMap((room) => room.spans.filter(isOpen)) const across = (span: BoundarySpan) => separatorSpans .filter( @@ -146,13 +181,35 @@ export function outdoorRoomIds( } return reach < span.t1 - SPAN_OVERLAP } + return { across, exposed } +} + +/** + * The rooms that are outside: a porch, deck or patio (`roomType`, outdoors whatever covers it), and + * a room without a ceiling left without crossing a wall, through a separator no room stands behind + * or into another such room. A porch made a room for its floor left its wall with no outside: the + * front door faced the hall. A room that only lost its ceiling is walled in and a kitchen open to a + * terrace keeps its ceiling, so both stay inside; so does a courtyard walled on every side, a limit + * of this rule. + */ +export function outdoorRoomIds( + rooms: readonly { spans: readonly BoundarySpan[]; hasCeiling: boolean; outdoor?: boolean }[], +): Set { + const { across, exposed } = separatorExposure(rooms) const open = new Map( rooms.flatMap((room) => - !room.hasCeiling && room.spans[0] ? [[room.spans[0].roomId, room.spans] as const] : [], + (!room.hasCeiling || room.outdoor) && room.spans[0] + ? [[room.spans[0].roomId, room.spans] as const] + : [], ), ) + const declared = new Set( + rooms.flatMap((room) => (room.outdoor && room.spans[0] ? [room.spans[0].roomId] : [])), + ) const queue = [...open].flatMap(([id, spans]) => - spans.some((span) => span.kind === 'separator' && exposed(span)) ? [id] : [], + declared.has(id) || spans.some((span) => span.kind === 'separator' && exposed(span)) + ? [id] + : [], ) const outdoor = new Set(queue) for (let id = queue.pop(); id !== undefined; id = queue.pop()) @@ -207,13 +264,14 @@ export function reconcileLevelStructure( function planLevelStructure({ levelId, nodes, + previousNodes, mintId, openingIndex, }: { levelId: string nodes: SceneNodes previousNodes?: SceneNodes - mintId: (kind: 'zone' | 'ceiling' | 'slab' | 'separator') => string + mintId: (kind: 'zone' | 'ceiling' | 'slab' | 'separator' | 'column' | 'beam') => string openingIndex?: FloorOpeningIndex }): { patches: NodePatch[]; snapshot: LevelStructureSnapshot; events: StructureEvent[] } { const snapshot: LevelStructureSnapshot = { levelId, rooms: [] } @@ -309,6 +367,20 @@ function planLevelStructure({ const zoneByFace = new Map() const retiredTo = new Map() const nextRoomName = roomNameAllocator(allZones) + // A new room closed off outside the house is a porch, or a deck when railings fence it + // (`lib/outdoor-room`). + const boundaryLengths = new Map( + [...walls, ...separators].map((boundary) => [ + boundary.id as string, + Math.hypot(boundary.end[0] - boundary.start[0], boundary.end[1] - boundary.start[1]), + ]), + ) + const outsideTypes = outsideRoomTypes( + faces, + (id) => boundaryLengths.get(id) ?? 0, + railingWallIds(walls), + ) + const roomNames = allZones.map((zone) => zone.name) faces.forEach((face, index) => { const rooms = zones .filter((zone) => preferred.get(zone.id) === index && !adoptionFits.has(zone.id)) @@ -333,21 +405,38 @@ function planLevelStructure({ (a, b) => scores.get(b.id)![index]! - scores.get(a.id)![index]! || compareIds(a.id, b.id), )[0] if (source) sourceByFace.set(index, source) + const outside = + !survivor && !source && face.spans[0] ? outsideTypes.get(face.spans[0].roomId) : undefined const zone = survivor ?? - newZone({ - id: mintId('zone'), - parentId: levelId, - name: nextRoomName(), - polygon: face.polygon.outer, - floor: source?.floor, - wallMaterial: source?.wallMaterial, - floorStepFinish: source?.floorStepFinish, - floorStepOverrides: source?.floorStepOverrides, - floorEdgeFinish: source?.floorEdgeFinish, - hasFloor: source?.hasFloor, - hasCeiling: source?.hasCeiling, - }) + (outside + ? newZone({ + id: mintId('zone'), + parentId: levelId, + name: nextOutdoorRoomName(outside, roomNames), + polygon: face.polygon.outer, + roomType: outside, + floor: { + drop: OUTDOOR_ROOM_DEFAULTS[outside].drop, + finish: OUTDOOR_ROOM_DEFAULTS[outside].finish, + }, + ...(OUTDOOR_ROOM_DEFAULTS[outside].covered ? {} : { hasCeiling: false }), + }) + : newZone({ + id: mintId('zone'), + parentId: levelId, + name: nextRoomName(), + polygon: face.polygon.outer, + floor: source?.floor, + wallMaterial: source?.wallMaterial, + floorStepFinish: source?.floorStepFinish, + floorStepOverrides: source?.floorStepOverrides, + floorEdgeFinish: source?.floorEdgeFinish, + hasFloor: source?.hasFloor, + hasCeiling: source?.hasCeiling, + roomType: source?.roomType, + })) + if (outside) roomNames.push(zone.name) const seed = zone.seed && containsPoint([face.polygon], zone.seed) ? zone.seed @@ -596,6 +685,7 @@ function planLevelStructure({ material: template?.material, materialPreset: template?.materialPreset, slots: template?.slots, + buildUp: template?.buildUp, visible: template?.visible, }) const updated: CeilingNode = { @@ -767,13 +857,39 @@ function planLevelStructure({ } for (const components of ceilingComponentsByFace.values()) for (const ceiling of components) ceiling.children = [...new Set(ceiling.children)].sort() + // A covered porch's posts and beam (`lib/porch-framing-sync`). + const porchFraming = reconcilePorchFraming({ + levelId, + nodes, + previousNodes, + zones: [ + ...new Map( + [...allZones, ...next.values()].flatMap((zone) => + zone.type === 'zone' && !deleted.has(zone.id) + ? [[zone.id, (next.get(zone.id) ?? zone) as ZoneNode] as const] + : [], + ), + ).values(), + ], + plates: floorPlan.plates, + current: (id) => next.get(id) ?? nodes[id], + mintId, + }) + for (const node of porchFraming.put) put(node) + for (const id of porchFraming.delete) deleted.add(id) const spans = snapshot.rooms.flatMap((room) => room.spans) const outdoor = outdoorRoomIds( - [...zoneByFace].map(([index, zone]) => ({ - spans: faces[index]!.spans, - hasCeiling: ((next.get(zone.id) ?? zone) as ZoneNode).hasCeiling !== false, - })), + [...zoneByFace].map(([index, zone]) => { + const current = (next.get(zone.id) ?? zone) as ZoneNode + return { + spans: faces[index]!.spans, + hasCeiling: current.hasCeiling !== false, + outdoor: isOutdoorRoom(current), + } + }), ) + // A room a railing fences off from the outside (a deck's guard) is outside too. + for (const id of railingFencedRoomIds(spans, railingWallIds(walls))) outdoor.add(id) for (const wall of walls) put({ ...wall, ...next.get(wall.id), ...classifyWallSides(wall, spans, outdoor) } as WallNode) for (const ceiling of allCeilings) { @@ -890,7 +1006,14 @@ export function createLevelStructurePreview(levelId: string, nodes: SceneNodes) .filter((node): node is WallNode => node.type === 'wall' && node.parentId === levelId) .map((node) => node.id), ) - return (walls: readonly WallNode[]) => { + let separatorIds = new Set( + Object.values(draft) + .filter((node) => node.type === 'separator' && node.parentId === levelId) + .map((node) => node.id), + ) + // `separators`, when given, are the level's invisible walls as the gesture + // leaves them (an invisible-wall push); left out, the stored ones stand. + return (walls: readonly WallNode[], separators?: readonly SeparatorNode[]) => { const currentIds = new Set() for (const wall of walls) { if (wall.parentId !== levelId) continue @@ -900,6 +1023,16 @@ export function createLevelStructurePreview(levelId: string, nodes: SceneNodes) // A level wall left out is one the gesture removes. for (const id of wallIds) if (!currentIds.has(id)) delete draft[id] wallIds = currentIds + if (separators) { + const current = new Set() + for (const separator of separators) { + if (separator.parentId !== levelId) continue + current.add(separator.id) + if (draft[separator.id] !== separator) draft[separator.id] = separator + } + for (const id of separatorIds) if (!current.has(id)) delete draft[id] + separatorIds = current + } let id = 0 // The stored surfaces are the committed reconciliation; only what the // moved walls reshape differs from them. diff --git a/packages/core/src/lib/structure-reconcile.ts b/packages/core/src/lib/structure-reconcile.ts index a92d70a268..46ce218fff 100644 --- a/packages/core/src/lib/structure-reconcile.ts +++ b/packages/core/src/lib/structure-reconcile.ts @@ -15,8 +15,11 @@ import { type SceneNodes, type StructureEvent, } from './structure-kernel' +import { surfaceIntentZoneIds } from './surface-intent' -export type StructureIdFactory = (kind: 'zone' | 'ceiling' | 'slab' | 'separator') => string +export type StructureIdFactory = ( + kind: 'zone' | 'ceiling' | 'slab' | 'separator' | 'column' | 'beam', +) => string export type SceneStructureInput = { nodes: SceneNodes levelIds?: string[] @@ -126,6 +129,7 @@ export function reconcileSceneStructure({ if (draft[levelId]?.type !== 'level') return const children = levelChildren(draft, levelId) const previousChildren = previousNodes ? levelChildren(previousNodes, levelId) : [] + const plates = children.filter((node) => node.type === 'slab') for (const surface of previousChildren) { if ( (surface.type !== 'ceiling' && surface.type !== 'slab') || @@ -133,8 +137,8 @@ export function reconcileSceneStructure({ draft[surface.id] ) continue - const ids = surface.type === 'ceiling' ? [surface.zoneId] : (surface.zoneIds ?? []) - for (const id of ids) { + // The rooms it was the floor or ceiling of: a porch on a plate of its own keeps its floor. + for (const id of surfaceIntentZoneIds(surface, plates)) { const zone = id ? draft[id] : undefined if (zone?.type === 'zone') draft[zone.id] = { diff --git a/packages/core/src/lib/surface-intent.ts b/packages/core/src/lib/surface-intent.ts new file mode 100644 index 0000000000..0c5a15f2f4 --- /dev/null +++ b/packages/core/src/lib/surface-intent.ts @@ -0,0 +1,26 @@ +import type { AnyNode, CeilingNode, SlabNode } from '../schema' + +/** + * The rooms whose construction a derived surface IS, among `plates` (the level's floor plates as + * they stand after the edit): a ceiling's room, and the rooms a plate is the floor of. A shared base + * plate also lists the rooms that stand on plates of their own (a porch or deck a step down, a raised + * platform), but it is not their floor, so losing it leaves theirs. Without this, deleting the house's + * plate took every porch and deck floor on the level with it. + */ +export function surfaceIntentZoneIds( + node: SlabNode | CeilingNode, + plates: readonly AnyNode[], +): string[] { + const ids = [ + ...new Set(node.type === 'slab' ? (node.zoneIds ?? []) : node.zoneId ? [node.zoneId] : []), + ] + if (node.type !== 'slab' || node.plateRole !== 'base') return ids + const own = new Set( + plates.flatMap((plate) => + plate.type === 'slab' && plate.id !== node.id && plate.plateRole && plate.plateRole !== 'base' + ? (plate.zoneIds ?? []) + : [], + ), + ) + return ids.filter((id) => !own.has(id)) +} diff --git a/packages/core/src/lib/wall-face-script.ts b/packages/core/src/lib/wall-face-script.ts index 4f0e3f6703..e59df924e7 100644 --- a/packages/core/src/lib/wall-face-script.ts +++ b/packages/core/src/lib/wall-face-script.ts @@ -88,11 +88,14 @@ type FaceFrame = { top: number } -/** A wall whose faces a script can build on: straight, not a curtain wall, standing on a level. */ +/** + * A wall whose faces a script can build on: straight, not a curtain wall or a railing (neither has + * faces), standing on a level. + */ function scriptableWall(nodes: SceneNodes, wallId: string): WallNode | null { const wall = nodes[wallId] if (wall?.type !== 'wall' || !wall.parentId) return null - if (wall.wallType === 'curtain' || isCurvedWall(wall)) return null + if (wall.wallType === 'curtain' || wall.wallType === 'railing' || isCurvedWall(wall)) return null return Math.hypot(wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]) < EPS ? null : wall } diff --git a/packages/core/src/material-kinds.ts b/packages/core/src/material-kinds.ts index 41ca0239dd..1c28004c30 100644 --- a/packages/core/src/material-kinds.ts +++ b/packages/core/src/material-kinds.ts @@ -5,6 +5,7 @@ export const MATERIAL_CATEGORIES = [ 'colors', 'wood', + 'siding', 'stone', 'brick', 'tile', diff --git a/packages/core/src/material-library.test.ts b/packages/core/src/material-library.test.ts index 344cbbb1be..8102e2af38 100644 --- a/packages/core/src/material-library.test.ts +++ b/packages/core/src/material-library.test.ts @@ -1,5 +1,11 @@ import { describe, expect, test } from 'bun:test' -import { getLibraryMaterialIdFromRef, getSceneMaterialIdFromRef } from './material-library' +import { + getCatalogMaterialById, + getLibraryMaterialIdFromRef, + getMaterialSwatch, + getMaterialsForCategory, + getSceneMaterialIdFromRef, +} from './material-library' describe('material references', () => { test('rejects malformed runtime values instead of calling string methods', () => { @@ -11,3 +17,39 @@ describe('material references', () => { } }) }) + +describe('siding', () => { + test('has its own category, apart from wood, under the ids scenes already reference', () => { + const siding = getMaterialsForCategory('siding').map((item) => item.id) + for (const id of ['siding-lap-white', 'siding-lap-cedar', 'siding-batten-charcoal']) { + expect(siding).toContain(id) + expect(getCatalogMaterialById(id)?.category).toBe('siding') + } + expect(siding).toContain('siding-vinyl-white') + expect(getMaterialsForCategory('wood').some((item) => item.id.startsWith('siding-'))).toBe( + false, + ) + }) + + test('a siding swatch is its profile texture tinted by its colour, not a flat chip', () => { + const navy = getCatalogMaterialById('siding-lap-navy')! + expect(getMaterialSwatch(navy)).toEqual({ + color: navy.preset.mapProperties.color, + imageUrl: navy.preset.maps.albedoMap!, + tinted: true, + }) + const batten = getMaterialSwatch(getCatalogMaterialById('siding-batten-olive')!) + expect(batten.imageUrl).toContain('batten') + }) + + test('a thumbnail shows as is, and a flat colour stays a colour', () => { + const wood = getCatalogMaterialById('wood-finewood27')! + expect(getMaterialSwatch(wood)).toEqual({ + color: wood.previewColor!, + imageUrl: wood.previewThumbnailUrl!, + tinted: false, + }) + const white = getMaterialSwatch(getCatalogMaterialById('preset-white')!) + expect(white.imageUrl).toBeNull() + }) +}) diff --git a/packages/core/src/material-library.ts b/packages/core/src/material-library.ts index 77e7e7975c..df4a73bb6c 100644 --- a/packages/core/src/material-library.ts +++ b/packages/core/src/material-library.ts @@ -2165,6 +2165,41 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ }, }, }, + { + // The default interior wall paint (`DEFAULT_INTERIOR_WALL_PAINT`): Light grey's + // colour under its own name. Listed after it, so a nearest-colour snap still + // lands on Light grey. + id: 'preset-seattlegrey', + label: 'Seattle grey', + category: 'colors', + description: 'Cool light grey interior wall paint', + previewColor: '#d8d6d1', + preset: { + maps: {}, + mapProperties: { + color: '#d8d6d1', + roughness: 0.9, + metalness: 0, + repeatX: 1, + repeatY: 1, + rotation: 0, + wrapS: 'Repeat', + wrapT: 'Repeat', + normalScaleX: 1, + normalScaleY: 1, + emissiveIntensity: 1, + displacementScale: 0.02, + transparent: false, + flipY: true, + bumpScale: 1, + emissiveColor: '#000000', + aoMapIntensity: 1, + side: 0, + opacity: 1, + lightMapIntensity: 1, + }, + }, + }, { id: 'preset-greige', label: 'Greige', @@ -4248,12 +4283,14 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ * board-and-batten — so imported exterior walls could be color-true only as * flat paint. One white seamless albedo per profile, * tinted per entry via mapProperties.color (same mechanism the flat color - * presets use), keeps this to two textures for twelve finishes. + * presets use), keeps this to two textures for every finish. Painted wood / + * fiber-cement and vinyl differ by sheen (roughness). The `siding` category + * keeps them apart from wood flooring; ids are stable scene references. */ { id: 'siding-lap-white', label: 'Lap siding — white', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted white', previewColor: '#e9e9e9', @@ -4288,7 +4325,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-greige', label: 'Lap siding — greige', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted greige', previewColor: '#c8c1b8', @@ -4323,7 +4360,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-gray', label: 'Lap siding — gray', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted gray', previewColor: '#8b8a86', @@ -4358,7 +4395,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-sage', label: 'Lap siding — sage', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted sage', previewColor: '#bcc5b2', @@ -4393,7 +4430,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-cedar', label: 'Lap siding — cedar', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, stained cedar', previewColor: '#b5825a', @@ -4428,7 +4465,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-forest', label: 'Lap siding — forest', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted forest', previewColor: '#4f6b57', @@ -4463,7 +4500,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-navy', label: 'Lap siding — navy', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted navy', previewColor: '#2f4865', @@ -4498,7 +4535,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-yellow', label: 'Lap siding — yellow', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted yellow', previewColor: '#f2e3b3', @@ -4533,7 +4570,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-nearblack', label: 'Lap siding — nearblack', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted nearblack', previewColor: '#232322', @@ -4568,7 +4605,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-lap-clay', label: 'Lap siding — clay', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior clapboard lap siding, painted clay', previewColor: '#8f4a2e', @@ -4603,7 +4640,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-batten-cream', label: 'Board & batten — cream', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior board-and-batten siding, painted cream', previewColor: '#efe3cc', @@ -4638,7 +4675,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-batten-sand', label: 'Board & batten — sand', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior board-and-batten siding, painted sand', previewColor: '#ddccae', @@ -4673,7 +4710,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-batten-charcoal', label: 'Board & batten — charcoal', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior board-and-batten siding, painted charcoal', previewColor: '#4e5257', @@ -4708,7 +4745,7 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ { id: 'siding-batten-olive', label: 'Board & batten — olive', - category: 'wood', + category: 'siding', surfaces: ['wall', 'outdoor'], description: 'Exterior board-and-batten siding, painted olive', previewColor: '#8d9368', @@ -4740,6 +4777,146 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [ }, }, }, + { + id: 'siding-vinyl-white', + label: 'Vinyl lap siding — white', + category: 'siding', + surfaces: ['wall', 'outdoor'], + description: 'Exterior vinyl lap siding, white, with a low sheen', + previewColor: '#ecebe6', + preset: { + maps: { + albedoMap: '/material/siding/lap_albedo_512.webp', + }, + mapProperties: { + color: '#ecebe6', + roughness: 0.55, + metalness: 0, + repeatX: 1, + repeatY: 1, + rotation: 0, + wrapS: 'Repeat', + wrapT: 'Repeat', + normalScaleX: 1, + normalScaleY: 1, + emissiveIntensity: 1, + displacementScale: 0.02, + bumpScale: 1, + transparent: false, + flipY: true, + emissiveColor: '#000000', + aoMapIntensity: 1, + side: 0, + opacity: 1, + lightMapIntensity: 1, + }, + }, + }, + { + id: 'siding-vinyl-almond', + label: 'Vinyl lap siding — almond', + category: 'siding', + surfaces: ['wall', 'outdoor'], + description: 'Exterior vinyl lap siding, almond, with a low sheen', + previewColor: '#ddd2bd', + preset: { + maps: { + albedoMap: '/material/siding/lap_albedo_512.webp', + }, + mapProperties: { + color: '#ddd2bd', + roughness: 0.55, + metalness: 0, + repeatX: 1, + repeatY: 1, + rotation: 0, + wrapS: 'Repeat', + wrapT: 'Repeat', + normalScaleX: 1, + normalScaleY: 1, + emissiveIntensity: 1, + displacementScale: 0.02, + bumpScale: 1, + transparent: false, + flipY: true, + emissiveColor: '#000000', + aoMapIntensity: 1, + side: 0, + opacity: 1, + lightMapIntensity: 1, + }, + }, + }, + { + id: 'siding-vinyl-gray', + label: 'Vinyl lap siding — gray', + category: 'siding', + surfaces: ['wall', 'outdoor'], + description: 'Exterior vinyl lap siding, gray, with a low sheen', + previewColor: '#a3a6a4', + preset: { + maps: { + albedoMap: '/material/siding/lap_albedo_512.webp', + }, + mapProperties: { + color: '#a3a6a4', + roughness: 0.55, + metalness: 0, + repeatX: 1, + repeatY: 1, + rotation: 0, + wrapS: 'Repeat', + wrapT: 'Repeat', + normalScaleX: 1, + normalScaleY: 1, + emissiveIntensity: 1, + displacementScale: 0.02, + bumpScale: 1, + transparent: false, + flipY: true, + emissiveColor: '#000000', + aoMapIntensity: 1, + side: 0, + opacity: 1, + lightMapIntensity: 1, + }, + }, + }, + { + id: 'siding-vinyl-vertical-white', + label: 'Vinyl vertical siding — white', + category: 'siding', + surfaces: ['wall', 'outdoor'], + description: 'Exterior vinyl board-and-batten style vertical siding, white, with a low sheen', + previewColor: '#ecebe6', + preset: { + maps: { + albedoMap: '/material/siding/batten_albedo_512.webp', + }, + mapProperties: { + color: '#ecebe6', + roughness: 0.55, + metalness: 0, + repeatX: 1, + repeatY: 1, + rotation: 0, + wrapS: 'Repeat', + wrapT: 'Repeat', + normalScaleX: 1, + normalScaleY: 1, + emissiveIntensity: 1, + displacementScale: 0.02, + bumpScale: 1, + transparent: false, + flipY: true, + emissiveColor: '#000000', + aoMapIntensity: 1, + side: 0, + opacity: 1, + lightMapIntensity: 1, + }, + }, + }, ] const STATIC_CATALOG_IDS = new Set(MATERIAL_CATALOG.map((item) => item.id)) @@ -4797,6 +4974,36 @@ export function getMaterialsForCategory(category: MaterialCategory): MaterialCat return items } +/** How a catalog material reads as a small flat swatch (a tile, a chip, a cursor badge). */ +export type MaterialSwatch = { + color: string + imageUrl: string | null + /** + * The image is a white albedo the colour tints, as the 3D material does + * (`mapProperties.color` × map): draw it multiplied over `color`. + */ + tinted: boolean +} + +const BROWSER_IMAGE = /\.(?:webp|png|jpe?g|avif|gif)(?:[?#]|$)/i + +/** + * A catalog material's swatch: its thumbnail; else its albedo when a browser can + * show it, tinted by the material's colour (a siding's lap or batten relief in its + * paint colour rather than a flat chip); else its preview colour. + */ +export function getMaterialSwatch(item: MaterialCatalogItem): MaterialSwatch { + const tint = item.preset.mapProperties.color + const color = item.previewColor ?? tint ?? '#f3f4f6' + if (item.previewThumbnailUrl) return { color, imageUrl: item.previewThumbnailUrl, tinted: false } + const albedo = item.preset.maps.albedoMap + if (albedo && BROWSER_IMAGE.test(albedo)) { + const tinted = !!tint && tint.toLowerCase() !== '#ffffff' + return { color: tinted ? tint : color, imageUrl: albedo, tinted } + } + return { color, imageUrl: null, tinted: false } +} + /** A community material's id (`mtl_…`, as the host's database mints them), well formed. */ export function isCommunityMaterialId(id: string): boolean { return /^mtl_[\w-]+$/.test(id) diff --git a/packages/core/src/node-slots/wall.ts b/packages/core/src/node-slots/wall.ts index 2316efcd5a..a48c0f2ce3 100644 --- a/packages/core/src/node-slots/wall.ts +++ b/packages/core/src/node-slots/wall.ts @@ -1,6 +1,7 @@ import type { SlotDeclaration } from '../registry/types' import { getCurtainWallConfig } from '../schema/nodes/curtain-wall' -import { WALL_SURFACE_SLOT_DEFAULTS, type WallNode } from '../schema/nodes/wall' +import { getRailingWallConfig, railingWallSlotDefault } from '../schema/nodes/railing-wall' +import { getWallFaceDefault, WALL_SURFACE_SLOT_DEFAULTS, type WallNode } from '../schema/nodes/wall' /** * A wall exposes two paintable faces — `a` (left of start → end) and `b` — @@ -19,9 +20,26 @@ export function wallSlots(node?: WallNode): SlotDeclaration[] { { slotId: 'curtain-solid', label: 'Solid panels', default: config.solidColor }, ] } + // A railing has no faces: it paints its posts and rails, and its infill. + if (node?.wallType === 'railing') { + const { style } = getRailingWallConfig(node) + return [ + { + slotId: 'railing-frame', + label: 'Posts & rails', + default: railingWallSlotDefault(style, 'railing-frame'), + }, + { + slotId: 'railing-infill', + label: style === 'glass' ? 'Glass' : 'Infill', + default: railingWallSlotDefault(style, 'railing-infill'), + }, + ] + } + // A face's default follows its side: the interior paint, raw drywall outside. return [ - { slotId: 'a', label: 'Side A', default: WALL_SURFACE_SLOT_DEFAULTS.a }, - { slotId: 'b', label: 'Side B', default: WALL_SURFACE_SLOT_DEFAULTS.b }, + { slotId: 'a', label: 'Side A', default: getWallFaceDefault(node ?? {}, 'a') }, + { slotId: 'b', label: 'Side B', default: getWallFaceDefault(node ?? {}, 'b') }, { slotId: 'aSkirting', label: 'Skirting (side A)', diff --git a/packages/core/src/registry/index.ts b/packages/core/src/registry/index.ts index cb8d9acafc..51af894b22 100644 --- a/packages/core/src/registry/index.ts +++ b/packages/core/src/registry/index.ts @@ -149,6 +149,8 @@ export type { PaintPatchArgs, PaintPreviewArgs, PaintResolveArgs, + PaintScopeId, + PaintSurfaceScopes, ParamAction, ParametricDescriptor, ParamField, diff --git a/packages/core/src/registry/types.ts b/packages/core/src/registry/types.ts index f7f51ae749..c584e27abc 100644 --- a/packages/core/src/registry/types.ts +++ b/packages/core/src/registry/types.ts @@ -656,6 +656,16 @@ export type FloorplanGeometry = */ affordance?: string payload?: unknown + /** The arrow this many times its usual size (a big outline's side push); 1 by default. */ + scale?: number + /** + * `pull`: a wall end's "pull to continue" grip — a screen-sized arrow a + * few pixels past `point` (the wall end) along `angle`, in its own colour, + * so it reads apart from the end's move grip. + */ + variant?: 'pull' + /** Tooltip shown while the pointer rests on the arrow. */ + title?: string } /** * Curved two-headed rotation arrow — the 2D counterpart of the 3D @@ -685,6 +695,8 @@ export type FloorplanGeometry = * already compute the pivot to place the handle should pass it through. */ pivot?: FloorplanPoint + /** Size against the default icon (1), for a node far larger than the handle, like a roof. */ + scale?: number } /** * Centered length / distance label. Renders as a small rounded @@ -1950,6 +1962,17 @@ export type SlotDeclaration = { default?: string } +/** How far one paint click spreads: the editor's painter application scopes. */ +export type PaintScopeId = 'single' | 'object' | 'matching' | 'room' + +/** A surface's own scope set — see `PaintCapability.scopes`. */ +export type PaintSurfaceScopes = { + scopes: PaintScopeId[] + labels?: Partial> + /** The role a click paints under `scope`; the hovered role when omitted. */ + roleFor?: (scope: PaintScopeId) => string +} + export type PaintCapability = { /** * Material-picker target represented by this paint capability. Omit when @@ -1975,6 +1998,15 @@ export type PaintCapability = { * back to the user as the raw id. Return `null` to keep the derived label. */ roleLabel?: (node: AnyNode, role: string) => string | null + /** + * Optional: the application scopes a surface offers itself, in place of the + * derived set (`single`, plus `object` / `matching` / `room` from the node's + * slots, asset and `roomScope`). `labels` names a scope over its generic + * label; `roleFor` is the role a click paints under a wider scope (a roof + * gable's `matching` paints every gable of the roof). Return `null` to keep + * the derived set. + */ + scopes?: (node: AnyNode, role: string) => PaintSurfaceScopes | null /** * Build the node-update patch that applies the new material at * `role`. Returned partial is merged into the node by the editor. @@ -2986,6 +3018,15 @@ export type ParamField = min?: number max?: number step?: number + /** Shown when the node omits the key (an optional field with a schema-level fallback). */ + default?: number + /** + * The nodes the field edits. A multi-selection shows it while any selected + * node qualifies and reads and writes only those; a single node that does + * not qualify hides it. Unlike `visibleIf`, one non-qualifying node does + * not hide it for the whole selection. + */ + appliesTo?: (n: N) => boolean visibleIf?: (n: N) => boolean customEditor?: ComponentType } diff --git a/packages/core/src/schema/__fixtures__/node-fixtures.ts b/packages/core/src/schema/__fixtures__/node-fixtures.ts index 4883f01cb0..4be280e6ea 100644 --- a/packages/core/src/schema/__fixtures__/node-fixtures.ts +++ b/packages/core/src/schema/__fixtures__/node-fixtures.ts @@ -12,6 +12,7 @@ import { AnyNode, type AnyNodeType, nodeKindOf } from '../types' /** Fields a kind requires beyond the defaults its own schema fills in. */ export const NODE_REQUIRED_FIELDS: Record> = { + beam: { start: [0, 0], end: [4, 0] }, ceiling: { polygon: [ [0, 0], diff --git a/packages/core/src/schema/assembly.ts b/packages/core/src/schema/assembly.ts index 510d6a2e30..4ae27ec3ed 100644 --- a/packages/core/src/schema/assembly.ts +++ b/packages/core/src/schema/assembly.ts @@ -51,6 +51,8 @@ export const AssemblyLayer = z.object({ thickness: z.number().finite().nonnegative(), /** Body only, at most one: the structural layer (framing, block), the one generators frame. */ core: z.literal(true).optional(), + /** The core layer's framing members on centre, metres (studs at 16 in); absent = the framer's own. */ + spacing: z.number().finite().positive().optional(), /** * What the layer is built of, as a construction-material kind * (`stucco`, `siding`, `osb`, `wood`, `cmu`, `drywall`, …). A label for @@ -122,6 +124,17 @@ export const Assembly = z }) } + for (const [list, layers] of lists) { + layers.forEach((layer, index) => { + if (layer.spacing !== undefined && !layer.core) { + ctx.addIssue({ + code: 'custom', + message: 'Only the core layer has a framing spacing', + path: [list, index, 'spacing'], + }) + } + }) + } assembly.layers.forEach((layer, index) => { if (layer.core && !STRUCTURAL_ROLES.has(layer.role)) { ctx.addIssue({ diff --git a/packages/core/src/schema/build-up.ts b/packages/core/src/schema/build-up.ts new file mode 100644 index 0000000000..6710c7217a --- /dev/null +++ b/packages/core/src/schema/build-up.ts @@ -0,0 +1,152 @@ +import { z } from 'zod' + +/** + * Typed layer build-ups for roof planes, ceilings and framed floors: each layer is a product + * choice (a roofing, a sheathing, a framing size at a spacing) rather than an + * F2 layer list, so framing tools read the structure they frame and the + * inspector edits choices, not strings. Thicknesses are metres; spacings are + * metres on centre. + */ + +const Thickness = z.number().finite().nonnegative() +/** What the person calls a layer's material where the kind's own name won't do ("Brazilian cherry"). */ +const LayerName = z.string().trim().min(1).max(60).optional() + +/** Nominal dimensional lumber, by its actual depth (`LUMBER_DEPTH`). */ +export const NominalLumber = z.enum(['2x4', '2x6', '2x8', '2x10', '2x12']) +export type NominalLumber = z.infer + +export const RoofingKind = z.enum([ + 'asphalt-3-tab', + 'architectural-shingle', + 'standing-seam-metal', + 'concrete-tile', + 'clay-tile', + 'wood-shake', + 'slate', +]) +export type RoofingKind = z.infer + +export const UnderlaymentKind = z.enum(['felt-15', 'felt-30', 'synthetic', 'ice-and-water']) +export type UnderlaymentKind = z.infer + +/** Structural panels: roof sheathing and attic decking. */ +export const PanelKind = z.enum(['osb', 'plywood']) +export type PanelKind = z.infer + +/** Batt and blown fill the framing cavity; rigid board runs continuous over the framing. */ +export const InsulationKind = z.enum(['batt', 'blown', 'rigid']) +export type InsulationKind = z.infer + +export const InteriorFinishKind = z.enum(['drywall', 'plaster', 'wood-plank']) +export type InteriorFinishKind = z.infer + +export const FlooringKind = z.enum([ + 'hardwood', + 'engineered-wood', + 'laminate', + 'vinyl-plank', + 'ceramic-tile', + 'carpet', +]) +export type FlooringKind = z.infer + +export const RoofingLayer = z.object({ kind: RoofingKind, thickness: Thickness, name: LayerName }) +export const UnderlaymentLayer = z.object({ + kind: UnderlaymentKind, + thickness: Thickness, + name: LayerName, +}) +export const PanelLayer = z.object({ kind: PanelKind, thickness: Thickness, name: LayerName }) +export const InsulationLayer = z.object({ + kind: InsulationKind, + thickness: Thickness, + name: LayerName, +}) +export const InteriorFinishLayer = z.object({ + kind: InteriorFinishKind, + thickness: Thickness, + name: LayerName, +}) +export const FlooringLayer = z.object({ kind: FlooringKind, thickness: Thickness, name: LayerName }) + +/** For trusses, `size` is the top chord: the depth the roof plane's deck carries. */ +export const RoofStructure = z.object({ + system: z.enum(['rafters', 'trusses']), + size: NominalLumber, + spacing: z.number().finite().positive(), +}) +export type RoofStructure = z.infer + +/** `truss-chords`: the ceiling hangs from the roof trusses' bottom chords, `size` deep. */ +export const CeilingFraming = z.object({ + system: z.enum(['joists', 'truss-chords']), + size: NominalLumber, + spacing: z.number().finite().positive(), +}) +export type CeilingFraming = z.infer + +/** + * A roof plane, outside in. Absent optional layers are none. The plane's + * `deckThickness` and `shingleThickness` derive from it (`roofBuildUpPatch`). + */ +export const RoofBuildUp = z.object({ + presetId: z.string().optional(), + roofing: RoofingLayer, + underlayment: UnderlaymentLayer.optional(), + sheathing: PanelLayer, + structure: RoofStructure, + insulation: InsulationLayer.optional(), + /** Under the structure, the room-facing face of a cathedral ceiling. */ + finish: InteriorFinishLayer.optional(), +}) +export type RoofBuildUp = z.infer + +/** + * A ceiling, bottom up from its stored height (the finished underside). + * Absent layers are none. + */ +export const CeilingBuildUp = z.object({ + presetId: z.string().optional(), + finish: InteriorFinishLayer.optional(), + framing: CeilingFraming.optional(), + /** Over the framing, for attic storage. */ + decking: PanelLayer.optional(), + insulation: InsulationLayer.optional(), +}) +export type CeilingBuildUp = z.infer + +/** Engineered wood I-joists, by their depth in inches. */ +export const IJoistDepth = z.enum(['9-1/2', '11-7/8', '14', '16']) +export type IJoistDepth = z.infer + +/** A floor's joists: sawn lumber by its nominal size, or I-joists by their depth. */ +export const FloorStructure = z + .object({ + system: z.enum(['joists', 'i-joists']), + size: z.union([NominalLumber, IJoistDepth]), + spacing: z.number().finite().positive(), + }) + .refine( + (structure) => + structure.system === 'joists' + ? NominalLumber.safeParse(structure.size).success + : IJoistDepth.safeParse(structure.size).success, + { message: 'Joists take a lumber size, I-joists a depth' }, + ) +export type FloorStructure = z.infer + +/** + * A framed floor, top down: the flooring, the subfloor sheathing on the joists, the joists, and + * the ceiling hung under them. Absent optional layers are none (a floor over a crawl space has no + * ceiling; exposed joists in a basement none either). The floor plate's `thickness` derives from it + * (`floorBuildUpPatch`). + */ +export const FloorBuildUp = z.object({ + presetId: z.string().optional(), + flooring: FlooringLayer.optional(), + sheathing: PanelLayer.optional(), + structure: FloorStructure, + ceiling: InteriorFinishLayer.optional(), +}) +export type FloorBuildUp = z.infer diff --git a/packages/core/src/schema/host-children.test.ts b/packages/core/src/schema/host-children.test.ts index f6263387e1..7baa6538af 100644 --- a/packages/core/src/schema/host-children.test.ts +++ b/packages/core/src/schema/host-children.test.ts @@ -41,7 +41,10 @@ test('frozen pre-slice corpus covers every kind predating separators', () => { kind !== 'separator' && kind !== 'floor-opening' && kind !== 'fence-gate' && - kind !== 'fence-opening', + kind !== 'fence-opening' && + kind !== 'footing' && + kind !== 'foundation' && + kind !== 'beam', ).sort(), ) }) @@ -53,7 +56,14 @@ test.each(baseline)( saved.type === 'zone' ? { ...expected, boundarySeparatorIds: [], holes: [] } : saved.type === 'door' || saved.type === 'window' - ? { ...expected, floorThresholdVersion: 1 } + ? { + ...expected, + floorThresholdVersion: 1, + // Saved before casing: cased on both faces, 2 in wide. + casingFaceA: true, + casingFaceB: true, + casingWidth: 0.0508, + } : saved.type === 'fence' ? { ...expected, diff --git a/packages/core/src/schema/index.ts b/packages/core/src/schema/index.ts index 7091c47b54..6e409fb2e8 100644 --- a/packages/core/src/schema/index.ts +++ b/packages/core/src/schema/index.ts @@ -20,6 +20,29 @@ export { parseAllowedOrigins, } from './asset-url' export { BaseNode, generateId, Material, nodeType, objectId } from './base' +// Roof-plane, ceiling and floor layer build-ups +export { + CeilingBuildUp, + CeilingFraming, + FloorBuildUp, + FlooringKind, + FlooringLayer, + FloorStructure, + IJoistDepth, + InsulationKind, + InsulationLayer, + InteriorFinishKind, + InteriorFinishLayer, + NominalLumber, + PanelKind, + PanelLayer, + RoofBuildUp, + RoofingKind, + RoofingLayer, + RoofStructure, + UnderlaymentKind, + UnderlaymentLayer, +} from './build-up' // Camera export { CameraSchema } from './camera' // Collections @@ -81,6 +104,15 @@ export { planAutomaticDownspouts, resolveAutomaticDownspoutLength, } from './nodes/automatic-downspout' +export { + BEAM_SIZE_PRESETS, + BeamMaterial, + BeamNode, + BeamPlacement, + type BeamSizePreset, + type BeamSizePresetId, + DEFAULT_BEAM_SIZE_PRESET_ID, +} from './nodes/beam' export { BlockEdge, BlockFace, @@ -115,6 +147,7 @@ export { ColumnCrossSection, ColumnNode, ColumnPanelShape, + ColumnPostBase, type ColumnPresetId, ColumnRingPlacement, ColumnShaftDetail, @@ -200,6 +233,8 @@ export { } from './nodes/fence' export { type FenceFeatureNode, FenceGateNode, FenceOpeningNode } from './nodes/fence-feature' export { FloorOpeningNode } from './nodes/floor-opening' +export { FOOTING_SIZES, FootingNode, FootingShape } from './nodes/footing' +export { FoundationConstructionChoice, FoundationNode } from './nodes/foundation' export { GuideNode, GuideScaleReference } from './nodes/guide' export { computeGutterEaveY, @@ -269,6 +304,24 @@ export { export { PipeFittingNode } from './nodes/pipe-fitting' export { PipeSegmentNode } from './nodes/pipe-segment' export { PipeTrapNode } from './nodes/pipe-trap' +export { + DEFAULT_RAILING_WALL, + getRailingWallConfig, + getRailingWallHeight, + isRailingWall, + RAILING_GUARD_HEIGHT, + RAILING_GUARD_HEIGHT_TALL, + RAILING_MAX_POST_SPACING, + RAILING_SPHERE_LIMIT, + RAILING_STYLE_DEFAULTS, + RailingWallConfig, + type RailingWallPatch, + type RailingWallSlotId, + RailingWallStyle, + railingOpeningFields, + railingWallDefaults, + railingWallSlotDefault, +} from './nodes/railing-wall' // Nodes export { createDefaultRidgeVentsForSegment, @@ -311,6 +364,7 @@ export type { RoofSegmentSurfaceMaterialRole, RoofSegmentSurfaceMaterialSpec, RoofSegmentVisibleTopBounds, + RoofWallBand, SegmentSlopeFrame, } from './nodes/roof-segment' export { @@ -323,6 +377,7 @@ export { getRoofSegmentEndReach, getRoofSegmentSurfaceY, getRoofSegmentVisibleTopBounds, + getRoofWallBandMaterial, getSegmentFasciaMaterial, getSegmentSlopeFrame, hasSegmentMaterialOverride, @@ -330,10 +385,13 @@ export { MIN_ROOF_SEGMENT_TRIM_SPAN, normalizeRoofSegmentTrim, ROOF_SHAPE_DEFAULTS, + ROOF_WALL_BANDS, + RoofEaveCut, RoofSegmentNode, RoofSegmentTrim, RoofType, roofTypeHasRakes, + roofWallBandFields, } from './nodes/roof-segment' export { getRoofSegmentResizePatch } from './nodes/roof-segment-resize' export type { @@ -388,7 +446,12 @@ export { SkylightType, type SkylightTypePreset, } from './nodes/skylight' -export { MIN_GROUND_FLOOR_THICKNESS, MIN_SLAB_THICKNESS, SlabNode } from './nodes/slab' +export { + DEFAULT_UPPER_FLOOR_THICKNESS, + MIN_GROUND_FLOOR_THICKNESS, + MIN_SLAB_THICKNESS, + SlabNode, +} from './nodes/slab' export { SolarPanelMaterialRole, SolarPanelNode, @@ -407,6 +470,14 @@ export { } from './nodes/stair' export { StairConstruction } from './nodes/stair-construction' export { AttachmentSide, StairSegmentNode, StairSegmentType } from './nodes/stair-segment' +export { + STAIR_STRINGER_SPACINGS_IN, + StairStringerStock, + StairStringerType, + StairStructure, + StairStructureType, + StairTreadStock, +} from './nodes/stair-structure' export { StructuralGridNode } from './nodes/structural-grid' export { SurfaceHoleMetadata } from './nodes/surface-hole-metadata' export { SurfacePaintRegion } from './nodes/surface-paint-region' @@ -422,8 +493,11 @@ export type { WallTrimSlotId, } from './nodes/wall' export { + DEFAULT_EXTERIOR_WALL_FINISH, + DEFAULT_INTERIOR_WALL_PAINT, getEffectiveWallFaceMaterial, getEffectiveWallSurfaceMaterial, + getWallFaceDefault, getWallSurfaceMaterialSignature, getWallTrimFaces, getWallTrimSlotId, @@ -504,3 +578,9 @@ export type { } from './types' // Union types export { AnyNode, nodeKindOf } from './types' +export { + CUSTOM_WALL_TYPE_PREFIX, + newWallTypeId, + WallTypeDefinition, + WallTypeDefinitionId, +} from './wall-type' diff --git a/packages/core/src/schema/nodes/beam.ts b/packages/core/src/schema/nodes/beam.ts new file mode 100644 index 0000000000..829fba2945 --- /dev/null +++ b/packages/core/src/schema/nodes/beam.ts @@ -0,0 +1,175 @@ +import dedent from 'dedent' +import { z } from 'zod' +import { BaseNode, nodeType, objectId } from '../base' + +const INCH = 0.0254 +const PlanPoint = z.tuple([z.number(), z.number()]) + +/** + * What a beam is made of: sawn lumber (a solid timber or 2x plies nailed up), the same + * pressure-treated for ground contact and the weather, LVL, glulam, or a steel W shape. + */ +export const BeamMaterial = z.enum(['sawn', 'pressure-treated', 'lvl', 'glulam', 'steel']) +export type BeamMaterial = z.infer + +/** + * How the joists it carries meet the beam (under the joists, or among them): a + * dropped beam is under them, its top the plane they bear on; a flush beam is among them, the + * joists hung from its sides. + */ +export const BeamPlacement = z.enum(['dropped', 'flush']) +export type BeamPlacement = z.infer + +/** One stock beam: a nominal size, how many plies of it, its actual section and what it is. */ +export type BeamSizePreset = { + id: string + /** The size as a framing plan prints it: "6x8", "(2) 2x10", "1-3/4x11-7/8 LVL". */ + label: string + /** Nominal size of one ply. */ + size: string + plies: number + /** Actual width of the whole member (every ply), metres. */ + width: number + /** Actual depth, metres. */ + depth: number + material: BeamMaterial +} + +/** A sawn stock size: one timber, or `plies` of a 2x nailed up (`width` is one ply's, inches). */ +function sawn( + id: Id, + size: string, + width: number, + depth: number, + plies = 1, +) { + return { + id, + label: plies > 1 ? `(${plies}) ${size}` : size, + size, + plies, + width: width * plies * INCH, + depth: depth * INCH, + material: 'sawn' as const, + } +} + +/** + * The stock sizes a beam is picked from, at their actual dressed sizes: a sawn 4x is 3-1/2 in + * wide and a 6x 5-1/2 in; a 2x ply 1-1/2 in; LVL 1-3/4 in a ply; glulam 5-1/8 in; a W8x10 is + * 3.94 in across its flanges and 7.89 in deep. + */ +export const BEAM_SIZE_PRESETS = [ + sawn('4x6', '4x6', 3.5, 5.5), + sawn('4x8', '4x8', 3.5, 7.25), + sawn('4x10', '4x10', 3.5, 9.25), + sawn('4x12', '4x12', 3.5, 11.25), + sawn('6x6', '6x6', 5.5, 5.5), + sawn('6x8', '6x8', 5.5, 7.5), + sawn('6x10', '6x10', 5.5, 9.5), + sawn('6x12', '6x12', 5.5, 11.5), + sawn('(2)2x8', '2x8', 1.5, 7.25, 2), + sawn('(2)2x10', '2x10', 1.5, 9.25, 2), + sawn('(2)2x12', '2x12', 1.5, 11.25, 2), + sawn('(3)2x10', '2x10', 1.5, 9.25, 3), + sawn('(3)2x12', '2x12', 1.5, 11.25, 3), + { + id: 'lvl-1-3/4x9-1/4', + label: '1-3/4x9-1/4 LVL', + size: '1-3/4x9-1/4', + plies: 1, + width: 1.75 * INCH, + depth: 9.25 * INCH, + material: 'lvl', + }, + { + id: 'lvl-1-3/4x11-7/8', + label: '1-3/4x11-7/8 LVL', + size: '1-3/4x11-7/8', + plies: 1, + width: 1.75 * INCH, + depth: 11.875 * INCH, + material: 'lvl', + }, + { + id: '(2)lvl-1-3/4x11-7/8', + label: '(2) 1-3/4x11-7/8 LVL', + size: '1-3/4x11-7/8', + plies: 2, + width: 3.5 * INCH, + depth: 11.875 * INCH, + material: 'lvl', + }, + { + id: 'glulam-5-1/8x9', + label: '5-1/8x9 GLB', + size: '5-1/8x9', + plies: 1, + width: 5.125 * INCH, + depth: 9 * INCH, + material: 'glulam', + }, + { + id: 'glulam-5-1/8x12', + label: '5-1/8x12 GLB', + size: '5-1/8x12', + plies: 1, + width: 5.125 * INCH, + depth: 12 * INCH, + material: 'glulam', + }, + { + id: 'W8x10', + label: 'W8x10', + size: 'W8x10', + plies: 1, + width: 3.94 * INCH, + depth: 7.89 * INCH, + material: 'steel', + }, +] as const satisfies readonly BeamSizePreset[] + +export type BeamSizePresetId = (typeof BEAM_SIZE_PRESETS)[number]['id'] + +/** A new beam's size: a 6x8, the header a porch's posts carry. */ +export const DEFAULT_BEAM_SIZE_PRESET_ID = '6x8' satisfies BeamSizePresetId + +export const BeamNode = BaseNode.extend({ + id: objectId('beam'), + type: nodeType('beam'), + start: PlanPoint, + end: PlanPoint, + width: z + .number() + .positive() + .default(5.5 * INCH), + depth: z + .number() + .positive() + .default(7.5 * INCH), + size: z.string().optional(), + plies: z.number().int().min(1).max(5).default(1), + material: BeamMaterial.default('sawn'), + placement: BeamPlacement.default('dropped'), + topElevation: z.number().optional(), + // A slab the beam belongs to (a porch's or a deck's): a reference for what builds and frames + // around it, never its height. + supportSlabId: z.string().optional(), +}).describe( + dedent` + Beam node - a horizontal framing member drawn by hand: a header over a porch's posts, a deck's + beam, a girder under a floor, a beam across an open span + - start/end: the beam's centreline, level-local plan points like a wall's + - width/depth: the actual section, metres (every ply together across) + - size: the nominal size of one ply as printed ("6x8", "2x10", "1-3/4x11-7/8"); absent = a + custom section, labelled by its actual size + - plies: how many plies of size are nailed up side by side + - material: sawn, pressure-treated, lvl, glulam or steel + - placement: dropped (the joists bear on its top) or flush (the joists hang from its sides) + - topElevation: its top, level-local metres; absent = the level's ceiling plane over it (the + top of the walls), a dropped beam hanging under it, a flush one in the ceiling framing + - supportSlabId: the slab it belongs to, if any (a porch or a deck) + `, +) + +export type BeamNode = z.infer diff --git a/packages/core/src/schema/nodes/ceiling.ts b/packages/core/src/schema/nodes/ceiling.ts index 180d7b7322..151d485302 100644 --- a/packages/core/src/schema/nodes/ceiling.ts +++ b/packages/core/src/schema/nodes/ceiling.ts @@ -1,6 +1,7 @@ import dedent from 'dedent' import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' +import { CeilingBuildUp } from '../build-up' import { MaterialSchema } from '../material' import { ItemNode } from './item' import { SurfaceHoleMetadata } from './surface-hole-metadata' @@ -26,6 +27,9 @@ export const CeilingNode = BaseNode.extend({ // − CEILING_CLAMP_MARGIN (see `resolveCeilingHeight`). Present = an // explicit custom height, still write-clamped under that bound. height: z.number().optional(), + // Layers bottom up from `height`, the finished underside: the ceiling's + // thickness rises from there (`ceilingBuildUpThickness`). Absent = no thickness. + buildUp: CeilingBuildUp.optional(), // Painted parts of a manual ceiling (later wins). An automatic ceiling draws // its room's `zone.ceiling.regions` instead: it is rebuilt from the room. regions: z.array(SurfacePaintRegion).optional(), @@ -39,6 +43,7 @@ export const CeilingNode = BaseNode.extend({ - holes: array of polygons representing holes in the ceiling - holeMetadata: metadata parallel to holes, used to preserve manual and auto-managed cutouts - height: explicit height in meters; absent = follows the level top automatically + - buildUp: layers bottom up from the underside (finish, framing, decking, insulation) - regions: painted parts of a manual ceiling ([x, z] polygons, later wins) - autoFromWalls: whether the ceiling is automatically generated from a closed wall loop `, diff --git a/packages/core/src/schema/nodes/column.ts b/packages/core/src/schema/nodes/column.ts index ff12f55382..889cda5c45 100644 --- a/packages/core/src/schema/nodes/column.ts +++ b/packages/core/src/schema/nodes/column.ts @@ -57,6 +57,12 @@ export const ColumnRingPlacement = z.enum(['ends', 'even', 'top', 'bottom']) export const ColumnCarvingPlacement = z.enum(['shaft', 'base', 'capital', 'all']) +/** + * What a framing post (`role: 'post'`) stands on: straight on the floor, a steel post base + * holding it off the concrete, or a concrete pier above the floor or the grade. + */ +export const ColumnPostBase = z.enum(['none', 'post-base', 'pier']) + export const ColumnSupportStyle = z.enum([ 'vertical', 'a-frame', @@ -81,6 +87,7 @@ export type ColumnCapitalStyle = z.infer export type ColumnRingPlacement = z.infer export type ColumnCarvingPlacement = z.infer export type ColumnSupportStyle = z.infer +export type ColumnPostBase = z.infer export const ColumnNode = BaseNode.extend({ id: objectId('column'), @@ -165,7 +172,10 @@ export const ColumnNode = BaseNode.extend({ braceTopSpread: z.number().min(0).default(0.12), bracePlateEnabled: z.boolean().default(true), // A declaration consumed by the selected shared support checker; old columns stay unchanged. - role: z.enum(['ornament']).optional(), + // 'post' marks a framing post (core `createFramingPost`): a square sawn post under a beam. + role: z.enum(['ornament', 'post']).optional(), + // A framing post's base, drawn by the base fields (`framingPostBasePatch` sets both). + postBase: ColumnPostBase.optional(), material: MaterialSchema.optional(), materialPreset: z.string().optional(), // Unified paint-slot refs (`scene:`/`library:` MaterialRef per slot id), @@ -184,6 +194,8 @@ export const ColumnNode = BaseNode.extend({ - baseHeight/capitalHeight: bottom and top block proportions - ring/flute/spiral/panel/lathe/carving fields: procedural detail controls - supportStyle/brace fields: vertical column or procedural support assembly + - role: 'post' for a framing post (a square sawn post carrying a beam), 'ornament' for decoration + - postBase: a framing post's base: none, a steel post base, or a concrete pier `) export const COLUMN_PRESETS = { diff --git a/packages/core/src/schema/nodes/door.ts b/packages/core/src/schema/nodes/door.ts index 7c16701dd5..ea0597006a 100644 --- a/packages/core/src/schema/nodes/door.ts +++ b/packages/core/src/schema/nodes/door.ts @@ -3,7 +3,12 @@ import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' import { GeometryScriptSource } from '../geometry-source' import { MaterialSchema } from '../material' -import { DEFAULT_DOOR_CONTENT_PADDING, DoorType, defaultDoorSegments } from './opening-types' +import { + DEFAULT_DOOR_CONTENT_PADDING, + DEFAULT_OPENING_CASING_WIDTH, + DoorType, + defaultDoorSegments, +} from './opening-types' export { DoorType } from './opening-types' @@ -135,6 +140,15 @@ export const DoorNode = BaseNode.extend({ threshold: z.boolean().default(true), thresholdHeight: z.number().default(0.02), + // Casing: flat boards on the finished faces of the host wall around the + // opening (head and both legs, none at the floor), never inside it, so the + // wall cutout stays width × height. The faces are the host's `a` / `b` + // (WallFace), not the door's front and back, so flipping the swing leaves + // the casing where it is. Saves from before casing load with it on both faces. + casingFaceA: z.boolean().default(true), + casingFaceB: z.boolean().default(true), + casingWidth: z.number().positive().default(DEFAULT_OPENING_CASING_WIDTH), + // Swing hingesSide: z.enum(['left', 'right']).default('left'), swingDirection: z.enum(['inward', 'outward']).default('inward'), @@ -163,11 +177,14 @@ export const DoorNode = BaseNode.extend({ panicBarHeight: z.number().default(1.0), }).describe(dedent`Door node - a parametric door placed on a wall - position: center of the door in wall-local coordinate system (Y = height/2, always at floor) + - width/height: overall outer dimensions, which are also the wall cutout - doorCategory/doorType: explicit operation family, defaulting old doors to interior hinged - openingKind/openingShape: hinged door or frameless wall opening shape - segments: rows stacked top to bottom, each defining its own columnRatios - type 'empty' = no leaf fill for that segment, 'panel' = raised/recessed panel, 'glass' = glazed - hingesSide/swingDirection/swingAngle: which way the door opens and how far it is currently open + - casingFaceA/casingFaceB: casing (head and legs) on the host wall's face a / b, around the cutout + - casingWidth: face width of the casing boards (2 in by default) - doorCloser/panicBar: commercial and emergency hardware options `) diff --git a/packages/core/src/schema/nodes/footing.ts b/packages/core/src/schema/nodes/footing.ts new file mode 100644 index 0000000000..d2434965ec --- /dev/null +++ b/packages/core/src/schema/nodes/footing.ts @@ -0,0 +1,42 @@ +import dedent from 'dedent' +import { z } from 'zod' +import { BaseNode, nodeType, objectId } from '../base' + +const INCH = 0.0254 +const PlanPoint = z.tuple([z.number(), z.number()]) + +export const FootingShape = z.enum(['strip', 'pad']) +export type FootingShape = z.infer + +/** The sizes a new footing takes (IRC R403.1): a strip 12 × 8 in, a pad 24 × 24 × 10 in. */ +export const FOOTING_SIZES = { + strip: { width: 12 * INCH, thickness: 8 * INCH }, + pad: { width: 24 * INCH, length: 24 * INCH, thickness: 10 * INCH }, +} as const + +export const FootingNode = BaseNode.extend({ + id: objectId('footing'), + type: nodeType('footing'), + shape: FootingShape.default('strip'), + start: PlanPoint.optional(), + end: PlanPoint.optional(), + center: PlanPoint.optional(), + width: z.number().positive().default(FOOTING_SIZES.strip.width), + length: z.number().positive().optional(), + rotation: z.number().optional(), + thickness: z.number().positive().default(FOOTING_SIZES.strip.thickness), + depth: z.number().positive().optional(), +}).describe( + dedent` + Footing node - a concrete footing poured under the building's lowest level, its height derived + from the building's foundation record (metadata.foundation), never stored + - shape: 'strip' runs start → end (level-local plan points, like a wall); 'pad' stands at center + - width: across a strip; a pad's side along its local x + - length: a pad's side along its local z (absent = square) + - rotation: a pad's turn about Y, radians + - thickness: the footing's own depth of concrete, metres + - depth: its bottom under the grade beside it, metres (absent = the building's) + `, +) + +export type FootingNode = z.infer diff --git a/packages/core/src/schema/nodes/foundation.ts b/packages/core/src/schema/nodes/foundation.ts new file mode 100644 index 0000000000..50b747d4ed --- /dev/null +++ b/packages/core/src/schema/nodes/foundation.ts @@ -0,0 +1,29 @@ +import dedent from 'dedent' +import { z } from 'zod' +import { BaseNode, nodeType, objectId } from '../base' + +const PlanPoint = z.tuple([z.number(), z.number()]) + +/** What a foundation outline pours: the building's own foundation, or a slab or raised floor of its own. */ +export const FoundationConstructionChoice = z.enum(['building', 'slab', 'raised']) +export type FoundationConstructionChoice = z.infer + +export const FoundationNode = BaseNode.extend({ + id: objectId('foundation'), + type: nodeType('foundation'), + polygon: z.array(PlanPoint).default([]), + construction: FoundationConstructionChoice.default('building'), + drop: z.number().min(0).default(0), +}).describe( + dedent` + Foundation node - an outline a building's foundation is poured to, on its lowest level, in place + of the one traced from its exterior walls; every size comes from the building's foundation record + (metadata.foundation), never stored here + - polygon: the outline's corners, level-local plan points, at the foundation's outside face + - construction: 'building' pours what the record says (slab, raised floor, piles); 'slab' or + 'raised' pours that whatever the house stands on (a porch slab beside a raised house) + - drop: its top this far under the floor of its level, metres (a porch a step down) + `, +) + +export type FoundationNode = z.infer diff --git a/packages/core/src/schema/nodes/level.ts b/packages/core/src/schema/nodes/level.ts index b6a0ce436a..b5d37c5522 100644 --- a/packages/core/src/schema/nodes/level.ts +++ b/packages/core/src/schema/nodes/level.ts @@ -2,6 +2,7 @@ import dedent from 'dedent' import { z } from 'zod' import type { ProceduralItemNode } from '../../procedural-items/node' import { BaseNode, nodeType, objectId } from '../base' +import type { BeamNode } from './beam' import type { BlockNode } from './block' import type { CeilingNode } from './ceiling' import type { ColumnNode } from './column' @@ -11,6 +12,8 @@ import type { DuctSegmentNode } from './duct-segment' import type { DuctTerminalNode } from './duct-terminal' import type { FenceNode } from './fence' import type { FloorOpeningNode } from './floor-opening' +import type { FootingNode } from './footing' +import type { FoundationNode } from './foundation' import type { GuideNode } from './guide' import type { HvacEquipmentNode } from './hvac-equipment' import type { ImportedMeshNode } from './imported-mesh' @@ -36,8 +39,11 @@ type CoreLevelChildId = | WallNode['id'] | SeparatorNode['id'] | FloorOpeningNode['id'] + | FootingNode['id'] + | FoundationNode['id'] | FenceNode['id'] | ColumnNode['id'] + | BeamNode['id'] | ConstructionDimensionNode['id'] | BlockNode['id'] | StructuralGridNode['id'] diff --git a/packages/core/src/schema/nodes/opening-types.ts b/packages/core/src/schema/nodes/opening-types.ts index ca65de92bb..c78b1256ef 100644 --- a/packages/core/src/schema/nodes/opening-types.ts +++ b/packages/core/src/schema/nodes/opening-types.ts @@ -39,6 +39,14 @@ export const defaultDoorSegments = () => [ /** A new door's leaf margin [x, y] (m). */ export const DEFAULT_DOOR_CONTENT_PADDING: [number, number] = [0.04, 0.04] +const INCH = 0.0254 + +/** A door's or window's casing face width when none is given: 2 in boards (m). */ +export const DEFAULT_OPENING_CASING_WIDTH = 2 * INCH + +/** How far casing stands proud of the finished wall face: 3/4 in stock, like skirting (m). */ +export const OPENING_CASING_PROUD = 0.75 * INCH + export const WindowType = z.enum([ 'fixed', 'sliding', diff --git a/packages/core/src/schema/nodes/railing-wall.test.ts b/packages/core/src/schema/nodes/railing-wall.test.ts new file mode 100644 index 0000000000..fe35fc81e3 --- /dev/null +++ b/packages/core/src/schema/nodes/railing-wall.test.ts @@ -0,0 +1,110 @@ +import { wallSlots } from '../../node-slots/wall' +import { + DEFAULT_RAILING_WALL, + getRailingWallConfig, + getRailingWallHeight, + isRailingWall, + RAILING_GUARD_HEIGHT, + RAILING_GUARD_HEIGHT_TALL, + RAILING_SPHERE_LIMIT, + RAILING_STYLE_DEFAULTS, + railingOpeningFields, + railingWallDefaults, +} from './railing-wall' +import { WallNode } from './wall' + +const IN = 0.0254 + +describe('railing wall defaults', () => { + test('the default railing is the DCA 6 deck guard: 4x4 posts at most 6 ft apart, 2x2 balusters 3½ in clear on a rail 3½ in up, a 2x6 cap', () => { + expect(DEFAULT_RAILING_WALL).toEqual({ + style: 'post-and-rail', + postSpacing: 72 * IN, + postSize: 3.5 * IN, + infillSize: 1.5 * IN, + infillGap: 3.5 * IN, + bottomClearance: 3.5 * IN, + cap: true, + }) + expect(RAILING_GUARD_HEIGHT).toBeCloseTo(0.9144, 9) + expect(RAILING_GUARD_HEIGHT_TALL).toBeCloseTo(1.0668, 9) + }) + + test('every style starts with openings a 4 in sphere cannot pass', () => { + for (const members of Object.values(RAILING_STYLE_DEFAULTS)) { + expect(members.infillGap).toBeLessThan(RAILING_SPHERE_LIMIT) + expect(members.bottomClearance).toBeLessThan(RAILING_SPHERE_LIMIT) + } + }) + + test('a drawn railing is a 36 in guard of its style, as thick as its posts, with no assembly', () => { + const patch = railingWallDefaults() + expect(patch).toEqual({ + wallType: 'railing', + thickness: 3.5 * IN, + height: RAILING_GUARD_HEIGHT, + railing: DEFAULT_RAILING_WALL, + assembly: undefined, + }) + const cable = railingWallDefaults('cable') + expect(cable.railing.style).toBe('cable') + expect(cable.railing.postSize).toBeCloseTo(2 * IN, 9) + expect(cable.thickness).toBe(cable.railing.postSize) + + const wall = WallNode.parse({ start: [0, 0], end: [4, 0], ...patch }) + expect(isRailingWall(wall)).toBe(true) + expect(wall.height).toBe(RAILING_GUARD_HEIGHT) + expect(wall.assembly).toBeUndefined() + }) +}) + +describe('railing wall config', () => { + test('a partial config reads over the defaults; a wall saved before railings parses as it was', () => { + const wall = WallNode.parse({ + start: [0, 0], + end: [4, 0], + wallType: 'railing', + railing: { style: 'glass' }, + }) + expect(wall.railing).toEqual({ ...DEFAULT_RAILING_WALL, style: 'glass' }) + expect(getRailingWallConfig({ railing: { postSpacing: 1.2 } })).toEqual({ + ...DEFAULT_RAILING_WALL, + postSpacing: 1.2, + }) + + const plain = WallNode.parse({ start: [0, 0], end: [4, 0] }) + expect(plain.railing).toBeUndefined() + expect(isRailingWall(plain)).toBe(false) + expect(getRailingWallConfig(plain)).toBe(DEFAULT_RAILING_WALL) + }) + + test('a railing never follows the storey: with no height of its own it is a 36 in guard', () => { + expect(getRailingWallHeight({})).toBe(RAILING_GUARD_HEIGHT) + expect(getRailingWallHeight({ height: RAILING_GUARD_HEIGHT_TALL })).toBe( + RAILING_GUARD_HEIGHT_TALL, + ) + }) + + test('a door on a railing is a gap as tall as the guard, with no casing or threshold', () => { + expect(railingOpeningFields({ height: RAILING_GUARD_HEIGHT_TALL })).toEqual({ + openingKind: 'opening', + height: RAILING_GUARD_HEIGHT_TALL, + threshold: false, + casingFaceA: false, + casingFaceB: false, + }) + }) + + test('a railing paints its posts and rails and its infill, not two faces', () => { + const wood = WallNode.parse({ start: [0, 0], end: [4, 0], ...railingWallDefaults() }) + expect(wallSlots(wood)).toEqual([ + { slotId: 'railing-frame', label: 'Posts & rails', default: 'library:preset-white' }, + { slotId: 'railing-infill', label: 'Infill', default: 'library:preset-white' }, + ]) + const glass = WallNode.parse({ start: [0, 0], end: [4, 0], ...railingWallDefaults('glass') }) + expect(wallSlots(glass).map((slot) => [slot.slotId, slot.default])).toEqual([ + ['railing-frame', 'library:metal-steel'], + ['railing-infill', 'library:preset-glass'], + ]) + }) +}) diff --git a/packages/core/src/schema/nodes/railing-wall.ts b/packages/core/src/schema/nodes/railing-wall.ts new file mode 100644 index 0000000000..64341768d4 --- /dev/null +++ b/packages/core/src/schema/nodes/railing-wall.ts @@ -0,0 +1,189 @@ +import { z } from 'zod' +import { StairRailingStyle } from './stair-types' + +/** + * A railing wall (`WallNode.wallType: 'railing'`): drawn and joined + * like any wall, and like any wall it closes rooms (a deck or a porch its railings fence is a + * room), but it stands as an open guard instead of a framed body: posts at its ends and equally + * spaced between them, rails, and the infill its style sets. Its doors are gaps in the guard (a + * doorway: no leaf, casing or threshold), with a post either side. It takes no finishes, assembly, + * framing or foundation, and it keeps its own height rather than following the storey. + */ + +const INCH = 0.0254 + +/** A guard's height over the floor, IRC R312.1.2: 36 in where the drop is more than 30 in. */ +export const RAILING_GUARD_HEIGHT = 36 * INCH +/** The taller guard the IBC (1015.3) and some local codes ask for: 42 in. */ +export const RAILING_GUARD_HEIGHT_TALL = 42 * INCH +/** The largest sphere a guard's openings may pass, IRC R312.1.3: 4 in. */ +export const RAILING_SPHERE_LIMIT = 4 * INCH +/** Guard posts on centre at most this far apart: 6 ft, the AWC Deck Construction Guide (DCA 6). */ +export const RAILING_MAX_POST_SPACING = 72 * INCH + +/** The railing styles: the stair guards' vocabulary (`StairRailingStyle`). */ +export const RailingWallStyle = StairRailingStyle +export type RailingWallStyle = z.infer + +/** + * How a railing wall is built, metres. The guard's height is the wall's own `height`; its + * `thickness` follows `postSize`, so the plan outline and the room it closes run along the posts. + * - `postSpacing`: the most the posts stand apart on centre; they are equally spaced between the + * run's ends (each end, and each side of an opening, has its post). + * - `postSize`: the posts' square section. + * - `infillSize`: a baluster's (picket's, bar's) square section, a cable's diameter, a board's + * face height, a glass pane's thickness. + * - `infillGap`: the clear space between the infill members (keep it under the 4 in sphere). + * - `bottomClearance`: the clear space under the bottom rail (or the lowest infill) over the floor. + * - `cap`: a 2x6 cap laid flat over the top rail (the post-and-rail and boards styles). + */ +export const RailingWallConfig = z.object({ + style: RailingWallStyle.default('post-and-rail'), + postSpacing: z.number().finite().positive().default(RAILING_MAX_POST_SPACING), + postSize: z + .number() + .finite() + .positive() + .default(3.5 * INCH), + infillSize: z + .number() + .finite() + .positive() + .default(1.5 * INCH), + infillGap: z + .number() + .finite() + .nonnegative() + .default(3.5 * INCH), + bottomClearance: z + .number() + .finite() + .nonnegative() + .default(3.5 * INCH), + cap: z.boolean().default(true), +}) +export type RailingWallConfig = z.infer + +/** + * The default railing: the DCA 6 deck guard — 4x4 posts at most 6 ft apart, a 2x6 cap over a + * 2x4 top rail, a 2x4 bottom rail 3½ in off the floor, and 2x2 balusters 3½ in clear. + */ +export const DEFAULT_RAILING_WALL: RailingWallConfig = RailingWallConfig.parse({}) + +/** + * The members each style is drawn with when picked: its posts' section, its infill's section and + * clear gap, and its clearance over the floor (the stair guards' members, `nodes/stair/*-guard`). + */ +export const RAILING_STYLE_DEFAULTS: Record< + RailingWallStyle, + Pick +> = { + // DCA 6: 4x4 posts, 2x2 balusters 3½ in clear on a 2x4 bottom rail 3½ in up. + 'post-and-rail': { + postSize: 3.5 * INCH, + infillSize: 1.5 * INCH, + infillGap: 3.5 * INCH, + bottomClearance: 3.5 * INCH, + }, + // Joinery: newels, 1¼ in square balusters between a handrail and a shoe rail. + balusters: { + postSize: 3.5 * INCH, + infillSize: 1.25 * INCH, + infillGap: 3.5 * INCH, + bottomClearance: 2.25 * INCH, + }, + // 2 in posts, ⅜ in cables 3 in on centre under a flat cap. + cable: { + postSize: 2 * INCH, + infillSize: 0.375 * INCH, + infillGap: 2.625 * INCH, + bottomClearance: 2.75 * INCH, + }, + // 4x4 posts, 1x6 boards on the flat 3½ in apart under a 2x6 cap. + boards: { + postSize: 3.5 * INCH, + infillSize: 5.5 * INCH, + infillGap: 3.5 * INCH, + bottomClearance: 3.5 * INCH, + }, + // 1½ in posts and one ½ in tempered pane per bay. + glass: { + postSize: 1.5 * INCH, + infillSize: 0.5 * INCH, + infillGap: 0, + bottomClearance: 3 * INCH, + }, + // 1½ in steel posts on baseplates, ⅝ in square bars between two flat rails. + metal: { + postSize: 1.5 * INCH, + infillSize: 0.625 * INCH, + infillGap: 3.75 * INCH, + bottomClearance: 3 * INCH, + }, +} + +/** A railing wall's config with every field present: its own over the defaults. */ +export function getRailingWallConfig(wall: { railing?: Partial }) { + return wall.railing ? { ...DEFAULT_RAILING_WALL, ...wall.railing } : DEFAULT_RAILING_WALL +} + +/** Whether a node is a railing wall. */ +export function isRailingWall(node: { type?: string; wallType?: string } | null | undefined) { + return node?.type === 'wall' && node.wallType === 'railing' +} + +/** A railing's height: its own, else the 36 in guard (a railing never follows the storey). */ +export function getRailingWallHeight(wall: { height?: number }): number { + return wall.height ?? RAILING_GUARD_HEIGHT +} + +export type RailingWallPatch = { + wallType: 'railing' + thickness: number + height: number + railing: RailingWallConfig + assembly: undefined +} + +/** + * What a newly drawn railing wall is (the Build panel's Railing): a railing of `style` with that + * style's members, as thick as its posts, 36 in tall, and no assembly (a railing has no layers). + */ +export function railingWallDefaults(style: RailingWallStyle = 'post-and-rail'): RailingWallPatch { + const railing = { ...DEFAULT_RAILING_WALL, ...RAILING_STYLE_DEFAULTS[style], style } + return { + wallType: 'railing', + thickness: railing.postSize, + height: RAILING_GUARD_HEIGHT, + railing, + assembly: undefined, + } +} + +/** + * What a door placed on a railing becomes: a gap in the guard as tall as the guard, with no leaf, + * casing or threshold. The guard stands a post either side of it. + */ +export function railingOpeningFields(wall: { height?: number }) { + return { + openingKind: 'opening' as const, + height: getRailingWallHeight(wall), + threshold: false, + casingFaceA: false, + casingFaceB: false, + } +} + +/** A railing wall's paint slots: its posts and rails, and its infill. */ +export type RailingWallSlotId = 'railing-frame' | 'railing-infill' + +const WOOD_STYLES: ReadonlySet = new Set(['post-and-rail', 'balusters', 'boards']) + +/** + * What an unpainted railing shows: a wood guard painted white, a metal one steel, and glass + * panes glass. + */ +export function railingWallSlotDefault(style: RailingWallStyle, slot: RailingWallSlotId): string { + if (slot === 'railing-infill' && style === 'glass') return 'library:preset-glass' + return WOOD_STYLES.has(style) ? 'library:preset-white' : 'library:metal-steel' +} diff --git a/packages/core/src/schema/nodes/roof-segment.ts b/packages/core/src/schema/nodes/roof-segment.ts index 5c0408521e..b64eea2d2d 100644 --- a/packages/core/src/schema/nodes/roof-segment.ts +++ b/packages/core/src/schema/nodes/roof-segment.ts @@ -1,13 +1,14 @@ import dedent from 'dedent' import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' +import { RoofBuildUp } from '../build-up' import type { MaterialSchema as MaterialSchemaType } from '../material' import { MaterialSchema } from '../material' import { getBarrelRoofProfile } from './barrel-roof' import { RoofGlazing, RoofSystemType } from './roof-glazing' -import { RoofType } from './roof-types' +import { RoofEaveCut, RoofType } from './roof-types' -export { RoofType } from './roof-types' +export { RoofEaveCut, RoofType } from './roof-types' export const MIN_ROOF_SEGMENT_TRIM_SPAN = 0.1 const DEFAULT_ROOF_SEGMENT_WIDTH = 8 const DEFAULT_ROOF_SEGMENT_DEPTH = 6 @@ -142,6 +143,9 @@ export const RoofSegmentNode = BaseNode.extend({ // gambrel's ends, a shed's sides). Absent, as far as the eaves reach (`overhang` is along the slope). gableOverhang: z.number().min(0).optional(), shingleThickness: z.number().default(0.05), + // The plane's layers, outside in. Present, `deckThickness` and `shingleThickness` are derived + // from it and written with it (`roofBuildUpPatch`); absent, they are the plane's own. + buildUp: RoofBuildUp.optional(), arc: z .object({ centerX: z.number(), @@ -172,20 +176,32 @@ export const RoofSegmentNode = BaseNode.extend({ managedByParent: z.boolean().default(false), wallShell: z.enum(['auto', 'include', 'omit']).default('auto'), shedInsetEndPanels: z.boolean().default(false), - // Plumb 1x8 fascia and rake boards on the eaves and rakes (gable, hip, - // shed, flat), built from the segment itself so they follow its pitch, - // size, overhang and type. + // Fascia and rake boards on the eaves and rakes (gable, hip, shed, flat), built from the segment + // itself so they follow its pitch, size, overhang, layers and eave cut (core's `roofEaveProfile`). fascia: z.boolean().optional(), + // How the rafter tails are cut at the eaves, the fascia with them (`RoofEaveCut`); absent, plumb. + eaveCut: RoofEaveCut.optional(), // Shed only: the board on the high edge. Off when that edge meets a wall // (a porch cover on a ledger); a lean-to never has one. fasciaHighEdge: z.boolean().optional(), - // The boards' height and thickness; a 1x8 (0.184 × 0.019 m) when absent. With a height set, a - // flat roof's boards rise to the roof's top: a coping band over the deck and membrane edges. + // The boards' height and thickness; absent, a 1x as deep as the tails need (`roofEaveProfile`). + // With a height set, a flat roof's boards rise to the roof's top: a coping band over the deck and + // membrane edges. fasciaHeight: z.number().positive().optional(), fasciaDepth: z.number().positive().optional(), // The boards' and rakes' own finish; absent, they take the wall role's, as they always did. fasciaMaterial: MaterialSchema.optional(), fasciaMaterialPreset: z.string().optional(), + // The wall band between the top plate and the eaves (a raised heel) is wall, not trim: its own + // finish; absent, the roof's, then the walls' below it. + heelMaterial: MaterialSchema.optional(), + heelMaterialPreset: z.string().optional(), + // Each gable end's own finish (−x is the left, +x the right, as `getRoofSegmentWallFaces` names + // them); absent, the edge role's, as every gable took before they could be painted apart. + gableLeftMaterial: MaterialSchema.optional(), + gableLeftMaterialPreset: z.string().optional(), + gableRightMaterial: MaterialSchema.optional(), + gableRightMaterialPreset: z.string().optional(), // Shape-specific ratios. Only the pair matching `roofType` is read; the // rest are inert. Defined on every segment so the panel can flip // roofType without losing the previous shape's tuning. @@ -257,10 +273,14 @@ export const RoofSegmentNode = BaseNode.extend({ - overhang: eave overhang distance, along the slope - gableOverhang: reach past the gable-end walls in plan (gable, gambrel, shed); absent = the eaves' reach - shingleThickness: outer shingle layer thickness - - fascia: plumb fascia and rake boards on the eaves and rakes (gable, hip, shed, flat) (off when absent) + - buildUp: layers outside in (roofing, underlayment, sheathing, structure, insulation, finish); sets deckThickness and shingleThickness + - fascia: fascia and rake boards on the eaves and rakes (gable, hip, shed, flat) (off when absent) + - eaveCut: how the rafter tails are cut at the eaves — plumb, square (to the slope) or plumb-level (a boxed eave with a level soffit); plumb when absent - fasciaHighEdge: shed only — a board on the high edge (off when it meets a wall; on when absent) - - fasciaHeight/fasciaDepth: the boards' size (a 1x8 when absent); with a height, a flat roof's boards reach its top + - fasciaHeight/fasciaDepth: the boards' size (a 1x as deep as the tails need when absent); with a height, a flat roof's boards reach its top - fasciaMaterial/fasciaMaterialPreset: the boards' and rakes' finish (the wall role's when absent) + - heelMaterial/heelMaterialPreset: the wall band under the eaves (the roof's, then the walls' below, when absent) + - gableLeftMaterial/gableRightMaterial (+Preset): one gable end's finish (the edge role's when absent) - gambrelLowerWidthRatio / gambrelLowerHeightRatio: kink position on gambrel roofs - mansardSteepWidthRatio / mansardSteepHeightRatio: waist position on mansard roofs - dutchHipWidthRatio / dutchHipHeightRatio: hip-to-gable split on dutch roofs @@ -912,6 +932,51 @@ export function getEffectiveSegmentSurfaceMaterial( return parentFallback ?? { material: undefined, materialPreset: undefined } } +/** A segment's wall-band surfaces painted apart from the edge role: the heel band and each gable. */ +export type RoofWallBand = 'heel' | 'gable-left' | 'gable-right' + +export const ROOF_WALL_BANDS: readonly RoofWallBand[] = ['heel', 'gable-left', 'gable-right'] + +const WALL_BAND_FIELDS = { + heel: ['heelMaterial', 'heelMaterialPreset'], + 'gable-left': ['gableLeftMaterial', 'gableLeftMaterialPreset'], + 'gable-right': ['gableRightMaterial', 'gableRightMaterialPreset'], +} as const + +/** The node fields one wall band's finish is stored in. */ +export function roofWallBandFields(band: RoofWallBand) { + const [material, materialPreset] = WALL_BAND_FIELDS[band] + return { material, materialPreset } +} + +/** + * A wall band's own finish on a segment (or, for the heel, a roof), or null when it has none and + * the band follows its fallback: the heel the roof's, then the walls' below; a gable the edge role. + */ +export function getRoofWallBandMaterial( + node: Partial< + Pick< + RoofSegmentNode, + | 'heelMaterial' + | 'heelMaterialPreset' + | 'gableLeftMaterial' + | 'gableLeftMaterialPreset' + | 'gableRightMaterial' + | 'gableRightMaterialPreset' + > + >, + band: RoofWallBand, +): RoofSegmentSurfaceMaterialSpec | null { + const fields = roofWallBandFields(band) + const material = node[fields.material] + const materialPreset = node[fields.materialPreset] + if (material === undefined && typeof materialPreset !== 'string') return null + return { + material, + materialPreset: typeof materialPreset === 'string' ? materialPreset : undefined, + } +} + /** The fascia and rake boards' own finish, or null when they take the wall role's. */ export function getSegmentFasciaMaterial( node: RoofSegmentNode, @@ -943,6 +1008,7 @@ export function hasSegmentMaterialOverride(node: RoofSegmentNode): boolean { node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string' || node.fasciaMaterial !== undefined || - typeof node.fasciaMaterialPreset === 'string' + typeof node.fasciaMaterialPreset === 'string' || + ROOF_WALL_BANDS.some((band) => getRoofWallBandMaterial(node, band) !== null) ) } diff --git a/packages/core/src/schema/nodes/roof-types.ts b/packages/core/src/schema/nodes/roof-types.ts index c24e25b2d3..29e0c327f1 100644 --- a/packages/core/src/schema/nodes/roof-types.ts +++ b/packages/core/src/schema/nodes/roof-types.ts @@ -15,3 +15,12 @@ export const RoofType = z.enum([ ]) export type RoofType = z.infer + +/** + * How a roof plane's rafter tails are cut at its eaves, and so the fascia over them: `plumb` + * (vertical, the fascia plumb), `square` (square to the slope, the fascia with it) or `plumb-level` + * (plumb, with a level cut under it for a soffit nailed up flat: a boxed eave). + */ +export const RoofEaveCut = z.enum(['plumb', 'square', 'plumb-level']) + +export type RoofEaveCut = z.infer diff --git a/packages/core/src/schema/nodes/roof.ts b/packages/core/src/schema/nodes/roof.ts index e33ab76779..8edaa847c0 100644 --- a/packages/core/src/schema/nodes/roof.ts +++ b/packages/core/src/schema/nodes/roof.ts @@ -38,6 +38,10 @@ export const RoofNode = BaseNode.extend({ edgeMaterialPreset: z.string().optional(), wallMaterial: MaterialSchema.optional(), wallMaterialPreset: z.string().optional(), + // The wall band between the top plate and the eaves (a raised heel): its own finish, under every + // segment that has none; absent, the walls' below it. + heelMaterial: MaterialSchema.optional(), + heelMaterialPreset: z.string().optional(), position: z.tuple([z.number(), z.number(), z.number()]).default([0, 0, 0]), // Rotation around Y axis in radians rotation: z.number().default(0), diff --git a/packages/core/src/schema/nodes/site.ts b/packages/core/src/schema/nodes/site.ts index 49658da981..90a0f7cdfa 100644 --- a/packages/core/src/schema/nodes/site.ts +++ b/packages/core/src/schema/nodes/site.ts @@ -5,6 +5,7 @@ import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' import { SitePresentation } from '../site-presentation' import { TerrainData } from '../terrain' +import { WallTypeDefinition } from '../wall-type' import { SiteAddress, SiteParcel, SiteSetbacks } from './site-facts' // 2D Polygon @@ -143,6 +144,11 @@ export const SiteNode = BaseNode.extend({ ), }) .optional(), + /** + * The project's own wall types (`WallTypeDefinition`), listed beside the presets where walls + * are drafted. Absent = none, the state every scene that predates them loads in. + */ + wallTypes: z.array(WallTypeDefinition).max(64).optional(), children: z.array(z.string()).default([]), }).describe( dedent` @@ -155,6 +161,7 @@ export const SiteNode = BaseNode.extend({ - setbacks: required yards in metres; setbacksSource / zone cite them - frontEdge: index of the street-facing polygon edge - northRotation: true north offset from plan up, radians + - wallTypes: the project's own wall types: { id: 'custom-…', label, assembly } with the layers listed from the exterior face around one core - children: array of child node ids (buildings, items) `, ) diff --git a/packages/core/src/schema/nodes/slab.ts b/packages/core/src/schema/nodes/slab.ts index 309dcb0c93..d6f9bd9af0 100644 --- a/packages/core/src/schema/nodes/slab.ts +++ b/packages/core/src/schema/nodes/slab.ts @@ -1,6 +1,7 @@ import dedent from 'dedent' import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' +import { FloorBuildUp } from '../build-up' import { MaterialSchema } from '../material' import { SurfaceHoleMetadata } from './surface-hole-metadata' import { ZoneFloorScript } from './zone' @@ -14,6 +15,11 @@ export const MIN_SLAB_THICKNESS = 0.02 export const MIN_GROUND_FLOOR_THICKNESS = 0.01 export const DEFAULT_SLAB_ELEVATION = 0.05 +// A storey's first floor over rooms below is a framed floor: ¾ in hardwood, ⅝ in T&G plywood, +// 2x12 joists and ½ in drywall under them, 13⅛ in (`DEFAULT_FLOOR_BUILD_UP`, whose thickness this +// is). The schema default stays 0.05 so saved plates keep their thickness. +export const DEFAULT_UPPER_FLOOR_THICKNESS = 13.125 * 0.0254 + export const SlabNode = BaseNode.extend({ id: objectId('slab'), type: nodeType('slab'), @@ -48,6 +54,9 @@ export const SlabNode = BaseNode.extend({ }) .optional(), thickness: z.number().default(0.05), // Grows downward from the surface + buildUp: FloorBuildUp.optional().describe( + 'Framed floor plate only: its layers top down (flooring, sheathing, joists, ceiling); the thickness derives from them. Absent reads the default framed floor.', + ), recessed: z.boolean().default(false), recessedRimElevation: z.number().finite().optional(), fillToTerrain: z.boolean().optional(), diff --git a/packages/core/src/schema/nodes/stair-structure.ts b/packages/core/src/schema/nodes/stair-structure.ts new file mode 100644 index 0000000000..0efe1c84b0 --- /dev/null +++ b/packages/core/src/schema/nodes/stair-structure.ts @@ -0,0 +1,50 @@ +import { z } from 'zod' + +/** + * How a stair is actually built, apart from the body it is drawn with (`construction`): + * 'wood-stringers' — sawn 2x stringers carrying wood treads (the house and deck standard); + * 'concrete' — a solid poured flight on a footing (a porch stoop); 'steel-stringers' — steel + * channel stringers with treads between them. A stair without one keeps its original body. + */ +export const StairStructureType = z.enum(['wood-stringers', 'concrete', 'steel-stringers']) +export type StairStructureType = z.infer + +/** Stringer stock: a 2x12 (the standard, DCA 6) or a 2x10 for a short flight. */ +export const StairStringerStock = z.enum(['2x12', '2x10']) +export type StairStringerStock = z.infer + +/** + * 'cut' — sawtooth carriages notched for every tread and riser; 'housed' — solid outer + * stringers with the treads and risers housed (dadoed) into their faces, cut carriages between. + */ +export const StairStringerType = z.enum(['cut', 'housed']) +export type StairStringerType = z.infer + +/** + * Tread stock: two 2x6s (the deck standard), two 5/4x6 deck boards, one 2x12, or a 3/4 in + * plywood subtread under a 3/4 in finish tread (carpet or hardwood inside the house). + */ +export const StairTreadStock = z.enum(['2x6-pair', '5/4x6-pair', '2x12', 'plywood-finish']) +export type StairTreadStock = z.infer + +/** The maximum stringer spacings offered, in inches on centre. */ +export const STAIR_STRINGER_SPACINGS_IN = [12, 16, 18, 24] as const + +export const StairStructure = z.object({ + type: StairStructureType.default('wood-stringers'), + stringerStock: StairStringerStock.default('2x12'), + stringerType: StairStringerType.default('cut'), + /** The most the stringers may stand apart on centre (m) — 16 in; drives the automatic count. */ + stringerSpacing: z.number().positive().default(0.4064), + /** A stringer count chosen by hand; absent = as many as keep every bay within `stringerSpacing`. */ + stringerCount: z.number().int().min(2).max(12).optional(), + /** Absent = two 2x6s on an exterior stair, one 2x12 inside the house. */ + treadStock: StairTreadStock.optional(), + /** Riser boards; absent = closed once the stair rises more than 30 in (IRC R311.7.5.1). */ + closedRisers: z.boolean().optional(), + /** Pressure-treated lumber; absent = treated on an exterior stair. */ + treated: z.boolean().optional(), + /** Nosing projection (m); absent = 1 in on wood and steel treads, none on concrete. */ + nosing: z.number().nonnegative().optional(), +}) +export type StairStructure = z.infer diff --git a/packages/core/src/schema/nodes/stair.ts b/packages/core/src/schema/nodes/stair.ts index c8021c8a85..a2e80cc2db 100644 --- a/packages/core/src/schema/nodes/stair.ts +++ b/packages/core/src/schema/nodes/stair.ts @@ -11,6 +11,7 @@ export { StairRailingMode, StairRailingStyle } from './stair-types' import { StairConstruction } from './stair-construction' import { StairSegmentNode } from './stair-segment' +import { StairStructure } from './stair-structure' export const StairType = z.enum(['straight', 'curved', 'spiral']) export const StairTopLandingMode = z.enum(['none', 'integrated']) @@ -70,6 +71,7 @@ export const StairNode = BaseNode.extend({ thickness: z.number().default(0.25), fillToFloor: z.boolean().default(true), construction: StairConstruction.optional(), + structure: StairStructure.optional(), innerRadius: z.number().default(0.9), sweepAngle: z.number().default(Math.PI / 2), topLandingMode: StairTopLandingMode.default('none'), @@ -115,6 +117,7 @@ export const StairNode = BaseNode.extend({ - totalRise: total stair height - stepCount: number of visible steps - construction: optional explicit construction and finish details, inherited by child segments; absence preserves legacy bodies + - structure: how the stair is built — wood-stringers (2x12 or 2x10 stringers, cut or housed, the count automatic from a maximum spacing of 16 in o.c. unless set; 2x6-pair, 5/4x6-pair, 2x12 or plywood-finish treads; closed risers once it rises over 30 in), concrete (a solid poured flight on a footing) or steel-stringers (steel channels); it draws the body when no explicit construction is set. Absent keeps the original body - autoSurfaceSnap: magnetically connect straight flights to nearby registered top surfaces - thickness: stair slab / tread thickness - fillToFloor: whether the stair mass fills down to the floor or uses tread thickness only diff --git a/packages/core/src/schema/nodes/wall.ts b/packages/core/src/schema/nodes/wall.ts index 1f15d6596f..07196875c4 100644 --- a/packages/core/src/schema/nodes/wall.ts +++ b/packages/core/src/schema/nodes/wall.ts @@ -8,6 +8,7 @@ import { CurtainWallConfig } from './curtain-wall' import { DoorNode } from './door' import { ItemNode } from './item' import { LeanToExtensionNode } from './lean-to-extension' +import { RailingWallConfig } from './railing-wall' import { WindowNode } from './window' // Geometric faces: `a` is left of start → end, `b` is right (wall-frame convention). @@ -83,9 +84,27 @@ export const WALL_SKIRTING_SLOT_DEFAULT = 'library:preset-softwhite' export const WALL_CROWN_SLOT_DEFAULT = 'library:preset-white' export const WALL_CHAIR_RAIL_SLOT_DEFAULT = 'library:preset-cream' +/** + * The paint an interior wall face shows until someone paints it: Seattle grey, + * a cool light grey flat colour from the catalog. Both faces of a partition and + * the inside face of an exterior wall wear it (`getWallFaceDefault`); an + * exterior face shows its assembly's cladding, or its bare block or concrete, + * else `DEFAULT_EXTERIOR_WALL_FINISH`. Raw drywall (`library:concrete-drywall`) + * stays in the catalog for a face meant to be bare. + */ +export const DEFAULT_INTERIOR_WALL_PAINT = 'library:preset-seattlegrey' + +/** + * What an unpainted exterior wall face with no cladding shows: raw drywall, the + * default every face drew before the interior paint, kept outside so an old + * save's exterior walls look as they did and meet the roof's gable and heel + * bands (drywall too) without a seam at the plate. + */ +export const DEFAULT_EXTERIOR_WALL_FINISH = 'library:concrete-drywall' + export const WALL_SURFACE_SLOT_DEFAULTS = { - a: 'library:concrete-drywall', - b: 'library:concrete-drywall', + a: DEFAULT_INTERIOR_WALL_PAINT, + b: DEFAULT_INTERIOR_WALL_PAINT, aSkirting: WALL_SKIRTING_SLOT_DEFAULT, bSkirting: WALL_SKIRTING_SLOT_DEFAULT, aCrown: WALL_CROWN_SLOT_DEFAULT, @@ -214,6 +233,8 @@ export const WallAssembly = z framing: z.object({ kind: WallAssemblyFramingKind, depth: z.number().nonnegative(), + /** Studs on centre, metres; absent = the framer's own. */ + spacing: z.number().positive().optional(), }), interior: z .object({ @@ -244,8 +265,10 @@ export type WallAssembly = z.infer export const WallNode = BaseNode.extend({ id: objectId('wall'), type: nodeType('wall'), - wallType: z.enum(['standard', 'curtain']).optional(), + wallType: z.enum(['standard', 'curtain', 'railing']).optional(), curtainWall: CurtainWallConfig.optional(), + // How a railing wall's guard is built — see RailingWallConfig. + railing: RailingWallConfig.optional(), children: z .array( z.union([ @@ -332,6 +355,7 @@ export const WallNode = BaseNode.extend({ }).describe( dedent` Wall node - used to represent a wall in the building + - wallType: standard (framed), curtain (glazed, curtainWall) or railing (an open guard, railing: { style, postSpacing, postSize, infillSize, infillGap, bottomClearance, cap }; it closes rooms like any wall, its doors are gaps in the guard, its height is its own and its thickness its posts') - thickness: TOTAL thickness in meters (all assembly layers together) - assembly: optional layered construction stack; when present it is the single source of truth and thickness is re-derived from it on every edit @@ -353,16 +377,34 @@ export type WallNode = z.infer /** Legacy semantic side, read only by load migrations. */ export type WallSurfaceSide = 'interior' | 'exterior' -// Declared default appearance for an unpainted wall face in colored mode — -// visual parity with the retired DEFAULT_WALL_MATERIAL. Lives in core so the +// Declared default appearance for an unpainted wall face in colored mode — the +// interior paint (`DEFAULT_INTERIOR_WALL_PAINT`). Lives in core so the // slot declaration (nodes) and the material resolver (viewer) share one value. // May be a `#rrggbb` colour or a `library:` ref. Textures-off still -// collapses to the themed wall role (the escape hatch). +// collapses to the themed wall role (the escape hatch). A face on the wall's +// exterior side defaults to `DEFAULT_EXTERIOR_WALL_FINISH` instead +// (`getWallFaceDefault`). export const WALL_SLOT_DEFAULT: Record = { a: WALL_SURFACE_SLOT_DEFAULTS.a, b: WALL_SURFACE_SLOT_DEFAULTS.b, } +/** + * The default an unpainted face of `wall` shows (`a` is the +normal face, the + * `frontSide`): `DEFAULT_EXTERIOR_WALL_FINISH` where that side faces outdoors, + * the interior paint everywhere else — both faces of a partition or of a wall + * whose rooms are not yet detected, and the inside face of an exterior wall. + * An assembly's cladding or bare structure on the exterior face is drawn ahead + * of it by the renderer. + */ +export function getWallFaceDefault( + wall: Partial>, + face: WallFace, +): string { + const side = face === 'a' ? wall.frontSide : wall.backSide + return side === 'exterior' ? DEFAULT_EXTERIOR_WALL_FINISH : WALL_SLOT_DEFAULT[face] +} + export type WallTrimKind = 'skirting' | 'crown' | 'chairRail' export type WallTrimSlotId = `${WallFace}${'Skirting' | 'Crown' | 'ChairRail'}` diff --git a/packages/core/src/schema/nodes/window.ts b/packages/core/src/schema/nodes/window.ts index 1b7b29f331..79bcfb5439 100644 --- a/packages/core/src/schema/nodes/window.ts +++ b/packages/core/src/schema/nodes/window.ts @@ -3,7 +3,7 @@ import { z } from 'zod' import { BaseNode, nodeType, objectId } from '../base' import { GeometryScriptSource } from '../geometry-source' import { MaterialSchema } from '../material' -import { WindowType } from './opening-types' +import { DEFAULT_OPENING_CASING_WIDTH, WindowType } from './opening-types' export { WindowType } from './opening-types' @@ -118,14 +118,25 @@ export const WindowNode = BaseNode.extend({ sill: z.boolean().default(true), sillDepth: z.number().default(0.08), sillThickness: z.number().default(0.03), + + // Casing: flat boards on the finished faces of the host wall around the + // opening, never inside it, so the wall cutout stays width × height. The + // faces are the host's `a` / `b` (WallFace), not the unit's own front and + // back, so flipping the window leaves its casing where it is. Saves from + // before casing load with it on both faces. + casingFaceA: z.boolean().default(true), + casingFaceB: z.boolean().default(true), + casingWidth: z.number().positive().default(DEFAULT_OPENING_CASING_WIDTH), }).describe(dedent`Window node - a parametric window placed on a wall - position: center of the window in wall-local coordinate system - - width/height: overall outer dimensions + - width/height: overall outer dimensions, which are also the wall cutout - windowType: explicit window family, defaulting old windows to fixed - frameThickness: width of the frame members - frameDepth: how deep the frame sits within the wall - columnRatios/rowRatios: pane division ratios - sill: whether to show a window sill + - casingFaceA/casingFaceB: picture-frame casing on the host wall's face a / b, around the cutout + - casingWidth: face width of the casing boards (2 in by default) - corner: joined to a window on the other wall at this wall's start or end (a corner window) `) diff --git a/packages/core/src/schema/nodes/zone.ts b/packages/core/src/schema/nodes/zone.ts index 2bc81bd511..44241de1c8 100644 --- a/packages/core/src/schema/nodes/zone.ts +++ b/packages/core/src/schema/nodes/zone.ts @@ -37,6 +37,37 @@ export const ZoneNode = BaseNode.extend({ finish: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(), regions: z.array(SurfacePaintRegion).optional(), script: ZoneFloorScript.optional(), + // What the room's floor stands on, chosen in its panel: a raised floor or a slab on grade + // (a garage off a raised house). Absent, the building's foundation type. + foundation: z.enum(['raised', 'slab']).optional(), + // How far the floor steps down from the floor it would otherwise share, metres (a porch + // 1.5 in under the house), so it follows the house's floor. An `elevation` wins over it. + drop: z.number().min(0).optional(), + }) + .optional(), + // The outdoor room types: a porch (a covered floor a step down from the house), a + // deck (a framed floor) or a patio (an uncovered slab). Such a room is outdoors whatever covers + // it (`lib/outdoor-room`). Absent, an ordinary room. + roomType: z.enum(['porch', 'deck', 'patio']).optional(), + // A porch's posts and the beam on them (`lib/porch-framing`): placed by the structure kernel + // while `auto` holds (absent = on; a deck's or patio's only once `auto` is true), at these sizes + // and at most `postSpacing` metres apart. + porchFraming: z + .object({ + auto: z.boolean().optional(), + postSize: z.string().optional(), + postSpacing: z.number().positive().optional(), + beamSize: z.string().optional(), + }) + .optional(), + // A deck's boards (`lib/deck-boards`): their stock, the gap between them in metres and the way + // they run against the house, the joists under them following. Absent, 5/4x6 boards 1/4 in apart + // running along the house. + decking: z + .object({ + boardSize: z.enum(['5/4x6', '2x6', '2x4', 'composite']).optional(), + gap: z.number().positive().optional(), + direction: z.enum(['parallel', 'diagonal', 'perpendicular']).optional(), }) .optional(), // Painted parts of the room's ceiling. The automatic ceilings are rebuilt @@ -99,6 +130,11 @@ export const ZoneNode = BaseNode.extend({ - roomNumber/finishes/ceilingHeight/occupancy: construction-document room metadata - floor.regions / ceiling.regions: painted parts of the room's floor and ceiling ([x, z] polygons, later wins) - floor.script: a three.js module that builds the room's floor finish (boards, tiles) for its floor polygon, drawn over the floor; it owns its colours + - floor.foundation: "raised" or "slab", what the room's floor stands on; absent, the building's foundation type (a garage named so defaults to "slab") + - floor.drop: metres the floor steps down from the floor it would share (a porch under the house's floor); an explicit floor.elevation wins + - roomType: "porch", "deck" or "patio" for a room outside the house (outdoors even when covered); absent, an ordinary room + - porchFraming: a porch's posts and beam (auto placement on unless false; a deck's or patio's only when auto is true; post size, max post spacing in metres, beam size) + - decking: a deck's boards (boardSize "5/4x6" | "2x6" | "2x4" | "composite", gap in metres, direction "parallel" | "diagonal" | "perpendicular" to the house); absent, 5/4x6 at 1/4 in along the house - floorStepFinish: finish of the room's steps; floorStepOverrides: per-doorway step finishes keyed by door id (or the lower room id), optional step index - enclosureStatus: auto-detected, explicitly enclosed, or open - clearDimensionPolicy: optional room clear-dimension datum preference diff --git a/packages/core/src/schema/types.ts b/packages/core/src/schema/types.ts index c57ae599f0..5581caa9f9 100644 --- a/packages/core/src/schema/types.ts +++ b/packages/core/src/schema/types.ts @@ -1,5 +1,6 @@ import z from 'zod' import { ProceduralItemNode } from '../procedural-items/node' +import { BeamNode } from './nodes/beam' import { BlockNode } from './nodes/block' import { BoxVentNode } from './nodes/box-vent' import { BuildingNode } from './nodes/building' @@ -20,6 +21,8 @@ import { EyebrowVentNode } from './nodes/eyebrow-vent' import { FenceNode } from './nodes/fence' import { FenceGateNode, FenceOpeningNode } from './nodes/fence-feature' import { FloorOpeningNode } from './nodes/floor-opening' +import { FootingNode } from './nodes/footing' +import { FoundationNode } from './nodes/foundation' import { GuideNode } from './nodes/guide' import { GutterNode } from './nodes/gutter' import { HvacEquipmentNode } from './nodes/hvac-equipment' @@ -95,12 +98,15 @@ export const AnyNode = nodeUnion([ LevelNode, LeanToExtensionNode, ColumnNode, + BeamNode, ConstructionDimensionNode, BlockNode, StructuralGridNode, WallNode, SeparatorNode, FloorOpeningNode, + FootingNode, + FoundationNode, FenceNode, FenceGateNode, FenceOpeningNode, diff --git a/packages/core/src/schema/wall-type.ts b/packages/core/src/schema/wall-type.ts new file mode 100644 index 0000000000..4f493c1680 --- /dev/null +++ b/packages/core/src/schema/wall-type.ts @@ -0,0 +1,37 @@ +import { z } from 'zod' +import { Assembly } from './assembly' +import { generateId } from './base' + +/** Every project wall type's id starts with this, so a pick can never collide with a preset id. */ +export const CUSTOM_WALL_TYPE_PREFIX = 'custom-' + +export const WallTypeDefinitionId = z.string().regex(/^custom-[A-Za-z0-9_-]{1,40}$/) + +/** A fresh project wall type id: `custom-` and 16 random characters. */ +export function newWallTypeId(): string { + return generateId('custom').replace('_', '-') +} + +/** + * A wall type of the project's own: a name and the + * layers it builds from the exterior face inward around one framing core (an `Assembly` with + * `face: 'exterior'`). Saved on the site (`SiteNode.wallTypes`), so it travels with the project + * when it is reloaded or shared. A wall drawn with one copies its stack (`wallAssemblyPatch`): the + * definition is the library entry, not a live link, so editing it changes the walls drawn next. + */ +export const WallTypeDefinition = z + .object({ + id: WallTypeDefinitionId, + label: z.string().trim().min(1).max(60), + assembly: Assembly, + }) + .superRefine((type, ctx) => { + if (!type.assembly.layers.some((layer) => layer.core)) { + ctx.addIssue({ + code: 'custom', + message: 'A wall type is built around one core layer', + path: ['assembly', 'layers'], + }) + } + }) +export type WallTypeDefinition = z.infer diff --git a/packages/core/src/services/roof-bearing.test.ts b/packages/core/src/services/roof-bearing.test.ts new file mode 100644 index 0000000000..ea48ba4da3 --- /dev/null +++ b/packages/core/src/services/roof-bearing.test.ts @@ -0,0 +1,40 @@ +import { expect, test } from 'bun:test' +import { type AnyNode, LevelNode, WallNode } from '../schema' +import { shapeOfRect, wallsUnder } from './roof-bearing' + +const RAIL = 3.5 * 0.0254 +const corners: Array<[number, number]> = [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], +] + +// A 4 x 3 m deck with a 36 in guard round it, the south side a wall in `walled`. +function deck(walled = false) { + const level = LevelNode.parse({ id: 'level_deck', level: 0, height: 3 }) + const sides = corners.map((start, i) => + WallNode.parse({ + id: `wall_side${i}`, + parentId: level.id, + start, + end: corners[(i + 1) % 4], + ...(walled && i === 0 + ? { thickness: 0.2, height: 2.5 } + : { wallType: 'railing', thickness: RAIL, height: 36 * 0.0254 }), + }), + ) + level.children = sides.map((wall) => wall.id) + const nodes: Record = Object.fromEntries( + [level, ...sides].map((node) => [node.id, node]), + ) + return { level, nodes } +} + +test('a roof over a deck bears on none of its railings, on a wall along its edge it does', () => { + const shape = shapeOfRect([0, 0, 4, 3], 'x', 'gable', 30) + expect(wallsUnder(deck().nodes, 'level_deck', shape)).toEqual([]) + expect(wallsUnder(deck(true).nodes, 'level_deck', shape).map((wall) => wall.id)).toEqual([ + 'wall_side0', + ]) +}) diff --git a/packages/core/src/services/roof-bearing.ts b/packages/core/src/services/roof-bearing.ts index e60ac94614..3f3966885c 100644 --- a/packages/core/src/services/roof-bearing.ts +++ b/packages/core/src/services/roof-bearing.ts @@ -6,6 +6,7 @@ import { getConicalRoofCoverage, getRoofSegmentDeckReach, getSegmentSlopeFrame, + isRailingWall, type LevelNode, type SlabNode, type WallNode, @@ -167,7 +168,10 @@ type LevelPlan = { walls: WallNode[]; rooms: ZoneNode[]; spans: Map>() -/** A level's visible walls, its rooms and where its walls stand. */ +/** + * A level's visible walls, its rooms and where its walls stand. A railing is a guard round a deck + * or a porch, not a wall: no roof sits on it. + */ function planOf(nodes: Nodes, levelId: string): LevelPlan { let memo = planMemo.get(nodes) if (!memo) { @@ -180,7 +184,7 @@ function planOf(nodes: Nodes, levelId: string): LevelPlan { const rooms: ZoneNode[] = [] for (const node of Object.values(nodes)) { if (node.parentId !== levelId) continue - if (node.type === 'wall' && node.visible !== false) walls.push(node) + if (node.type === 'wall' && node.visible !== false && !isRailingWall(node)) walls.push(node) else if (node.type === 'zone' && node.polygon.length >= 3) rooms.push(node) } const plan = { walls, rooms, spans: wallSpans(nodes, levelId) } diff --git a/packages/core/src/store/actions/node-actions.ts b/packages/core/src/store/actions/node-actions.ts index b33e82f1f2..5968e3fe1c 100644 --- a/packages/core/src/store/actions/node-actions.ts +++ b/packages/core/src/store/actions/node-actions.ts @@ -1480,6 +1480,9 @@ const applyNodeChangesActionImpl = ( removeFromCollections(nextCollections, id) delete nextNodes[id] + // A mark left on a deleted node would sit in the dirty set for good, + // as `deleteNodes` notes: a preview may have marked it before the drop. + nodesToClearDirty.add(id) } refreshDefaultGuttersForRoofIds(nextNodes, roofsToRefresh, nodesToMarkDirty, nodesToClearDirty) @@ -1494,14 +1497,16 @@ const applyNodeChangesActionImpl = ( return { nodes: nextNodes, rootNodeIds: resolvedRootIds, collections: nextCollections } }) + // A store subscriber may already have deleted a node this write touched (the + // structure sync merging a room away): nothing is left to rebuild there. for (const id of nodesToMarkDirty) { - get().markDirty(id) + if (get().nodes[id]) get().markDirty(id) } for (const id of nodesToClearDirty) { get().clearDirty(id) } for (const id of parentsToMarkDirty) { - get().markDirty(id) + if (get().nodes[id]) get().markDirty(id) const parent = get().nodes[id] if (parent && 'children' in parent && Array.isArray(parent.children)) { for (const childId of parent.children) { @@ -1657,7 +1662,9 @@ const updateNodesActionImpl = ( // A disposed scene's frame reaches nothing. if (environment.disposed) return pendingUpdates.forEach((id) => { - get().markDirty(id) + // A node deleted since its update (a room merged away by the same drop) + // has nothing left to rebuild; marking it would leave it in the set. + if (get().nodes[id]) get().markDirty(id) }) pendingUpdates.clear() } diff --git a/packages/core/src/store/derived-node-guard.ts b/packages/core/src/store/derived-node-guard.ts index cc4bcd3f21..2fc1e9f872 100644 --- a/packages/core/src/store/derived-node-guard.ts +++ b/packages/core/src/store/derived-node-guard.ts @@ -6,6 +6,7 @@ import { import { expandFloorIntentChanges, floorIntentConflicts } from '../lib/floor-intent-changes' import { ownFloorIntentChanges } from '../lib/own-floor-intent' import { type Ring, union } from '../lib/polygon-boolean' +import { surfaceIntentZoneIds } from '../lib/surface-intent' import type { AnyNode, AnyNodeId, CeilingNode, SlabNode, ZoneNode } from '../schema' import { FloorOpeningNode, generateId } from '../schema' import { DEFAULT_SLAB_ELEVATION } from '../schema/nodes/slab' @@ -708,8 +709,9 @@ function linkedZoneIds(node: DerivedSurfaceNode): string[] { * Explicit deletion of derived construction is an intent edit, not a refusal: * removing a room's auto ceiling means "this room has no ceiling" * (`hasCeiling: false`), removing its plate means "no floor" (`hasFloor: - * false`). Returns the zone updates that record it, so the reconciler does not - * simply rebuild what the user deleted. + * false`) for the rooms it is the floor of (`surfaceIntentZoneIds`). Returns + * the zone updates that record it, so the reconciler does not simply rebuild + * what the user deleted. * * Cascaded descendants (deleting the level that owns the plate) carry no * intent — the zone is going away with them. @@ -720,11 +722,15 @@ export function derivedDeletionIntent( ): { id: AnyNodeId; data: Partial }[] { const deleted = new Set(ids) const intent = new Map>() + // The plates still standing after the delete: a room on one of its own keeps its floor. + const plates = Object.values(nodes).filter( + (node) => node.type === 'slab' && !deleted.has(node.id as AnyNodeId), + ) for (const id of ids) { const node = nodes[id] if (!isDerivedNode(node)) continue const field = node.type === 'ceiling' ? 'hasCeiling' : 'hasFloor' - for (const zoneId of linkedZoneIds(node)) { + for (const zoneId of surfaceIntentZoneIds(node, plates)) { const zone = nodes[zoneId] if (zone?.type !== 'zone' || deleted.has(zone.id)) continue if (zone[field] === false) continue diff --git a/packages/core/src/store/floor-opening-live.test.ts b/packages/core/src/store/floor-opening-live.test.ts index 5cad6a0524..77d5e35659 100644 --- a/packages/core/src/store/floor-opening-live.test.ts +++ b/packages/core/src/store/floor-opening-live.test.ts @@ -2,15 +2,28 @@ import { expect, test } from 'bun:test' import { createMezzanine } from '../commands/structure/create-mezzanine' import { createZone } from '../commands/structure/create-zone' import { cutFloorOpening, removeFloorOpening } from '../commands/structure/floor-opening' +import { setFloorFoundation } from '../commands/structure/set-floor-foundation' import { structureChangeBatch } from '../commands/structure/shared' +import { initSpatialGridSync } from '../hooks/spatial-grid/spatial-grid-sync' import { area, difference, union } from '../lib/polygon-boolean' import { initSpaceDetectionSync, pauseSpaceDetection, resumeSpaceDetection, } from '../lib/space-detection' -import { BuildingNode, generateId, LevelNode, StairNode, StairSegmentNode } from '../schema' +import { + BuildingNode, + generateId, + LevelNode, + type SlabNode, + StairNode, + StairSegmentNode, +} from '../schema' +import { getStoredLevelHeight } from '../services/storey' +import { authorStairOpeningPatches } from '../systems/owned-floor-openings' import { initializeStairOpeningSync } from '../systems/stair/stair-opening-system' +import { resolveStairTotalRise } from '../systems/stair/stair-rise' +import { reconcileStructureOnLoad } from '../utils/reconcile-structure-on-load' import useScene, { clearSceneHistory } from './use-scene' const rect = (x0: number, z0: number, x1: number, z1: number): [number, number][] => [ @@ -287,3 +300,244 @@ test('stair edits own the live floor and ceiling cuts in one undo step', async ( globalThis.cancelAnimationFrame = previousCancel } }) + +test('a stair opening reshaped in plan cuts the floor and ceiling to that shape and keeps it', async () => { + const previousFrame = globalThis.requestAnimationFrame + const previousCancel = globalThis.cancelAnimationFrame + globalThis.requestAnimationFrame = ((callback: FrameRequestCallback) => { + callback(0) + return 1 + }) as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = () => {} + const building = BuildingNode.parse({ + id: 'building_shaped_stair', + children: ['level_shaped_stair_0', 'level_shaped_stair_1'], + }) + const ground = LevelNode.parse({ id: 'level_shaped_stair_0', parentId: building.id, level: 0 }) + const upper = LevelNode.parse({ id: 'level_shaped_stair_1', parentId: building.id, level: 1 }) + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground, [upper.id]: upper }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + materials: {}, + collections: {}, + readOnly: false, + }) + clearSceneHistory() + const stopStructure = initSpaceDetectionSync(useScene, { + getState: () => ({ spaces: {}, setSpaces: () => {} }), + }) + const stopStair = initializeStairOpeningSync() + const settle = async () => { + for (let index = 0; index < 4; index++) await Promise.resolve() + } + const nodes = () => useScene.getState().nodes + const owned = () => + Object.values(nodes()).filter( + (node) => node.type === 'floor-opening' && node.ownerId === 'stair_shaped', + ) + const holes = (levelId: string, type: 'slab' | 'ceiling') => + Object.values(nodes()).flatMap((node) => + node.parentId === levelId && node.type === type + ? node.holes.map((outer) => ({ outer, holes: [] })) + : [], + ) + try { + for (const levelId of [ground.id, upper.id]) { + const plan = createZone(nodes(), { + levelId, + polygon: rect(0, 0, 8, 5), + enclose: true, + mintId: generateId, + }) + useScene.getState().applyNodeChanges(structureChangeBatch(plan.changes)) + } + await settle() + const stair = StairNode.parse({ + id: 'stair_shaped', + parentId: ground.id, + fromLevelId: ground.id, + toLevelId: upper.id, + slabOpeningMode: 'destination', + position: [3, 0, 1], + children: ['sseg_shaped'], + }) + const segment = StairSegmentNode.parse({ + id: 'sseg_shaped', + parentId: stair.id, + length: 3, + height: 3, + width: 1, + }) + useScene.getState().applyNodeChanges( + structureChangeBatch([ + { op: 'create', node: stair }, + { op: 'create', node: segment }, + ]), + ) + await settle() + const floorOpening = owned().find( + (node) => node.type === 'floor-opening' && node.drawnOn === 'floor', + )! + // Widen the opening by half a metre on the +X side, as the plan's edge handle does. + const widened = rect(2.5, 1, 4, 4) + const patches = authorStairOpeningPatches(nodes(), floorOpening.id, widened) + expect(patches).toHaveLength(2) + useScene.getState().updateNodes(patches) + await settle() + const inside = [3.75, 2.5] as [number, number] + expect(area(difference(holes(upper.id, 'slab'), [{ outer: widened, holes: [] }]))).toBeLessThan( + 1e-6, + ) + expect(area(holes(upper.id, 'slab'))).toBeCloseTo(area([{ outer: widened, holes: [] }]), 3) + expect( + area( + union([ + ...holes(ground.id, 'ceiling'), + { + outer: [inside, [inside[0] + 0.01, inside[1]], [inside[0], inside[1] + 0.01]], + holes: [], + }, + ]), + ), + ).toBeCloseTo(area(holes(ground.id, 'ceiling')), 4) + // Touching the stair (a reselect re-syncs it) keeps the drawn shape. + useScene.getState().updateNode(stair.id, { name: 'Main stair' }) + await settle() + expect(area(holes(upper.id, 'slab'))).toBeCloseTo(1.5 * 3, 3) + // Moving the stair carries the drawn shape along. + useScene.getState().updateNode(stair.id, { position: [4, 0, 1] }) + await settle() + const moved = owned().find((node) => node.type === 'floor-opening' && node.drawnOn === 'floor') + expect( + moved?.type === 'floor-opening' && Math.max(...moved.polygon.map(([x]) => x)), + ).toBeCloseTo(5) + expect(area(holes(upper.id, 'slab'))).toBeCloseTo(1.5 * 3, 3) + // A reload keeps it. + const reloaded = reconcileStructureOnLoad(nodes()).nodes + const plateHoles = Object.values(reloaded).flatMap((node) => + node.parentId === upper.id && node.type === 'slab' + ? node.holes.map((outer) => ({ outer, holes: [] })) + : [], + ) + expect(area(plateHoles)).toBeCloseTo(1.5 * 3, 3) + } finally { + stopStair() + stopStructure() + clearSceneHistory() + globalThis.requestAnimationFrame = previousFrame + globalThis.cancelAnimationFrame = previousCancel + } +}) + +test('a stair that follows its levels re-rises live when the floor above thickens or a storey grows', async () => { + const previousFrame = globalThis.requestAnimationFrame + const previousCancel = globalThis.cancelAnimationFrame + globalThis.requestAnimationFrame = ((callback: FrameRequestCallback) => { + callback(0) + return 1 + }) as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = () => {} + const building = BuildingNode.parse({ + id: 'building_rise_stair', + children: ['level_rise_stair_0', 'level_rise_stair_1'], + }) + const ground = LevelNode.parse({ id: 'level_rise_stair_0', parentId: building.id, level: 0 }) + const upper = LevelNode.parse({ id: 'level_rise_stair_1', parentId: building.id, level: 1 }) + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground, [upper.id]: upper }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + materials: {}, + collections: {}, + readOnly: false, + }) + clearSceneHistory() + const stopGrid = initSpatialGridSync() + const stopStructure = initSpaceDetectionSync(useScene, { + getState: () => ({ spaces: {}, setSpaces: () => {} }), + }) + const stopStair = initializeStairOpeningSync() + const settle = async () => { + for (let index = 0; index < 8; index++) await Promise.resolve() + } + const nodes = () => useScene.getState().nodes + const stairNode = () => nodes()['stair_rise' as never] as StairNode + const flight = () => (nodes()['sseg_rise' as never] as StairSegmentNode).height + const rise = () => resolveStairTotalRise(stairNode(), nodes()) + try { + for (const levelId of [ground.id, upper.id]) { + const plan = createZone(nodes(), { + levelId, + polygon: rect(0, 0, 8, 5), + enclose: true, + mintId: generateId, + }) + useScene.getState().applyNodeChanges(structureChangeBatch(plan.changes)) + } + await settle() + const stair = StairNode.parse({ + id: 'stair_rise', + parentId: ground.id, + fromLevelId: ground.id, + toLevelId: upper.id, + slabOpeningMode: 'destination', + uniformRisers: true, + position: [3, 0, 1], + children: ['sseg_rise'], + }) + const segment = StairSegmentNode.parse({ + id: 'sseg_rise', + parentId: stair.id, + length: 3, + height: 2.5, + width: 1, + stepCount: 15, + }) + useScene.getState().applyNodeChanges( + structureChangeBatch([ + { op: 'create', node: stair }, + { op: 'create', node: segment }, + ]), + ) + await settle() + const start = rise() + expect(flight()).toBeCloseTo(start, 4) + // A thicker floor above: the stair still lands on its top, no reload. + const plate = Object.values(nodes()).find( + (node): node is SlabNode => + node.type === 'slab' && node.parentId === upper.id && node.plateRole === 'base', + )! + const thicker = 0.45 - plate.thickness + useScene + .getState() + .applyNodeChanges( + structureChangeBatch( + setFloorFoundation(nodes(), { slabId: plate.id, patch: { thickness: 0.45 } }).changes, + ), + ) + await settle() + expect(rise()).toBeCloseTo(start + thicker, 4) + expect(flight()).toBeCloseTo(start + thicker, 4) + // A custom rise holds; back to Follows level it re-reaches the floor. + useScene.getState().updateNode(stair.id, { totalRise: 2.5 }) + await settle() + expect(flight()).toBeCloseTo(2.5, 4) + useScene.getState().updateNode(stair.id, { totalRise: undefined }) + await settle() + expect(flight()).toBeCloseTo(start + thicker, 4) + // A taller storey below: the risers recompute live. + const storey = getStoredLevelHeight(nodes()[ground.id] as LevelNode) + useScene.getState().updateNode(ground.id, { height: storey + 0.3 }) + await settle() + expect(rise()).toBeCloseTo(start + thicker + 0.3, 4) + expect(flight()).toBeCloseTo(start + thicker + 0.3, 4) + } finally { + stopStair() + stopStructure() + stopGrid() + clearSceneHistory() + globalThis.requestAnimationFrame = previousFrame + globalThis.cancelAnimationFrame = previousCancel + } +}) diff --git a/packages/core/src/systems/owned-floor-openings.ts b/packages/core/src/systems/owned-floor-openings.ts index f97756949a..c2be3bd3bd 100644 --- a/packages/core/src/systems/owned-floor-openings.ts +++ b/packages/core/src/systems/owned-floor-openings.ts @@ -127,6 +127,69 @@ function cutsFromSurfaces(surfaces: Record, owners: Record, +) { + return opening.metadata.ownerOpeningTarget === 'source' || opening.drawnOn === 'ceiling' + ? owner.fromLevelId + : owner.toLevelId +} + +/** + * A stair opening reshaped by hand: the floor cut and the ceiling cut below it (the stair's openings + * on its own two levels) take `polygon` and become openings the stair owns as drawn, so the plate + * and ceiling are cut by that shape (not the derived rectangle) and keep it when the stair is + * reselected or re-synced. The stair's current pose is recorded so they still follow it when it + * moves or resizes. Returns the patches; empty when `openingId` is not a stair opening that can be + * authored this way (an opening on a level between two the stair joins keeps following it). + */ +export function authorStairOpeningPatches( + nodes: Record, + openingId: string, + polygon: [number, number][], +): Array<{ id: AnyNodeId; data: Partial }> { + const opening = nodes[openingId] + if (opening?.type !== 'floor-opening' || opening.source !== 'stair' || !opening.ownerId) return [] + const owner = nodes[opening.ownerId] + if (owner?.type !== 'stair' || authoredOpeningTarget(opening, owner) !== opening.parentId) + return [] + const segment = owner.children + .map((id) => nodes[id]) + .find((node) => node?.type === 'stair-segment') + const footprint = stairFootprintAABB({ ...owner, position: [0, 0, 0], rotation: 0 }, nodes) + const pose: OwnerPose = { + position: owner.position, + rotation: owner.rotation, + width: owner.width, + runLength: segment?.type === 'stair-segment' ? segment.length : 1, + ...(isBox(footprint) ? { footprint } : {}), + } + const ring = canonicalOpeningRing(polygon) + return Object.values(nodes).flatMap((node) => + node.type === 'floor-opening' && + node.source === 'stair' && + node.ownerId === owner.id && + authoredOpeningTarget(node, owner) === node.parentId + ? [ + { + id: node.id as AnyNodeId, + data: { + polygon: ring, + surfaceId: undefined, + legacyPlateCuts: undefined, + legacyCeilingCuts: undefined, + cutsPrimary: true, + cutsAdjacent: false, + metadata: { ...node.metadata, ownerPose: pose }, + }, + }, + ] + : [], + ) +} + export function planOwnedFloorOpenings( nodes: Record, options: { diff --git a/packages/core/src/systems/roof/roof-elevation.test.ts b/packages/core/src/systems/roof/roof-elevation.test.ts index b314b757e6..12d873c633 100644 --- a/packages/core/src/systems/roof/roof-elevation.test.ts +++ b/packages/core/src/systems/roof/roof-elevation.test.ts @@ -131,6 +131,17 @@ describe('resolveRoofElevation', () => { expect(resolveRoofElevation(roof, nodes)).toBe(-0.5) }) + // A deck's 36 in guard pulled the roof over it down onto the railing tops. + test("a railing's top never seats a roof: the walls' do, else it keeps its height", () => { + const { walls, roof, nodes } = scene([2.5]) + const railing = (wall: WallNode) => + ({ ...wall, wallType: 'railing', height: 36 * 0.0254, thickness: 0.0889 }) as WallNode + const deck = { ...nodes, ...Object.fromEntries(walls.map((wall) => [wall.id, railing(wall)])) } + expect(resolveRoofElevation(roof, deck)).toBe(0) + const leaning = { ...deck, [walls[0]!.id]: walls[0]! } + expect(resolveRoofElevation(roof, leaning)).toBe(-0.5) + }) + test('freezes without an enclosure, retaining follow intent', () => { const { walls, roof, nodes } = scene([4]) const remaining = { ...nodes } diff --git a/packages/core/src/systems/roof/roof-elevation.ts b/packages/core/src/systems/roof/roof-elevation.ts index a3da613fde..b2ea856f1f 100644 --- a/packages/core/src/systems/roof/roof-elevation.ts +++ b/packages/core/src/systems/roof/roof-elevation.ts @@ -9,6 +9,7 @@ import type { RoofSegmentNode, WallNode, } from '../../schema' +import { isRailingWall } from '../../schema/nodes/railing-wall' import { coveredStoreyId, servesAsRoofLevel } from '../../services/roof-level' import { findLevelAboveId, @@ -94,7 +95,10 @@ function wallsUnderRoof( }) } -/** The tallest of these walls' tops, from `targetLevelId`'s floor; undefined for none. */ +/** + * The tallest of these walls' tops, from `targetLevelId`'s floor; undefined for none. A railing is a + * guard round a deck or a porch, not a wall: no roof stands on its top. + */ function highestWallTop( targetLevelId: string, wallIds: readonly string[], @@ -104,7 +108,7 @@ function highestWallTop( let highest: number | undefined for (const id of wallIds) { const wall = nodes[id] - if (wall?.type !== 'wall') continue + if (wall?.type !== 'wall' || isRailingWall(wall)) continue const top = resolveRoofWallTopElevation( targetLevelId as LevelNode['id'], wall, diff --git a/packages/core/src/systems/roof/roof-footprint.test.ts b/packages/core/src/systems/roof/roof-footprint.test.ts new file mode 100644 index 0000000000..ca8bb421e1 --- /dev/null +++ b/packages/core/src/systems/roof/roof-footprint.test.ts @@ -0,0 +1,230 @@ +import { describe, expect, test } from 'bun:test' +import { type AnyNode, LevelNode, SeparatorNode, WallNode, ZoneNode } from '../../schema' +import { + outdoorRoomRoofOutline, + resolveRoomRoofFootprint, + resolveRoomRoofFootprintOnLevel, +} from './roof-footprint' + +const RAIL = 3.5 * 0.0254 +type P = [number, number] + +const near = (value: readonly (readonly number[])[], expected: P[]) => { + expect(value).toHaveLength(expected.length) + value.forEach((point, i) => { + expect(point[0]).toBeCloseTo(expected[i]![0], 6) + expect(point[1]).toBeCloseTo(expected[i]![1], 6) + }) +} + +// A 10 x 8 m house of 0.2 m walls, a porch 6 m along its south wall (z 0), 2.5 m deep. +function porch(open: 'separators' | 'railings' = 'separators') { + const level = LevelNode.parse({ id: 'level_ground', level: 0, height: 3 }) + const corners: P[] = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const house = corners.map((start, i) => + WallNode.parse({ + id: `wall_house${i}`, + parentId: level.id, + start, + end: corners[(i + 1) % 4], + thickness: 0.2, + }), + ) + const sides: Array<[P, P]> = [ + [ + [2, 0], + [2, -2.5], + ], + [ + [2, -2.5], + [8, -2.5], + ], + [ + [8, -2.5], + [8, 0], + ], + ] + const edges = sides.map(([start, end], i) => + open === 'separators' + ? SeparatorNode.parse({ id: `separator_p${i}`, parentId: level.id, start, end }) + : WallNode.parse({ + id: `wall_rail${i}`, + parentId: level.id, + start, + end, + wallType: 'railing', + thickness: RAIL, + height: 36 * 0.0254, + }), + ) + const zone = ZoneNode.parse({ + parentId: level.id, + name: open === 'separators' ? 'Porch' : 'Deck', + roomType: open === 'separators' ? 'porch' : 'deck', + polygon: [ + [2, 0], + [2, -2.5], + [8, -2.5], + [8, 0], + ], + }) + level.children = [...house, ...edges, zone].map((node) => node.id) as LevelNode['children'] + const nodes: Record = Object.fromEntries( + [level, ...house, ...edges, zone].map((node) => [node.id, node]), + ) + return { level, house, edges, zone, nodes } +} + +describe('a porch, deck or patio roof outline', () => { + test('a porch keeps its invisible walls as its edge, the house wall on its centre line', () => { + const { house, edges, zone } = porch() + const outline = outdoorRoomRoofOutline(zone, house, edges as SeparatorNode[]) + near(outline.polygon, zone.polygon) + expect(outline.edges.map((edge) => edge?.kind)).toEqual([ + 'separator', + 'separator', + 'separator', + 'wall', + ]) + expect(outline.edges[3]?.id).toBe('wall_house0') + }) + + test("a deck's railings move out to their outside faces, meeting at the posts' corners", () => { + const { house, edges, zone } = porch('railings') + const outline = outdoorRoomRoofOutline(zone, [...house, ...(edges as WallNode[])], []) + const h = RAIL / 2 + near(outline.polygon, [ + [2 - h, 0], + [2 - h, -2.5 - h], + [8 + h, -2.5 - h], + [8 + h, 0], + ]) + expect(outline.edges.map((edge) => edge?.kind)).toEqual([ + 'railing', + 'railing', + 'railing', + 'wall', + ]) + }) + + test('a railing running on into an invisible wall along one line steps out at the join', () => { + const { house, zone } = porch() + const level = zone.parentId! + const rail = WallNode.parse({ + parentId: level, + start: [5, -2.5], + end: [2, -2.5], + wallType: 'railing', + thickness: RAIL, + }) + const separators = [ + SeparatorNode.parse({ parentId: level, start: [2, 0], end: [2, -2.5] }), + SeparatorNode.parse({ parentId: level, start: [8, -2.5], end: [5, -2.5] }), + SeparatorNode.parse({ parentId: level, start: [8, 0], end: [8, -2.5] }), + ] + const room = { + polygon: [ + [2, 0], + [8, 0], + [8, -2.5], + [5, -2.5], + [2, -2.5], + ] as P[], + } + const outline = outdoorRoomRoofOutline(room, [...house, rail], separators) + const h = RAIL / 2 + near(outline.polygon, [ + [2, 0], + [8, 0], + [8, -2.5], + [5, -2.5], + [5, -2.5 - h], + [2, -2.5 - h], + ]) + expect(outline.edges.map((edge) => edge?.kind ?? null)).toEqual([ + 'wall', + 'separator', + 'separator', + null, + 'railing', + 'separator', + ]) + }) +}) + +describe('the Room pick over a porch, deck or patio', () => { + test('roofs a porch to its invisible walls, standing on the house wall it leans on', () => { + const { level, nodes } = porch() + const target = resolveRoomRoofFootprint(level.id, nodes, [5, -1])! + expect(target).toMatchObject({ rectangular: true, levelId: level.id }) + expect(target.width * target.depth).toBeCloseTo(6 * 2.5, 6) + expect(Math.max(target.width, target.depth)).toBeCloseTo(6, 6) + near([target.center], [[5, -1.25]]) + expect(target.wallIds).toEqual(['wall_house0']) + expect([...(target.edgeIds ?? [])].sort()).toEqual([ + 'separator_p0', + 'separator_p1', + 'separator_p2', + ]) + }) + + test("roofs a deck to its railings' outside faces, its railings no walls it stands on", () => { + const { level, nodes } = porch('railings') + const target = resolveRoomRoofFootprint(level.id, nodes, [5, -1])! + const h = RAIL / 2 + expect(Math.max(target.width, target.depth)).toBeCloseTo(6 + 2 * h, 6) + expect(Math.min(target.width, target.depth)).toBeCloseTo(2.5 + h, 6) + near([target.center], [[5, -(2.5 + h) / 2]]) + expect(target.wallIds).toEqual(['wall_house0']) + expect([...(target.edgeIds ?? [])].sort()).toEqual(['wall_rail0', 'wall_rail1', 'wall_rail2']) + }) + + test('finds the porch on the storey below from the roof level', () => { + const { level, nodes } = porch() + const roofLevel = LevelNode.parse({ id: 'level_roof', level: 1, height: 3 }) + const target = resolveRoomRoofFootprint( + roofLevel.id, + { ...nodes, [roofLevel.id]: roofLevel }, + [5, -1], + ) + expect(target?.levelId).toBe(level.id) + expect(target?.wallIds).toEqual(['wall_house0']) + }) + + test("an invisible wall inside the house never shrinks the house's roof to one side of it", () => { + const { level, nodes } = porch() + const divider = SeparatorNode.parse({ parentId: level.id, start: [5, 0], end: [5, 8] }) + const kitchen = ZoneNode.parse({ + parentId: level.id, + name: 'Kitchen', + polygon: [ + [0, 0], + [5, 0], + [5, 8], + [0, 8], + ], + }) + const scene = { + ...nodes, + [divider.id]: divider, + [kitchen.id]: kitchen, + [level.id]: { ...level, children: [...level.children, divider.id, kitchen.id] }, + } as Record + const target = resolveRoomRoofFootprint(level.id, scene, [2, 4])! + expect(target.width * target.depth).toBeCloseTo(10 * 8, 6) + expect(target.wallIds).toHaveLength(4) + expect(target.edgeIds).toBeUndefined() + }) + + test("the storey's own room test still reads walls alone, so a porch on posts stays put", () => { + const { level, nodes } = porch() + // `standsOverStorey` asks this: a porch closed by invisible walls is no room of the walls. + expect(resolveRoomRoofFootprintOnLevel(level.id, nodes, [5, -1])).toBeNull() + expect(resolveRoomRoofFootprintOnLevel(level.id, nodes, [5, 4])?.wallIds).toHaveLength(4) + }) +}) diff --git a/packages/core/src/systems/roof/roof-footprint.ts b/packages/core/src/systems/roof/roof-footprint.ts index 5fa4b8adf1..364f2f310d 100644 --- a/packages/core/src/systems/roof/roof-footprint.ts +++ b/packages/core/src/systems/roof/roof-footprint.ts @@ -1,12 +1,25 @@ +import { isOutdoorRoomType } from '../../lib/outdoor-room' import { pointInPolygon as pointInPolygon2D } from '../../lib/polygon-relations' import { detectSpacesForLevel } from '../../lib/space-detection' -import type { AnyNode, LevelNode, WallNode } from '../../schema' +import type { AnyNode, LevelNode, SeparatorNode, WallNode, ZoneNode } from '../../schema' +import { isRailingWall } from '../../schema/nodes/railing-wall' import { getLevelBelow } from '../../services/storey' +import { isCurvedWall } from '../wall/wall-curve' +import { getWallThickness } from '../wall/wall-footprint' +import { getWallBodyLine } from '../wall/wall-frame' export type RoofFootprintTarget = { id: string polygon: Array<[number, number]> + /** The walls it stands on: a room's walls; a porch's, deck's or patio's house walls. */ wallIds: WallNode['id'][] + /** + * A porch's, deck's or patio's invisible walls and railings along its outside edge: the edge the + * roof's outer face stands on, not walls it stands on (`outdoorRoomRoofOutline`). + */ + edgeIds?: string[] + /** The level the room is on. */ + levelId?: LevelNode['id'] center: [number, number] width: number depth: number @@ -14,6 +27,130 @@ export type RoofFootprintTarget = { rectangular: boolean } +type P = [number, number] + +/** What runs along an edge of a room's roof outline: a wall, a railing or an invisible wall. */ +export type RoofOutlineEdge = { kind: 'wall' | 'railing' | 'separator'; id: string } | null + +/** A room's outline edge this near a boundary's line, and parallel to it, runs along it (m). */ +const ON_BOUNDARY = 0.01 + +/** What runs along the room edge `a` → `b`: the nearest straight wall or invisible wall on it. */ +function boundaryAlong( + a: P, + b: P, + walls: readonly WallNode[], + separators: readonly SeparatorNode[], +): (RoofOutlineEdge & { thickness: number }) | null { + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (length < 1e-9) return null + const dir: P = [(b[0] - a[0]) / length, (b[1] - a[1]) / length] + const mid: P = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] + const lines = [ + // A wall drawn by a face runs its rooms along its body's centre, half its thickness off it. + ...walls.flatMap((wall) => { + if (isCurvedWall(wall)) return [] + const body = getWallBodyLine(wall) + return [ + { + kind: isRailingWall(wall) ? ('railing' as const) : ('wall' as const), + id: wall.id, + start: [body.start.x, body.start.y] as P, + end: [body.end.x, body.end.y] as P, + thickness: getWallThickness(wall), + }, + ] + }), + ...separators.map((separator) => ({ + kind: 'separator' as const, + id: separator.id, + start: separator.start, + end: separator.end, + thickness: 0, + })), + ] + let found: (RoofOutlineEdge & { thickness: number }) | null = null + let nearest = ON_BOUNDARY + for (const line of lines) { + const run: P = [line.end[0] - line.start[0], line.end[1] - line.start[1]] + const span = Math.hypot(run[0], run[1]) + if (span < 1e-6) continue + const u: P = [run[0] / span, run[1] / span] + if (Math.abs(u[0] * dir[1] - u[1] * dir[0]) > 1e-3) continue + const rel: P = [mid[0] - line.start[0], mid[1] - line.start[1]] + const across = Math.abs(rel[0] * u[1] - rel[1] * u[0]) + const along = rel[0] * u[0] + rel[1] * u[1] + if (across > nearest || along < -ON_BOUNDARY || along > span + ON_BOUNDARY) continue + found = { kind: line.kind, id: line.id, thickness: line.thickness } + nearest = across + } + return found +} + +/** + * The outside edge of a porch, deck or patio (`lib/outdoor-room`) a roof over it lines up with, + * in its level's plan: the room's outline with each edge on a railing moved out to the railing's + * outside face (to its posts' outer corners where two railings meet). An edge on an invisible wall + * stays on its line, which is the room's edge, and one on the house wall the room leans on stays + * on the wall's centre line, as a room's walls do in the house's own roof footprint. `edges[i]` + * runs from `polygon[i]` to `polygon[i + 1]`: what runs along it, null on the short jog where a + * railing runs on into an invisible wall along one line. + */ +export function outdoorRoomRoofOutline( + zone: Pick, + walls: readonly WallNode[], + separators: readonly SeparatorNode[], +): { polygon: P[]; edges: RoofOutlineEdge[] } { + const points = zone.polygon.filter((point, index, all) => { + const next = all[(index + 1) % all.length]! + return Math.hypot(next[0] - point[0], next[1] - point[1]) > 1e-9 + }) as P[] + const n = points.length + if (n < 3) return { polygon: points, edges: points.map(() => null) } + let area = 0 + for (let i = 0; i < n; i++) { + const a = points[i]! + const b = points[(i + 1) % n]! + area += a[0] * b[1] - b[0] * a[1] + } + const turn = area > 0 ? 1 : -1 + const sides = points.map((a, i) => { + const b = points[(i + 1) % n]! + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + const dir: P = [(b[0] - a[0]) / length, (b[1] - a[1]) / length] + const boundary = boundaryAlong(a, b, walls, separators) + const edge: RoofOutlineEdge = boundary && { kind: boundary.kind, id: boundary.id } + return { + dir, + out: [dir[1] * turn, -dir[0] * turn] as P, + offset: boundary?.kind === 'railing' ? boundary.thickness / 2 : 0, + edge, + } + }) + const polygon: P[] = [] + const edges: RoofOutlineEdge[] = [] + sides.forEach((side, i) => { + const prev = sides[(i - 1 + n) % n]! + const vertex = points[i]! + const from: P = [vertex[0] + prev.out[0] * prev.offset, vertex[1] + prev.out[1] * prev.offset] + const to: P = [vertex[0] + side.out[0] * side.offset, vertex[1] + side.out[1] * side.offset] + const denom = prev.dir[0] * side.dir[1] - prev.dir[1] * side.dir[0] + if (Math.abs(denom) > 1e-9) { + // Where the two edges' moved lines meet. + const t = ((to[0] - from[0]) * side.dir[1] - (to[1] - from[1]) * side.dir[0]) / denom + polygon.push([from[0] + prev.dir[0] * t, from[1] + prev.dir[1] * t]) + } else { + polygon.push(from) + if (Math.hypot(to[0] - from[0], to[1] - from[1]) > 1e-9) { + edges.push(null) + polygon.push(to) + } + } + edges.push(side.edge) + }) + return { polygon, edges } +} + function polygonArea(polygon: ReadonlyArray): number { return Math.abs( polygon.reduce((area, point, index) => { @@ -82,24 +219,68 @@ export function fitRoofFootprint( } } +/** + * The Room pick's footprint at `point`, on the level or else the level below: the porch, deck or + * patio there, to its outside edge (`resolveOutdoorRoomRoofFootprintOnLevel`), else the smallest + * room the level's walls enclose (`resolveRoomRoofFootprintOnLevel`). + */ export function resolveRoomRoofFootprint( levelId: LevelNode['id'], nodes: Readonly>, point: [number, number], options: { rectangularOnly?: boolean } = {}, ): RoofFootprintTarget | null { - const activeTarget = resolveRoomRoofFootprintOnLevel(levelId, nodes, point) + const onLevel = (id: LevelNode['id']): RoofFootprintTarget | null => { + const target = + resolveOutdoorRoomRoofFootprintOnLevel(id, nodes, point) ?? + resolveRoomRoofFootprintOnLevel(id, nodes, point) + return target && { ...target, levelId: id } + } + const activeTarget = onLevel(levelId) if (activeTarget && (!options.rectangularOnly || activeTarget.rectangular)) return activeTarget if (activeTarget) return null const levelBelow = getLevelBelow(levelId, nodes as Record) - const levelBelowTarget = levelBelow - ? resolveRoomRoofFootprintOnLevel(levelBelow.id, nodes, point) - : null + const levelBelowTarget = levelBelow ? onLevel(levelBelow.id) : null return levelBelowTarget && (!options.rectangularOnly || levelBelowTarget.rectangular) ? levelBelowTarget : null } +/** + * The porch, deck or patio at `point` on `levelId` (the smallest), as a roof over it stands: its + * outside edge (`outdoorRoomRoofOutline`), standing on the house walls it leans on, its invisible + * walls and railings the edge its outer face lines up with. Null where no such room is. Indoors the + * Room pick reads the walls alone, so an invisible wall splitting a room off inside the house + * never shrinks the house's roof to it. + */ +export function resolveOutdoorRoomRoofFootprintOnLevel( + levelId: LevelNode['id'], + nodes: Readonly>, + point: [number, number], +): RoofFootprintTarget | null { + const level = nodes[levelId] + if (level?.type !== 'level') return null + const children = level.children.map((id) => nodes[id]) + const zone = children + .filter( + (node): node is ZoneNode => + node?.type === 'zone' && + isOutdoorRoomType(node.roomType) && + node.polygon.length >= 3 && + pointInPolygon2D(point, node.polygon), + ) + .sort((left, right) => polygonArea(left.polygon) - polygonArea(right.polygon))[0] + if (!zone) return null + const walls = children.filter((node): node is WallNode => node?.type === 'wall') + const separators = children.filter((node): node is SeparatorNode => node?.type === 'separator') + const { polygon, edges } = outdoorRoomRoofOutline(zone, walls, separators) + const ids = (kinds: ReadonlyArray['kind']>) => [ + ...new Set(edges.flatMap((edge) => (edge && kinds.includes(edge.kind) ? [edge.id] : []))), + ] + const target = fitRoofFootprint(zone.id, polygon, ids(['wall']) as WallNode['id'][]) + return target && { ...target, edgeIds: ids(['railing', 'separator']) } +} + export function resolveRoomRoofFootprintOnLevel( levelId: LevelNode['id'], nodes: Readonly>, diff --git a/packages/core/src/systems/stair/stair-clearance.test.ts b/packages/core/src/systems/stair/stair-clearance.test.ts index 6774ac5a38..aa7de8bf34 100644 --- a/packages/core/src/systems/stair/stair-clearance.test.ts +++ b/packages/core/src/systems/stair/stair-clearance.test.ts @@ -172,3 +172,36 @@ test('intermediate slab cuts stop above the floating body and hidden flights ret expect(surfaces[0]?.walkingLine[0][2]).toBeCloseTo(5) expect(surfaces[0]?.top).toBeCloseTo(3.2) }) + +test('a widened straight-flight opening stays a square-cornered rectangle', () => { + const building = BuildingNode.parse({}) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1 }) + const flight = StairSegmentNode.parse({ height: 3, length: 4, stepCount: 15, width: 1 }) + const stair = StairNode.parse({ + parentId: ground.id, + fromLevelId: ground.id, + toLevelId: upper.id, + children: [flight.id], + totalRise: 3, + slabOpeningMode: 'destination', + openingOffset: 0.2, + }) + const nodes: Record = Object.fromEntries( + [building, ground, upper, stair, { ...flight, parentId: stair.id }].map((node) => [ + node.id, + node, + ]), + ) + const [ring, ...rest] = stairClearanceOpening(stair, nodes, 2.8, 0.2, 3)! + expect(rest).toEqual([]) + expect(ring).toHaveLength(4) + const xs = ring!.map(([x]) => x) + expect(Math.min(...xs)).toBeCloseTo(-0.7) + expect(Math.max(...xs)).toBeCloseTo(0.7) + for (const [i, a] of ring!.entries()) { + const b = ring![(i + 1) % 4]! + const c = ring![(i + 2) % 4]! + expect((b[0] - a[0]) * (c[0] - b[0]) + (b[1] - a[1]) * (c[1] - b[1])).toBeCloseTo(0) + } +}) diff --git a/packages/core/src/systems/stair/stair-clearance.ts b/packages/core/src/systems/stair/stair-clearance.ts index c42fb5d38a..6fc54e4763 100644 --- a/packages/core/src/systems/stair/stair-clearance.ts +++ b/packages/core/src/systems/stair/stair-clearance.ts @@ -573,17 +573,35 @@ function expand(region: Polygon, offset: number): Polygon[] { ], holes: [], }) - pieces.push({ - outer: Array.from({ length: 12 }, (_, j) => [ - p[0] + Math.cos((j * TAU) / 12) * offset, - p[1] + Math.sin((j * TAU) / 12) * offset, - ]), - holes: [], - }) + pieces.push(...squareCorner(ring[(i + ring.length - 1) % ring.length]!, p, q, offset)) } return union(pieces) } +/** + * The corner pieces at `p` (between edges `prev → p` and `p → next`) on both sides of the ring: + * a mitred, square-cornered join, so a rectangular flight's opening widens to a rectangle, not a + * rounded box. A corner sharper than ~60° is bevelled instead of mitred to a spike. + */ +function squareCorner(prev: Ring[number], p: Ring[number], next: Ring[number], offset: number) { + const unitNormal = (a: Ring[number], b: Ring[number]): [number, number] | null => { + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + return length ? [-(b[1] - a[1]) / length, (b[0] - a[0]) / length] : null + } + const n1 = unitNormal(prev, p) + const n2 = unitNormal(p, next) + if (!(n1 && n2)) return [] + const dot = n1[0] * n2[0] + n1[1] * n2[1] + return [1, -1].map((side): Polygon => { + const a: [number, number] = [p[0] + side * n1[0] * offset, p[1] + side * n1[1] * offset] + const b: [number, number] = [p[0] + side * n2[0] * offset, p[1] + side * n2[1] * offset] + if (1 + dot < 0.5) return { outer: [p, a, b], holes: [] } + const scale = (side * offset) / (1 + dot) + const miter: [number, number] = [p[0] + (n1[0] + n2[0]) * scale, p[1] + (n1[1] + n2[1]) * scale] + return { outer: [p, a, miter, b], holes: [] } + }) +} + /** Surface-hole rings cannot contain islands; split annular cuts while preserving their centre. */ function openingRings(regions: Polygon[]): Ring[] { return regions.flatMap((region) => { diff --git a/packages/core/src/systems/stair/stair-construction.ts b/packages/core/src/systems/stair/stair-construction.ts index 51f6f03258..73f7c53bdd 100644 --- a/packages/core/src/systems/stair/stair-construction.ts +++ b/packages/core/src/systems/stair/stair-construction.ts @@ -1,10 +1,19 @@ -import type { StairConstruction, StairNode, StairSegmentNode } from '../../schema' +import type { StairNode, StairSegmentNode } from '../../schema' import { measureStairDetail, STAIR_DETAIL_SURFACE_BUDGET, stairSegmentDetailError, } from './stair-detail-budget' import { resolveStairArcDimensions } from './stair-layout' +import { + type ResolvedStairConstruction, + resolveStairStringerLayout, + type StairStructureHost, + stairLandingFrame, + stairStringerFlight, + stairStringerSlices, + stairStructureConstructionOf, +} from './stair-structure' import { resolveStairWinder } from './stair-winder' export type StairConstructionPiece = { @@ -22,11 +31,19 @@ export type StairConstructionPiece = { top: number } +/** + * The body a segment is drawn with: its own construction, its stair's, or the one the stair's + * structure draws (`stairStructureConstructionOf`) — null keeps the original body. + */ export function resolveStairConstruction( node: Pick, - parent?: Pick, -): StairConstruction | null { - return node.construction ?? parent?.construction ?? null + parent?: Pick & StairStructureHost, +): ResolvedStairConstruction | null { + return ( + node.construction ?? + parent?.construction ?? + (parent ? stairStructureConstructionOf(parent) : null) + ) } export function stairConstructionError( @@ -98,6 +115,14 @@ export function stairSegmentConstructionError( (construction.mode === 'center-stringer' && construction.stringerWidth > segment.width) ) return 'Stringers must fit within the flight width.' + const structure = construction.structure + if ( + structure && + structure.type !== 'concrete' && + !winder && + resolveStairStringerLayout(segment.width, structure).count < 2 + ) + return 'Stringers must fit within the flight width.' if ( winder && construction.mode === 'center-stringer' && @@ -264,6 +289,33 @@ export function resolveStraightStairConstruction( }) } } + // A structure's stringers, each sliced per step at its place across the flight (cut carriages + // notched under the treads, housed boards over the nosing line); a landing's frame under it. + const structure = construction.structure + if (structure && structure.type !== 'concrete' && !segment.winder) { + if (!landing) { + const flight = stairStringerFlight(segment, construction) + for (const band of resolveStairStringerLayout(segment.width, structure).bands) + for (const slice of stairStringerSlices(band, flight)) { + const runs = slice.profile.map((point) => point[0]) + pieces.push({ + role: 'body', + index: slice.index, + x0: band.x0, + x1: band.x1, + z0: Math.min(...runs), + z1: Math.max(...runs), + underside: slice.underside, + top: slice.top, + profile: slice.profile, + }) + } + } else { + const frame = stairLandingFrame(segment, treadThickness + finishThickness, absoluteHeight) + for (const part of [...frame.rims, ...frame.joists, ...frame.posts]) + box('body', 0, part.x0, part.x1, part.z0, part.z1, part.bottom, part.top) + } + } return pieces } @@ -476,20 +528,38 @@ export function resolveArcStairConstruction( } export function straightStairConstructionDetailCost( - segment: Pick, + segment: Pick & + Partial>, parent?: StairNode, ): number { const construction = resolveStairConstruction(segment, parent) const count = segment.segmentType === 'landing' ? 1 : segment.stepCount if (!construction) return count - if (segment.segmentType === 'landing') return 1 + Number(construction.finishThickness > 0) + // a structure's stringers add one slice per stringer per step, a landing its frame + const structure = construction.structure?.type === 'concrete' ? undefined : construction.structure + const width = segment.width ?? parent?.width ?? 1 + if (segment.segmentType === 'landing') { + const frame = structure + ? stairLandingFrame( + { width, length: segment.length ?? width }, + construction.treadThickness + construction.finishThickness, + Number.POSITIVE_INFINITY, + ) + : null + return ( + 1 + + Number(construction.finishThickness > 0) + + (frame ? frame.rims.length + frame.joists.length + frame.posts.length : 0) + ) + } const closed = construction.mode === 'solid' || construction.mode === 'waist' const parts = 1 + Number(closed && construction.nosing > 0) + Number(construction.finishThickness > 0) + Number(!closed && construction.closedRisers) + - (construction.mode === 'side-stringers' ? 2 : construction.mode === 'center-stringer' ? 1 : 0) + (construction.mode === 'side-stringers' ? 2 : construction.mode === 'center-stringer' ? 1 : 0) + + (structure ? resolveStairStringerLayout(width, structure).count : 0) return count * parts } diff --git a/packages/core/src/systems/stair/stair-sizing.ts b/packages/core/src/systems/stair/stair-sizing.ts index a08c453508..34203619da 100644 --- a/packages/core/src/systems/stair/stair-sizing.ts +++ b/packages/core/src/systems/stair/stair-sizing.ts @@ -12,6 +12,7 @@ import { import { resolveStairArcDimensions } from './stair-layout' import { resolveStairRailPaths } from './stair-rail-path' import { resolveStairTotalRise } from './stair-rise-query' +import { stairStructureDiagnostics } from './stair-structure' import { resolveStairWinder } from './stair-winder' export const DEFAULT_STAIR_DESIGN_TARGETS = StairDesignTargets.parse({}) @@ -241,6 +242,12 @@ export function measureStair(stair: StairNode, nodes: Record) { ) else if (headroom.obstructions.length) report('headroom-target', stair.id, 'Overhead surfaces leave less than the target headroom.') + // a stair with a structure owes the code's numbers too (IRC R311.7 / R312, DCA 6) + for (const issue of stairStructureDiagnostics(stair, measurements, { + totalRise, + headroom: headroom.status === 'unresolved' ? null : headroom.minimum, + })) + report(issue.code, issue.nodeId, issue.message, issue.severity) return { totalRise, targets, diff --git a/packages/core/src/systems/stair/stair-structure.test.ts b/packages/core/src/systems/stair/stair-structure.test.ts new file mode 100644 index 0000000000..068845a35b --- /dev/null +++ b/packages/core/src/systems/stair/stair-structure.test.ts @@ -0,0 +1,343 @@ +import { describe, expect, it } from 'bun:test' +import { + type AnyNode, + autoStairStringerCount, + inferStairStructure, + measureStair, + resolveStairConstruction, + resolveStairStringerLayout, + resolveStairStructure, + resolveStraightStairConstruction, + STAIR_CODE, + STAIR_STRINGER_STOCK, + StairNode, + StairSegmentNode, + StairStructure, + stairLandingFrame, + stairStringerCut, + stairStringerFlight, + stairStructureConstruction, +} from '../../index' + +const IN = 0.0254 + +/** A straight stair with one flight of `risers` × `riser` in over `going` in treads. */ +function flightStair( + risers: number, + riser: number, + going: number, + stair: Partial = {}, + width = 36 * IN, +) { + const flight = StairSegmentNode.parse({ + width, + height: risers * riser * IN, + length: risers * going * IN, + stepCount: risers, + }) + const node = StairNode.parse({ + width, + totalRise: risers * riser * IN, + stepCount: risers, + railingMode: 'both', + railingHeight: 36 * IN, + children: [flight.id], + ...stair, + }) + flight.parentId = node.id + const nodes: Record = { [node.id]: node, [flight.id]: flight } + return { stair: node, flight, nodes } +} + +describe('stair structure schema', () => { + it('defaults to cut 2x12 wood stringers at 16 in o.c. and leaves stairs without one alone', () => { + expect(StairStructure.parse({})).toEqual({ + type: 'wood-stringers', + stringerStock: '2x12', + stringerType: 'cut', + stringerSpacing: 16 * IN, + }) + const legacy = StairNode.parse({}) + expect(legacy.structure).toBeUndefined() + expect(resolveStairStructure(legacy)).toBeNull() + expect(resolveStairConstruction(StairSegmentNode.parse({}), legacy)).toBeNull() + }) + + it('makes its automatic choices from where the stair stands and how far it rises', () => { + const inside = resolveStairStructure(StairNode.parse({ structure: {} }))! + expect(inside).toMatchObject({ + treadStock: '2x12', + closedRisers: true, + treated: false, + nosing: 1 * IN, + exterior: false, + }) + // a short porch flight: open risers are fine up to a 30 in rise (R311.7.5.1) + const porch = resolveStairStructure( + StairNode.parse({ structure: {}, deckSlabId: 'slab_porch', totalRise: 21 * IN }), + )! + expect(porch).toMatchObject({ + treadStock: '2x6-pair', + closedRisers: false, + treated: true, + exterior: true, + }) + const deck = resolveStairStructure( + StairNode.parse({ structure: {}, destinationSurfaceId: 'deck', totalRise: 40 * IN }), + )! + expect(deck.closedRisers).toBe(true) + expect( + resolveStairStructure(StairNode.parse({ structure: { type: 'concrete' } }))!.nosing, + ).toBe(0) + // the hand-set choices win + expect( + resolveStairStructure( + StairNode.parse({ + structure: { treadStock: '5/4x6-pair', closedRisers: false, treated: true, nosing: 0.03 }, + }), + ), + ).toMatchObject({ treadStock: '5/4x6-pair', closedRisers: false, treated: true, nosing: 0.03 }) + }) + + it('infers a legacy stair as concrete when it is painted concrete, wood stringers otherwise', () => { + expect( + inferStairStructure(StairNode.parse({ materialPreset: 'library:concrete-raw' })).type, + ).toBe('concrete') + expect(inferStairStructure(StairNode.parse({ material: { preset: 'concrete' } })).type).toBe( + 'concrete', + ) + expect( + inferStairStructure(StairNode.parse({ materialPreset: 'library:wood-floorplank1' })).type, + ).toBe('wood-stringers') + expect( + inferStairStructure(StairNode.parse({ structure: { type: 'steel-stringers' } })).type, + ).toBe('steel-stringers') + }) +}) + +describe('stringer count and spacing', () => { + it('counts ceil(width / spacing) + 1 stringers, at least two and three from 36 in wide', () => { + expect(autoStairStringerCount(36 * IN, 16 * IN)).toBe(4) + expect(autoStairStringerCount(36 * IN, 18 * IN)).toBe(3) + expect(autoStairStringerCount(36 * IN, 24 * IN)).toBe(3) + expect(autoStairStringerCount(36 * IN, 12 * IN)).toBe(4) + expect(autoStairStringerCount(48 * IN, 16 * IN)).toBe(4) + expect(autoStairStringerCount(24 * IN, 24 * IN)).toBe(2) + expect(autoStairStringerCount(30 * IN, 24 * IN)).toBe(3) + expect(autoStairStringerCount(1, 16 * IN)).toBe(4) + }) + + it('lays the outer stringers flush with the sides and the rest evenly between, never past the spacing', () => { + const width = 1.0 + for (const spacing of [12, 16, 18, 24]) { + const structure = resolveStairStructure( + StairNode.parse({ structure: { stringerSpacing: spacing * IN } }), + )! + const layout = resolveStairStringerLayout(width, structure) + expect(layout.auto).toBe(true) + expect(layout.count).toBe(autoStairStringerCount(width, spacing * IN)) + expect(layout.spacing).toBeLessThanOrEqual(spacing * IN + 1e-9) + expect(layout.bands[0]!.x0).toBeCloseTo(-width / 2, 9) + expect(layout.bands.at(-1)!.x1).toBeCloseTo(width / 2, 9) + for (let i = 1; i < layout.bands.length; i++) + expect(layout.bands[i]!.x - layout.bands[i - 1]!.x).toBeCloseTo(layout.spacing, 9) + expect(layout.bands.every((band) => band.kind === 'cut' && !band.steel)).toBe(true) + expect(layout.thickness).toBeCloseTo(1.5 * IN, 9) + expect(layout.depth).toBeCloseTo(STAIR_STRINGER_STOCK['2x12'].depth, 9) + } + const set = resolveStairStructure( + StairNode.parse({ + structure: { stringerCount: 5, stringerType: 'housed', stringerStock: '2x10' }, + }), + )! + const housed = resolveStairStringerLayout(36 * IN, set) + expect(housed.auto).toBe(false) + expect(housed.count).toBe(5) + expect(housed.depth).toBeCloseTo(9.25 * IN, 9) + expect(housed.bands.map((band) => band.kind)).toEqual(['housed', 'cut', 'cut', 'cut', 'housed']) + const steel = resolveStairStructure( + StairNode.parse({ structure: { type: 'steel-stringers' } }), + )! + expect(resolveStairStringerLayout(1, steel).count).toBe(2) + expect(resolveStairStringerLayout(1.6, steel).count).toBe(3) + expect(resolveStairStringerLayout(1, steel).bands.map((band) => band.kind)).toEqual([ + 'housed', + 'housed', + ]) + // never more stringers than fit side by side + expect(resolveStairStringerLayout(3 * IN, set).count).toBe(2) + }) +}) + +describe('stringer geometry', () => { + // ten 7 in risers over 11 in treads, 2x12 stringers, two 2x6 treads, open risers + const structure = resolveStairStructure( + StairNode.parse({ structure: { treadStock: '2x6-pair', closedRisers: false } }), + )! + const construction = stairStructureConstruction(structure) + const flight = stairStringerFlight( + { stepCount: 10, height: 70 * IN, length: 110 * IN }, + construction, + ) + + it('notches a cut stringer for every tread: seats under the treads, a throat of D − r·cos θ, a level seat on the base and a plumb cut at the top', () => { + const cut = stairStringerCut({ kind: 'cut', depth: 11.25 * IN }, flight) + expect(cut.slices).toHaveLength(10) + const cos = 11 / Math.hypot(7, 11) + expect(cut.throat).toBeCloseTo((11.25 - 7 * cos) * IN, 9) + expect(cut.throat).toBeGreaterThan(STAIR_CODE.minThroat) + for (const [i, slice] of cut.slices.entries()) { + // each seat is the walking top less the 1-1/2 in tread + expect(slice.top).toBeCloseTo(((i + 1) * 7 - 1.5) * IN, 9) + expect(Math.min(...slice.profile.map((point) => point[1]))).toBeGreaterThanOrEqual(-1e-9) + } + // the first riser cut stands on the base and the board is cut level along it + expect(cut.seat![0]).toBeCloseTo(0, 9) + expect(cut.seat![1]).toBeGreaterThan(11 * IN) + expect(cut.plumb.z).toBeCloseTo(110 * IN, 9) + expect(cut.plumb.top).toBeCloseTo((70 - 1.5) * IN, 9) + // the cut length: the run along the slope plus the seat height over the top + const sin = 7 / Math.hypot(7, 11) + expect(cut.length).toBeCloseTo(110 * IN * cos + (70 - 1.5) * IN * sin, 9) + }) + + it('sets the riser cuts back behind riser boards and keeps the last seat inside the run', () => { + const closed = stairStringerFlight( + { stepCount: 10, height: 70 * IN, length: 110 * IN }, + stairStructureConstruction({ ...structure, closedRisers: true }), + ) + const cut = stairStringerCut({ kind: 'cut', depth: 11.25 * IN }, closed) + expect(Math.min(...cut.slices[0]!.profile.map((point) => point[0]))).toBeCloseTo(0.75 * IN, 9) + expect(Math.max(...cut.slices.at(-1)!.profile.map((point) => point[0]))).toBeCloseTo( + 110 * IN, + 9, + ) + }) + + it('stands a housed stringer over the nosing line, level at the base and the floor it reaches', () => { + const housed = stairStringerCut({ kind: 'housed', depth: 11.25 * IN }, flight) + const points = housed.slices.flatMap((slice) => slice.profile) + expect(housed.slices).toHaveLength(10) + expect(Math.min(...points.map((point) => point[1]))).toBeGreaterThanOrEqual(-1e-9) + expect(Math.max(...points.map((point) => point[1]))).toBeLessThanOrEqual(70 * IN + 1e-9) + // its top edge runs 2 in (square) over the nosing line: through each nosing at (i·11 − 1, (i+1)·7) + const slope = 7 / 11 + const reveal = 2 * Math.hypot(1, slope) + for (let i = 0; i < 8; i++) { + const z1 = (i + 1) * 11 + const nosingLine = slope * (z1 + 1) + 7 + expect(housed.slices[i]!.top).toBeCloseTo((nosingLine + reveal) * IN, 9) + } + // it runs an inch ahead of the bottom nosing before its plumb cut + expect(Math.min(...points.map((point) => point[0]))).toBeCloseTo(-2 * IN, 9) + expect(housed.throat).toBeCloseTo(11.25 * IN, 9) + }) +}) + +describe('the body a structure draws', () => { + it('draws treads, risers and a cut stringer per step at every stringer place for wood stringers', () => { + const { stair, flight } = flightStair(10, 7, 11, { + structure: StairStructure.parse({ closedRisers: true }), + }) + const pieces = resolveStraightStairConstruction(flight, 0, stair)! + const layout = resolveStairStringerLayout(flight.width, resolveStairStructure(stair)!) + expect(layout.count).toBe(4) + for (const band of layout.bands) { + const own = pieces.filter((piece) => piece.x0 === band.x0 && piece.x1 === band.x1) + expect(own).toHaveLength(10) + expect(own.every((piece) => piece.role === 'body')).toBe(true) + } + const treads = pieces.filter((piece) => piece.role === 'tread') + expect(treads).toHaveLength(10) + // the walking tops stay the finished heights; the nosing projects 1 in + expect(Math.max(...treads.map((piece) => piece.top))).toBeCloseTo(70 * IN, 9) + expect(Math.min(...treads.map((piece) => piece.z0))).toBeCloseTo(-1 * IN, 9) + // one riser board per step, in front of the riser cuts + const risers = pieces.filter( + (piece) => + piece.role === 'body' && piece.x0 === -flight.width / 2 && piece.x1 === flight.width / 2, + ) + expect(risers).toHaveLength(10) + }) + + it('pours concrete as one solid mass to the base with no stringers', () => { + const { stair, flight } = flightStair(4, 7, 11, { + structure: StairStructure.parse({ type: 'concrete' }), + }) + const construction = resolveStairConstruction(flight, stair)! + expect(construction.mode).toBe('solid') + expect(construction.nosing).toBe(0) + const pieces = resolveStraightStairConstruction(flight, 0, stair)! + expect(pieces).toHaveLength(4) + expect(pieces.every((piece) => piece.role === 'body')).toBe(true) + expect(pieces.every((piece) => piece.underside[1] === 0)).toBe(true) + }) + + it('keeps an explicit body over the structure and frames a wood landing', () => { + const { stair, flight } = flightStair(10, 7, 11, { + structure: StairStructure.parse({}), + construction: { mode: 'solid' } as StairNode['construction'], + }) + expect(resolveStairConstruction(flight, stair)!.structure).toBeUndefined() + const landing = StairSegmentNode.parse({ + segmentType: 'landing', + width: 1, + length: 1, + height: 0, + }) + const wood = StairNode.parse({ structure: {} }) + const pieces = resolveStraightStairConstruction(landing, 1.2, wood)! + const frame = stairLandingFrame({ width: 1, length: 1 }, 1.5 * IN, 1.2) + expect(frame.rims).toHaveLength(4) + expect(frame.posts).toHaveLength(4) + expect(frame.joists.length).toBeGreaterThan(0) + expect(pieces).toHaveLength(1 + frame.rims.length + frame.joists.length + frame.posts.length) + // a landing barely off the base sits on blocking, not posts + expect(stairLandingFrame({ width: 1, length: 1 }, 1.5 * IN, 0.2).posts).toHaveLength(0) + }) +}) + +describe('code checks', () => { + const codes = (stair: StairNode, nodes: Record) => + measureStair(stair, nodes).diagnostics.map((issue) => issue.code) + + it('passes a code-sized wood stair and reports nothing extra for a stair without a structure', () => { + const good = flightStair(14, 7.25, 10.5, { structure: StairStructure.parse({}) }) + expect(codes(good.stair, good.nodes).filter((code) => /^(code|stringer)-/.test(code))).toEqual( + [], + ) + const legacy = flightStair(4, 8, 9, { railingMode: 'none' }) + expect( + codes(legacy.stair, legacy.nodes).filter((code) => /^(code|stringer)-/.test(code)), + ).toEqual([]) + }) + + it('flags risers, treads, nosings, the handrail, the guard, open risers and DCA 6 stringer limits', () => { + const steep = flightStair(5, 8, 9, { + railingMode: 'none', + structure: StairStructure.parse({ closedRisers: false, nosing: 0, stringerCount: 2 }), + }) + const found = codes(steep.stair, steep.nodes) + for (const code of [ + 'code-riser', + 'code-tread', + 'code-nosing', + 'code-handrail', + 'code-guard', + 'code-open-risers', + 'stringer-spacing', + ]) + expect(found).toContain(code) + // a deck stair: a long cut-stringer span and a thin 2x10 throat + const long = flightStair(10, 7.75, 10, { + deckSlabId: 'slab_deck', + structure: StairStructure.parse({ stringerStock: '2x10' }), + }) + const longCodes = codes(long.stair, long.nodes) + expect(longCodes).toContain('stringer-span') + expect(longCodes).toContain('stringer-throat') + // a narrow stair + const narrow = flightStair(4, 7, 11, { structure: StairStructure.parse({}) }, 30 * IN) + expect(codes(narrow.stair, narrow.nodes)).toContain('code-width') + }) +}) diff --git a/packages/core/src/systems/stair/stair-structure.ts b/packages/core/src/systems/stair/stair-structure.ts new file mode 100644 index 0000000000..86bb30798a --- /dev/null +++ b/packages/core/src/systems/stair/stair-structure.ts @@ -0,0 +1,763 @@ +import type { StairConstruction as StairConstructionType, StairNode } from '../../schema' +import { StairConstruction } from '../../schema/nodes/stair-construction' +import { + type StairStringerStock, + type StairStringerType, + StairStructure, + type StairStructureType, + type StairTreadStock, +} from '../../schema/nodes/stair-structure' + +/** + * A stair's structure — how it is really built, apart from the body it is drawn with — resolved + * to numbers: the stringer layout across each flight, the cut profile of every stringer, the + * treads and risers they carry, the construction the viewer draws for it, and the code checks the + * panel reports. Shared by the viewer (the body), the panel (the counts) and Bones (the members), + * so all three put a stringer in the same place. + */ + +const IN = 0.0254 +const EPS = 1e-9 + +/** + * The stair numbers of the IRC (R311.7 stairways, R312 guards) and AWC DCA 6 (deck stairs) the + * structure is checked against, in metres. + */ +export const STAIR_CODE = { + /** R311.7.5.1: risers 7-3/4 in at most. */ + maxRiser: 7.75 * IN, + /** R311.7.5.2: treads 10 in at least. */ + minTread: 10 * IN, + /** R311.7.5.3: a 3/4 in to 1-1/4 in nosing, required where the tread is under 11 in. */ + minNosing: 0.75 * IN, + maxNosing: 1.25 * IN, + nosingWaivedTread: 11 * IN, + /** R311.7.1: 36 in wide. */ + minWidth: 36 * IN, + /** R311.7.2: 6 ft 8 in of headroom. */ + minHeadroom: 80 * IN, + /** R311.7.3: a flight rises 151 in at most between floors or landings. */ + maxFlightRise: 151 * IN, + /** R311.7.6: landings at least 36 in in the direction of travel. */ + minLandingDepth: 36 * IN, + /** R311.7.8: a flight of four or more risers carries a handrail, 34–38 in over the nosings. */ + handrailRisers: 4, + minHandrailHeight: 34 * IN, + maxHandrailHeight: 38 * IN, + /** R312.1.1: a guard where the open side drops more than 30 in. */ + guardDrop: 30 * IN, + /** R311.7.5.1: open risers must stop a 4 in sphere once the stair rises more than 30 in. */ + openRiserSphere: 4 * IN, + openRiserFreeRise: 30 * IN, + /** DCA 6: cut stringers at most 18 in o.c. and 6 ft between supports, with a 5 in throat. */ + cutStringerMaxSpacing: 18 * IN, + cutStringerMaxSpan: 72 * IN, + minThroat: 5 * IN, +} as const + +/** Wood stringer stock, dressed (m). */ +export const STAIR_STRINGER_STOCK: Record< + StairStringerStock, + { thickness: number; depth: number } +> = { + '2x12': { thickness: 1.5 * IN, depth: 11.25 * IN }, + '2x10': { thickness: 1.5 * IN, depth: 9.25 * IN }, +} + +/** The steel stringer: an MC12x10.6 channel, 12 in deep with a 1-1/2 in flange. */ +export const STAIR_STEEL_STRINGER = { name: 'MC12x10.6', thickness: 1.5 * IN, depth: 12 * IN } + +/** Steel channels stand at the sides; a third goes down the middle past this width. */ +const STEEL_STRINGER_MAX_SPACING = 48 * IN + +/** + * Tread stock: the boards laid front to back across one tread (`boards` of `boardWidth`, `gap` + * apart; a sheet fills the tread), their thickness, and the finish laid over them. + */ +export const STAIR_TREAD_STOCK: Record< + StairTreadStock, + { boards: number; boardWidth: number; thickness: number; gap: number; finish: number } +> = { + '2x6-pair': { boards: 2, boardWidth: 5.5 * IN, thickness: 1.5 * IN, gap: 0.25 * IN, finish: 0 }, + '5/4x6-pair': { boards: 2, boardWidth: 5.5 * IN, thickness: 1 * IN, gap: 0.25 * IN, finish: 0 }, + '2x12': { boards: 1, boardWidth: 11.25 * IN, thickness: 1.5 * IN, gap: 0, finish: 0 }, + 'plywood-finish': { + boards: 1, + boardWidth: Number.POSITIVE_INFINITY, + thickness: 0.75 * IN, + gap: 0, + finish: 0.75 * IN, + }, +} + +/** Riser boards are 1x stock, 3/4 in. */ +export const STAIR_RISER_THICKNESS = 0.75 * IN +/** A wood or steel tread's nosing when none is set: 1 in, inside R311.7.5.3's 3/4–1-1/4 in. */ +export const DEFAULT_STAIR_NOSING = 1 * IN +/** A housed stringer's top edge stands this far over the nosing line, square to it. */ +export const HOUSED_STRINGER_REVEAL = 2 * IN +/** A housed stringer runs this far ahead of the bottom nosing before its plumb cut. */ +const HOUSED_STRINGER_LEAD = 1 * IN +/** The housing (dado) a housed tread or riser sits in. */ +export const STAIR_HOUSING_DEPTH = 0.5 * IN +/** A concrete flight's nosing lip, when it has one. */ +const CONCRETE_NOSING_LIP = 1.5 * IN + +export type ResolvedStairStructure = { + type: StairStructureType + stringerStock: StairStringerStock + stringerType: StairStringerType + /** The most the stringers may stand apart on centre (m). */ + stringerSpacing: number + /** The count set by hand; undefined = automatic. */ + stringerCount: number | undefined + treadStock: StairTreadStock + closedRisers: boolean + treated: boolean + nosing: number + exterior: boolean +} + +/** What a structure is read off: the stair's own fields (any of them may be absent). */ +export type StairStructureHost = Partial< + Pick +> + +/** An exterior stair: one fitted to a deck, porch or patio edge. */ +export function stairIsExterior( + stair: Pick, +): boolean { + return Boolean(stair.deckSlabId || stair.destinationSurfaceId) +} + +/** + * The stair's structure with every automatic choice made: two 2x6 treads and treated stock on an + * exterior stair, a 2x12 tread inside; risers closed once the stair rises more than 30 in; a 1 in + * nosing on wood and steel treads. `context` overrides what the stair alone cannot tell (Bones + * knows a stair that lands on grade is exterior). Null without a structure. + */ +export function resolveStairStructure( + stair: StairStructureHost, + context: { exterior?: boolean; totalRise?: number } = {}, +): ResolvedStairStructure | null { + if (!stair.structure) return null + const parsed = StairStructure.safeParse(stair.structure) + if (!parsed.success) return null + const structure = parsed.data + const exterior = context.exterior ?? stairIsExterior(stair) + // a stair that follows its level climbs a whole storey + const rise = context.totalRise ?? stair.totalRise ?? Number.POSITIVE_INFINITY + return { + type: structure.type, + stringerStock: structure.stringerStock, + stringerType: structure.stringerType, + stringerSpacing: structure.stringerSpacing, + stringerCount: structure.stringerCount, + treadStock: structure.treadStock ?? (exterior ? '2x6-pair' : '2x12'), + closedRisers: structure.closedRisers ?? rise > STAIR_CODE.openRiserFreeRise + EPS, + treated: structure.treated ?? exterior, + nosing: structure.nosing ?? (structure.type === 'concrete' ? 0 : DEFAULT_STAIR_NOSING), + exterior, + } +} + +/** + * The structure a stair is framed with: its own, or for a stair drawn before structures existed + * the one its body reads as — a concrete flight when it is painted concrete, wood stringers + * otherwise. + */ +export function inferStairStructure( + stair: Pick, +): StairStructure { + if (stair.structure) { + const parsed = StairStructure.safeParse(stair.structure) + if (parsed.success) return parsed.data + } + const refs = [stair.materialPreset, stair.material?.preset, ...Object.values(stair.slots ?? {})] + const concrete = refs.some((ref) => typeof ref === 'string' && /concrete/i.test(ref)) + return StairStructure.parse({ type: concrete ? 'concrete' : 'wood-stringers' }) +} + +/** + * The automatic stringer count: enough that no bay between neighbours exceeds `maxSpacing` — + * ceil(width / spacing) + 1 — and never fewer than two, or three on a stair 36 in wide or more. + */ +export function autoStairStringerCount(width: number, maxSpacing: number): number { + const floor = width >= STAIR_CODE.minWidth - EPS ? 3 : 2 + if (!(width > 0 && maxSpacing > 0)) return floor + return Math.max(Math.ceil(width / maxSpacing - EPS) + 1, floor) +} + +export type StairStringerBand = { + index: number + /** Centre across the flight, in the flight's frame (m). */ + x: number + x0: number + x1: number + thickness: number + depth: number + /** 'cut' — notched under the treads; 'housed' — a solid board over the nosing line. */ + kind: 'cut' | 'housed' + steel: boolean +} + +export type StairStringerLayout = { + count: number + /** True when the count follows the width and the spacing. */ + auto: boolean + /** Centre to centre between neighbouring stringers (m). */ + spacing: number + thickness: number + depth: number + steel: boolean + bands: StairStringerBand[] +} + +/** + * The stringers across a flight of `width`: the outer two flush with its sides and the rest evenly + * between them, as many as the structure asks (or as its spacing needs). Housed wood stringers + * and steel channels are the outer two; any between them are cut carriages. + */ +export function resolveStairStringerLayout( + width: number, + structure: Pick< + ResolvedStairStructure, + 'type' | 'stringerStock' | 'stringerType' | 'stringerSpacing' | 'stringerCount' + >, +): StairStringerLayout { + const steel = structure.type === 'steel-stringers' + const stock = steel ? STAIR_STEEL_STRINGER : STAIR_STRINGER_STOCK[structure.stringerStock] + const auto = structure.stringerCount === undefined + const wanted = + structure.stringerCount ?? + (steel + ? Math.max(2, Math.ceil(width / STEEL_STRINGER_MAX_SPACING - EPS) + 1) + : autoStairStringerCount(width, structure.stringerSpacing)) + const count = Math.max(0, Math.min(wanted, Math.floor((width + EPS) / stock.thickness))) + const spacing = count > 1 ? (width - stock.thickness) / (count - 1) : 0 + const housedSides = steel || structure.stringerType === 'housed' + const bands: StairStringerBand[] = [] + for (let index = 0; index < count; index++) { + const x = count > 1 ? -width / 2 + stock.thickness / 2 + index * spacing : 0 + bands.push({ + index, + x, + x0: x - stock.thickness / 2, + x1: x + stock.thickness / 2, + thickness: stock.thickness, + depth: stock.depth, + kind: housedSides && (index === 0 || index === count - 1) ? 'housed' : 'cut', + steel, + }) + } + return { + count, + auto, + spacing, + thickness: stock.thickness, + depth: stock.depth, + steel, + bands, + } +} + +/** A construction a structure draws, carrying the structure so the stringers can be laid out. */ +export type ResolvedStairConstruction = StairConstructionType & { + structure?: ResolvedStairStructure +} + +/** + * The body a structure draws: a solid mass for concrete (its nosing a lip over each riser); open + * treads of the tread stock — closed risers when it has them — over the stringers otherwise. + */ +export function stairStructureConstruction( + structure: ResolvedStairStructure, +): ResolvedStairConstruction { + if (structure.type === 'concrete') + return { + ...StairConstruction.parse({ + mode: 'solid', + nosing: structure.nosing, + treadThickness: CONCRETE_NOSING_LIP, + finishThickness: 0, + }), + structure, + } + const tread = STAIR_TREAD_STOCK[structure.treadStock] + const stringer = + structure.type === 'steel-stringers' + ? STAIR_STEEL_STRINGER + : STAIR_STRINGER_STOCK[structure.stringerStock] + return { + ...StairConstruction.parse({ + mode: 'open', + treadThickness: tread.thickness, + finishThickness: tread.finish, + nosing: structure.nosing, + riserThickness: STAIR_RISER_THICKNESS, + closedRisers: structure.closedRisers, + stringerWidth: stringer.thickness, + stringerDepth: stringer.depth, + }), + structure, + } +} + +const constructionMemo = new WeakMap() + +/** + * The construction a straight stair's structure draws, or null (no structure, or a curved or + * spiral stair, which keeps its own body). Memoised on the structure object. + */ +export function stairStructureConstructionOf( + stair: StairStructureHost, +): ResolvedStairConstruction | null { + if (!stair.structure || (stair.stairType ?? 'straight') !== 'straight') return null + const key = `${stair.totalRise ?? ''}|${stairIsExterior(stair)}` + const cached = constructionMemo.get(stair.structure) + if (cached?.key === key) return cached.value + const structure = resolveStairStructure(stair) + if (!structure) return null + const value = stairStructureConstruction(structure) + constructionMemo.set(stair.structure, { key, value }) + return value +} + +/** A landing's joists and rims: 2x8, 16 in o.c. — a stair landing spans a few feet at most. */ +export const STAIR_LANDING_JOIST = { size: '2x8', thickness: 1.5 * IN, depth: 7.25 * IN } as const +export const STAIR_LANDING_JOIST_SPACING = 16 * IN +/** A landing's corner posts: 4x4. */ +export const STAIR_LANDING_POST = 3.5 * IN +/** A landing closer to the base than this over its frame sits on blocking, not posts. */ +const STAIR_LANDING_MIN_POST = 6 * IN + +/** A box in a segment's frame: across `x0..x1`, along the run `z0..z1`, `bottom..top` high. */ +export type StairFrameBox = { + x0: number + x1: number + z0: number + z1: number + bottom: number + top: number +} + +/** + * A wood landing's frame, in the landing's frame (its top at 0): rims all round under the decking + * (`drop` thick), joists between them along the run at 16 in o.c., and a 4x4 post under each + * corner down to the stair's base `baseDepth` below — none when the frame sits within 6 in of it. + */ +export function stairLandingFrame( + landing: { width: number; length: number }, + drop: number, + baseDepth: number, +): { rims: StairFrameBox[]; joists: StairFrameBox[]; posts: StairFrameBox[] } { + const { width, length } = landing + const t = STAIR_LANDING_JOIST.thickness + const top = -drop + const bottom = top - STAIR_LANDING_JOIST.depth + const x0 = -width / 2 + const x1 = width / 2 + const rims: StairFrameBox[] = [] + const joists: StairFrameBox[] = [] + const posts: StairFrameBox[] = [] + if (!(width > 2 * t && length > 2 * t)) return { rims, joists, posts } + rims.push( + { x0, x1, z0: 0, z1: t, bottom, top }, + { x0, x1, z0: length - t, z1: length, bottom, top }, + { x0, x1: x0 + t, z0: t, z1: length - t, bottom, top }, + { x0: x1 - t, x1, z0: t, z1: length - t, bottom, top }, + ) + const bays = Math.max(1, Math.ceil((width - t) / STAIR_LANDING_JOIST_SPACING - EPS)) + for (let i = 1; i < bays; i++) { + const x = x0 + t / 2 + (i * (width - t)) / bays + joists.push({ x0: x - t / 2, x1: x + t / 2, z0: t, z1: length - t, bottom, top }) + } + if (baseDepth + bottom >= STAIR_LANDING_MIN_POST - EPS) { + const p = STAIR_LANDING_POST + for (const [px, pz] of [ + [x0, 0], + [x1 - p, 0], + [x0, length - p], + [x1 - p, length - p], + ] as const) + posts.push({ x0: px, x1: px + p, z0: pz, z1: pz + p, bottom: -baseDepth, top: bottom }) + } + return { rims, joists, posts } +} + +/** One flight as its stringers see it. */ +export type StairStringerFlight = { + stepCount: number + height: number + length: number + /** The structural tread sits this far under each walking top: the tread and its finish. */ + treadDrop: number + /** Each riser cut sits this far behind its riser face: the riser board, when there is one. */ + riserSetback: number + nosing: number + /** False when the flight starts on another flight, not on a floor, pad or landing. */ + bearsOnBase?: boolean +} + +/** The flight a segment and its construction make for the stringers. */ +export function stairStringerFlight( + segment: { stepCount: number; height: number; length: number }, + construction: Pick< + StairConstructionType, + 'treadThickness' | 'finishThickness' | 'riserThickness' | 'closedRisers' | 'nosing' + >, +): StairStringerFlight { + return { + stepCount: Math.max(1, Math.round(segment.stepCount)), + height: segment.height, + length: segment.length, + treadDrop: construction.treadThickness + construction.finishThickness, + riserSetback: construction.closedRisers ? construction.riserThickness : 0, + nosing: construction.nosing, + } +} + +export type StairStringerSlice = { + index: number + /** Closed (run, height) outline in the flight's frame; always convex. */ + profile: [number, number][] + /** The board's underside: height = slope · run + intercept. */ + underside: [number, number] + top: number +} + +/** Keeps the part of a convex polygon on one side of a level line (Sutherland–Hodgman). */ +function clipLevel( + polygon: [number, number][], + level: number, + keepAbove: boolean, +): [number, number][] { + const inside = (p: [number, number]) => (keepAbove ? p[1] >= level - EPS : p[1] <= level + EPS) + const out: [number, number][] = [] + for (let i = 0; i < polygon.length; i++) { + const a = polygon[i]! + const b = polygon[(i + 1) % polygon.length]! + const aIn = inside(a) + const bIn = inside(b) + if (aIn) out.push(a) + if (aIn !== bIn) { + const t = (level - a[1]) / (b[1] - a[1]) + out.push([a[0] + (b[0] - a[0]) * t, level]) + } + } + const same = (a: [number, number], b: [number, number]) => + Math.hypot(a[0] - b[0], a[1] - b[1]) <= EPS + const kept = out.filter((p, i) => i === 0 || !same(p, out[i - 1]!)) + if (kept.length > 1 && same(kept[0]!, kept.at(-1)!)) kept.pop() + return kept +} + +/** + * The uncut board's edges, as heights over the run (height = slope · run + intercept): a cut + * stringer's top edge runs through the outer corners of its notches, a housed one stands + * `HOUSED_STRINGER_REVEAL` over the nosing line; the bottom edge is the board's depth under it. + */ +export function stairStringerEdges( + band: Pick, + flight: StairStringerFlight, +): { slope: number; top: number; bottom: number } { + const count = Math.max(1, Math.round(flight.stepCount)) + const riser = flight.height / count + const slope = flight.length > 0 ? flight.height / flight.length : 0 + const vertical = band.depth * Math.hypot(1, slope) + const top = + band.kind === 'cut' + ? riser - flight.treadDrop - slope * flight.riserSetback + : slope * flight.nosing + riser + HOUSED_STRINGER_REVEAL * Math.hypot(1, slope) + return { slope, top, bottom: top - vertical } +} + +/** + * One stringer down a flight, sliced per step (each slice convex, so the viewer extrudes it and + * Bones boxes it). A cut stringer is notched for every tread and riser: each slice tops out flat + * at its tread seat — the walking top less the tread — its riser cut set back behind the riser + * board, its underside the board's bottom edge. A housed stringer is the solid board standing + * `HOUSED_STRINGER_REVEAL` over the nosing line. Both are cut level where they bear on the base, + * plumb at the top of the run, and a housed one level at the floor it reaches. + */ +export function stairStringerSlices( + band: Pick, + flight: StairStringerFlight, +): StairStringerSlice[] { + const count = Math.max(1, Math.round(flight.stepCount)) + const { height, length } = flight + if (!(height > 0 && length > 0 && band.depth > 0)) return [] + const going = length / count + const riser = height / count + const { slope, top, bottom } = stairStringerEdges(band, flight) + const slices: StairStringerSlice[] = [] + const finish = (index: number, polygon: [number, number][], underside: [number, number]) => { + let profile = polygon + if (flight.bearsOnBase !== false) profile = clipLevel(profile, 0, true) + if (band.kind === 'housed') profile = clipLevel(profile, height, false) + if (profile.length < 3) return + slices.push({ + index, + profile, + underside, + top: Math.max(...profile.map((point) => point[1])), + }) + } + if (band.kind === 'cut') { + const setback = flight.riserSetback + for (let index = 0; index < count; index++) { + const z0 = setback + index * going + const z1 = Math.min(length, setback + (index + 1) * going) + const seat = (index + 1) * riser - flight.treadDrop + if (!(z1 - z0 > EPS && seat > EPS)) continue + finish( + index, + [ + [z0, seat], + [z1, seat], + [z1, slope * z1 + bottom], + [z0, slope * z0 + bottom], + ], + [slope, bottom], + ) + } + return slices + } + for (let index = 0; index < count; index++) { + const z0 = index === 0 ? -(flight.nosing + HOUSED_STRINGER_LEAD) : index * going + const z1 = index === count - 1 ? length : (index + 1) * going + finish( + index, + [ + [z0, slope * z0 + top], + [z1, slope * z1 + top], + [z1, slope * z1 + bottom], + [z0, slope * z0 + bottom], + ], + [slope, bottom], + ) + } + return slices +} + +export type StairStringerCut = { + slices: StairStringerSlice[] + /** Rise over run of the flight. */ + slope: number + /** The board's cut length: the stringer's extent along the slope. */ + length: number + /** Wood left under the notches, square to the slope (DCA 6 asks 5 in); the board depth when housed. */ + throat: number + /** The run the stringer bears on its base along (its level cut), or null when it bears on none. */ + seat: [number, number] | null + /** The plumb cut at the top of the run: its run and the heights it spans. */ + plumb: { z: number; top: number; bottom: number } + /** The uncut board's top and bottom edges (intercepts: height = slope · run + intercept). */ + edge: { top: number; bottom: number } +} + +/** A stringer's cut: its slices and the numbers the takeoff and the code checks read off them. */ +export function stairStringerCut( + band: Pick, + flight: StairStringerFlight, +): StairStringerCut { + const slices = stairStringerSlices(band, flight) + const { slope, top, bottom } = stairStringerEdges(band, flight) + const norm = Math.hypot(1, slope) + const along = (point: [number, number]) => (point[0] + point[1] * slope) / norm + const points = slices.flatMap((slice) => slice.profile) + const extents = points.map(along) + const riser = flight.height / Math.max(1, Math.round(flight.stepCount)) + const onBase = points.filter((point) => Math.abs(point[1]) < 1e-7).map((point) => point[0]) + const atTop = points + .filter((point) => Math.abs(point[0] - flight.length) < 1e-7) + .map((point) => point[1]) + return { + slices, + slope, + length: extents.length ? Math.max(...extents) - Math.min(...extents) : 0, + throat: band.kind === 'cut' ? band.depth - riser / norm : band.depth, + seat: onBase.length > 1 ? [Math.min(...onBase), Math.max(...onBase)] : null, + plumb: { + z: flight.length, + top: atTop.length ? Math.max(...atTop) : 0, + bottom: atTop.length ? Math.min(...atTop) : 0, + }, + edge: { top, bottom }, + } +} + +/** Inches as a builder reads them, to the nearest eighth: 7.75 in → `7-3/4 in`. */ +export function formatStairInches(metres: number): string { + const eighths = Math.round(metres / IN / 0.125) + const whole = Math.floor(eighths / 8) + let numerator = eighths - whole * 8 + let denominator = 8 + while (numerator > 0 && numerator % 2 === 0) { + numerator /= 2 + denominator /= 2 + } + if (numerator === 0) return `${whole} in` + return whole > 0 ? `${whole}-${numerator}/${denominator} in` : `${numerator}/${denominator} in` +} + +export type StairStructureIssue = { + code: string + severity: 'error' | 'warning' + nodeId: string + message: string +} + +/** + * The IRC R311.7 / R312 and DCA 6 checks a stair with a structure owes: risers, treads, nosings, + * width, flight rise, headroom, the handrail on four or more risers, the guard past a 30 in drop, + * open risers on a tall stair, and for cut wood stringers their spacing, throat and span. Stairs + * without a structure keep the design-target checks alone. + */ +export function stairStructureDiagnostics( + stair: StairNode, + flights: readonly { + nodeId: string + count: number + rise: number + length: number + width: number + riserHeight: number | null + going: number | null + }[], + options: { totalRise: number; headroom: number | null }, +): StairStructureIssue[] { + if ((stair.stairType ?? 'straight') !== 'straight') return [] + const structure = resolveStairStructure(stair) + if (!structure) return [] + const issues: StairStructureIssue[] = [] + const warn = (code: string, nodeId: string, message: string) => + issues.push({ code, severity: 'warning', nodeId, message }) + const construction = stairStructureConstruction(structure) + const railings = (stair.railingMode ?? 'none') !== 'none' + const handrail = stair.handrail && stair.handrail.mode !== 'none' + for (const flight of flights) { + const { riserHeight: riser, going } = flight + if (riser === null || going === null) continue + if (riser > STAIR_CODE.maxRiser + EPS) + warn( + 'code-riser', + flight.nodeId, + `Risers of ${formatStairInches(riser)} exceed the 7-3/4 in maximum (IRC R311.7.5.1).`, + ) + if (going < STAIR_CODE.minTread - EPS) + warn( + 'code-tread', + flight.nodeId, + `Treads of ${formatStairInches(going)} fall short of the 10 in minimum (IRC R311.7.5.2).`, + ) + if (structure.type !== 'concrete' || structure.nosing > 0) { + if ( + going < STAIR_CODE.nosingWaivedTread - EPS && + structure.nosing < STAIR_CODE.minNosing - EPS + ) + warn( + 'code-nosing', + flight.nodeId, + 'Treads under 11 in need a 3/4 in to 1-1/4 in nosing (IRC R311.7.5.3).', + ) + else if (structure.nosing > STAIR_CODE.maxNosing + EPS) + warn( + 'code-nosing', + flight.nodeId, + `A ${formatStairInches(structure.nosing)} nosing projects past the 1-1/4 in IRC R311.7.5.3 allows.`, + ) + } + if (flight.width < STAIR_CODE.minWidth - EPS) + warn( + 'code-width', + flight.nodeId, + `The flight is ${formatStairInches(flight.width)} wide; IRC R311.7.1 asks 36 in.`, + ) + if (flight.rise > STAIR_CODE.maxFlightRise + EPS) + warn( + 'code-flight-rise', + flight.nodeId, + `A flight rises ${formatStairInches(flight.rise)}; IRC R311.7.3 allows 151 in between floors or landings. Add a landing.`, + ) + if (flight.count >= STAIR_CODE.handrailRisers && !railings && !handrail) + warn( + 'code-handrail', + flight.nodeId, + `A flight of ${flight.count} risers needs a handrail 34 in to 38 in over the nosings (IRC R311.7.8).`, + ) + if (structure.type !== 'concrete') { + const layout = resolveStairStringerLayout(flight.width, structure) + const carriages = layout.bands.some((band) => band.kind === 'cut') + if (!layout.steel && carriages) { + if (layout.spacing > STAIR_CODE.cutStringerMaxSpacing + EPS) + warn( + 'stringer-spacing', + flight.nodeId, + `Stringers stand ${formatStairInches(layout.spacing)} on centre; DCA 6 allows cut stringers 18 in at most. Add a stringer.`, + ) + const cut = stairStringerCut( + { kind: 'cut', depth: layout.depth }, + stairStringerFlight( + { stepCount: flight.count, height: flight.rise, length: flight.length }, + construction, + ), + ) + if (cut.throat < STAIR_CODE.minThroat - EPS) + warn( + 'stringer-throat', + flight.nodeId, + `The cut stringers keep ${formatStairInches(cut.throat)} of wood under the notches; DCA 6 asks 5 in. Use 2x12 stock or a shallower riser.`, + ) + // DCA 6's span limit is the deck stair's, open below; inside, the walls alongside carry them + if (structure.exterior && flight.length > STAIR_CODE.cutStringerMaxSpan + EPS) + warn( + 'stringer-span', + flight.nodeId, + `The cut stringers span ${formatStairInches(flight.length)} level; DCA 6 limits them to 6 ft between supports. Support them at mid-span on a beam and posts.`, + ) + } + } + } + if (railings && !handrail && Number.isFinite(stair.railingHeight)) { + const height = stair.railingHeight + if (height < STAIR_CODE.minHandrailHeight - EPS || height > STAIR_CODE.maxHandrailHeight + EPS) + warn( + 'code-handrail-height', + stair.id, + `The railing stands ${formatStairInches(height)} over the nosings; a handrail belongs 34 in to 38 in (IRC R311.7.8.1).`, + ) + } + if (options.totalRise > STAIR_CODE.guardDrop + EPS && !railings) + warn( + 'code-guard', + stair.id, + `The stair rises ${formatStairInches(options.totalRise)}; an open side more than 30 in above the floor needs a guard (IRC R312.1.1) unless a wall encloses it.`, + ) + if ( + structure.type !== 'concrete' && + !structure.closedRisers && + options.totalRise > STAIR_CODE.openRiserFreeRise + EPS + ) { + const opening = Math.max( + 0, + ...flights.map( + (flight) => + (flight.riserHeight ?? 0) - construction.treadThickness - construction.finishThickness, + ), + ) + if (opening >= STAIR_CODE.openRiserSphere - EPS) + warn( + 'code-open-risers', + stair.id, + `Open risers leave ${formatStairInches(opening)} between treads; past a 30 in rise they must stop a 4 in sphere (IRC R311.7.5.1). Close them with riser boards.`, + ) + } + if (options.headroom !== null && options.headroom < STAIR_CODE.minHeadroom - EPS) + warn( + 'code-headroom', + stair.id, + `Headroom of ${formatStairInches(options.headroom)} is under the 6 ft 8 in minimum (IRC R311.7.2).`, + ) + return issues +} diff --git a/packages/core/src/systems/wall/wall-assembly.test.ts b/packages/core/src/systems/wall/wall-assembly.test.ts index ea13f3c97f..47c210ac0b 100644 --- a/packages/core/src/systems/wall/wall-assembly.test.ts +++ b/packages/core/src/systems/wall/wall-assembly.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from 'bun:test' import { FOUNDATION_CONSTRUCTIONS } from '../../lib/foundation-construction' -import type { WallNode } from '../../schema' +import { Assembly, type WallNode } from '../../schema' import type { WallAssembly } from '../../schema/nodes/wall' import { assemblyThickness, @@ -19,6 +19,7 @@ import { wallAssemblyExposedStructure, wallAssemblyFromLegacy, wallAssemblyPatch, + wallAssemblyToLegacy, wallLayerBoundaryOffsets, } from './wall-assembly' import { calculateLevelMiters } from './wall-mitering' @@ -109,6 +110,20 @@ describe('assemblyThickness', () => { expect(patch.thickness).toBeCloseTo(assemblyThickness(stack), 12) expect(patch.assembly).toBe(stack) }) + + test("the core's stud spacing rides the inspector's view both ways and adds no thickness", () => { + const spaced = { ...EXT_2X6, framing: { ...EXT_2X6.framing, spacing: 24 * IN } } + const stack = f2(spaced) + expect(stack.layers.find((layer) => layer.core)?.spacing).toBe(24 * IN) + expect(wallAssemblyToLegacy(stack)?.framing).toEqual(spaced.framing) + expect(assemblyThickness(stack)).toBeCloseTo(assemblyThickness(f2(EXT_2X6)), 12) + expect(Assembly.safeParse(stack).success).toBe(true) + const misplaced = { + ...stack, + layers: stack.layers.map((layer) => (layer.core ? layer : { ...layer, spacing: 16 * IN })), + } + expect(Assembly.safeParse(misplaced).success).toBe(false) + }) }) // --------------------------------------------------------------------------- diff --git a/packages/core/src/systems/wall/wall-assembly.ts b/packages/core/src/systems/wall/wall-assembly.ts index 11a75e7cc1..b35cc95647 100644 --- a/packages/core/src/systems/wall/wall-assembly.ts +++ b/packages/core/src/systems/wall/wall-assembly.ts @@ -44,9 +44,15 @@ * faces and no cladding or sheathing is drawn (see `resolveWallAssembly`). */ -import type { WallNode } from '../../schema' +import { + getLibraryMaterialIdFromRef, + getMaterialsForCategory, + type MaterialCatalogItem, +} from '../../material-library' +import type { AnyNode, WallNode } from '../../schema' import type { Assembly, AssemblyLayer } from '../../schema/assembly' -import { WallAssembly } from '../../schema/nodes/wall' +import { DEFAULT_INTERIOR_WALL_PAINT, WallAssembly, type WallFace } from '../../schema/nodes/wall' +import { WallTypeDefinition } from '../../schema/wall-type' import { DEFAULT_WALL_THICKNESS } from './wall-footprint' import { type Point2D, pointToKey, type WallMiterData } from './wall-mitering' @@ -74,6 +80,19 @@ export const GYPSUM_SHEATHING = inches(0.5) export const STUD_2X4 = inches(3.5) /** 2x6 stud, actual 5-1/2 in. Same source as STUD_2X4. */ export const STUD_2X6 = inches(5.5) +/** 2x8 stud, actual 7-1/4 in: the PS 20 dressed size the IRC lumber tables assume. */ +export const STUD_2X8 = inches(7.25) +/** + * Steel stud webs, 3-1/2, 5-1/2 and 8 in: members 350S162, 550S162 and 800S162, whose designation + * leads with the web depth in hundredths of an inch (AISI S201, the designations 2021 IRC R603 + * uses). + */ +export const LGS_STUD_350 = inches(3.5) +export const LGS_STUD_550 = inches(5.5) +export const LGS_STUD_800 = inches(8) +/** Stud spacings the 2021 IRC stud tables are written for, 16 and 24 in on centre (Table R602.3(5)). */ +export const STUD_SPACING_16 = inches(16) +export const STUD_SPACING_24 = inches(24) /** Vinyl / wood lap siding bounding depth, 3/4 in. 2021 IRC R703.11 / R703.5 + Table R703.3(1). */ export const SIDING_LAP = inches(0.75) /** Fiber cement lap board, 5/16 in board. 2021 IRC R703.10.2 + Table R703.3(1). */ @@ -229,6 +248,7 @@ export function wallAssemblyFromLegacy(legacy: WallAssembly): Assembly { core: true, material: legacy.framing.kind, thickness: legacy.framing.depth, + ...(legacy.framing.spacing !== undefined ? { spacing: legacy.framing.spacing } : {}), }) if (interior) layers.push({ id: 'interior', ...interior }) return { @@ -290,6 +310,7 @@ export function wallAssemblyToLegacy(assembly: Assembly): WallAssembly | null { legacy.framing = { kind: framing.material as WallAssembly['framing']['kind'], depth: framing.thickness, + ...(framing.spacing !== undefined ? { spacing: framing.spacing } : {}), } const lining = take('lining') if (layers.length > 0) return null @@ -316,6 +337,7 @@ export function wallAssemblyToLegacy(assembly: Assembly): WallAssembly | null { restored.role === layer.role && restored.core === layer.core && restored.material === layer.material && + restored.spacing === layer.spacing && Math.abs(restored.thickness - layer.thickness) < 1e-12 ) }) @@ -353,6 +375,10 @@ const INTERIOR_FINISH_LABEL: Record = { plaster: 'plaster', none: 'none', } +const INSULATION_LABEL: Record = { + 'rigid-foam': 'rigid foam insulation', + 'mineral-wool': 'mineral wool board', +} /** An F2 layer as the WS5 drawing role and its inspector / poché label. */ function drawnLayer(layer: AssemblyLayer): { role: WallAssemblyLayerRole; material: string } { @@ -374,9 +400,14 @@ function drawnLayer(layer: AssemblyLayer): { role: WallAssemblyLayerRole; materi case 'substrate': case 'membrane': case 'underlay': + // Continuous insulation is a board on the face of the frame, drawn as one. + case 'insulation': return { role: 'sheathing', - material: (material && SHEATHING_LABEL[material]) ?? material ?? layer.role, + material: + (material && (SHEATHING_LABEL[material] ?? INSULATION_LABEL[material])) ?? + material ?? + layer.role, } default: return { @@ -635,15 +666,273 @@ export function wallAssemblyFraming( return { depth: core.thickness, ...(core.material ? { kind: core.material } : {}) } } -/** `library:` ref for the wall's assembly cladding, or null when it has none. */ +/** + * `library:` ref for the wall's assembly cladding, or null when it has none: the product picked + * for its cladding layer while drafting (`wallAssemblyFinishSlots`), else the kind's own + * (`WALL_FINISH_LIBRARY_REF`). A picked product that no longer dresses the cladding (the layer + * changed from siding to stucco since) gives way to the kind's own. + */ export function wallAssemblyFinishRef( - wall: Pick, + wall: Pick, ): string | null { - const finish = wallAssemblyExteriorFinish(wall) - if (!finish) return null + const layer = layersOutsideIn(wall).layers.find((layer) => layer.role === 'finish') + const finish = layer?.material + if (!(layer && finish)) return null + const picked = wall.slots?.[assemblyLayerSlotKey(layer)] + if (isWallCladdingProduct(finish, picked)) return picked! return WALL_FINISH_LIBRARY_REF[finish] ?? null } +/** + * The finish the wall's assembly gives one geometric face (`a` is the +normal face) where the + * face has no paint of its own: on the exterior face its cladding (`wallAssemblyFinishRef`), on a + * lined face the paint picked for that lining while drafting. Null where the assembly says + * nothing, and the face shows the wall's slot default. + */ +export function wallAssemblyFaceFinish( + wall: Pick, + face: WallFace, +): string | null { + const { layers, exteriorSideResolved } = layersOutsideIn(wall) + const exterior = face === (exteriorSideResolved === 1 ? 'a' : 'b') + if (exterior) { + const cladding = wallAssemblyFinishRef(wall) + if (cladding) return cladding + } + const drawn = layers.filter((layer) => layer.thickness > 0) + const outer = exterior ? drawn[0] : drawn[drawn.length - 1] + if (outer?.role !== 'lining') return null + return wall.slots?.[assemblyLayerSlotKey(outer)] ?? null +} + +// ============================================================================ +// FINISHES PICKED WHILE DRAFTING +// ============================================================================ + +/** The `slots` key a layer's look is kept under: its own `slot`, else `layer:` (`AssemblyLayer.slot`). */ +export function assemblyLayerSlotKey(layer: Pick): string { + return layer.slot ?? `layer:${layer.id}` +} + +/** An outdoor wall finish: a brick or stone made for a wall outside, not a floor tile. */ +const outdoorWall = (item: MaterialCatalogItem) => + !!item.surfaces?.includes('wall') && item.surfaces.includes('outdoor') + +/** Which catalog finishes dress each cladding kind. */ +const CLADDING_PRODUCTS: Record MaterialCatalogItem[]> = { + siding: () => getMaterialsForCategory('siding'), + 'fiber-cement': () => getMaterialsForCategory('siding'), + // The textured coat, then the flat colours of an integrally coloured or painted finish coat. + stucco: () => [ + ...getMaterialsForCategory('concrete').filter((item) => item.id.includes('stucco')), + ...getMaterialsForCategory('colors'), + ], + brick: () => getMaterialsForCategory('brick').filter(outdoorWall), + stone: () => getMaterialsForCategory('stone').filter(outdoorWall), +} + +/** + * The catalog products a cladding kind (`siding`, `stucco`, `brick`, …) can be finished with, + * the kind's own (`WALL_FINISH_LIBRARY_REF`) among them: what the drafting finish picker offers + * for a wall type with that cladding. Empty for a kind with none (`none`, an unknown kind). + */ +export function wallCladdingProducts(kind: string | undefined): MaterialCatalogItem[] { + return (kind && CLADDING_PRODUCTS[kind]?.()) || [] +} + +/** Whether `ref` is a `library:` product `wallCladdingProducts(kind)` lists. */ +export function isWallCladdingProduct(kind: string | undefined, ref: string | undefined): boolean { + const id = getLibraryMaterialIdFromRef(ref) + return !!id && wallCladdingProducts(kind).some((item) => item.id === id) +} + +/** The paints a wall's lining can be finished with while drafting: the catalog's flat colours. */ +export function wallPaintProducts(): MaterialCatalogItem[] { + return getMaterialsForCategory('colors') +} + +/** The cladding kind a stack is finished with outside (`siding`, `stucco`, …), if any. */ +export function wallAssemblyCladding(assembly: Assembly): string | undefined { + return assembly.layers.find((layer) => layer.role === 'finish' && layer.thickness > 0)?.material +} + +/** + * Whether a stack builds an exterior wall, read from its own layers: it has a cladding side — a + * finish or sheathing layer, or a lining on one face only (block or framing left bare outside). + * A stack lined on both faces, or bare on both, is a partition. + */ +export function isExteriorWallAssembly(assembly: Assembly): boolean { + const layers = [...assembly.layers, ...(assembly.backing ?? [])].filter( + (layer) => layer.thickness > 0, + ) + if (layers.some((layer) => layer.role === 'finish' || layer.role === 'sheathing')) return true + return (layers[0]?.role === 'lining') !== (layers[layers.length - 1]?.role === 'lining') +} + +/** + * The `slots` a wall drawn with `assembly` carries for the finishes picked while drafting: the + * cladding product on its exterior finish layer and the paint on every lining. They follow the + * layers, not a geometric face, so the cladding stays outside and the paint inside however the + * wall's rooms are detected later (`wallAssemblyFaceFinish` reads them back). A pick that is the + * layer's own default (`WALL_FINISH_LIBRARY_REF`, `DEFAULT_INTERIOR_WALL_PAINT`) is left out, and + * a product that does not dress the stack's cladding is never written. + */ +export function wallAssemblyFinishSlots( + assembly: Assembly, + picks: { cladding?: string | null; paint?: string | null }, +): Record { + const slots: Record = {} + const cladding = assembly.layers.find((layer) => layer.role === 'finish' && layer.thickness > 0) + const kind = cladding?.material + if ( + cladding && + picks.cladding && + picks.cladding !== WALL_FINISH_LIBRARY_REF[kind ?? ''] && + isWallCladdingProduct(kind, picks.cladding) + ) + slots[assemblyLayerSlotKey(cladding)] = picks.cladding + if (picks.paint && picks.paint !== DEFAULT_INTERIOR_WALL_PAINT) { + for (const layer of assembly.layers) + if (layer.role === 'lining') slots[assemblyLayerSlotKey(layer)] = picks.paint + } + return slots +} + +// ============================================================================ +// PROJECT WALL TYPES +// ============================================================================ + +export type WallTypeCore = { + id: string + label: string + /** The core layer's construction kind, as the framers read it (`wallAssemblyFraming`). */ + material: 'wood' | 'lgs' | 'cmu' + thickness: number + /** Studs on centre, metres; absent for block. */ + spacing?: number +} + +/** + * The framing cores a project wall type is built around: wood studs (2x4, 2x6, 2x8) and steel + * studs (the 3-1/2, 5-1/2 and 8 in webs the framers size steel walls by) at 16 or 24 in on + * centre, or an 8 in block. The core's depth and spacing are what a framer studs the wall at. + */ +export const WALL_TYPE_CORES: readonly WallTypeCore[] = [ + ...( + [ + ['2x4', STUD_2X4], + ['2x6', STUD_2X6], + ['2x8', STUD_2X8], + ] as const + ).flatMap(([size, thickness]) => + ([16, 24] as const).map((oc) => ({ + id: `wood-${size}-${oc}`, + label: `${size} wood studs @ ${oc}" o.c.`, + material: 'wood' as const, + thickness, + spacing: oc === 16 ? STUD_SPACING_16 : STUD_SPACING_24, + })), + ), + ...( + [ + ['3-1/2"', '350', LGS_STUD_350], + ['5-1/2"', '550', LGS_STUD_550], + ['8"', '800', LGS_STUD_800], + ] as const + ).flatMap(([depth, web, thickness]) => + ([16, 24] as const).map((oc) => ({ + id: `lgs-${web}-${oc}`, + label: `${depth} steel studs @ ${oc}" o.c.`, + material: 'lgs' as const, + thickness, + spacing: oc === 16 ? STUD_SPACING_16 : STUD_SPACING_24, + })), + ), + { id: 'cmu-8', label: 'CMU 8" block', material: 'cmu', thickness: CMU_8_ACTUAL }, +] + +const near = (a: number, b: number) => Math.abs(a - b) < 1e-6 + +/** + * The listed core a stack is built around, or undefined for one off the list. A core with no + * spacing of its own (every preset's: the framer's default) reads as the 16 in one. + */ +export function wallTypeCoreOf(assembly: Assembly): WallTypeCore | undefined { + const core = assembly.layers.find((layer) => layer.core) + if (!core) return undefined + const spacing = core.spacing ?? STUD_SPACING_16 + return WALL_TYPE_CORES.find( + (option) => + option.material === core.material && + near(option.thickness, core.thickness) && + (option.spacing === undefined || near(option.spacing, spacing)), + ) +} + +/** The id each layer role takes in a project wall type, as the presets name them. */ +const WALL_TYPE_LAYER_ID: Partial> = { + finish: 'exterior', + air: 'air-space', + sheathing: 'sheathing', + structure: 'framing', +} + +/** + * A project wall type's stack from its layers listed outside-in, with ids as the presets give + * them (`exterior`, `sheathing`, `framing`, `interior`, a lining outside the core + * `interior-back`, a repeat numbered `-2`, `-3`…), so a stack the inspector's cladding / + * sheathing / framing / interior rows can express stays editable there. + */ +export function wallTypeAssembly( + id: string, + layers: readonly Omit[], +): Assembly { + const coreIndex = layers.findIndex((layer) => layer.core) + const used = new Set() + return { + layers: layers.map((layer, index) => { + const base = + layer.role === 'lining' + ? index < coreIndex + ? 'interior-back' + : 'interior' + : (WALL_TYPE_LAYER_ID[layer.role] ?? layer.role) + let layerId = base + for (let n = 2; used.has(layerId); n += 1) layerId = `${base}-${n}` + used.add(layerId) + return { id: layerId, ...layer } + }), + face: 'exterior', + presetId: id, + } +} + +/** + * The project's own wall types: those saved on its site (`SiteNode.wallTypes`), in their saved + * order. A scene is loaded without parsing, so an entry that does not parse, or repeats an id, is + * skipped rather than trusted. + */ +export function projectWallTypes( + nodes: Readonly>, + rootNodeIds: readonly string[], +): WallTypeDefinition[] { + const site = rootNodeIds.map((id) => nodes[id]).find((node) => node?.type === 'site') + return parseWallTypes(site?.type === 'site' ? site.wallTypes : undefined) +} + +/** A saved `SiteNode.wallTypes` list as `projectWallTypes` reads it: parsed, first id wins. */ +export function parseWallTypes(saved: unknown): WallTypeDefinition[] { + const seen = new Set() + const types: WallTypeDefinition[] = [] + for (const entry of Array.isArray(saved) ? saved : []) { + const parsed = WallTypeDefinition.safeParse(entry) + if (!parsed.success || seen.has(parsed.data.id)) continue + seen.add(parsed.data.id) + types.push(parsed.data) + } + return types +} + /** The structural cores that are left bare where nothing covers them: block and cast concrete. */ export type ExposedStructure = 'cmu' | 'concrete' diff --git a/packages/core/src/systems/wall/wall-finish.test.ts b/packages/core/src/systems/wall/wall-finish.test.ts index b0e6bc021d..0f2a5987a7 100644 --- a/packages/core/src/systems/wall/wall-finish.test.ts +++ b/packages/core/src/systems/wall/wall-finish.test.ts @@ -1,7 +1,14 @@ import { describe, expect, test } from 'bun:test' import { extractRooms } from '../../lib/room-graph' +import { getMaterialPresetByRef, MATERIAL_CATALOG } from '../../material-library' +import { wallSlots } from '../../node-slots/wall' import { type AnyNode, LevelNode } from '../../schema' -import { WallNode } from '../../schema/nodes/wall' +import { + DEFAULT_EXTERIOR_WALL_FINISH, + DEFAULT_INTERIOR_WALL_PAINT, + getWallFaceDefault, + WallNode, +} from '../../schema/nodes/wall' import { ZoneNode } from '../../schema/nodes/zone' import { buildWallFinishLayout, @@ -156,7 +163,7 @@ describe('wall finish resolution order', () => { expect(resolveWallFaceChain(host, 'a')).toEqual({ kind: 'ref', ref: 'library:slot-a' }) expect(resolveWallFaceChain(host, 'b')).toEqual({ kind: 'default', - ref: 'library:concrete-drywall', + ref: 'library:preset-seattlegrey', }) }) @@ -181,8 +188,59 @@ describe('wall finish resolution order', () => { expect(resolveWallFaceChain(withSlot, 'a')).toEqual({ kind: 'ref', ref: 'library:slot' }) expect(resolveWallFaceChain(host, 'a')).toEqual({ kind: 'default', + ref: 'library:preset-seattlegrey', + }) + }) + + test('an unpainted face wears Seattle grey; a painted face keeps its paint, raw drywall included', () => { + const plain = WallNode.parse({ start: [0, 0], end: [4, 0] }) + expect(DEFAULT_INTERIOR_WALL_PAINT).toBe('library:preset-seattlegrey') + expect(getMaterialPresetByRef(DEFAULT_INTERIOR_WALL_PAINT)?.mapProperties.color).toBe('#d8d6d1') + expect(MATERIAL_CATALOG.find((m) => m.id === 'preset-seattlegrey')?.label).toBe('Seattle grey') + for (const face of ['a', 'b'] as const) + expect(resolveWallFaceChain(plain, face)).toEqual({ + kind: 'default', + ref: DEFAULT_INTERIOR_WALL_PAINT, + }) + // Raw drywall is still a finish to pick on purpose. + const painted = { ...plain, slots: { a: 'library:concrete-drywall', b: 'library:preset-sage' } } + expect(getMaterialPresetByRef('library:concrete-drywall')).not.toBeNull() + expect(resolveWallFaceChain(painted, 'a')).toEqual({ + kind: 'ref', ref: 'library:concrete-drywall', }) + expect(resolveWallFaceChain(painted, 'b')).toEqual({ kind: 'ref', ref: 'library:preset-sage' }) + }) + + test("Seattle grey stays inside: an exterior face keeps raw drywall, a partition's faces go grey", () => { + const at = (frontSide: string, backSide: string) => { + const wall = WallNode.parse({ start: [0, 0], end: [4, 0], frontSide, backSide }) + return (['a', 'b'] as const).map((face) => { + const chain = resolveWallFaceChain(wall, face) + return chain.kind === 'default' ? chain.ref : chain.kind + }) + } + expect(DEFAULT_EXTERIOR_WALL_FINISH).toBe('library:concrete-drywall') + const grey = DEFAULT_INTERIOR_WALL_PAINT + const drywall = DEFAULT_EXTERIOR_WALL_FINISH + // An exterior wall: drywall outside (the default before the interior paint), grey inside. + expect(at('exterior', 'interior')).toEqual([drywall, grey]) + expect(at('interior', 'exterior')).toEqual([grey, drywall]) + expect(at('exterior', 'unknown')).toEqual([drywall, grey]) + // A partition, or a wall whose rooms are not detected yet: grey both faces. + expect(at('interior', 'interior')).toEqual([grey, grey]) + expect(at('unknown', 'unknown')).toEqual([grey, grey]) + // Outdoors on both sides: no inside face. + expect(at('exterior', 'exterior')).toEqual([drywall, drywall]) + // The face's slot declaration says the same, and paint on an exterior face still wins. + const outside = WallNode.parse({ start: [0, 0], end: [4, 0], frontSide: 'exterior' }) + expect(getWallFaceDefault(outside, 'a')).toBe(drywall) + expect(wallSlots(outside).find((slot) => slot.slotId === 'a')?.default).toBe(drywall) + expect(wallSlots(outside).find((slot) => slot.slotId === 'b')?.default).toBe(grey) + expect(resolveWallFaceChain({ ...outside, slots: { a: 'library:preset-sage' } }, 'a')).toEqual({ + kind: 'ref', + ref: 'library:preset-sage', + }) }) test('later regions win and legacy inline finishes sit between slot and default', () => { diff --git a/packages/core/src/systems/wall/wall-finish.ts b/packages/core/src/systems/wall/wall-finish.ts index 544f731fde..9e8ee9725f 100644 --- a/packages/core/src/systems/wall/wall-finish.ts +++ b/packages/core/src/systems/wall/wall-finish.ts @@ -3,8 +3,8 @@ import { sampleWallPointsForRoomDetection, WALL_JUNCTION_TOLERANCE } from '../.. import type { AnyNode, AnyNodeId } from '../../schema' import { getEffectiveWallFaceMaterial, + getWallFaceDefault, WALL_FACE_REGION_LIMIT, - WALL_SLOT_DEFAULT, type WallFace, type WallFaceRegion, type WallNode, @@ -325,7 +325,8 @@ export function wallFinishMaterialIndex( /** * The wall's own chain for one face, in the order the renderer walks it: * `slots[face]` ref → legacy inline finish (per-face, then wall-wide) → the - * declared default. A dangling ref renders the declared default. + * face's default (`getWallFaceDefault`: the interior paint, or raw drywall on + * the exterior side). A dangling ref renders the face's default. */ export type WallFaceChainFinish = | { kind: 'ref'; ref: string } @@ -333,14 +334,15 @@ export type WallFaceChainFinish = | { kind: 'default'; ref: string } export function resolveWallFaceChain( - wall: Pick, + wall: Pick & + Partial>, face: WallFace, ): WallFaceChainFinish { const ref = wall.slots?.[face] if (ref) return { kind: 'ref', ref } const spec = getEffectiveWallFaceMaterial(wall, face) if (spec.materialPreset || spec.material) return { kind: 'legacy', spec } - return { kind: 'default', ref: WALL_SLOT_DEFAULT[face] } + return { kind: 'default', ref: getWallFaceDefault(wall, face) } } // Paint roles a wall resolves to (the paint tool offers `room:`): diff --git a/packages/core/src/systems/wall/wall-footprint.ts b/packages/core/src/systems/wall/wall-footprint.ts index 4a363ca878..393e81b6a9 100644 --- a/packages/core/src/systems/wall/wall-footprint.ts +++ b/packages/core/src/systems/wall/wall-footprint.ts @@ -72,11 +72,11 @@ export function getWallPlanFootprint(wallNode: WallNode, miterData: WallMiterDat const polygon: Point2D[] = [pStartRight, pEndRight] if (endJunction) { - polygon.push(endJunction.closing ?? wallEnd) + polygon.push(...(endJunction.closingPath ?? [endJunction.closing ?? wallEnd])) } polygon.push(pEndLeft, pStartLeft) if (startJunction) { - polygon.push(startJunction.closing ?? wallStart) + polygon.push(...(startJunction.closingPath ?? [startJunction.closing ?? wallStart])) } return polygon diff --git a/packages/core/src/systems/wall/wall-merge.test.ts b/packages/core/src/systems/wall/wall-merge.test.ts index e39218f1e4..99d74b88bb 100644 --- a/packages/core/src/systems/wall/wall-merge.test.ts +++ b/packages/core/src/systems/wall/wall-merge.test.ts @@ -106,11 +106,25 @@ describe('explicit wall merge', () => { ).toThrow('These walls have a different height.') }) - test('refuses anything that would change the layout', () => { + test('merges a straight run across a T: the wall meeting it stays on its body', () => { const a = wall([0, 0], [4, 0]) const b = wall([4, 0], [8, 0]) const tee = wall([4, 0], [4, 3]) - expect(() => planWallMerge(map([a, b, tee]), [a.id, b.id])).toThrow('Another wall') + const { changes, wallId } = planWallMerge(map([a, b, tee]), [a.id, b.id]) + const merged = changes.update.find((op) => op.id === wallId)!.data as Partial + expect([merged.start, merged.end]).toEqual([ + [0, 0], + [8, 0], + ]) + expect(changes.update.some((op) => op.id === tee.id)).toBe(false) + expect(changes.delete).toHaveLength(1) + }) + + test('refuses anything that would change the layout', () => { + const a = wall([0, 0], [4, 0]) + const b = wall([4, 0], [8, 0]) + const along = wall([4, 0], [6, 0]) + expect(() => planWallMerge(map([a, b, along]), [a.id, b.id])).toThrow('Another wall') const corner = wall([4, 0], [4, 4]) expect(() => planWallMerge(map([a, corner]), [a.id, corner.id])).toThrow('straight line') const thick = wall([4, 0], [8, 0], { thickness: 0.3 }) @@ -200,6 +214,7 @@ describe('explicit wall merge through the store', () => { rotation: [0, 0.25, 0], hingesSide: 'left', handleSide: 'right', + casingFaceB: false, }) kept.children = keptWindows.map((w) => w.id) absorbed.children = [absorbedWindow.id, absorbedDoor.id] @@ -219,6 +234,8 @@ describe('explicit wall merge through the store', () => { expect(door.rotation[1]).toBeCloseTo(0.25 - Math.PI) expect(door.hingesSide).toBe('left') expect(door.handleSide).toBe('right') + // Its casing, kept per wall face, stays on the same side of the wall. + expect([door.casingFaceA, door.casingFaceB]).toEqual([false, true]) // The kept wall's own openings keep their side and their world place. const keptFirst = nodes[keptWindows[0]!.id] as WindowNode expect(keptFirst.side).toBeUndefined() diff --git a/packages/core/src/systems/wall/wall-merge.ts b/packages/core/src/systems/wall/wall-merge.ts index f1bfc9991b..be777e7acd 100644 --- a/packages/core/src/systems/wall/wall-merge.ts +++ b/packages/core/src/systems/wall/wall-merge.ts @@ -72,11 +72,13 @@ export function wallStyleMismatch( Math.abs((a.height ?? 0) - (b.height ?? 0)) > 1e-6 ) return 'height' - // Faces are compared as the merged wall will carry them: b read in a's direction. + // Faces are compared as the merged wall will carry them: b read in a's direction. An unpainted + // face's default follows its room side, which room detection reclassifies, so it is set aside. + const painted = (wall: WallNode) => ({ ...wall, frontSide: undefined, backSide: undefined }) for (const face of ['a', 'b'] as const) { if ( - JSON.stringify(resolveWallFaceChain(a, face)) !== - JSON.stringify(resolveWallFaceChain(alignedB, face)) + JSON.stringify(resolveWallFaceChain(painted(a), face)) !== + JSON.stringify(resolveWallFaceChain(painted(alignedB), face)) ) return `side ${face.toUpperCase()} finish` } @@ -327,13 +329,18 @@ export function resolveMergedWallEndpoints( * 0 front / π back). A wall read backwards turns that frame by 180° about Y, * so the face and the yaw swap and the caller mirrors depth. Hinges, handles * and swing hang off the node's own yaw (the plan symbol flips them from it), - * so they stay as stored. + * so they stay as stored. Door and window casing is kept per wall face, so it + * swaps faces too and stays on the same side of the wall. */ function reversedWallChildPatch(child: AnyNode): Partial { const patch: Record = {} if ('side' in child && (child.side === 'front' || child.side === 'back')) { patch.side = child.side === 'front' ? 'back' : 'front' } + if (child.type === 'door' || child.type === 'window') { + const [faceA, faceB] = [child.casingFaceA !== false, child.casingFaceB !== false] + if (faceA !== faceB) Object.assign(patch, { casingFaceA: faceB, casingFaceB: faceA }) + } if ('rotation' in child && Array.isArray(child.rotation)) { const yaw = child.rotation[1] + Math.PI patch.rotation = [ @@ -451,16 +458,29 @@ export function planWallMerge( } } if (!(next && joint)) throw Error('Merge walls that touch end to end.') + if (!areWallsCollinearAcrossPoint(merged, next, joint)) + throw Error('Merge walls that continue in a straight line.') + // A wall meeting the joint from the side stays T'd into the merged wall's + // body there; one running along the line would be buried in it. + const runX = merged.end[0] - merged.start[0] + const runZ = merged.end[1] - merged.start[1] + const alongRun = (wall: WallNode) => { + const free = getWallFreeEndpoint(wall, joint) + const x = free[0] - joint[0] + const z = free[1] - joint[1] + const length = Math.hypot(x, z) * Math.hypot(runX, runZ) + return length < 1e-12 || Math.abs(x * runZ - z * runX) / length < 1e-4 + } const atJoint = Object.values(virtual).filter( - (node) => + (node): node is WallNode => node?.type === 'wall' && + node.id !== merged.id && + node.id !== next.id && (node.parentId ?? null) === levelId && getWallEndpointAtPoint(node, joint) !== null, ) - if (atJoint.length !== 2) + if (atJoint.some(alongRun)) throw Error('Another wall meets this joint, so merging would disconnect it.') - if (!areWallsCollinearAcrossPoint(merged, next, joint)) - throw Error('Merge walls that continue in a straight line.') const mismatch = wallStyleMismatch(merged, next, { sides: false, heightOf: (wall) => getWallEffectiveHeightForNodes(wall, virtual), diff --git a/packages/core/src/systems/wall/wall-mitering.test.ts b/packages/core/src/systems/wall/wall-mitering.test.ts index cbe1b331d3..9be398d9e3 100644 --- a/packages/core/src/systems/wall/wall-mitering.test.ts +++ b/packages/core/src/systems/wall/wall-mitering.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'bun:test' import type { WallNode } from '../../schema' +import { getWallPlanFootprint } from './wall-footprint' import { calculateLevelMiters, getWallMiterBoundaryPoints, pointToKey } from './wall-mitering' function wall(id: string, start: [number, number], end: [number, number]): WallNode { @@ -129,3 +130,97 @@ describe('junction grid prefilter', () => { expect(junction?.connectedWalls.map((cw) => cw.wall.id)).toEqual(['spur', 'long']) }) }) + +describe('outside corner with a partition in line', () => { + // Two outside walls 0.18 thick turn a corner at the origin: one runs off to + // -x, the other to +z, outdoors between them. A 0.11 partition carries the + // first one's line on to +x. + const outsideLeft = { + ...wall('wall_left', [-3, 0], [0, 0]), + thickness: 0.18, + frontSide: 'exterior', + backSide: 'interior', + } as WallNode + const outsideDown = { + ...wall('wall_down', [0, 0], [0, 3]), + thickness: 0.18, + frontSide: 'exterior', + backSide: 'interior', + } as WallNode + const partition = { + ...wall('wall_part', [0, 0], [3, 0]), + thickness: 0.11, + frontSide: 'interior', + backSide: 'interior', + } as WallNode + const walls = [outsideLeft, outsideDown, partition] + const near = (p: { x: number; y: number } | undefined, x: number, y: number) => + !!p && Math.abs(p.x - x) < 1e-9 && Math.abs(p.y - y) < 1e-9 + const inside = (polygon: { x: number; y: number }[], x: number, y: number) => { + let hit = false + for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + const a = polygon[i]! + const b = polygon[j]! + if (a.y > y !== b.y > y && x < ((b.x - a.x) * (y - a.y)) / (b.y - a.y) + a.x) hit = !hit + } + return hit + } + const corner = () => calculateLevelMiters(walls).junctionData.get(pointToKey({ x: 0, y: 0 }))! + + test('the outside faces miter to the corner', () => { + for (const id of ['wall_left', 'wall_down']) { + const ends = corner().get(id)! + expect(near(ends.left, -0.09, 0.09) || near(ends.right, -0.09, 0.09)).toBe(true) + } + }) + + test('the partition stops square at the inside face of the corner', () => { + const miter = calculateLevelMiters(walls) + const ends = miter.junctionData.get(pointToKey({ x: 0, y: 0 }))!.get('wall_part')! + expect(near(ends.left, 0.09, 0.055)).toBe(true) + expect(near(ends.right, 0.09, -0.055)).toBe(true) + const footprint = getWallPlanFootprint(partition, miter) + expect(Math.min(...footprint.map((p) => p.x))).toBeCloseTo(0.09, 9) + }) + + test("each room's own walls cover its side of the corner", () => { + const miter = calculateLevelMiters(walls) + const left = getWallPlanFootprint(outsideLeft, miter) + const down = getWallPlanFootprint(outsideDown, miter) + // The room beyond the line (z < 0) is walled by the left wall and the + // partition: the corner's half on its side is the left wall's. + for (const [x, y] of [ + [0.05, -0.05], + [0.08, -0.01], + [-0.05, -0.08], + ] as const) { + expect(inside(left, x, y)).toBe(true) + expect(inside(down, x, y)).toBe(false) + } + // The room between the partition and the down wall (x, z > 0) is theirs. + expect(inside(down, 0.05, 0.05)).toBe(true) + expect(inside(left, 0.05, 0.05)).toBe(false) + }) + + test('a corner of walls that are not outside walls keeps the shared miter', () => { + const indoors = walls.map( + (w) => ({ ...w, frontSide: 'interior', backSide: 'interior' }) as WallNode, + ) + const ends = calculateLevelMiters(indoors) + .junctionData.get(pointToKey({ x: 0, y: 0 }))! + .get('wall_part') + expect(ends?.closing).toBeUndefined() + }) +}) + +describe('a wall T-joined into a wall body', () => { + test('ends square on the host face, not in a wedge to its middle', () => { + const host = wall('wall_host', [-3, 0], [3, 0]) + const stem = { ...wall('wall_stem', [0, 0], [0, 2]), thickness: 0.1 } as WallNode + const miter = calculateLevelMiters([host, stem]) + const ends = miter.junctionData.get(pointToKey({ x: 0, y: 0 }))!.get('wall_stem')! + expect(ends.closing?.y).toBeCloseTo(0.05, 9) + const footprint = getWallPlanFootprint(stem, miter) + expect(Math.min(...footprint.map((p) => p.y))).toBeCloseTo(0.05, 9) + }) +}) diff --git a/packages/core/src/systems/wall/wall-mitering.ts b/packages/core/src/systems/wall/wall-mitering.ts index 65a6cfbf09..0ada55a42c 100644 --- a/packages/core/src/systems/wall/wall-mitering.ts +++ b/packages/core/src/systems/wall/wall-mitering.ts @@ -25,7 +25,12 @@ interface LineEquation { } // Map of wallId -> { left?: Point2D, right?: Point2D } for each junction -type WallIntersections = Map +// `closingPath` (left to right) closes an end through several points where one +// `closing` point can't. +type WallIntersections = Map< + string, + { left?: Point2D; right?: Point2D; closing?: Point2D; closingPath?: Point2D[] } +> // Map of junctionKey -> WallIntersections type JunctionData = Map @@ -293,6 +298,162 @@ interface ProcessedWall { halfThickness: number // Used to bound the miter joint against runaway spikes isPassthrough: boolean // True if wall passes through junction (T-junction) wallId: string + /** An outside wall: one face is outdoors (`frontSide` / `backSide`). */ + outside: boolean + /** Unit direction out of the junction, and its left normal. */ + dir: Point2D + normal: Point2D + /** The body across `normal` from the meeting point: edge B at `lo`, edge A at `hi`. */ + lo: number + hi: number +} + +function isOutsideWall(wall: WallNode) { + return wall.frontSide === 'exterior' || wall.backSide === 'exterior' +} + +/** Each wall's joint with its neighbours round the junction (sorted by angle). */ +function miterAdjacentWalls( + processedWalls: ProcessedWall[], + meetingPoint: Point2D, +): WallIntersections { + const wallIntersections: WallIntersections = new Map() + const n = processedWalls.length + + // Calculate intersections between adjacent walls (exactly like demo) + for (let i = 0; i < n; i++) { + const wall1 = processedWalls[i]! + const wall2 = processedWalls[(i + 1) % n]! + + // Intersect left edge of wall1 with right edge of wall2 + const det = wall1.edgeA.a * wall2.edgeB.b - wall2.edgeB.a * wall1.edgeA.b + + // If lines are parallel (det ≈ 0), skip this intersection - walls will use defaults + if (Math.abs(det) < 1e-9) { + continue + } + + const p = { + x: (wall1.edgeA.b * wall2.edgeB.c - wall2.edgeB.b * wall1.edgeA.c) / det, + y: (wall2.edgeB.a * wall1.edgeA.c - wall1.edgeA.a * wall2.edgeB.c) / det, + } + + // Miter limit: `det` only catches walls that are *exactly* parallel. Two + // walls meeting at a shallow angle have a small-but-nonzero `det`, so `p` + // lands far from the junction (∝ 1/sin θ) and the wall renders as an + // infinite spike. Reject any joint farther than MITER_LIMIT half-thicknesses + // from the meeting point — those walls fall back to a square joint. + const maxMiter = MITER_LIMIT * Math.max(wall1.halfThickness, wall2.halfThickness) + const dx = p.x - meetingPoint.x + const dy = p.y - meetingPoint.y + if ( + !(Number.isFinite(p.x) && Number.isFinite(p.y)) || + dx * dx + dy * dy > maxMiter * maxMiter + ) { + continue + } + + // Only assign intersection to non-passthrough walls + // Passthrough walls don't receive junction data (their geometry doesn't change) + if (!wall1.isPassthrough) { + if (!wallIntersections.has(wall1.wallId)) { + wallIntersections.set(wall1.wallId, {}) + } + wallIntersections.get(wall1.wallId)!.left = p + } + + if (!wall2.isPassthrough) { + if (!wallIntersections.has(wall2.wallId)) { + wallIntersections.set(wall2.wallId, {}) + } + wallIntersections.get(wall2.wallId)!.right = p + } + } + + return wallIntersections +} + +// Walls within about 15° of each other don't make an outside corner. +const CORNER_MIN_SINE = Math.sin(Math.PI / 12) +// Walls within about 1° of each other run in line. +const IN_LINE_SINE = Math.sin(Math.PI / 180) + +const dot2 = (a: Point2D, b: Point2D) => a.x * b.x + a.y * b.y +const cross2 = (a: Point2D, b: Point2D) => a.x * b.y - a.y * b.x + +/** + * An outside corner keeps its own miter when a partition carries one of its + * walls on past the corner (two outside walls at an angle, one inside wall in + * line with one of them): the outside faces miter to the corner, the partition + * stops square at the other outside wall's inside face, and the in-line outside + * wall fills the corner on the partition's room side. So each room's own walls + * cover its side of the corner, and its floor and ceiling stop at them, where a + * three-way miter would notch the corner. Null for any other junction. + */ +function outsideCornerIntersections( + processedWalls: ProcessedWall[], + meetingPoint: Point2D, +): WallIntersections | null { + if (processedWalls.length !== 3 || processedWalls.some((wall) => wall.isPassthrough)) return null + const outside = processedWalls.filter((wall) => wall.outside) + const inside = processedWalls.find((wall) => !wall.outside) + if (outside.length !== 2 || !inside) return null + const inLine = outside.find( + (wall) => + Math.abs(cross2(wall.dir, inside.dir)) < IN_LINE_SINE && dot2(wall.dir, inside.dir) < 0, + ) + const across = outside.find((wall) => wall !== inLine) + if (!(inLine && across) || Math.abs(cross2(inLine.dir, across.dir)) < CORNER_MIN_SINE) { + return null + } + + const corner = miterAdjacentWalls(outside, meetingPoint) + const inLineEnds = corner.get(inLine.wallId) + const acrossEnds = corner.get(across.wallId) + if (!(inLineEnds?.left && inLineEnds.right && acrossEnds?.left && acrossEnds.right)) return null + + // Of the L's two joints, the one toward the partition is its inside corner. + const toward = (point: Point2D) => + dot2({ x: point.x - meetingPoint.x, y: point.y - meetingPoint.y }, inside.dir) + // Where a line parallel to the partition, `offset` across it, meets the + // across wall's face toward the partition. + const face = dot2(inside.dir, across.normal) > 0 ? across.hi : across.lo + const rate = dot2(inside.dir, across.normal) + const onFace = (offset: number): Point2D => { + const origin = { + x: meetingPoint.x + inside.normal.x * offset, + y: meetingPoint.y + inside.normal.y * offset, + } + const along = (face - offset * dot2(inside.normal, across.normal)) / rate + return { x: origin.x + inside.dir.x * along, y: origin.y + inside.dir.y * along } + } + // The shared line's point on that face: the in-line wall fills the corner up to it. + const heel = onFace(0) + + corner.set(inLine.wallId, { + left: inLineEnds.left, + right: inLineEnds.right, + closingPath: + toward(inLineEnds.left) > toward(inLineEnds.right) + ? [heel, meetingPoint] + : [meetingPoint, heel], + }) + corner.set( + across.wallId, + toward(acrossEnds.left) > toward(acrossEnds.right) + ? { left: heel, right: acrossEnds.right } + : { left: acrossEnds.left, right: heel }, + ) + const left = onFace(inside.hi) + const right = onFace(inside.lo) + corner.set(inside.wallId, { + left, + right, + // Square across the partition: closing through the junction would reach + // back into the corner. + closing: { x: (left.x + right.x) / 2, y: (left.y + right.y) / 2 }, + }) + return corner } function calculateJunctionIntersections( @@ -335,6 +496,11 @@ function calculateJunctionIntersections( edgeB, isPassthrough: true, halfThickness: halfT, + outside: isOutsideWall(wall), + dir: { x: v.x / L, y: v.y / L }, + normal: nUnit, + lo: b, + hi: a, }) } } else { @@ -361,6 +527,11 @@ function calculateJunctionIntersections( edgeB, isPassthrough: false, halfThickness: halfT, + outside: isOutsideWall(wall), + dir: { x: v.x / L, y: v.y / L }, + normal: nUnit, + lo: b, + hi: a, }) } } @@ -373,75 +544,38 @@ function calculateJunctionIntersections( return a.wallId < b.wallId ? -1 : a.wallId > b.wallId ? 1 : 0 }) - const wallIntersections: WallIntersections = new Map() - const n = processedWalls.length - - if (n < 2) return wallIntersections - - // Calculate intersections between adjacent walls (exactly like demo) - for (let i = 0; i < n; i++) { - const wall1 = processedWalls[i]! - const wall2 = processedWalls[(i + 1) % n]! - - // Intersect left edge of wall1 with right edge of wall2 - const det = wall1.edgeA.a * wall2.edgeB.b - wall2.edgeB.a * wall1.edgeA.b + if (processedWalls.length < 2) return new Map() - // If lines are parallel (det ≈ 0), skip this intersection - walls will use defaults - if (Math.abs(det) < 1e-9) { - continue - } - - const p = { - x: (wall1.edgeA.b * wall2.edgeB.c - wall2.edgeB.b * wall1.edgeA.c) / det, - y: (wall2.edgeB.a * wall1.edgeA.c - wall1.edgeA.a * wall2.edgeB.c) / det, - } - - // Miter limit: `det` only catches walls that are *exactly* parallel. Two - // walls meeting at a shallow angle have a small-but-nonzero `det`, so `p` - // lands far from the junction (∝ 1/sin θ) and the wall renders as an - // infinite spike. Reject any joint farther than MITER_LIMIT half-thicknesses - // from the meeting point — those walls fall back to a square joint. - const maxMiter = MITER_LIMIT * Math.max(wall1.halfThickness, wall2.halfThickness) - const dx = p.x - meetingPoint.x - const dy = p.y - meetingPoint.y - if ( - !(Number.isFinite(p.x) && Number.isFinite(p.y)) || - dx * dx + dy * dy > maxMiter * maxMiter - ) { - continue - } + if (!connectedWalls.some(({ wall }) => wall.justification !== undefined)) { + const corner = outsideCornerIntersections(processedWalls, meetingPoint) + if (corner) return corner + } - // Only assign intersection to non-passthrough walls - // Passthrough walls don't receive junction data (their geometry doesn't change) - if (!wall1.isPassthrough) { - if (!wallIntersections.has(wall1.wallId)) { - wallIntersections.set(wall1.wallId, {}) - } - wallIntersections.get(wall1.wallId)!.left = p - } + const wallIntersections = miterAdjacentWalls(processedWalls, meetingPoint) - if (!wall2.isPassthrough) { - if (!wallIntersections.has(wall2.wallId)) { - wallIntersections.set(wall2.wallId, {}) + const host = connectedWalls.find(({ endType }) => endType === 'passthrough') + if (host) { + // A wall T'd into another's body ends square on its face: both edges + // already meet the host's near face, and closing through the reference + // vertex would add a triangular sliver inside the host. + for (const entry of connectedWalls) { + if (entry.endType === 'passthrough') continue + const edges = wallIntersections.get(entry.wall.id) + if (edges?.left && edges.right) { + edges.closing = { + x: (edges.left.x + edges.right.x) / 2, + y: (edges.left.y + edges.right.y) / 2, + } } - wallIntersections.get(wall2.wallId)!.right = p } } if (connectedWalls.some(({ wall }) => wall.justification !== undefined)) { - const host = connectedWalls.find(({ endType }) => endType === 'passthrough') for (const entry of connectedWalls) { if (entry.endType === 'passthrough') continue const edges = wallIntersections.get(entry.wall.id) - if (!edges) continue - if (host && edges.left && edges.right) { - // Both edges already meet the host's near face. Closing through the - // reference vertex would add a triangular sliver inside the host. - edges.closing = { - x: (edges.left.x + edges.right.x) / 2, - y: (edges.left.y + edges.right.y) / 2, - } - } else if (connectedWalls.length === 2) { + if (!edges || (host && edges.left && edges.right)) continue + if (connectedWalls.length === 2) { const other = connectedWalls.find((candidate) => candidate !== entry)! if (other.endType === 'passthrough') continue const bodyLine = (candidate: typeof entry) => { diff --git a/packages/core/src/systems/wall/wall-move.ts b/packages/core/src/systems/wall/wall-move.ts index 0197de2e93..03c375cace 100644 --- a/packages/core/src/systems/wall/wall-move.ts +++ b/packages/core/src/systems/wall/wall-move.ts @@ -6,20 +6,22 @@ export type WallPlanPoint = [number, number] // Unit direction vector (x,z) to constrain move deltas along. export type WallMoveAxis = [number, number] export type WallMoveEndpoint = 'start' | 'end' +/** A wall's line as the move planners read it: invisible walls move by the same rules. */ +export type WallMoveLine = Pick & { id: string } -export type WallMoveBridgePlan> = { +export type WallMoveBridgePlan = { wall: TWall originalPoint: WallPlanPoint movedEndpoint: WallMoveEndpoint } -export type WallMoveLinkedWallTargetPlan> = { +export type WallMoveLinkedWallTargetPlan = { wall: TWall originalPoint: WallPlanPoint targetPoint: WallPlanPoint } -export type WallMoveJunctionPlan> = { +export type WallMoveJunctionPlan = { linkedWallsToMove: TWall[] linkedWallTargetPlans: Array> bridgePlans: Array> @@ -66,7 +68,7 @@ function otherWallEndpoint(wall: Pick, point: WallPla } type MoveWallRelation = 'same-direction' | 'opposite-direction' | 'off-axis' | 'stationary' -type RelatedWallEntry> = { +type RelatedWallEntry = { wall: TWall relation: MoveWallRelation } @@ -111,7 +113,7 @@ function getMoveWallRelation( * matchPoint/targetPoint pair encodes the pivot, not the original ↔ next * endpoint mapping. */ -export function getLinkedWallUpdates>( +export function getLinkedWallUpdates( linkedWalls: Array<{ wall: TWall matchPoint?: WallPlanPoint @@ -150,7 +152,7 @@ export function getLinkedWallUpdates>( +export function getPlannedLinkedWallUpdates( plan: WallMoveJunctionPlan, originalStart: WallPlanPoint, originalEnd: WallPlanPoint, @@ -183,7 +185,7 @@ export function getPlannedLinkedWallUpdates>( +export function planWallMoveJunctions( linkedWalls: TWall[], originalStart: WallPlanPoint, originalEnd: WallPlanPoint, @@ -321,3 +323,72 @@ export function planWallMoveJunctions>( + walls: readonly TWall[], + originalStart: WallPlanPoint, + originalEnd: WallPlanPoint, + nextStart: WallPlanPoint, + nextEnd: WallPlanPoint, +): Array<{ id: TWall['id']; start: WallPlanPoint; end: WallPlanPoint }> { + const dx = originalEnd[0] - originalStart[0] + const dz = originalEnd[1] - originalStart[1] + const length = Math.hypot(dx, dz) + const mx = nextEnd[0] - nextStart[0] + const mz = nextEnd[1] - nextStart[1] + const movedLength = Math.hypot(mx, mz) + if (length < AXIS_EPSILON || movedLength < AXIS_EPSILON) return [] + const ux = dx / length + const uz = dz / length + + const updates: Array<{ id: TWall['id']; start: WallPlanPoint; end: WallPlanPoint }> = [] + for (const wall of walls) { + if ((wall.curveOffset ?? 0) !== 0) continue + if (wallTouchesPoint(wall, originalStart) || wallTouchesPoint(wall, originalEnd)) continue + const next = { start: wall.start, end: wall.end } + let moved = false + for (const end of ['start', 'end'] as const) { + const point = wall[end] + const along = (point[0] - originalStart[0]) * ux + (point[1] - originalStart[1]) * uz + const across = (point[1] - originalStart[1]) * ux - (point[0] - originalStart[0]) * uz + if ( + Math.abs(across) > ATTACH_TOLERANCE || + along <= ATTACH_TOLERANCE || + along >= length - ATTACH_TOLERANCE + ) { + continue + } + const other = end === 'start' ? wall.end : wall.start + const wx = point[0] - other[0] + const wz = point[1] - other[1] + const denominator = wx * mz - wz * mx + if (Math.abs(denominator) <= ATTACH_MIN_SINE * Math.hypot(wx, wz) * movedLength) continue + // other + w·s meets nextStart + m·t. + const ox = nextStart[0] - other[0] + const oz = nextStart[1] - other[1] + const s = (ox * mz - oz * mx) / denominator + const t = (ox * wz - oz * wx) / denominator + if (t < -AXIS_EPSILON || t > 1 + AXIS_EPSILON) continue + const target: WallPlanPoint = [other[0] + wx * s, other[1] + wz * s] + if (Math.hypot(target[0] - other[0], target[1] - other[1]) < ATTACHED_MIN_LENGTH) continue + next[end] = target + moved = true + } + if (moved) updates.push({ id: wall.id, start: next.start, end: next.end }) + } + return updates +} diff --git a/packages/core/src/systems/wall/wall-operations.test.ts b/packages/core/src/systems/wall/wall-operations.test.ts index 83a115c380..f54e22d067 100644 --- a/packages/core/src/systems/wall/wall-operations.test.ts +++ b/packages/core/src/systems/wall/wall-operations.test.ts @@ -7,6 +7,7 @@ import { WindowNode, ZoneNode, } from '../../schema' +import { getWallAssemblyPreset, wallAssemblyPatch } from './wall-assembly' import { getWallCurveFrameAt, getWallCurveLength } from './wall-curve' import { planWallDivision, @@ -56,6 +57,49 @@ describe('wall creation operations', () => { } expect(JSON.stringify(nodes)).toBe(before) }) + test('drawn walls take the exterior type, walls drawn inside a room the interior one', () => { + const wallTypes = { + exterior: wallAssemblyPatch(getWallAssemblyPreset('exterior-2x6-siding')!.assembly), + interior: wallAssemblyPatch(getWallAssemblyPreset('interior-2x4-drywall')!.assembly), + } + const outline = planWallRectangle(map([]), { + levelId: level.id, + start: [0, 0], + end: [8, 6], + wallTypes, + }) + for (const wall of outline.walls) { + expect(wall.assembly?.presetId).toBe('exterior-2x6-siding') + expect(wall.thickness).toBeCloseTo(wallTypes.exterior.thickness, 9) + } + const closet = planWallRectangle(map(rectangle()), { + levelId: level.id, + start: [1, 1], + end: [3, 2], + wallTypes, + }) + expect(closet.walls).toHaveLength(4) + for (const wall of closet.walls) expect(wall.assembly?.presetId).toBe('interior-2x4-drywall') + const addition = planWallRectangle(map(rectangle()), { + levelId: level.id, + start: [8, 0], + end: [12, 6], + wallTypes, + }) + for (const wall of addition.walls) expect(wall.assembly?.presetId).toBe('exterior-2x6-siding') + // A preset placed by the host (an explicit thickness) keeps its own construction. + const seeded = planWallRectangle(map([]), { + levelId: level.id, + start: [0, 0], + end: [8, 6], + wallDefaults: { thickness: 0.3 }, + wallTypes, + }) + for (const wall of seeded.walls) { + expect(wall.assembly).toBeUndefined() + expect(wall.thickness).toBe(0.3) + } + }) test('adjacent rectangles reuse their shared wall', () => { const existing = rectangle() const plan = planWallRectangle(map(existing), { diff --git a/packages/core/src/systems/wall/wall-operations.ts b/packages/core/src/systems/wall/wall-operations.ts index 6177761e3c..0425672eee 100644 --- a/packages/core/src/systems/wall/wall-operations.ts +++ b/packages/core/src/systems/wall/wall-operations.ts @@ -2,6 +2,7 @@ import type { AnyNode, AnyNodeId, WallNode } from '../../schema' import { getWallCurveFrameAt, getWallCurveLength } from './wall-curve' import type { WallPlanPoint } from './wall-move' import { + type DrawnWallTypes, planWallInsertion, planWallSplitAtPoint, uncoveredWallSegments, @@ -35,6 +36,7 @@ export function planWallRectangle( start: WallPlanPoint end: WallPlanPoint wallDefaults?: Partial + wallTypes?: DrawnWallTypes }, ): { changes: WallTopologyChanges; walls: WallNode[] } { if (nodes[args.levelId]?.type !== 'level') throw Error('Select an editable floor.') diff --git a/packages/core/src/systems/wall/wall-top.test.ts b/packages/core/src/systems/wall/wall-top.test.ts index c568f9c950..4df19975e8 100644 --- a/packages/core/src/systems/wall/wall-top.test.ts +++ b/packages/core/src/systems/wall/wall-top.test.ts @@ -36,6 +36,11 @@ describe('resolveWallTop', () => { expect(resolveWallTop({}, 3, -0.4)).toBe(3) }) + test('a railing never follows its storey: its own height, else a 36 in guard, over its base', () => { + expect(resolveWallTop({ wallType: 'railing', height: 1.0668 }, 1.0668, 0.2)).toBeCloseTo(1.2668) + expect(resolveWallTop({ wallType: 'railing' }, 3, 0.2)).toBeCloseTo(0.2 + 0.9144) + }) + test('an authored storey-height wall ends at the covering underside, not its support plus height', () => { expect(resolveWallTop({ height: 3.6 }, 3.6, 0.05)).toBeCloseTo(3.6) expect(resolveWallEffectiveHeight({ height: 3.6 }, 3.6, 0.05)).toBeCloseTo(3.55) diff --git a/packages/core/src/systems/wall/wall-top.ts b/packages/core/src/systems/wall/wall-top.ts index 793875fbd5..59f90c8531 100644 --- a/packages/core/src/systems/wall/wall-top.ts +++ b/packages/core/src/systems/wall/wall-top.ts @@ -1,3 +1,4 @@ +import { RAILING_GUARD_HEIGHT } from '../../schema/nodes/railing-wall' import type { WallNode } from '../../schema/nodes/wall' /** @@ -10,9 +11,11 @@ import type { WallNode } from '../../schema/nodes/wall' export const MIN_WALL_HEIGHT = 0.5 export function wallFollowsStorey( - wall: Pick, + wall: Pick, authoredStoreyHeight: number | null, ): boolean { + // A railing is a guard as tall as itself, never the storey (36 in when it says nothing). + if (wall.wallType === 'railing') return false return ( wall.height == null || (authoredStoreyHeight !== null && @@ -34,13 +37,14 @@ export function wallFollowsStorey( * Returns the top in level-local Y (same frame as `electedBase`). */ export function resolveWallTop( - wall: Pick, + wall: Pick, storeyHeight: number, electedBase: number, authoredStoreyHeight: number | null = storeyHeight, ): number { if (wallFollowsStorey(wall, authoredStoreyHeight)) return storeyHeight - return electedBase + wall.height! + // Only a railing reaches here with no height of its own. + return electedBase + (wall.height ?? RAILING_GUARD_HEIGHT) } /** diff --git a/packages/core/src/systems/wall/wall-topology.test.ts b/packages/core/src/systems/wall/wall-topology.test.ts index 3e16a8b6f6..aa7d4e0595 100644 --- a/packages/core/src/systems/wall/wall-topology.test.ts +++ b/packages/core/src/systems/wall/wall-topology.test.ts @@ -1031,3 +1031,91 @@ describe('planJoinOpenWallEnd', () => { ).toEqual([]) }) }) + +describe('a wall started in a corner block', () => { + // Two 0.18 walls turn a corner at the origin. + const across = WallNode.parse({ + id: 'wall_across', + parentId: LEVEL_ID, + start: [-3, 0], + end: [0, 0], + thickness: 0.18, + }) + const down = WallNode.parse({ + id: 'wall_down', + parentId: LEVEL_ID, + start: [0, 0], + end: [0, 3], + thickness: 0.18, + }) + + test('joins the corner instead of splitting a stub off the wall', () => { + // 8 cm short of the corner, inside the other wall's body. + const result = planWallInsertion(nodeMap([across, down]), { + levelId: LEVEL_ID, + start: [-0.08, 0], + end: [3, 0], + joinRadius: 0.05, + }) + expect(result.ok).toBe(true) + if (!result.ok) return + expect(result.plan.resolvedStart).toEqual([0, 0]) + expect(result.plan.changes.delete).toEqual([]) + expect(result.plan.insertedWalls).toHaveLength(1) + }) + + test('past the corner block it still T-joins the wall', () => { + const result = planWallInsertion(nodeMap([across, down]), { + levelId: LEVEL_ID, + start: [-0.5, 0], + end: [-0.5, -2], + joinRadius: 0.05, + }) + expect(result.ok).toBe(true) + if (!result.ok) return + expect(result.plan.resolvedStart).toEqual([-0.5, 0]) + expect(result.plan.changes.delete).toEqual(['wall_across']) + }) +}) + +describe('drawing into a wall it leaves whole', () => { + const host = WallNode.parse({ id: 'wall_host', parentId: LEVEL_ID, start: [0, 0], end: [6, 0] }) + + test('an end on its body T-joins it without splitting it', () => { + const result = planWallInsertion(nodeMap([host]), { + levelId: LEVEL_ID, + start: [3, 3], + end: [3, 0.02], + joinRadius: 0.05, + keepHostsWhole: true, + }) + expect(result.ok).toBe(true) + if (!result.ok) return + expect(result.plan.resolvedEnd).toEqual([3, 0]) + expect(result.plan.changes.delete).toEqual([]) + expect(result.plan.insertedWalls).toHaveLength(1) + }) + + test('a wall drawn across it is split either side, the host stays one wall', () => { + const result = planWallInsertion(nodeMap([host]), { + levelId: LEVEL_ID, + start: [3, -2], + end: [3, 2], + joinRadius: 0.05, + keepHostsWhole: true, + }) + expect(result.ok).toBe(true) + if (!result.ok) return + expect(result.plan.changes.delete).toEqual([]) + expect(result.plan.insertedWalls.map((wall) => [wall.start, wall.end])).toEqual([ + [ + [3, -2], + [3, 0], + ], + [ + [3, 0], + [3, 2], + ], + ]) + }) +}) diff --git a/packages/core/src/systems/wall/wall-topology.ts b/packages/core/src/systems/wall/wall-topology.ts index cf550e1d29..f7f1f56916 100644 --- a/packages/core/src/systems/wall/wall-topology.ts +++ b/packages/core/src/systems/wall/wall-topology.ts @@ -1,4 +1,5 @@ import { GROUND_SUPPORT_ID } from '../../hooks/spatial-grid/support-host-id' +import { enclosedWallIds } from '../../lib/exterior-wall-loops' import { withFoundationConstruction } from '../../lib/foundation-wall' import { detectOpenWallEnds, type OpenWallEnd, wallEndJoinCandidates } from '../../lib/room-graph' import { terrainSupportLift } from '../../lib/terrain-support' @@ -8,11 +9,13 @@ import { type DoorNode, getScaledDimensions, type ItemNode, + SeparatorNode, type WallNode, WallNode as WallSchema, type WindowNode, } from '../../schema' import { getWallArcData, getWallCurveFrameAt, getWallCurveLength, isCurvedWall } from './wall-curve' +import { getWallFaceOffsets } from './wall-frame' import type { WallPlanPoint } from './wall-move' export const WALL_MIN_LENGTH = 0.01 @@ -37,6 +40,40 @@ export type WallInsertionPlan = { resolvedEnd: WallPlanPoint } +/** + * The construction a drawing tool gives the walls it draws, by where each lands: `interior` for a + * wall a closed circuit of walls encloses (a partition, `enclosedWallIds`), `exterior` for every + * other (the outline, an open run, the first walls of a plan). Each is a wall patch, typically an + * assembly and its thickness (`wallAssemblyPatch`). + */ +export type DrawnWallTypes = { + exterior?: Partial + interior?: Partial +} + +/** + * `inserted` (among `levelWalls`, the level's walls with them in) with the drawing tool's wall + * types. A caller who gave a thickness or an assembly keeps it, as a foundation wall keeps the + * foundation construction it was just given. + */ +function withDrawnWallTypes( + levelWalls: WallNode[], + inserted: WallNode[], + given: Partial, + types: DrawnWallTypes | undefined, +): WallNode[] { + if (!(types?.exterior || types?.interior)) return inserted + if (given.assembly !== undefined || given.thickness !== undefined) return inserted + // A curtain wall and a railing have no assembly to give. + if (given.wallType === 'curtain' || given.wallType === 'railing') return inserted + const enclosed = enclosedWallIds(levelWalls.filter((wall) => wall.visible !== false)) + return inserted.map((wall) => { + if (wall.assembly !== undefined) return wall + const patch = enclosed.has(wall.id) ? types.interior : types.exterior + return patch && Object.keys(patch).length > 0 ? WallSchema.parse({ ...wall, ...patch }) : wall + }) +} + export type WallTopologyRejection = { ok: false reason: 'covered-existing-wall' | 'segment-too-short' @@ -185,6 +222,30 @@ function projectPointOntoWallCenterline( } } +/** + * How near `end` of `host` a join on `host` counts as that end: the split + * epsilon, or as far as the bodies of the other walls meeting there reach, so a + * join inside a corner block takes the corner rather than splitting off a stub + * of `host` buried in it. + */ +function cornerBlockReach( + end: WallPlanPoint, + host: WallNode, + walls: WallNode[], + ignoreWallIds: ReadonlySet, +) { + let reach = WALL_SPLIT_ENDPOINT_EPSILON + for (const other of walls) { + if (other.id === host.id || ignoreWallIds.has(other.id)) continue + if (distanceSquared(other.start, end) > 1e-12 && distanceSquared(other.end, end) > 1e-12) { + continue + } + const { a, b } = getWallFaceOffsets(other) + reach = Math.max(reach, a, -b) + } + return reach +} + function nearestWallProjection( point: WallPlanPoint, walls: WallNode[], @@ -199,11 +260,10 @@ function nearestWallProjection( if (!projection) continue const candidateDistance = distanceSquared(point, projection.point) if (candidateDistance > radius * radius || candidateDistance >= bestDistance) continue - const corner = ([wall.start, wall.end] as WallPlanPoint[]).find( - (candidate) => - distanceSquared(projection.point, candidate) <= - WALL_SPLIT_ENDPOINT_EPSILON * WALL_SPLIT_ENDPOINT_EPSILON, - ) + const corner = ([wall.start, wall.end] as WallPlanPoint[]).find((candidate) => { + const reach = cornerBlockReach(candidate, wall, walls, ignoreWallIds) + return distanceSquared(projection.point, candidate) <= reach * reach + }) best = corner ? { wall: null, point: [corner[0], corner[1]], wallT: projection.wallT } : { wall, ...projection } @@ -363,7 +423,10 @@ function segmentCurveOffset(wall: WallNode, startT: number, endT: number) { return arc.direction * arc.radius * (1 - Math.cos(angle / 2)) } -function attachmentSpan(node: AnyNode): { min: number; max: number; center: number } | null { +/** Where a door, window or wall item sits along its wall: metres from the wall start. */ +export function wallAttachmentSpan( + node: AnyNode, +): { min: number; max: number; center: number } | null { if (node.type === 'door') { const door = node as DoorNode return { @@ -470,7 +533,7 @@ export function splitWall( const segmentChildren = segments.map(() => [] as AnyNodeId[]) const updates: WallTopologyChanges['update'] = [] for (const attachment of wallAttachments(wall, nodes)) { - const span = attachmentSpan(attachment) + const span = wallAttachmentSpan(attachment) if (!span) return null const segmentIndex = parameters.slice(0, -1).findIndex((startT, index) => { const endT = parameters[index + 1]! @@ -504,7 +567,20 @@ export function planWallInsertion( end: WallPlanPoint joinRadius: number wallDefaults?: Partial + /** The construction for walls the user draws, by exterior / interior placement. */ + wallTypes?: DrawnWallTypes mintId?: () => string + /** + * Draw along walls that already cover the span instead of refusing: the + * caller has the new wall take that stretch over (`finishWallEdit`). + */ + replaceCovered?: boolean + /** + * Leave the walls the new one meets whole: an end on a wall's body joins + * it there as a T (rooms and miters read the junction off the body), and a + * wall it crosses stays one wall, the new one split either side of it. + */ + keepHostsWhole?: boolean }, ): WallInsertionResult { const walls = Object.values(nodes).filter( @@ -514,7 +590,7 @@ export function planWallInsertion( const startProjection = nearestWallProjection(args.start, walls, args.joinRadius) const resolvedStart = startProjection?.point ?? args.start const resolvedEnd = endProjection?.point ?? args.end - if (wallSegmentsCoverSegment(resolvedStart, resolvedEnd, walls)) { + if (!args.replaceCovered && wallSegmentsCoverSegment(resolvedStart, resolvedEnd, walls)) { return { ok: false, reason: 'covered-existing-wall' } } const crossings = walls @@ -561,12 +637,17 @@ export function planWallInsertion( end: vertices[index + 1]!, }), ) - const insertedWalls = withFoundationConstruction( - nodes, - args.levelId, + const insertedWalls = withDrawnWallTypes( [...walls, ...drawnWalls], - drawnWalls, + withFoundationConstruction( + nodes, + args.levelId, + [...walls, ...drawnWalls], + drawnWalls, + wallProperties, + ), wallProperties, + args.wallTypes, ) const splitWalls = new Map() const addSplitParameter = (wallId: WallNode['id'], wallT: number) => { @@ -577,12 +658,13 @@ export function planWallInsertion( splitWalls.set(wallId, parameters) } for (const projection of [startProjection, endProjection]) { - if (projection?.wall) { + if (projection?.wall && !args.keepHostsWhole) { addSplitParameter(projection.wall.id, projection.wallT) } } for (const crossing of crossings) { if ( + args.keepHostsWhole || crossing.wallT <= WALL_INTERSECTION_EPSILON || crossing.wallT >= 1 - WALL_INTERSECTION_EPSILON ) { @@ -632,6 +714,119 @@ export function planWallInsertion( return { ok: true, plan } } +/** + * The stretches of `start` → `end` no line already closes rooms along: an invisible wall on it or + * a straight wall's reference line. Two lines on top of each other close no room; they leave a + * sliver between them. + */ +function uncoveredStretches( + start: WallPlanPoint, + end: WallPlanPoint, + lines: readonly { start: WallPlanPoint; end: WallPlanPoint }[], +): [WallPlanPoint, WallPlanPoint][] { + const length = Math.hypot(end[0] - start[0], end[1] - start[1]) + const dir = [(end[0] - start[0]) / length, (end[1] - start[1]) / length] as const + const along = (point: WallPlanPoint) => + (point[0] - start[0]) * dir[0] + (point[1] - start[1]) * dir[1] + const at = (t: number): WallPlanPoint => + t <= 0 ? start : t >= length ? end : [start[0] + dir[0] * t, start[1] + dir[1] * t] + const covered = lines + .flatMap((line) => { + if ( + distanceToLine(line.start, start, end) > 1e-3 || + distanceToLine(line.end, start, end) > 1e-3 + ) + return [] + const a = along(line.start) + const b = along(line.end) + const lo = Math.max(0, Math.min(a, b)) + const hi = Math.min(length, Math.max(a, b)) + return hi - lo > 1e-6 ? [[lo, hi] as const] : [] + }) + .sort((a, b) => a[0] - b[0]) + const stretches: [WallPlanPoint, WallPlanPoint][] = [] + let reach = 0 + for (const [lo, hi] of covered) { + if (lo > reach + 1e-6) stretches.push([at(reach), at(lo)]) + reach = Math.max(reach, hi) + } + if (reach < length - 1e-6) stretches.push([at(reach), at(length)]) + return stretches +} + +/** + * An invisible wall drawn `start` → `end` (a room divider): a room separator, so + * rooms close on it as on a wall, but it is no wall and cuts none. An end landing on a wall joins + * it on its reference line, where the room graph meets it (a join inside a corner block takes the + * corner), and the line breaks either side of a wall it crosses so each piece tees into it. A + * stretch drawn over an invisible wall or along a wall's reference line adds nothing. Null when + * nothing is left to add, or it would be too short. + */ +export function planSeparatorInsertion( + nodes: Record, + args: { + levelId: AnyNodeId + start: WallPlanPoint + end: WallPlanPoint + joinRadius: number + mintId?: () => string + }, +): { + changes: WallTopologyChanges + separators: SeparatorNode[] + resolvedStart: WallPlanPoint + resolvedEnd: WallPlanPoint +} | null { + const walls = Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === args.levelId, + ) + const resolvedStart = + nearestWallProjection(args.start, walls, args.joinRadius)?.point ?? args.start + const resolvedEnd = nearestWallProjection(args.end, walls, args.joinRadius)?.point ?? args.end + if (!isSegmentLongEnough(resolvedStart, resolvedEnd)) return null + const crossings = walls + .flatMap((wall) => + isCurvedWall(wall) + ? curvedSegmentIntersections(resolvedStart, resolvedEnd, wall) + : [straightSegmentIntersection(resolvedStart, resolvedEnd, wall)].filter( + (crossing): crossing is WallSegmentIntersection => crossing !== null, + ), + ) + .map((crossing) => joinCrossingAtNearbyWallEndpoint(crossing, walls)) + .filter( + ({ draftT }) => draftT > WALL_INTERSECTION_EPSILON && draftT < 1 - WALL_INTERSECTION_EPSILON, + ) + .sort((left, right) => left.draftT - right.draftT) + const vertices = [resolvedStart] + for (const { point } of crossings) + if (!vertices.some((vertex) => distanceSquared(vertex, point) <= 1e-12)) vertices.push(point) + vertices.push(resolvedEnd) + const lines = [ + ...Object.values(nodes).filter( + (node): node is SeparatorNode => node.type === 'separator' && node.parentId === args.levelId, + ), + ...walls.filter((wall) => !isCurvedWall(wall)), + ] + const separators = vertices + .slice(0, -1) + .flatMap((start, index) => uncoveredStretches(start, vertices[index + 1]!, lines)) + .filter(([from, to]) => isSegmentLongEnough(from, to)) + .map(([start, end]) => + SeparatorNode.parse({ id: args.mintId?.(), parentId: args.levelId, start, end }), + ) + if (!separators.length) return null + return { + changes: { + create: separators.map((node) => ({ node, parentId: args.levelId })), + update: [], + delete: [], + }, + separators, + resolvedStart, + resolvedEnd, + } +} + function distanceToLine(point: WallPlanPoint, a: WallPlanPoint, b: WallPlanPoint) { const length = Math.hypot(b[0] - a[0], b[1] - a[1]) return Math.abs((point[0] - a[0]) * (b[1] - a[1]) - (point[1] - a[1]) * (b[0] - a[0])) / length @@ -739,7 +934,7 @@ function planWallEndJoin( const dz = (after.end[1] - after.start[1]) / length const oldLength = wallLength(before) for (const attachment of wallAttachments(before, adjusted)) { - const span = attachmentSpan(attachment) + const span = wallAttachmentSpan(attachment) if (!span) return false const oldPoint = getWallCurveFrameAt(before, span.center / oldLength).point const projection = isCurvedWall(after) diff --git a/packages/core/src/systems/wall/wall-types.test.ts b/packages/core/src/systems/wall/wall-types.test.ts new file mode 100644 index 0000000000..1c1bf13589 --- /dev/null +++ b/packages/core/src/systems/wall/wall-types.test.ts @@ -0,0 +1,243 @@ +import { describe, expect, test } from 'bun:test' +import type { AnyNode, WallNode } from '../../schema' +import { SiteNode } from '../../schema/nodes/site' +import { DEFAULT_INTERIOR_WALL_PAINT } from '../../schema/nodes/wall' +import { newWallTypeId, WallTypeDefinition } from '../../schema/wall-type' +import { + assemblyThickness, + GYPSUM_HALF, + getWallAssemblyPreset, + isExteriorWallAssembly, + projectWallTypes, + STUCCO_3_COAT, + STUD_2X6, + STUD_SPACING_24, + WALL_ASSEMBLY_PRESETS, + WALL_FINISH_LIBRARY_REF, + WALL_TYPE_CORES, + WSP_SHEATHING, + wallAssemblyFaceFinish, + wallAssemblyFinishRef, + wallAssemblyFinishSlots, + wallAssemblyFraming, + wallAssemblyPatch, + wallAssemblyToLegacy, + wallCladdingProducts, + wallTypeAssembly, + wallTypeCoreOf, +} from './wall-assembly' + +const IN = 0.0254 +const core = (id: string) => WALL_TYPE_CORES.find((entry) => entry.id === id)! + +/** A 2x6 at 24 in stucco wall with an inch of foam outside the sheathing. */ +const STUCCO_FOAM = wallTypeAssembly('custom-stucco-foam', [ + { role: 'finish', material: 'stucco', thickness: STUCCO_3_COAT }, + { role: 'insulation', material: 'rigid-foam', thickness: 1 * IN }, + { role: 'sheathing', material: 'osb', thickness: WSP_SHEATHING }, + { + role: 'structure', + core: true, + material: 'wood', + thickness: core('wood-2x6-24').thickness, + spacing: core('wood-2x6-24').spacing, + }, + { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF }, +]) + +function wall(assembly = getWallAssemblyPreset('exterior-2x6-siding')!.assembly, extra = {}) { + return { + id: 'wall_a', + type: 'wall', + children: [], + start: [0, 0], + end: [4, 0], + frontSide: 'exterior', + backSide: 'interior', + ...wallAssemblyPatch(assembly), + ...extra, + } as unknown as WallNode +} + +describe('wall type placement, read from the stack', () => { + test('each preset lists under exterior when it has a cladding side, interior when both faces are lined', () => { + const exterior = WALL_ASSEMBLY_PRESETS.filter((preset) => + isExteriorWallAssembly(preset.assembly), + ).map((preset) => preset.id) + expect(exterior).toEqual([ + 'exterior-2x4-siding', + 'exterior-2x6-siding', + 'exterior-2x6-stucco', + 'exterior-2x6-brick', + 'exterior-cmu-stucco', + ]) + const interior = WALL_ASSEMBLY_PRESETS.filter( + (preset) => !isExteriorWallAssembly(preset.assembly), + ).map((preset) => preset.id) + // The furred block wall is drywall on both faces: a partition by its own layers. + expect(interior).toEqual([ + 'interior-2x4-drywall', + 'interior-2x6-plumbing', + 'cmu-8-furred-drywall', + ]) + }) + + test('a stack lined on one face only has an exterior side; bare on both is a partition', () => { + const block = { role: 'structure', core: true, material: 'cmu', thickness: 0.19 } as const + const lining = { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF } as const + expect(isExteriorWallAssembly(wallTypeAssembly('custom-a', [block, lining]))).toBe(true) + expect(isExteriorWallAssembly(wallTypeAssembly('custom-b', [lining, block, lining]))).toBe( + false, + ) + expect(isExteriorWallAssembly(wallTypeAssembly('custom-c', [block]))).toBe(false) + expect(isExteriorWallAssembly(STUCCO_FOAM)).toBe(true) + }) +}) + +describe('project wall types', () => { + test('the layers set the thickness, and the core frames at its size and spacing', () => { + const patch = wallAssemblyPatch(STUCCO_FOAM) + const expected = STUCCO_3_COAT + 1 * IN + WSP_SHEATHING + STUD_2X6 + GYPSUM_HALF + expect(patch.thickness).toBeCloseTo(expected, 12) + expect(assemblyThickness(STUCCO_FOAM)).toBeCloseTo(expected, 12) + expect(wallAssemblyFraming({ assembly: STUCCO_FOAM })).toEqual({ + depth: STUD_2X6, + kind: 'wood', + }) + const framing = STUCCO_FOAM.layers.find((layer) => layer.core)! + expect(framing.spacing).toBeCloseTo(STUD_SPACING_24, 12) + expect(wallTypeCoreOf(STUCCO_FOAM)?.id).toBe('wood-2x6-24') + }) + + test('layers take the ids the presets use, so a stack WS5 can express stays editable', () => { + expect(STUCCO_FOAM.layers.map((layer) => layer.id)).toEqual([ + 'exterior', + 'insulation', + 'sheathing', + 'framing', + 'interior', + ]) + const plain = wallTypeAssembly('custom-plain', [ + { role: 'finish', material: 'siding', thickness: 0.75 * IN }, + { role: 'sheathing', material: 'osb', thickness: WSP_SHEATHING }, + { role: 'structure', core: true, material: 'wood', thickness: STUD_2X6 }, + { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF }, + ]) + expect(wallAssemblyToLegacy(plain)?.framing.depth).toBe(STUD_2X6) + // A preset's own core (no spacing: the framer's default) reads as the 16 in one. + expect(wallTypeCoreOf(getWallAssemblyPreset('exterior-2x6-siding')!.assembly)?.id).toBe( + 'wood-2x6-16', + ) + expect(wallTypeCoreOf(getWallAssemblyPreset('exterior-cmu-stucco')!.assembly)?.id).toBe('cmu-8') + }) + + test('a project type round-trips through the site schema and a save, and legacy sites are unchanged', () => { + const id = newWallTypeId() + expect(id).toMatch(/^custom-[a-z0-9]{16}$/) + const type = WallTypeDefinition.parse({ + id, + label: '2x6 stucco + foam', + assembly: { ...STUCCO_FOAM, presetId: id }, + }) + const legacy = SiteNode.parse({ id: 'site_lot', children: ['building_house'] }) + expect(legacy.wallTypes).toBeUndefined() + const saved = JSON.parse(JSON.stringify({ ...legacy, wallTypes: [type] })) + const restored = SiteNode.parse(saved) + expect(restored.wallTypes).toEqual([type]) + const nodes = { [restored.id]: restored } as unknown as Record + expect(projectWallTypes(nodes, [restored.id])).toEqual([type]) + }) + + test('a type without a core, or with a preset-style id, does not parse; a bad saved entry is skipped', () => { + const coreless = { ...STUCCO_FOAM, layers: STUCCO_FOAM.layers.filter((layer) => !layer.core) } + expect( + WallTypeDefinition.safeParse({ id: 'custom-x', label: 'X', assembly: coreless }).success, + ).toBe(false) + expect( + WallTypeDefinition.safeParse({ id: 'exterior-2x6-siding', label: 'X', assembly: STUCCO_FOAM }) + .success, + ).toBe(false) + const good = { id: 'custom-good', label: 'Good', assembly: STUCCO_FOAM } + const site = { + ...SiteNode.parse({ id: 'site_lot' }), + wallTypes: [{ id: 'custom-bad', label: '' }, good, { ...good, label: 'Repeat' }], + } + const nodes = { [site.id]: site } as unknown as Record + expect(projectWallTypes(nodes, [site.id]).map((type) => type.label)).toEqual(['Good']) + }) +}) + +describe('finishes picked while drafting', () => { + const sage = 'library:siding-lap-sage' + const blush = 'library:preset-blush' + + test('the cladding product goes on the cladding layer and the paint on every lining', () => { + const assembly = getWallAssemblyPreset('exterior-2x6-siding')!.assembly + expect(wallAssemblyFinishSlots(assembly, { cladding: sage, paint: blush })).toEqual({ + 'layer:exterior': sage, + 'layer:interior': blush, + }) + const partition = getWallAssemblyPreset('interior-2x4-drywall')!.assembly + expect(wallAssemblyFinishSlots(partition, { cladding: sage, paint: blush })).toEqual({ + 'layer:interior-back': blush, + 'layer:interior': blush, + }) + }) + + test('the defaults write nothing, and a product that does not dress the cladding is refused', () => { + const siding = getWallAssemblyPreset('exterior-2x6-siding')!.assembly + expect( + wallAssemblyFinishSlots(siding, { + cladding: WALL_FINISH_LIBRARY_REF.siding, + paint: DEFAULT_INTERIOR_WALL_PAINT, + }), + ).toEqual({}) + const stucco = getWallAssemblyPreset('exterior-2x6-stucco')!.assembly + expect(wallAssemblyFinishSlots(stucco, { cladding: sage })).toEqual({}) + expect(wallAssemblyFinishSlots(stucco, { cladding: 'library:preset-sand' })).toEqual({ + 'layer:exterior': 'library:preset-sand', + }) + }) + + test('each kind offers its own products, its default among them', () => { + for (const kind of ['siding', 'fiber-cement', 'stucco', 'brick']) { + const ids = wallCladdingProducts(kind).map((item) => `library:${item.id}`) + expect(ids).toContain(WALL_FINISH_LIBRARY_REF[kind]!) + } + expect(wallCladdingProducts('siding').every((item) => item.category === 'siding')).toBe(true) + expect(wallCladdingProducts('brick').every((item) => item.category === 'brick')).toBe(true) + expect(wallCladdingProducts('none')).toEqual([]) + }) + + test('the faces follow the exterior side: cladding outside, paint inside, either way round', () => { + const slots = { 'layer:exterior': sage, 'layer:interior': blush } + const front = wall(undefined, { slots }) + expect(wallAssemblyFinishRef(front)).toBe(sage) + expect(wallAssemblyFaceFinish(front, 'a')).toBe(sage) + expect(wallAssemblyFaceFinish(front, 'b')).toBe(blush) + const back = wall(undefined, { slots, frontSide: 'interior', backSide: 'exterior' }) + expect(wallAssemblyFaceFinish(back, 'a')).toBe(blush) + expect(wallAssemblyFaceFinish(back, 'b')).toBe(sage) + // No picks: the kind's own cladding outside, nothing inside (the slot default shows). + const plain = wall() + expect(wallAssemblyFaceFinish(plain, 'a')).toBe(WALL_FINISH_LIBRARY_REF.siding!) + expect(wallAssemblyFaceFinish(plain, 'b')).toBeNull() + }) + + test('a partition shows its paint on both faces', () => { + const partition = wall(getWallAssemblyPreset('interior-2x4-drywall')!.assembly, { + frontSide: 'interior', + backSide: 'interior', + slots: { 'layer:interior-back': blush, 'layer:interior': blush }, + }) + expect(wallAssemblyFaceFinish(partition, 'a')).toBe(blush) + expect(wallAssemblyFaceFinish(partition, 'b')).toBe(blush) + }) + + test('a product picked for siding gives way once the cladding is changed to stucco', () => { + const changed = wall(getWallAssemblyPreset('exterior-2x6-stucco')!.assembly, { + slots: { 'layer:exterior': sage }, + }) + expect(wallAssemblyFinishRef(changed)).toBe(WALL_FINISH_LIBRARY_REF.stucco!) + }) +}) diff --git a/packages/core/src/utils/structure-id.ts b/packages/core/src/utils/structure-id.ts index 0d86d0bec2..afd6dc2638 100644 --- a/packages/core/src/utils/structure-id.ts +++ b/packages/core/src/utils/structure-id.ts @@ -51,6 +51,10 @@ function remapReferences(value: T, ids: Map): // Only newly minted rooms are remapped; pick their colour from the final id before persistence. if (fields.type === 'zone' && typeof fields.id === 'string' && ids.has(fields.id)) changes.color = zoneColorForSeed(ids.get(fields.id)!) + // A porch's post or beam names its porch (`lib/porch-framing-sync`). + const metadata = fields.metadata as { porchOf?: unknown } | undefined + if (typeof metadata?.porchOf === 'string' && ids.has(metadata.porchOf)) + changes.metadata = { ...metadata, porchOf: ids.get(metadata.porchOf) } for (const key of ['children', 'zoneIds', 'members']) { const references = fields[key] if (Array.isArray(references) && references.some((id) => ids.has(id))) { diff --git a/packages/core/src/utils/wall-face-migration.test.ts b/packages/core/src/utils/wall-face-migration.test.ts index 64e7853f8b..ea2feaee25 100644 --- a/packages/core/src/utils/wall-face-migration.test.ts +++ b/packages/core/src/utils/wall-face-migration.test.ts @@ -38,13 +38,18 @@ function oldSide(node: Raw, face: Face): Side { : 'exterior' } -function oldChain(node: Raw, side: Side): string { +function oldChain(node: Raw, face: Face, side: Side): string { const ref = node.slots?.[side] if (ref) return `ref:${ref}` const spec = getEffectiveWallSurfaceMaterial(node, side) if (spec.materialPreset) return `preset:${spec.materialPreset}` if (spec.material) return `inline:${JSON.stringify(spec.material)}` - return 'default:library:concrete-drywall' + // The default the migration leaves alone: raw drywall then; now the interior paint, with raw + // drywall kept on a face whose stored side is exterior. + const stored = face === 'a' ? node.frontSide : node.backSide + return stored === 'exterior' + ? 'default:library:concrete-drywall' + : 'default:library:preset-seattlegrey' } function oldBands(node: Raw) { @@ -73,7 +78,7 @@ function oldBands(node: Raw) { function oldFaceFinish(node: Raw, face: Face, y: number): string { const side = oldSide(node, face) const bands = oldBands(node) - if (!bands.enabled) return oldChain(node, side) + if (!bands.enabled) return oldChain(node, face, side) const band = y < bands.lowerTop ? 'lower' @@ -85,7 +90,7 @@ function oldFaceFinish(node: Raw, face: Face, y: number): string { ? 'top' : 'upper' const ref = node.slots?.[`${band}${SUFFIX[side]}`] - return ref ? `ref:${ref}` : oldChain(node, side) + return ref ? `ref:${ref}` : oldChain(node, face, side) } function oldTrimFace(node: Raw, side: Side): Face { diff --git a/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts b/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts new file mode 100644 index 0000000000..a173a8a47e --- /dev/null +++ b/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts @@ -0,0 +1,76 @@ +import { expect, test } from 'bun:test' +import { type AnyNode, BuildingNode, LevelNode, WallNode } from '@pascal-app/core' +import { normalizePersistedEditorLayoutState } from '../../store/use-editor' +import { adjacentWallLevels, adjacentWallOutlines } from './floorplan-adjacent-walls-layer' + +function house() { + const building = BuildingNode.parse({ id: 'building_adj' }) + const levels = ['ground', 'upper', 'roof'].map((name, level) => + LevelNode.parse({ + id: `level_adj_${name}`, + parentId: building.id, + level, + height: 3, + ...(name === 'roof' ? { metadata: { role: 'roof' } } : {}), + }), + ) + building.children = levels.map((level) => level.id) + const walls = [ + WallNode.parse({ + id: 'wall_adj_a', + parentId: levels[1]!.id, + start: [0, 0], + end: [6, 0], + thickness: 0.2, + }), + WallNode.parse({ + id: 'wall_adj_b', + parentId: levels[1]!.id, + start: [6, 0], + end: [6, 4], + thickness: 0.2, + }), + WallNode.parse({ id: 'wall_adj_c', parentId: levels[0]!.id, start: [0, 0], end: [6, 0] }), + ] + return Object.fromEntries( + [building, ...levels, ...walls].map((node) => [node.id, node]), + ) as Record +} + +test('the roof plan traces the top storey’s walls below, both faces, joined at the corner', () => { + const nodes = house() + expect(adjacentWallLevels(nodes, 'level_adj_roof', { below: true, above: true })).toEqual({ + below: 'level_adj_upper', + above: null, + }) + expect(adjacentWallLevels(nodes, 'level_adj_upper', { below: true, above: false })).toEqual({ + below: 'level_adj_ground', + above: null, + }) + const outlines = adjacentWallOutlines(nodes, 'level_adj_upper') + expect(outlines.map((outline) => outline.id).sort()).toEqual(['wall_adj_a', 'wall_adj_b']) + const points = outlines + .find((outline) => outline.id === 'wall_adj_a')! + .points.split(' ') + .map((pair) => pair.split(',').map(Number) as [number, number]) + const zs = points.map(([, z]) => z) + // Both faces of the 0.2 m wall, and mitred into the corner at x = 6 (past the centreline). + expect(Math.max(...zs) - Math.min(...zs)).toBeCloseTo(0.2) + expect(Math.max(...points.map(([x]) => x))).toBeCloseTo(6.1) +}) + +test('walls below start on for the roof plan and off for the floor plans, each kept as set', () => { + const fresh = normalizePersistedEditorLayoutState(undefined) + expect(fresh.showRoofWallsBelow).toBe(true) + expect(fresh.showWallsBelow).toBe(false) + // Saved before the roof plan had its own setting: the roof plan still starts on. + expect(normalizePersistedEditorLayoutState({ showWallsBelow: false }).showRoofWallsBelow).toBe( + true, + ) + const turnedOff = normalizePersistedEditorLayoutState({ + showRoofWallsBelow: false, + showWallsBelow: true, + }) + expect(turnedOff.showRoofWallsBelow).toBe(false) + expect(turnedOff.showWallsBelow).toBe(true) +}) diff --git a/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx b/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx new file mode 100644 index 0000000000..9c6dcc0908 --- /dev/null +++ b/packages/editor/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx @@ -0,0 +1,186 @@ +'use client' + +import { + type AnyNode, + type AnyNodeId, + calculateLevelMiters, + getLevelAbove, + getLevelBelow, + getWallPlanFootprint, + type LevelNode, + type Point2D, + servesAsRoofLevel, + useScene, + type WallNode, +} from '@pascal-app/core' +import { memo, useMemo } from 'react' +import { useShallow } from 'zustand/react/shallow' +import useEditor from '../../store/use-editor' +import { useLevelWallCopyOffer } from '../ui/level-wall-copy-prompt' + +/** Walls below read blue, walls above magenta: told apart at a glance and from the active walls. */ +export const ADJACENT_WALL_COLORS = { below: '#2563eb', above: '#c026d3' } as const + +export type AdjacentWallOutline = { id: string; points: string } + +/** + * Each visible wall of `levelId` as the outline of its full thickness (both faces, joined at the + * corners as the level draws them), in plan metres. + */ +export function adjacentWallOutlines( + nodes: Record, + levelId: string | null | undefined, +): AdjacentWallOutline[] { + if (!levelId) return [] + const walls = Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && node.parentId === levelId && node.visible !== false, + ) + if (!walls.length) return [] + const miters = calculateLevelMiters(walls) + return walls.flatMap((wall) => { + const polygon: Point2D[] = getWallPlanFootprint(wall, miters) + return polygon.length >= 3 + ? [{ id: wall.id, points: polygon.map((point) => `${point.x},${point.y}`).join(' ') }] + : [] + }) +} + +/** + * The levels directly below and above `levelId` in its building's stack (by ordinal, as the + * storey stack orders them): on a roof level, "below" is the top storey, the walls the roof bears + * on. Reads only the building's levels, so a plan selector can call it on every scene change. + */ +export function adjacentLevelIds( + nodes: Record, + levelId: string | null | undefined, +): { below: string | null; above: string | null } { + const level = levelId ? nodes[levelId] : undefined + if (level?.type !== 'level') return { below: null, above: null } + const building = level.parentId ? nodes[level.parentId] : undefined + if (building?.type !== 'building') { + const graph = nodes as Record + return { + below: getLevelBelow(level.id, graph)?.id ?? null, + above: getLevelAbove(level.id, graph)?.id ?? null, + } + } + let below: LevelNode | null = null + let above: LevelNode | null = null + for (const id of building.children) { + const other = nodes[id] + if (other?.type !== 'level' || other.id === level.id) continue + if (other.level < level.level && (!below || other.level > below.level)) below = other + if (other.level > level.level && (!above || other.level < above.level)) above = other + } + return { below: below?.id ?? null, above: above?.id ?? null } +} + +/** The adjacent levels whose walls the plan traces: each only while it is shown. */ +export function adjacentWallLevels( + nodes: Record, + levelId: string | null | undefined, + show: { below: boolean; above: boolean }, +): { below: string | null; above: string | null } { + const levels = adjacentLevelIds(nodes, levelId) + return { below: show.below ? levels.below : null, above: show.above ? levels.above : null } +} + +/** + * The "Walls below" setting that applies on `levelId`: the roof plan's, on until turned off, on a + * roof level; the floor plans', off until turned on, on a storey. + */ +export function useWallsBelowSetting(levelId: string | null | undefined): { + onRoofLevel: boolean + shown: boolean + setShown: (show: boolean) => void +} { + const onRoofLevel = useScene((state) => { + const level = levelId ? state.nodes[levelId as AnyNodeId] : undefined + return level?.type === 'level' && servesAsRoofLevel(state.nodes, level) + }) + const shown = useEditor((state) => + onRoofLevel ? state.showRoofWallsBelow : state.showWallsBelow, + ) + const setRoof = useEditor((state) => state.setShowRoofWallsBelow) + const setFloor = useEditor((state) => state.setShowWallsBelow) + return { onRoofLevel, shown, setShown: onRoofLevel ? setRoof : setFloor } +} + +/** The level's own walls, read through its children rather than the whole scene. */ +function useLevelWalls(levelId: string | null) { + return useScene( + useShallow((state) => { + const level = levelId ? state.nodes[levelId as AnyNodeId] : undefined + if (level?.type !== 'level') return [] as WallNode[] + return level.children.flatMap((id) => { + const node = state.nodes[id as AnyNodeId] + return node?.type === 'wall' ? [node] : [] + }) + }), + ) +} + +function Outlines({ color, outlines }: { color: string; outlines: AdjacentWallOutline[] }) { + return ( + <> + {outlines.map((outline) => ( + + ))} + + ) +} + +/** + * "Walls below / above": dashed outlines of the walls on the neighbouring levels, drawn over the + * active level's plan so second-storey walls line up with the ones under them and a roof outline + * can be checked against the walls it covers — while its toggle is on, or while a new level's + * "Copy exterior walls?" offer is open. Display only: never hit-tested, never snapped to. + */ +export const FloorplanAdjacentWallsLayer = memo(function FloorplanAdjacentWallsLayer({ + levelId, +}: { + levelId: string | null | undefined +}) { + const offer = useLevelWallCopyOffer((state) => + state.offer && state.offer.levelId === levelId ? state.offer.direction : null, + ) + const showBelow = useWallsBelowSetting(levelId).shown || offer === 'below' + const showAbove = useEditor((state) => state.showWallsAbove) || offer === 'above' + const levels = useScene( + useShallow((state) => + adjacentWallLevels(state.nodes as Record, levelId, { + below: showBelow, + above: showAbove, + }), + ), + ) + const belowWalls = useLevelWalls(levels.below) + const aboveWalls = useLevelWalls(levels.above) + const below = useMemo( + () => adjacentWallOutlines(Object.fromEntries(belowWalls.map((w) => [w.id, w])), levels.below), + [belowWalls, levels.below], + ) + const above = useMemo( + () => adjacentWallOutlines(Object.fromEntries(aboveWalls.map((w) => [w.id, w])), levels.above), + [aboveWalls, levels.above], + ) + if (!(below.length || above.length)) return null + return ( + + + + + ) +}) diff --git a/packages/editor/src/components/editor-2d/floorplan-alignment-guide-layer.tsx b/packages/editor/src/components/editor-2d/floorplan-alignment-guide-layer.tsx index 8478747b71..1cda92192b 100644 --- a/packages/editor/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +++ b/packages/editor/src/components/editor-2d/floorplan-alignment-guide-layer.tsx @@ -2,7 +2,7 @@ import { useViewer } from '@pascal-app/viewer' import { memo } from 'react' -import useAlignmentGuides from '../../store/use-alignment-guides' +import useAlignmentGuides, { type DraftGuideLine } from '../../store/use-alignment-guides' import { formatMeasurement } from '../editor/measurement-pill' import { useFloorplanRender } from './floorplan-render-context' @@ -24,16 +24,29 @@ import { useFloorplanRender } from './floorplan-render-context' * * Stroke widths and handle radii are scaled by `unitsPerPixel` so they * stay a constant size on screen no matter the zoom. + * + * A wall pulled from a wall end (and the run it continues) publishes its + * projection guides beside them (`useAlignmentGuides.draft`): dashed lines — + * the extension of the wall it comes from in amber, lines through other walls' + * ends in indigo, lines through the run's start in emerald — and a ringed ✕ + * where two of them cross, matching the 3D layer. */ -export const FloorplanAlignmentGuideLayer = memo(function FloorplanAlignmentGuideLayer() { +export const FloorplanAlignmentGuideLayer = memo(function FloorplanAlignmentGuideLayer({ + unitsPerPixel, +}: { + /** The plan's live scale, which keeps a wall draft's guides screen-sized. */ + unitsPerPixel?: number +}) { const guides = useAlignmentGuides((s) => s.guides) + const draft = useAlignmentGuides((s) => s.draft) const unit = useViewer((s) => s.unit) const metricNotation = useViewer((s) => s.metricNotation) const ctx = useFloorplanRender() - if (guides.length === 0) return null + if (guides.length === 0 && !draft) return null const upp = ctx?.unitsPerPixel ?? 0.01 + const draftUpp = unitsPerPixel ?? upp const sceneRot = ctx?.sceneRotationDeg ?? 0 // Pixel-budgeted sizes converted to world meters so visuals stay @@ -50,6 +63,47 @@ export const FloorplanAlignmentGuideLayer = memo(function FloorplanAlignmentGuid return ( + {draft?.lines.map((line, i) => ( + + ))} + {draft?.intersections.map((point, i) => ( + + + + + + ))} {guides.map((guide, i) => { const { from, to, axis } = guide const midX = (from.x + to.x) / 2 @@ -107,6 +161,12 @@ export const FloorplanAlignmentGuideLayer = memo(function FloorplanAlignmentGuid ) }) +const DRAFT_GUIDE_COLORS: Record = { + extension: '#f59e0b', // amber-500 + alignment: '#6366f1', // indigo-500 — the snap marker's colour + 'run-start': '#10b981', // emerald-500 — the pull grip's colour +} + function XCap({ color, size, diff --git a/packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx b/packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx new file mode 100644 index 0000000000..dd18446d22 --- /dev/null +++ b/packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx @@ -0,0 +1,243 @@ +'use client' + +import { + type AnyNode, + calculateLevelMiters, + type FloorplanGeometry, + type FoundationPlanShape, + foundationPlan, + foundationViewOf, + getWallPlanFootprint, + traceFoundationOutlines, + union, + useScene, + type WallNode, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { memo, useMemo } from 'react' +import { + foundationDimensionStrings, + foundationOutlineRings, +} from '../../lib/floorplan/foundation-dimensions' +import { editFoundationOutline, houseFoundationOutlines } from '../../lib/foundation-view' +import { formatLinearMeasurement } from '../../lib/measurements' +import useEditor from '../../store/use-editor' +import { + useFloorplanSceneRotation, + useFloorplanStaticUnitsPerPixel, +} from './floorplan-render-context' +import { FloorplanDimensionStringRenderer } from './renderers/floorplan-dimension-renderer' + +type P = [number, number] + +/** Concrete in plan: grey that reads on the light and the dark plan alike. */ +const CONCRETE = { fill: '#9ca3af', stroke: '#6b7280' } as const +/** The walls over the foundation, a faint ghost: dashes are the foundation's hidden lines alone. */ +const WALLS_OVER = '#64748b' +/** Concrete under other concrete (a slab's turned-down edge, a footing under its stem, a pile) is dashed. */ +const HIDDEN_DASH = '6 4' +/** The dimension rows stand this far out of the foundation and apart, on screen, pixels. */ +const DIMENSION_ROW_PX = 30 +/** An extension line runs this far past its dimension line, pixels. */ +const DIMENSION_OVERSHOOT_PX = 6 +/** The dimensions' text and ticks at this many pixels a point: about 12 px text. */ +const DIMENSION_PX_PER_PT = 1.5 + +const FILL_OPACITY: Record = { + slab: 0.12, + turndown: 0, + footing: 0, + 'grade-beam': 0.35, + stem: 0.45, + // Over its stem: the plan draws the stem it stands on. + cripple: 0, + pile: 0, +} + +export type FoundationPlanDrawing = { + shapes: FoundationPlanShape[] + /** The walls the foundation carries, merged: rings drawn as one shape. */ + walls: P[][] +} + +/** The walls of `levelId` as one merged footprint: no seams where they meet at the corners. */ +function wallsFootprint(nodes: Record, levelId: string | null): P[][] { + if (!levelId) return [] + const walls = Object.values(nodes).filter( + (node): node is WallNode => + node.type === 'wall' && node.parentId === levelId && node.visible !== false, + ) + if (walls.length === 0) return [] + const miters = calculateLevelMiters(walls) + const footprints = walls + .map((wall) => getWallPlanFootprint(wall, miters).map(({ x, y }) => [x, y] as P)) + .filter((ring) => ring.length >= 3) + return union(footprints).flatMap((polygon) => [polygon.outer, ...polygon.holes]) +} + +/** What the foundation view draws for `buildingId`: its foundation plan, and the walls it carries. */ +export function foundationPlanDrawing( + nodes: Record, + buildingId: string, +): FoundationPlanDrawing { + const graph = nodes as Parameters[0] + return { + shapes: foundationPlan(graph, buildingId), + walls: wallsFootprint(nodes, foundationViewOf(graph, buildingId).wallLevelId), + } +} + +/** Closed rings as one SVG path. */ +function pathOf(rings: ReadonlyArray>): string { + return rings + .filter((ring) => ring.length >= 3) + .map((ring) => `M ${ring.map(([x, z]) => `${x} ${z}`).join(' L ')} Z`) + .join(' ') +} + +function Shape({ shape }: { shape: FoundationPlanShape }) { + const common = { + 'data-foundation-hidden': shape.hidden ? '' : undefined, + 'data-foundation-member': shape.kind, + fill: shape.filled ? CONCRETE.fill : 'none', + fillOpacity: shape.filled ? FILL_OPACITY[shape.kind] : undefined, + stroke: CONCRETE.stroke, + strokeDasharray: shape.hidden ? HIDDEN_DASH : undefined, + strokeWidth: shape.hidden ? 1.1 : 1.2, + vectorEffect: 'non-scaling-stroke' as const, + } + if (shape.kind === 'pile') + return + return +} + +/** + * The foundation view's plan (`useViewer.foundationView`): the concrete the engine draws under the + * building, from above, under the walls it carries ghosted faintly. A slab is outlined, its + * turned-down edge dashed where it ends inside; a stem wall is drawn solid, its footing dashed. + * The outlines and footings drawn on the level stay the registry layer's, selectable and editable + * over it; while the house's outline follows the walls, a click on it makes it one of its own to + * drag (`editFoundationOutline`). Dimensioned all round (`foundationDimensionStrings`). + */ +export const FloorplanFoundationLayer = memo(function FloorplanFoundationLayer() { + const on = useViewer((state) => state.foundationView) + const buildingId = useViewer((state) => state.selection.buildingId) + const selecting = useEditor((state) => state.mode === 'select') + const nodes = useScene((state) => state.nodes) + const drawing = useMemo( + () => + on && buildingId ? foundationPlanDrawing(nodes as Record, buildingId) : null, + [on, buildingId, nodes], + ) + const traced = useMemo(() => { + if (!(on && selecting && buildingId)) return [] + const graph = nodes as Parameters[0] + return houseFoundationOutlines(graph, buildingId).length === 0 + ? traceFoundationOutlines(graph, buildingId) + : [] + }, [on, selecting, buildingId, nodes]) + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const unitsPerPixel = useFloorplanStaticUnitsPerPixel() + const rings = useMemo( + () => + on && buildingId ? foundationOutlineRings(nodes as Record, buildingId) : [], + [on, buildingId, nodes], + ) + // Sized on screen: the rows, the text and the ticks keep their size in pixels at any zoom. + const dimensions = useMemo( + () => + foundationDimensionStrings( + rings, + (meters) => formatLinearMeasurement(meters, unit, metricNotation), + { + rows: [DIMENSION_ROW_PX * unitsPerPixel, 2 * DIMENSION_ROW_PX * unitsPerPixel], + overshoot: DIMENSION_OVERSHOOT_PX * unitsPerPixel, + }, + ), + [rings, unit, metricNotation, unitsPerPixel], + ) + const sceneRotationDeg = useFloorplanSceneRotation() + if (!(drawing && buildingId)) return null + return ( + <> + + + {traced.length > 0 && ( + { + // The plan's background click would clear the selection the edit makes. + event.stopPropagation() + editFoundationOutline(buildingId) + }} + pointerEvents="stroke" + stroke="transparent" + strokeWidth={14} + style={{ cursor: 'pointer' }} + vectorEffect="non-scaling-stroke" + > + Click to edit the foundation’s outline + + )} + + ) +}) + +/** The foundation plan's dimension strings, drawn as the plan draws every dimension. */ +export function FoundationDimensionsGroup({ + strings, + sceneRotationDeg = 0, + annotationUnitsPerPoint, +}: { + strings: readonly Extract[] + sceneRotationDeg?: number + /** Plan units a point: sizes the text and ticks on screen. */ + annotationUnitsPerPoint?: number +}) { + if (strings.length === 0) return null + return ( + + {strings.map((geometry, index) => ( + // Strings have no ids of their own: their order is the sides', round the foundation. + + ))} + + ) +} + +/** The drawing itself, in plan metres: the walls' ghost, then the concrete bottom to top. */ +export function FoundationPlanGroup({ drawing }: { drawing: FoundationPlanDrawing }) { + return ( + + {drawing.walls.length > 0 && ( + + )} + {drawing.shapes.map((shape, index) => ( + // Shapes have no ids of their own; their order is the plan's, bottom to top. + + ))} + + ) +} diff --git a/packages/editor/src/components/editor-2d/floorplan-group-move.tsx b/packages/editor/src/components/editor-2d/floorplan-group-move.tsx index 28c7d11d90..6fb2a1ab48 100644 --- a/packages/editor/src/components/editor-2d/floorplan-group-move.tsx +++ b/packages/editor/src/components/editor-2d/floorplan-group-move.tsx @@ -607,7 +607,7 @@ const GROUP_BOX_ROTATE_CURSOR_STYLE = { * participants — what a group drag carries (connected walls outside the * selection stretch at their shared ends to stay joined) — and IS the group's * drag handle: press anywhere - * inside it to slide the group, click to pick it up. Holding a selection + * inside it and drag to slide the group; a click alone leaves it in place. Holding a selection * modifier (Cmd/Ctrl/Shift) lets pointer events pass through so members under * the box can still be toggled in and out. Rides the live drag delta so it * tracks the group mid-gesture. Mounted inside the floor-plan scene ``, so diff --git a/packages/editor/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx b/packages/editor/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx index 4a6fb14f7b..c5aa96d9bb 100644 --- a/packages/editor/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +++ b/packages/editor/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx @@ -1,6 +1,6 @@ 'use client' -import type { OpenWallEnd } from '@pascal-app/core' +import type { AnyNodeId, OpenWallEnd } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { memo, useEffect, useState } from 'react' import { createPortal } from 'react-dom' @@ -31,7 +31,8 @@ function sceneScreenPoint(point: [number, number]): { left: number; top: number * Shows where walls look joined but are not, so a room can't close: a red dot * on each open end, a dashed line to the wall it nearly meets with what is * wrong ("4 cm gap"), and — while drawing — the walls that bound no room dimmed. Hover or click a - * dot for "Join walls", which applies core's join planner as one undo step. + * dot for "Join walls", which applies core's join planner as one undo step; a + * click on a dot also selects its wall. * * On while walls or rooms are being drawn (the wall tool's variants, Divide); * otherwise only when a wall that bounds no room has a near miss. @@ -39,7 +40,9 @@ function sceneScreenPoint(point: [number, number]): { left: number; top: number export const FloorplanOpenWallEndsLayer = memo(function FloorplanOpenWallEndsLayer() { const visible = useEditor((state) => isViewVisible(state, VIEW_2D)) const suppressed = useOpenWallEndsSuppressed() - return visible && !suppressed ? : null + // The foundation view ghosts the walls: their open ends are not what it is about. + const foundationView = useViewer((state) => state.foundationView) + return visible && !suppressed && !foundationView ? : null }) const ActiveOpenWallEndsLayer = memo(function ActiveOpenWallEndsLayer() { @@ -76,7 +79,14 @@ const ActiveOpenWallEndsLayer = memo(function ActiveOpenWallEndsLayer() { label={openWallEndLabel(end, unit)} onHoverEnd={focus.releaseHover} onHoverStart={() => focus.keepHover(key)} - onPin={() => focus.togglePin(key)} + onPin={() => { + focus.togglePin(key) + // The dot can cover all of a wall a few centimetres long: its + // click selects that wall too, so it can be fixed or deleted. + if (!drawing && useEditor.getState().mode === 'select') { + useViewer.getState().setSelection({ selectedIds: [end.wallId as AnyNodeId] }) + } + }} sceneRotationDeg={sceneRotationDeg} unitsPerPixel={unitsPerPixel} /> diff --git a/packages/editor/src/components/editor-2d/floorplan-registry-move-overlay.tsx b/packages/editor/src/components/editor-2d/floorplan-registry-move-overlay.tsx index ef1de406d7..123c3e18c6 100644 --- a/packages/editor/src/components/editor-2d/floorplan-registry-move-overlay.tsx +++ b/packages/editor/src/components/editor-2d/floorplan-registry-move-overlay.tsx @@ -388,7 +388,13 @@ export function FloorplanRegistryMoveOverlay() { endTransientMarkers() }) sfxEmitter.emit('sfx:item-place') - useViewer.getState().setSelection({ selectedIds: initialAffectedIds }) + // The moved node stays selected, as after a 3D move: the joined walls and + // rooms a wall move reshapes are part of its write, not a pick. + useViewer.getState().setSelection({ + selectedIds: useScene.getState().nodes[movingNode.id] + ? [movingNode.id] + : initialAffectedIds, + }) return } diff --git a/packages/editor/src/components/editor-2d/floorplan-snap-beacon-layer.tsx b/packages/editor/src/components/editor-2d/floorplan-snap-beacon-layer.tsx index 0a943e2f18..b8b0d3c26e 100644 --- a/packages/editor/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +++ b/packages/editor/src/components/editor-2d/floorplan-snap-beacon-layer.tsx @@ -13,33 +13,92 @@ import { useFloorplanRender } from './floorplan-render-context' * * endpoint (corner) → square midpoint → triangle * intersection → ✕ cross wall body (edge) → circle + * perpendicular → ⊥ corner projection guide → diamond + * grid length → small + * * Indigo to match the 3D beacon, except the corner (endpoint) square which is - * green to match the alignment guides. Sizes are pixel-budgeted via - * `unitsPerPixel` so the marker stays a + * green to match the alignment guides. A point that carries a `label` (a wall + * pulled from a wall end) also names what it caught in a pill beside the + * glyph. Sizes are pixel-budgeted via `unitsPerPixel` so the marker stays a * constant size on screen at any zoom. Mounted inside the `data-floorplan-scene` * group so coordinates are world meters (XZ) 1:1, like the alignment guides. */ const COLOR = '#6366f1' // indigo-500 — matches the 3D beacon const ENDPOINT_COLOR = '#22c55e' // green-500 — corner (endpoint) snap accent -export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer() { +export const FloorplanSnapBeaconLayer = memo(function FloorplanSnapBeaconLayer({ + unitsPerPixel, +}: { + /** The plan's live scale, which keeps a labelled marker screen-sized. */ + unitsPerPixel?: number +}) { const point = useWallSnapIndicator((s) => s.point) const ctx = useFloorplanRender() if (!point) return null - const upp = ctx?.unitsPerPixel ?? 0.01 + // A labelled point (a wall pulled from a wall end) is sized on screen. + const upp = (point.label ? unitsPerPixel : undefined) ?? ctx?.unitsPerPixel ?? 0.01 const m = 6 * upp // base half-size of the glyph in world meters const stroke = 1.5 * upp return ( + {point.label ? ( + + ) : null} ) }) +/** What the snap caught, in a pill down and to the right of the glyph, kept upright. */ +function SnapLabel({ + color, + sceneRotationDeg, + text, + upp, + x, + z, +}: { + color: string + sceneRotationDeg: number + text: string + upp: number + x: number + z: number +}) { + const fontSize = 10.5 * upp + const padX = 5 * upp + const width = text.length * fontSize * 0.56 + padX * 2 + const height = fontSize + 6 * upp + return ( + + + + {text} + + + ) +} + function SnapMarker({ color, kind, @@ -72,6 +131,39 @@ function SnapMarker({ ) } + if (kind === 'perpendicular') { + // ⊥ — a base line with an upright from its middle, and the square corner. + const c = m * 1.3 + return ( + + + + + + ) + } + if (kind === 'alignment') { + const c = m * 1.2 + return ( + + ) + } + if (kind === 'grid') { + const c = m * 0.9 + return ( + + + + + ) + } // 'wall' (edge / along-wall) → circle return } diff --git a/packages/editor/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx b/packages/editor/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx index 640164afe1..239a4f1444 100644 --- a/packages/editor/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +++ b/packages/editor/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx @@ -11,6 +11,7 @@ import { resolveDimensionDrive, } from '../../../lib/floorplan/dimension-drive' import { formatScheduleLength } from '../../../lib/floorplan/schedules' +import { commitRoomPush } from '../../../lib/room-handle-drag' import useEditor from '../../../store/use-editor' /** @@ -20,7 +21,8 @@ import useEditor from '../../../store/use-editor' * plate, pre-filled with the current text. Enter commits, Esc cancels. * Committing DRIVES geometry: the anchor the dimension runs toward moves * along the dimension direction by the delta, and connected wall junctions - * follow (`planDimensionDrive`). When nothing bindable is under the far + * follow (`planDimensionDrive`); a dimension across a wall slides the whole + * wall, as its push arrow does (`commitRoomPush`). When nothing bindable is under the far * anchor the value is written as `textOverride` on a construction-dimension * node instead, and the label renders an "override" badge. * @@ -247,6 +249,21 @@ export function commitDimensionValue(args: { return { ok: true } } + if (resolution.target.kind === 'wall-offset') { + // The wall's own push: the whole wall slides along its normal, connected + // walls follow through the junction planner, rooms and floors re-form — + // one undo step, exactly as dragging its side arrow that far. + const delta = args.nextLength - args.target.value + if (Math.abs(delta) < 1e-9) return { ok: true } + return commitRoomPush( + { wallId: resolution.target.wallId, t0: 0, t1: 1 }, + resolution.target.direction, + delta, + ) + ? { ok: true } + : { ok: false, reason: "Can't move this wall there" } + } + const plan = planDimensionDrive({ nodes, target: resolution.target, diff --git a/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts b/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts index 58cccf2cae..8c3285fcfd 100644 --- a/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +++ b/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts @@ -24,6 +24,7 @@ import { collectFloorplanLinkedLevelNodes, computeAffectedSiblingIds, floorplanAffordanceReshapeScope, + floorplanEntryStripsHandles, floorplanEntryYieldsToTool, floorplanHandleDoubleClickAffordance, InteractiveGeometry, @@ -793,6 +794,17 @@ describe('floorplan entry routing while a tool is active', () => { expect(floorplanEntryYieldsToTool({ mode: 'select', openingPlacement: false })).toBe(false) expect(floorplanEntryYieldsToTool({ mode: 'delete', openingPlacement: false })).toBe(false) }) + + // A roof plane selected before arming the roof tool kept its live corner and edge handles: a + // click near its eave resized it instead of drawing. + test('a selected node shows no edit handles while a build tool is armed', () => { + const entry = { multiSelected: false, ceilingWithoutSession: false } + expect(floorplanEntryStripsHandles({ ...entry, mode: 'select' })).toBe(false) + expect(floorplanEntryStripsHandles({ ...entry, mode: 'build' })).toBe(true) + expect(floorplanEntryStripsHandles({ ...entry, mode: 'select', multiSelected: true })).toBe( + true, + ) + }) }) describe('isFloorplanHierarchyVisible', () => { diff --git a/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.tsx b/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.tsx index f23e2a8c79..f2ff2b5489 100644 --- a/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +++ b/packages/editor/src/components/editor-2d/renderers/floorplan-registry-layer.tsx @@ -78,6 +78,7 @@ import { resolveNodeForDrawingType } from '../../../lib/floorplan/drawing-coordi import { createFloorplanContextExtensions, type FloorplanAnnotationRole, + type FloorplanWallDimensionFaces, type FloorplanWallDimensionReference, getFloorplanNodeExtension, readFloorplanGeometryMetadata, @@ -115,6 +116,11 @@ import { createSessionWrites, type SessionWrites } from '../../../lib/session-wr import { sfxEmitter } from '../../../lib/sfx-bus' import { clearSurfacePlanSnapFeedback } from '../../../lib/surface-plan-snap' import { paintZoneMembership } from '../../../lib/units' +import { + startWallPull, + WALL_PULL_AFFORDANCE, + type WallPullGripPayload, +} from '../../../lib/wall-pull' import useDirectManipulationFeedback from '../../../store/use-direct-manipulation-feedback' import useDrawingView from '../../../store/use-drawing-view' import useEditor, { isAngleSnapActive, type Mode } from '../../../store/use-editor' @@ -128,9 +134,12 @@ import useInteractionScope, { import { expandSessionSelectionForNode } from '../../../store/use-session-groups' import { isViewVisible, VIEW_2D } from '../../../store/view-layout' import { FloorRegionControls2D } from '../../editor/floor-region-controls' -import { startGroupPickUp } from '../../editor/group-actions' import { classifyParticipant } from '../../editor/group-transform-shared' -import { RoomHandleDragPreview2D, RoomHandles2D } from '../../editor/room-handles-2d' +import { + RoomHandleDragPreview2D, + RoomHandles2D, + SelectedWallDimensions2D, +} from '../../editor/room-handles-2d' import { RoomHighlight2D } from '../../editor/room-highlight' import { RoomLabels2D } from '../../editor/room-labels' import { suppressBoxSelectForPointer } from '../../tools/select/box-select-state' @@ -199,6 +208,15 @@ const DIRECT_DRAG_THRESHOLD_PX = 4 const DIRECT_ROTATE_EPSILON = 1e-6 const DIRECT_ROTATE_RADIANS_PER_PIXEL = Math.PI / 180 const MAX_HANDLE_UNITS_PER_PIXEL = 0.015 +// A wall end's pull grip, in screen pixels out from the end along the wall: +// clear of the end's move grip (its hit ring reaches ~17 px), a short dashed +// leader, then a round button with an outward arrow. +const PULL_GRIP_LEADER_START_PX = 9 +const PULL_GRIP_CENTER_PX = 30 +const PULL_GRIP_RADIUS_PX = 10 +const PULL_GRIP_HIT_RADIUS_PX = 13 +const PULL_GRIP_FILL = '#10b981' // emerald-500 +const PULL_GRIP_HOVER_FILL = '#059669' // emerald-600 export function resolveFloorplanHandleUnitsPerPixel(unitsPerPixel: number): number { return Math.min(unitsPerPixel, MAX_HANDLE_UNITS_PER_PIXEL) @@ -388,6 +406,7 @@ type NodeDeps = { unit: 'metric' | 'imperial' metricNotation: 'meters' | 'millimeters' wallDimensionReference: FloorplanWallDimensionReference + wallDimensionFaces?: FloorplanWallDimensionFaces selected: boolean highlighted: boolean hovered: boolean @@ -497,6 +516,20 @@ export function floorplanEntryYieldsToTool(state: { return state.mode === 'build' || state.openingPlacement } +/** + * Whether an entry draws its node without edit handles or dimension chrome: a member of a + * multi-selection, a ceiling outside its edit session, or anything while a build tool is armed (a + * selected roof keeps its selection for a new plane to join it, but a press near its eave must + * draw, not resize it). + */ +export function floorplanEntryStripsHandles(state: { + mode: Mode + multiSelected: boolean + ceilingWithoutSession: boolean +}): boolean { + return state.multiSelected || state.ceilingWithoutSession || state.mode === 'build' +} + function floorplanEntryYieldsToToolNow(): boolean { const { mode, tool } = useEditor.getState() const movingNode = getMovingNode() @@ -520,6 +553,7 @@ const NO_MEMBER_IDS: readonly string[] = [] export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { const selectedLevelId = useViewer((s) => s.selection.levelId) const selectedBuildingId = useViewer((s) => s.selection.buildingId) + const foundationView = useViewer((s) => s.foundationView) const unit = useViewer((s) => s.unit) const metricNotation = useViewer((s) => s.metricNotation) const setSelection = useViewer((s) => s.setSelection) @@ -610,6 +644,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { const drawingType = useDrawingView((s) => s.drawingType) const annotationVisibility = useFloorplanAnnotationVisibility((s) => s.visibility) const wallDimensionReference = useFloorplanAnnotationVisibility((s) => s.wallDimensionReference) + const wallDimensionFaces = useFloorplanAnnotationVisibility((s) => s.wallDimensionFaces) const floorplanMode = useFloorplanMode((s) => s.mode) const effectiveWallDimensionReference = resolveFloorplanWallDimensionReference( floorplanMode, @@ -970,20 +1005,28 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { // Photoshop-style group drag: plain pointer-down on a transformable member // of a multi-selection slides the whole selection rigidly; a plain click - // (no drag) enters the group pick-up instead — parity with the single-item - // click-to-move (clicking outside still deselects). Modified clicks + // (no drag) selects the pressed member alone — a click never picks + // anything up (clicking outside still deselects). Modified clicks // (selection toggle) and Cmd-drag / direct-rotate keep their existing // paths. `immediate` engages without the drag threshold — the move-handle // dot's pick-up semantics. const startGroupMoveDrag = useCallback( - (id: AnyNodeId, event: ReactPointerEvent, immediate = false): boolean => { + ( + id: AnyNodeId, + event: ReactPointerEvent, + immediate = false, + selectOnClick = true, + ): boolean => { if (event.button !== 0) return false if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return false if (movingNode) return false if (useEditor.getState().mode === 'delete') return false const started = startFloorplanGroupMove(id, event, { immediate, - onClickFallthrough: immediate ? undefined : () => startGroupPickUp(), + onClickFallthrough: + immediate || !selectOnClick + ? undefined + : () => applyEntrySelection(id, { shouldToggle: false, isolateMember: false }), }) if (!started) return false event.preventDefault() @@ -991,7 +1034,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { suppressBoxSelectForPointer(event) return true }, - [movingNode], + [applyEntrySelection, movingNode], ) // Move-handle dot variant — routes the dot through the group session when @@ -1002,7 +1045,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { ) // The dashed selection box is itself the group's drag handle: a press - // anywhere inside it slides the group (or picks it up on a plain click), + // anywhere inside it slides the group (a plain click keeps the selection), // anchored on the first transformable member. const handleGroupBoxPointerDown = useCallback( (event: ReactPointerEvent) => { @@ -1013,7 +1056,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { classifyParticipant(sceneNodes[id as AnyNodeId], currentLevelId, sceneNodes) !== null, ) if (!anchor) return - startGroupMoveDrag(anchor as AnyNodeId, event) + startGroupMoveDrag(anchor as AnyNodeId, event, false, false) }, [startGroupMoveDrag], ) @@ -1299,6 +1342,16 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { runFloorplanWallPush(payload, event.clientX, event.clientY) return } + // A wall end's pull grip opens the Walls run at that end; the plan's + // wall draft then follows the pointer and the release places the wall. + if (affordance === WALL_PULL_AFFORDANCE) { + event.preventDefault() + event.stopPropagation() + suppressBoxSelectForPointer(event) + const grip = payload as WallPullGripPayload + startWallPull({ wallId: grip.wallId, endpoint: grip.endpoint, origin: '2d' }) + return + } const sceneNodes = useScene.getState().nodes const node = sceneNodes[nodeId] @@ -1580,7 +1633,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { } }, []) - const entries = floorplanData.entries + // The foundation view keeps the foundation's outlines and footings live and leaves the rest of + // the level to the foundation layer's ghosted walls; a level plan shows no foundation outline. + const entries = floorplanData.entries.filter((entry) => + foundationView + ? entry.node.type === 'footing' || entry.node.type === 'foundation' + : entry.node.type !== 'foundation', + ) if (entries.length === 0) return null const palette = renderCtx?.palette @@ -1620,6 +1679,7 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { unit={unit} metricNotation={metricNotation} wallDimensionReference={effectiveWallDimensionReference} + wallDimensionFaces={wallDimensionFaces} visibilityRootId={ entry.visibilityRootId ?? (entry.ctxOverrides ? undefined : (levelId as AnyNodeId)) } @@ -1653,9 +1713,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { higher-rank kinds (slabs, then walls / items / shelves) layer on top in the expected document-order z-stack. */} {renderEntries('base')} - - - + {!foundationView && ( + <> + + + + + )} {/* Overlay pass — interactive handles (vertex / midpoint / edge / move) and labels (text / dimensions). Painted after every base entry so polygon-editor chrome on a selected slab stays above @@ -1667,8 +1731,13 @@ export const FloorplanRegistryLayer = memo(function FloorplanRegistryLayer() { {/* Handles pass — every grabbable handle above every node's overlay, so a neighbour's outline never covers a selected wall's corner. */} {renderEntries('handles')} - - + {!foundationView && ( + <> + + + + + )} {/* Click-to-type dimension labels. Portals its HTML input to document.body, so mounting it inside the SVG tree is safe. */} @@ -1710,6 +1779,7 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) { const wallDimensionReference = useFloorplanAnnotationVisibility( (state) => state.wallDimensionReference, ) + const wallDimensionFaces = useFloorplanAnnotationVisibility((state) => state.wallDimensionFaces) const floorplanMode = useFloorplanMode((state) => state.mode) const layoutInputs = useMemo( () => ({ @@ -1719,6 +1789,7 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) { interactionIdle, settledLayoutEpoch, wallDimensionReference, + wallDimensionFaces, floorplanMode, }), [ @@ -1728,6 +1799,7 @@ function FloorplanAnnotationLayoutResolver({ active }: { active: boolean }) { interactionIdle, settledLayoutEpoch, wallDimensionReference, + wallDimensionFaces, floorplanMode, ], ) @@ -1997,6 +2069,7 @@ type FloorplanRegistryEntryProps = { unit: 'metric' | 'imperial' metricNotation: 'meters' | 'millimeters' wallDimensionReference: FloorplanWallDimensionReference + wallDimensionFaces: FloorplanWallDimensionFaces visibilityRootId: AnyNodeId | undefined } @@ -2033,6 +2106,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({ unit, metricNotation, wallDimensionReference, + wallDimensionFaces, visibilityRootId, }: FloorplanRegistryEntryProps): React.ReactElement | null { // A node an agent just built is drawn on its construction-reveal turn, as in 3D. @@ -2049,7 +2123,12 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({ const ceilingWithoutSession = useCeilingEditSession( (state) => node.type === 'ceiling' && state.session?.ceilingId !== nodeId, ) - const suppressHandles = multiSelected || ceilingWithoutSession + const mode = useEditor((state) => state.mode) + const suppressHandles = floorplanEntryStripsHandles({ + mode, + multiSelected, + ceilingWithoutSession, + }) const selectedLevelId = useViewer((state) => state.selection.levelId) const selectionProxyId = resolveSelectionProxyId( node, @@ -2221,6 +2300,7 @@ const FloorplanRegistryEntry = memo(function FloorplanRegistryEntry({ unit, metricNotation, wallDimensionReference, + wallDimensionFaces, visibilityRootId, }) const rawGeometry = cacheEntry ? cacheEntry[pass] : null @@ -2294,6 +2374,7 @@ type BuildFloorplanEntryGeometryArgs = { unit: 'metric' | 'imperial' metricNotation: 'meters' | 'millimeters' wallDimensionReference: FloorplanWallDimensionReference + wallDimensionFaces?: FloorplanWallDimensionFaces visibilityRootId: AnyNodeId | undefined } @@ -2354,6 +2435,7 @@ export function buildFloorplanEntryGeometry({ unit, metricNotation, wallDimensionReference, + wallDimensionFaces, visibilityRootId, }: BuildFloorplanEntryGeometryArgs): CacheEntry | null { const def = nodeRegistry.get(node.type) @@ -2382,6 +2464,7 @@ export function buildFloorplanEntryGeometry({ unit, metricNotation, wallDimensionReference, + wallDimensionFaces, selected, highlighted, hovered, @@ -2469,6 +2552,7 @@ export function buildFloorplanEntryGeometry({ metricNotation, roomLabelOverlay: true, wallDimensionReference, + wallDimensionFaces, highlighted, hovered, moving, @@ -2495,6 +2579,7 @@ export function buildFloorplanEntryGeometry({ purpose: 'edit', roomLabelOverlay: true, wallDimensionReference, + wallDimensionFaces, }), viewState: palette ? { @@ -2642,6 +2727,87 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({ return renderInteractive(geometry, 0) + /** + * A wall end's "pull to continue" grip: screen-sized, just past the end + * along the wall, emerald so it reads apart from the end's move grip. + */ + function renderPullGrip( + g: Extract, + keyHint: number, + ): React.ReactElement { + const handleId = `${makeHandleId(nodeId, g.payload)}:pull` + const isHovered = hoveredHandleId === handleId + const k = handleUnitsPerPixel + const c = PULL_GRIP_CENTER_PX * k + const r = PULL_GRIP_RADIUS_PX * (isHovered ? 1.12 : 1) * k + // Outward arrow inside the button: shaft then head, tip along +x. + const arrowD = [ + `M ${c - 5.5 * k},${-1.4 * k}`, + `L ${c + 0.5 * k},${-1.4 * k}`, + `L ${c + 0.5 * k},${-4.6 * k}`, + `L ${c + 6 * k},0`, + `L ${c + 0.5 * k},${4.6 * k}`, + `L ${c + 0.5 * k},${1.4 * k}`, + `L ${c - 5.5 * k},${1.4 * k}`, + 'Z', + ].join(' ') + const affordance = g.affordance + const payload = g.payload + return ( + e.stopPropagation()} + transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${(g.angle * 180) / Math.PI})`} + > + + + + { + if (affordance) { + onHandlePointerDown(affordance, payload, e as ReactPointerEvent) + } + }} + onPointerEnter={() => onHandleHoverChange(handleId)} + onPointerLeave={() => onHandleHoverChange(null)} + // Fill only: the default stroke is a plan metre wide, and would take + // the press meant for the end's move grip. + pointerEvents="fill" + r={PULL_GRIP_HIT_RADIUS_PX * k} + style={{ cursor: 'grab' }} + > + {g.title ? {g.title} : null} + + + ) + } + function renderInteractive(g: FloorplanGeometry, keyHint: number): React.ReactElement { switch (g.kind) { case 'group': { @@ -2914,7 +3080,8 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({ const head1 = `M ${t1x} ${t1y} L ${b1ax} ${b1ay} L ${b1bx} ${b1by} Z` const head2 = `M ${t2x} ${t2y} L ${b2ax} ${b2ay} L ${b2bx} ${b2by} Z` const fill = isHovered ? '#a5b4fc' : '#8381ed' - const strokeWidthPx = isHovered ? 2.4 : 1.8 + const scale = g.scale ?? 1 + const strokeWidthPx = (isHovered ? 2.4 : 1.8) * Math.sqrt(scale) const angleDeg = (g.angle * 180) / Math.PI const affordance = g.affordance const payload = g.payload @@ -2923,7 +3090,7 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({ e.stopPropagation()} - transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${angleDeg})`} + transform={`translate(${g.point[0]} ${g.point[1]}) rotate(${angleDeg}) scale(${scale})`} > + if (g.variant === 'pull') return renderPullGrip(g, keyHint) // Affordance-routed arrows (door width-resize) get a per-payload // handle id so each side can hover independently; default // (move-flow) arrows share the node's :move id like the dot. @@ -2987,13 +3155,14 @@ export const InteractiveGeometry = memo(function InteractiveGeometry({ // so it shrinks on zoom-out and grows on zoom-in, matching the // wall it accompanies. Composed of a rectangular shaft + triangular // head, drawn as a single path for a clean fill + stroke outline. - const sl = 0.1 // shaft length (shortened body) - const hl = 0.12 // head length - const sh = 0.04 // shaft half-height - const hh = 0.1 // head half-height + const k = g.scale ?? 1 + const sl = 0.1 * k // shaft length (shortened body) + const hl = 0.12 * k // head length + const sh = 0.04 * k // shaft half-height + const hh = 0.1 * k // head half-height // Inset the shaft start so the arrow sits a little off the wall // body (matches the 3D `HANDLE_OFFSET`). - const bi = 0.03 // base inset + const bi = 0.03 * k // base inset const arrowD = `M ${bi},${-sh} L ${bi + sl},${-sh} L ${bi + sl},${-hh} L ${bi + sl + hl},0 L ${bi + sl},${hh} L ${bi + sl},${sh} L ${bi},${sh} Z` // Indigo palette to match the 3D wall push arrows // (`ARROW_COLOR` / `ARROW_HOVER_COLOR`) and the corner-sphere @@ -3764,6 +3933,7 @@ function nodeDepsEqual(a: NodeDeps, b: NodeDeps): boolean { 'unit', 'metricNotation', 'wallDimensionReference', + 'wallDimensionFaces', 'selected', 'highlighted', 'hovered', diff --git a/packages/editor/src/components/editor/alignment-3d-guide-layer.tsx b/packages/editor/src/components/editor/alignment-3d-guide-layer.tsx index 1349720c4f..4914d84f88 100644 --- a/packages/editor/src/components/editor/alignment-3d-guide-layer.tsx +++ b/packages/editor/src/components/editor/alignment-3d-guide-layer.tsx @@ -4,10 +4,13 @@ import { type AlignmentGuide, sceneRegistry } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { useFrame } from '@react-three/fiber' import { memo, useMemo, useRef } from 'react' -import { BoxGeometry, CircleGeometry, type Group, Vector3 } from 'three' +import { BoxGeometry, CircleGeometry, type Group, RingGeometry, Vector3 } from 'three' import { MeshBasicNodeMaterial } from 'three/webgpu' import { EDITOR_LAYER } from '../../lib/constants' -import useAlignmentGuides from '../../store/use-alignment-guides' +import useAlignmentGuides, { + type DraftGuideLine, + type DraftGuides, +} from '../../store/use-alignment-guides' import { Html } from './localized-html' import { formatMeasurement } from './measurement-pill' @@ -22,13 +25,18 @@ import { formatMeasurement } from './measurement-pill' * the design reference — so they're real 3D geometry, not screen billboards. * Only the distance pill is screen-space (``). * - * Guide coordinates are XZ meters in the WORLD frame — alignment now resolves - * on the world axes (via `resolveAlignmentForActiveBuilding`) so the guides - * stay parallel to the visible world grid even when the active building is - * rotated. This layer is mounted OUTSIDE the building-local ToolManager group - * for the same reason. The whole ribbon is lifted to the active level's WORLD - * Y each frame so it lies on the floor being edited — not the building base — - * when floors are stacked. + * Guide coordinates are XZ meters in the active building's LOCAL frame — + * alignment resolves on the building's own axes, the axes its 3D grid is laid + * on — and the layer is mounted inside the building-local ToolManager group. + * The whole ribbon is lifted to the active level's WORLD Y each frame so it + * lies on the floor being edited — not the building base — when floors are + * stacked. + * + * A wall pulled from a wall end (and the run it continues) publishes its + * projection guides beside them (`useAlignmentGuides.draft`): the extension of + * the wall it comes from in amber, lines through other walls' ends in indigo, + * lines through the run's start in emerald, all dashed, with a ringed marker + * where two of them cross. */ const LINE_COLOR = 0x81_8c_f8 // indigo-400 — matches the editor's selection accent (box-select / wall highlights) @@ -59,11 +67,33 @@ const dotMaterial = new MeshBasicNodeMaterial({ }) const DASH_GEOMETRY = new BoxGeometry(1, 1, 1) const DOT_GEOMETRY = new CircleGeometry(1, 24) +const RING_GEOMETRY = new RingGeometry(0.7, 1, 28) + +// Draft projection guides: one material per kind, shared like the above. +const draftGuideMaterial = (color: number) => + new MeshBasicNodeMaterial({ + color, + depthTest: false, + depthWrite: false, + toneMapped: false, + transparent: true, + opacity: 0.9, + }) +const DRAFT_GUIDE_MATERIALS: Record = { + extension: draftGuideMaterial(0xf5_9e_0b), // amber-500 + alignment: guideMaterial, + 'run-start': draftGuideMaterial(0x10_b9_81), // emerald-500 — the pull grip's colour +} +const DRAFT_DASH_LEN = 0.14 +const DRAFT_DASH_GAP = 0.12 +const DRAFT_LINE_WIDTH = 0.035 +const INTERSECTION_RADIUS = 0.13 type Vec3 = [number, number, number] export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() { const guides = useAlignmentGuides((s) => s.guides) + const draft = useAlignmentGuides((s) => s.draft) const levelId = useViewer((s) => s.selection.levelId) const unit = useViewer((s) => s.unit) const metricNotation = useViewer((s) => s.metricNotation) @@ -82,16 +112,77 @@ export const Alignment3DGuideLayer = memo(function Alignment3DGuideLayer() { group.position.y = levelMesh ? levelMesh.getWorldPosition(worldYWork).y : 0 }) - if (guides.length === 0) return null + if (guides.length === 0 && !draft) return null return ( {guides.map((guide, i) => ( ))} + {draft ? : null} ) }) +/** A wall draft's dashed projection guides and the markers where they cross. */ +function DraftGuidesLayer({ draft }: { draft: DraftGuides }) { + return ( + <> + {draft.lines.map((line, i) => ( + + ))} + {draft.intersections.map((point, i) => ( + + ))} + + ) +} + +function DraftLine({ line }: { line: DraftGuideLine }) { + const [fx, fz] = line.from + const [tx, tz] = line.to + const { dashes, angleY } = useMemo(() => { + const dx = tx - fx + const dz = tz - fz + const length = Math.hypot(dx, dz) + const angle = -Math.atan2(dz, dx) + if (length < 1e-4) return { dashes: [] as Vec3[], angleY: angle } + const ux = dx / length + const uz = dz / length + const period = Math.max(DRAFT_DASH_LEN + DRAFT_DASH_GAP, length / MAX_DASHES) + const centres: Vec3[] = [] + for (let d = period / 2; d - DRAFT_DASH_LEN / 2 <= length; d += period) { + centres.push([fx + ux * d, GUIDE_Y, fz + uz * d]) + } + return { dashes: centres, angleY: angle } + }, [fx, fz, tx, tz]) + const material = DRAFT_GUIDE_MATERIALS[line.kind] + return ( + <> + {dashes.map((centre, i) => ( + + ))} + + ) +} + function GuideLine({ guide, metricNotation, diff --git a/packages/editor/src/components/editor/bake-thumbnail.tsx b/packages/editor/src/components/editor/bake-thumbnail.tsx index bfc9d578e5..c6f1a8e1f6 100644 --- a/packages/editor/src/components/editor/bake-thumbnail.tsx +++ b/packages/editor/src/components/editor/bake-thumbnail.tsx @@ -47,8 +47,9 @@ const THUMBNAIL_BACKDROP = { inner: '#FAFAFA', outer: '#E4E4E4' } const THUMBNAIL_SHADOW_CATCHER_OPACITY = 0.7 /** Soft shade hugging the ground-floor walls so the building never floats. */ const THUMBNAIL_WALL_CONTACT = { opacity: 0.5, spreadM: 0.9 } -/** Unpainted walls render as smooth plaster rather than the screw-dotted - * prepared-drywall default, which reads as unfinished at card size. */ +/** A roof's unpainted trim and gable bands render as smooth plaster rather + * than the screw-dotted prepared-drywall default, which reads as unfinished + * at card size. Unpainted walls already wear the interior paint. */ const THUMBNAIL_SLOT_DEFAULT_OVERRIDES = { 'library:concrete-drywall': 'library:concrete-plaster', } as const diff --git a/packages/editor/src/components/editor/custom-camera-controls.tsx b/packages/editor/src/components/editor/custom-camera-controls.tsx index 85263d8208..4e663ab72b 100644 --- a/packages/editor/src/components/editor/custom-camera-controls.tsx +++ b/packages/editor/src/components/editor/custom-camera-controls.tsx @@ -62,6 +62,7 @@ import { orbitFloorMinY, overviewEyeY, } from '../../lib/orbit-floor-clearance' +import { orbitNearPlane } from '../../lib/orbit-near-plane' import { editorOwnsOneFingerDrag } from '../../lib/touch-gesture-priority' import { frameViewerCamera } from '../../lib/viewer-selection' import { publishCameraPose } from '../../store/camera-pose-store' @@ -766,8 +767,16 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) = }, [camera, isFirstPersonMode, viewportSize]) const handleCameraUpdate = useCallback(() => { + const control = controls.current + if (control && isPerspectiveCamera(camera)) { + const near = orbitNearPlane(control.distance) + if (Math.abs(camera.near - near) > 1e-4) { + camera.near = near + camera.updateProjectionMatrix() + } + } publishCurrentPose() - }, [publishCurrentPose]) + }, [camera, publishCurrentPose]) useEffect(() => { publishInitialCameraPose(publishCurrentPose) @@ -1054,7 +1063,10 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) = updateNavigationCursor() } - const onWheel = () => { + const onWheel = (event: WheelEvent) => { + // Zooming in carries on past the closest orbit, the target with it; zooming out stops at + // the farthest, or the eye would drift off the site. Seen before the controls' own listener. + if (controls.current) controls.current.infinityDolly = event.deltaY < 0 beginLocalCameraInteraction() cameraDraggingLifecycle.scheduleEnd() } @@ -1452,13 +1464,12 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) = } }, [cameraDraggingLifecycle]) - // Preset capture mode frames a single subtree (often a 0.3–2m preset), - // so the default 2m minDistance prevents the user from getting close - // enough to compose a good thumbnail. Relax the clamp to 0.5m while - // capturing presets; reset on exit so general editing keeps the looser - // navigation guardrails. + // The wheel dollies toward the cursor and, at the closest orbit, carries the + // target on (infinity dolly), so the eye goes right up to a window's trim + // or through a doorway rather than stopping short of the orbit's pivot. + // Preset capture frames a single small subtree and orbits it closer still. const isPresetCapture = captureMode.mode === 'preset' - const minDistance = isPresetCapture ? 0.5 : 2 + const minDistance = isPresetCapture ? 0.25 : 0.5 // Over modeled or mapped surroundings (a neighbourhood instead of the // horizon disc) the camera may pull back far enough to take it all in. const groundReplaced = useSceneGroundReplacement() @@ -1471,6 +1482,7 @@ export const CustomCameraControls = ({ paused = false }: { paused?: boolean }) = return ( state.metricNotation) + // The thickness the wall commits at, as the 3D draft shows it. + const wallDraftThickness = useWallDraftThickness(DEFAULT_WALL_THICKNESS) + const sideDraftThickness = useWallSegmentDraftThickness(DEFAULT_WALL_THICKNESS) const wallDraftEnd = useFloorplanDraftPreview((s) => s.wallDraftEnd) const fenceDraftEnd = useFloorplanDraftPreview((s) => s.fenceDraftEnd) const roofDraftEnd = useFloorplanDraftPreview((s) => s.roofDraftEnd) @@ -4489,10 +4523,12 @@ function FloorplanLinearDraftLayer({ ) { return null } - const draftWall = getSharedFloorplanWall(buildDraftWall(levelId, wallDraftStart, wallDraftEnd)) + const draftWall = getSharedFloorplanWall( + buildDraftWall(levelId, wallDraftStart, wallDraftEnd, wallDraftThickness), + ) // Keep the live draft preview cheap; full level-wide mitering here runs on every mouse move. return getWallPlanFootprint(draftWall, EMPTY_WALL_MITER_DATA) - }, [levelId, wallDraftStart, wallDraftEnd]) + }, [levelId, wallDraftStart, wallDraftEnd, wallDraftThickness]) // Rectangle mode drafts four walls at once; they read exactly like a line // draft — same mitered footprints, measurement plates and axis guides. @@ -4502,12 +4538,13 @@ function FloorplanLinearDraftLayer({ } const corners = wallRectangleCorners(wallRectangleDraftStart, wallRectangleDraftEnd) if (corners.length !== 4) return null - const draftWalls = corners.map((start, index) => - getSharedFloorplanWall({ - ...buildDraftWall(levelId, start, corners[(index + 1) % 4]!), + const draftWalls = corners.map((start, index) => { + const end = corners[(index + 1) % 4]! + return getSharedFloorplanWall({ + ...buildDraftWall(levelId, start, end, sideDraftThickness(start, end)), id: `wall_draft_${index}` as WallNode['id'], - }), - ) + }) + }) const miterData = calculateLevelMiters(draftWalls) const polygons = draftWalls.map((wall) => formatPolygonPoints(getWallPlanFootprint(wall, miterData)), @@ -4536,6 +4573,7 @@ function FloorplanLinearDraftLayer({ isWallBuildActive, levelId, metricNotation, + sideDraftThickness, unit, wallRectangleDraftEnd, wallRectangleDraftStart, @@ -4545,15 +4583,16 @@ function FloorplanLinearDraftLayer({ // rectangle draft (mitered together) until the polygon is written. const polygonRoomDraft = useMemo(() => { if (!(levelId && isWallBuildActive && wallPolygonDraftPoints.length >= 2)) return null - const draftWalls = wallPolygonDraftPoints.slice(1).map((end, index) => - getSharedFloorplanWall({ - ...buildDraftWall(levelId, wallPolygonDraftPoints[index]!, end), + const draftWalls = wallPolygonDraftPoints.slice(1).map((end, index) => { + const start = wallPolygonDraftPoints[index]! + return getSharedFloorplanWall({ + ...buildDraftWall(levelId, start, end, sideDraftThickness(start, end)), id: `wall_polygon_draft_${index}` as WallNode['id'], - }), - ) + }) + }) const miterData = calculateLevelMiters(draftWalls) return draftWalls.map((wall) => formatPolygonPoints(getWallPlanFootprint(wall, miterData))) - }, [isWallBuildActive, levelId, wallPolygonDraftPoints]) + }, [isWallBuildActive, levelId, sideDraftThickness, wallPolygonDraftPoints]) const draftPolygonPoints = useMemo(() => { if (isRoofBuildActive && roofDraftStart && roofDraftEnd) { @@ -5067,6 +5106,9 @@ export function FloorplanPanel({ // Walls committed by the current 2D-only chain — exclusion set for the // T-junction chain-termination test (mirrors the 3D tool's `chainWallIds`). const wallChainWallIdsRef = useRef([]) + // Direction of the run's last wall (a pulled run's source first): the next + // holds square to it. Null before the run has a wall. + const wallRunReferenceRef = useRef(null) const setDraftEnd = useCallback( (next: WallPlanPoint | null | ((prev: WallPlanPoint | null) => WallPlanPoint | null)) => { const store = useFloorplanDraftPreview.getState() @@ -5082,14 +5124,10 @@ export function FloorplanPanel({ }, [], ) - const [roofDraftStart, setRoofDraftStart] = useState(null) - const setRoofDraftEnd = useCallback( - (next: WallPlanPoint | null | ((prev: WallPlanPoint | null) => WallPlanPoint | null)) => { - const store = useFloorplanDraftPreview.getState() - store.setRoofDraftEnd(typeof next === 'function' ? next(store.roofDraftEnd) : next) - }, - [], - ) + // The roof tool that owns the input (the 3D tool, or the plan's own in 2D-only view) writes the + // roof draft from the corners it snaps and commits; the plan only draws it, so its rectangle is + // the roof the next click makes. + const roofDraftStart = useFloorplanDraftPreview((s) => s.roofDraftStart) const [ceilingDraftPoints, setCeilingDraftPoints] = useState([]) const [slabDraftPoints, setSlabDraftPoints] = useState([]) const slabPlacementBaseRef = useRef(null) @@ -5102,9 +5140,6 @@ export function FloorplanPanel({ useEffect(() => { useFloorplanDraftPreview.getState().setFenceDraftStart(fenceDraftStart) }, [fenceDraftStart]) - useEffect(() => { - useFloorplanDraftPreview.getState().setRoofDraftStart(roofDraftStart) - }, [roofDraftStart]) const [zoneDraftPoints, setZoneDraftPoints] = useState([]) const [siteBoundaryDraft, setSiteBoundaryDraft] = useState(null) const [siteVertexDragState, setSiteVertexDragState] = useState(null) @@ -7890,20 +7925,29 @@ export function FloorplanPanel({ setWallChainFirstVertex(null) wallConstructionOptionsRef.current = undefined wallChainWallIdsRef.current = [] + wallRunReferenceRef.current = null setDraftEnd(null) useSegmentDraftChain.getState().clear('wall') + // A run pulled from a wall end ends with the plan's draft: back to Select. + endWallPullRun() }, [setDraftEnd]) const clearFencePlacementDraft = useCallback(() => { setFenceDraftStart(null) setFenceDraftEnd(null) }, [setFenceDraftEnd]) const clearRoofPlacementDraft = useCallback(() => { - setRoofDraftStart(null) - setRoofDraftEnd(null) - }, [setRoofDraftEnd]) + const draft = useFloorplanDraftPreview.getState() + draft.setRoofDraftStart(null) + draft.setRoofDraftEnd(null) + }, []) const clearCeilingPlacementDraft = useCallback(() => { setCeilingDraftPoints([]) }, []) + // Beside the 3D view its ceiling tool commits; in 2D-only view the plan does. Each keeps its own + // corners, so a polygon begun under one owner must not be closed against the other's. + useEffect(() => { + if (shownScene) clearCeilingPlacementDraft() + }, [clearCeilingPlacementDraft, shownScene]) const clearSlabPlacementDraft = useCallback(() => { setSlabDraftPoints([]) slabPlacementBaseRef.current = null @@ -7999,6 +8043,30 @@ export function FloorplanPanel({ } }, [clearDraft]) + // A wall end's pull grip pressed in the plan opens the Walls run at that + // end: start the plan's draft there, as if the end had been clicked. A pull + // from the 3D view drafts there (the plan beside it follows its moves). + const wallPullRun = useWallPullRun((state) => state.run) + const seededWallPullSeqRef = useRef(null) + useEffect(() => { + if (!(wallPullRun && wallPullRun.origin === '2d' && isWallBuildActive && levelId)) return + if (seededWallPullSeqRef.current === wallPullRun.seq) return + seededWallPullSeqRef.current = wallPullRun.seq + const start = wallPullRun.start + wallConstructionOptionsRef.current = resolveTerrainWallConstructionOptions( + useScene.getState().nodes, + levelId, + start, + useEditor.getState().toolDefaults.wall, + ) + wallChainWallIdsRef.current = [] + wallRunReferenceRef.current = wallPullRun.reference + setDraftStart(start) + setWallChainFirstVertex(start) + setDraftEnd(start) + setCursorPoint(start) + }, [isWallBuildActive, levelId, setCursorPoint, setDraftEnd, wallPullRun]) + const createZoneOnCurrentLevel = useCallback( (points: WallPlanPoint[]) => { if (!levelId) { @@ -9313,14 +9381,6 @@ export function FloorplanPanel({ setCursorPoint((previousPoint) => previousPoint && pointsEqual(previousPoint, snappedPoint) ? previousPoint : snappedPoint, ) - - if (roofDraftStart) { - setRoofDraftEnd((previousPoint) => - previousPoint && pointsEqual(previousPoint, snappedPoint) - ? previousPoint - : snappedPoint, - ) - } return } @@ -9535,35 +9595,57 @@ export function FloorplanPanel({ return } - // Wall draft: grid + magnetic snap, then Figma-style alignment. - // While a draft is open the segment locks to 15° rays from its start. - // Snapping is governed by the snapping mode (`'off'` is the bypass); - // there is no Shift hold-to-bypass. - const wallAngleSnap = draftStart !== null && isAngleSnapActive() - const wallSnap = snapWallDraftPointDetailed({ - point: planPoint, - walls: [...walls, ...wallPolygonDraftWalls()], - start: draftStart ?? undefined, - angleSnap: wallAngleSnap, - magnetic: isMagneticSnapActive(), - }) - const wallSnapped = wallSnap.point - // Locked onto existing geometry (corner / midpoint / crossing / edge) → - // that snap wins, so skip Figma alignment and stand the beacon there. - const lockedToWall = wallSnap.snap !== null - let snappedPoint = wallSnapped - if (lockedToWall) { - useAlignmentGuides.getState().clear() + // Wall draft. The first point: grid + magnetic snap, then Figma-style + // alignment. Once a draft is open the segment holds square to the last + // wall and the house's axes, with projection guides and a labelled snap + // marker (`snapWallRunDraftPoint`). Snapping is governed by the snapping + // mode (`'off'` is the bypass); Alt frees an open segment. An invisible + // wall draft catches the invisible walls already drawn as well. + let snappedPoint: WallPlanPoint + const draftSnapWalls = [ + ...walls, + ...wallPolygonDraftWalls(), + ...invisibleWallDraftSnapLines(), + ] + if (draftStart) { + const runSnap = snapWallRunDraftPoint({ + point: planPoint, + start: draftStart, + runStart: wallChainFirstVertex ?? draftStart, + reference: wallRunReferenceRef.current, + walls: draftSnapWalls, + free: isWallRunFree(event), + }) + snappedPoint = runSnap.point + publishWallRunFeedback(runSnap) } else { - // Alignment lines show in every mode; the pull applies only when - // magnetic ('lines') and the segment isn't angle-locked. - snappedPoint = alignFloorplanDraftPoint(wallSnapped, { - applySnap: isMagneticSnapActive() && !wallAngleSnap, + const wallSnap = snapWallDraftPointDetailed({ + point: planPoint, + walls: draftSnapWalls, + magnetic: isMagneticSnapActive(), + // Same as the click: an end aimed into a wall's body joins it. + captureWallBodies: true, }) + const wallSnapped = wallSnap.point + // Locked onto existing geometry (corner / midpoint / crossing / edge) → + // that snap wins, so skip Figma alignment and stand the beacon there. + const lockedToWall = wallSnap.snap !== null + snappedPoint = wallSnapped + if (lockedToWall) { + useAlignmentGuides.getState().clear() + } else { + // Alignment lines show in every mode; the pull applies only when + // magnetic ('lines'). + snappedPoint = alignFloorplanDraftPoint(wallSnapped, { + applySnap: isMagneticSnapActive(), + }) + } + useWallSnapIndicator + .getState() + .set( + wallSnap.snap ? { x: snappedPoint[0], z: snappedPoint[1], kind: wallSnap.snap } : null, + ) } - useWallSnapIndicator - .getState() - .set(wallSnap.snap ? { x: snappedPoint[0], z: snappedPoint[1], kind: wallSnap.snap } : null) // Emit `grid:move` so the registry-driven wall tool's 3D preview // tracks the cursor. The local draftEnd update below is what @@ -9621,15 +9703,14 @@ export function FloorplanPanel({ levelId, publishFloorplanNavigationPose, referenceScaleDraft, - roofDraftStart, siteVertexDragState, surfaceSize.height, surfaceSize.width, svgAspectRatio, walls, + wallChainFirstVertex, setCursorPoint, setDraftEnd, - setRoofDraftEnd, setFenceDraftEnd, ], ) @@ -9823,6 +9904,7 @@ export function FloorplanPanel({ ) { startWallPolygonDraft(levelId as AnyNodeId, point, wallConstructionOptionsRef.current) } + wallRunReferenceRef.current = null setDraftStart(point) setWallChainFirstVertex(point) setDraftEnd(point) @@ -9833,6 +9915,8 @@ export function FloorplanPanel({ if (!isSegmentLongEnough(draftStart, point)) { return } + // The run's next segment holds square to this one. + const placedDirection = wallRunDirection(draftStart, point) // The 3D wall tool's `grid:click` listener // (`packages/nodes/src/wall/tool.tsx`) owns the wall-create @@ -9849,7 +9933,7 @@ export function FloorplanPanel({ // ceiling 2D-only committers: create locally here, gated on the // view, so split / 3D keep their single-owner tool commit. const viewIs2DOnly = visibleScene(useEditor.getState()) === '2d' - let createdWall: WallNode | null = null + let createdWall: WallNode | SeparatorNode | null = null // 2D-only Polygon room: the corner joins the draft; nothing is written // until it closes, seals or tees (then every wall at once). if (viewIs2DOnly && getWallDrawVariant() === 'polygon') { @@ -9859,17 +9943,18 @@ export function FloorplanPanel({ setCursorPoint(null) return } + wallRunReferenceRef.current = placedDirection setDraftStart(point) setDraftEnd(point) setCursorPoint(point) return } if (viewIs2DOnly) { - createdWall = createWallOnCurrentLevel( - draftStart, - point, - wallConstructionOptionsRef.current, - ) + // The Invisible wall tile draws room separators, not walls. + createdWall = + getWallLineKind() === 'invisible' + ? createInvisibleWallOnCurrentLevel(draftStart, point) + : createWallOnCurrentLevel(draftStart, point, wallConstructionOptionsRef.current) } if (createdWall) { wallChainWallIdsRef.current.push(createdWall.id) @@ -9884,10 +9969,7 @@ export function FloorplanPanel({ ? (createdWall.end as WallPlanPoint) : (publishedNextStart ?? point) - if ( - useEditor.getState().getContinuation('wall') === 'single' || - (wallChainFirstVertex && isWithinWallJoinSnapRadius(nextStart, wallChainFirstVertex)) - ) { + if (wallChainFirstVertex && isWithinWallJoinSnapRadius(nextStart, wallChainFirstVertex)) { clearWallPlacementDraft() setCursorPoint(null) return @@ -9907,7 +9989,7 @@ export function FloorplanPanel({ levelWalls, wallChainWallIdsRef.current, ) || - wallClosesRoom(levelWalls, createdWall) + (createdWall.type === 'wall' && wallClosesRoom(levelWalls, createdWall)) ) { clearWallPlacementDraft() setCursorPoint(null) @@ -9923,6 +10005,7 @@ export function FloorplanPanel({ return } + wallRunReferenceRef.current = placedDirection setDraftStart(nextStart) setDraftEnd(nextStart) setCursorPoint(nextStart) @@ -9953,6 +10036,11 @@ export function FloorplanPanel({ }) => snapWallDraftPoint({ ...args, magnetic: isMagneticSnapActive() }), [], ) + // What the click needs to snap an open run's segment as the move did. + const getWallRunContext = useCallback( + () => ({ runStart: wallChainFirstVertex, reference: wallRunReferenceRef.current }), + [wallChainFirstVertex], + ) const { handleBackgroundPlacementClick } = useFloorplanBackgroundPlacement({ activePolygonDraftPoints, ceilingDraftPoints, @@ -9965,6 +10053,7 @@ export function FloorplanPanel({ findClosestWallPoint, floorplanOpeningLocalY, getSnappedFloorplanPoint, + getWallRunContext, handleCeilingItemPlacementClick, handleCeilingPlacementPoint, handleSlabPlacementPoint, @@ -9988,20 +10077,16 @@ export function FloorplanPanel({ isWallBuildActive, isZoneBuildActive, levelId, - roofDraftStart, setCursorPoint, setFenceDraftEnd, setFenceDraftStart, - setRoofDraftEnd, - setRoofDraftStart, snapPolygonDraftPoint, snapWallDraftPoint: snapWallDraftPointMagnetic, toPoint2D, walls, - // World-axis grid snap so drafts land on the visible grid even - // when the active building is rotated. The helper resolves the - // active building's pose internally; this hook stays oblivious to - // building rotation / position. + // Building-grid snap: the lattice this plan draws inside its rotated + // scene group, so drafts land on the visible grid however the + // building is turned, and where the 3D tool would put them. worldGridSnap: snapBuildingLocalToWorldGrid, }) @@ -10204,6 +10289,16 @@ export function FloorplanPanel({ ) const handleSvgPointerUp = useCallback( (event: ReactPointerEvent) => { + // Letting go of a wall end's pull grip places the pulled wall, as a click + // there would; the click the browser fires next is eaten. + const pull = useWallPullRun.getState().run + if (event.button === 0 && pull?.dragging && pull.origin === '2d') { + releaseWallPullDrag() + if (isWallBuildActive && draftStart) { + swallowNextClick() + handleBackgroundClick(event) + } + } if ( isFenceBuildActive && useEditor.getState().getContinuation('fence') === 'freehand' && @@ -10215,14 +10310,30 @@ export function FloorplanPanel({ endFloorplanNavigation(event) }, [ + draftStart, emitFloorplanGridEvent, endFloorplanNavigation, getPlanPointFromClientPoint, + handleBackgroundClick, isFenceBuildActive, + isWallBuildActive, ], ) const handleBackgroundDoubleClick = useCallback( (event: ReactMouseEvent) => { + // A double-click ends a wall run as the 3D tool's detail-2 click does: + // the walls already drawn stay, an open Polygon room is kept, and the + // draft goes. The click path does this on its second click; this also + // covers one the plan never routed there (the walls the run drew sit + // under the pointer by then). + if (isWallBuildActive) { + if (draftStart) { + commitWallPolygonDraft() + clearWallPlacementDraft() + setCursorPoint(null) + } + return + } if (!(isPolygonDraftBuildActive && !isRoofBuildActive)) { return } @@ -10268,6 +10379,8 @@ export function FloorplanPanel({ }, [ activePolygonDraftPoints, + clearWallPlacementDraft, + draftStart, emitFloorplanGridEvent, handleCeilingPlacementConfirm, getPlanPointFromClientPoint, @@ -10276,8 +10389,10 @@ export function FloorplanPanel({ isCeilingBuildActive, isPolygonDraftBuildActive, isRoofBuildActive, + isWallBuildActive, isZoneBuildActive, levelId, + setCursorPoint, ], ) @@ -11611,7 +11726,11 @@ export function FloorplanPanel({ {activeDrawingType === 'site-plan' ? ( ) : ( - + <> + {/* The foundation view's concrete, under the footings the registry draws. */} + + + )} {/* Faint footprint ghost of the node being placed by a registry placement tool (e.g. column), following the @@ -11626,6 +11745,11 @@ export function FloorplanPanel({ `floorplan-wall-move-ghost-layer.tsx`. */} + {/* Dashed walls of the level below / above, over the plan but + outside the measured group so they never change the fit. */} + {activeDrawingType !== 'site-plan' && ( + + )} {activeDrawingType !== 'site-plan' && } @@ -11641,7 +11765,7 @@ export function FloorplanPanel({ overlay during a free-translate drag. Sits above the registry layer so the red lines and distance pills paint on top of node geometry. */} - + {/* The property line, its vertex handles and its edge labels are drawn in BUILDING-LOCAL metres (the frame the level's walls @@ -11695,7 +11819,7 @@ export function FloorplanPanel({ {/* "Magnetic" wall-snap beacon — per-kind glyph at the active draft / endpoint-move snap point. Same store + coord space as the alignment guides. */} - + diff --git a/packages/editor/src/components/editor/footprint-height-handle.tsx b/packages/editor/src/components/editor/footprint-height-handle.tsx index ecc52f95d4..363623ef1f 100644 --- a/packages/editor/src/components/editor/footprint-height-handle.tsx +++ b/packages/editor/src/components/editor/footprint-height-handle.tsx @@ -13,6 +13,7 @@ import { useViewer } from '@pascal-app/viewer' import { type ThreeEvent, useFrame, useThree } from '@react-three/fiber' import { useRef, useState } from 'react' import { type Group, Plane, Vector2, Vector3 } from 'three' +import { floorHeldByDesignedFoundation } from '../../lib/building-foundation' import { beginFootprintHeightPreview, footprintHeightMinimum, @@ -35,7 +36,10 @@ const PILL = const NOTICE = 'pointer-events-none max-w-72 rounded-full border border-border/60 bg-background/95 px-3 py-1 text-center text-muted-foreground text-xs shadow-md backdrop-blur-md' -/** The selected footprint floor (base plate), when it is the one selection in select mode. */ +/** + * The selected footprint floor (base plate), when it is the one selection in select mode and its + * height is its own. + */ function useSelectedFootprint(): SlabNode | null { const selectedId = useViewer((s) => s.selection.selectedIds.length === 1 ? s.selection.selectedIds[0] : null, @@ -50,7 +54,10 @@ function useSelectedFootprint(): SlabNode | null { const node = selectedId ? s.nodes[selectedId as AnyNodeId] : undefined return node?.type === 'slab' && node.plateRole === 'base' ? node : null }) - return enabled && idle ? plate : null + // The Foundation section's finish floor sets this floor's height: dragging the plate's own would + // lift the floor off its seat. + const held = useScene((s) => (plate ? floorHeldByDesignedFoundation(s.nodes, plate) : false)) + return enabled && idle && !held ? plate : null } /** diff --git a/packages/editor/src/components/editor/grid.tsx b/packages/editor/src/components/editor/grid.tsx index bda40eb44e..72f046f5d8 100644 --- a/packages/editor/src/components/editor/grid.tsx +++ b/packages/editor/src/components/editor/grid.tsx @@ -15,7 +15,8 @@ import { MeshBasicNodeMaterial } from 'three/webgpu' import { useCeilingEvents } from '../../hooks/use-ceiling-events' import { useGridEvents } from '../../hooks/use-grid-events' import { getPlacementSurface, usesOrientedPlacementPlane } from '../../lib/active-placement-surface' -import { gridLocalNormal, gridLocalPoint } from '../../lib/grid-frame' +import { gridLocalNormal, gridLocalPoint, layHorizontalGrid } from '../../lib/grid-frame' +import { getActiveGridPose } from '../../lib/world-grid-snap' import useEditor, { getActiveSnapContext, isGridSnapActive } from '../../store/use-editor' import { getMovingNode } from '../../store/use-interaction-scope' @@ -27,10 +28,9 @@ export const EDITOR_GRID_INPUT_NAME = 'pascal-editor-grid-input' const PLACEMENT_REVEAL_RADIUS = 12 const UP = new Vector3(0, 1, 0) -// PlaneGeometry faces +Z; this is the orientation that lays it flat (its normal -// → world +Y), equivalent to the old `rotation-x={-π/2}`. +// PlaneGeometry faces +Z; the oriented (wall-plane) lattice turns this normal +// into the host's. The flat lattice is laid by `layHorizontalGrid`. const PLANE_LOCAL_NORMAL = new Vector3(0, 0, 1) -const HORIZONTAL_QUATERNION = new Quaternion().setFromUnitVectors(PLANE_LOCAL_NORMAL, UP) export const Grid = ({ cellSize = 0.5, @@ -184,10 +184,10 @@ export const Grid = ({ useCeilingEvents() // Track the last world-space cursor hit. The reveal-fade shader reads - // `positionLocal.xy` (vertex position on the un-transformed plane), and the - // laid-flat orientation maps `positionLocal.y` to world `-Z` relative to the - // mesh origin. The cursor is recomputed every frame from the stored world hit - // so the reveal stays put regardless of where the mesh origin sits. + // `positionLocal.xy` (vertex position on the un-transformed plane), so the + // cursor is taken into the plane's frame every frame from the stored world + // hit — the reveal stays put wherever the mesh origin sits and however it + // is turned. const lastWorldCursorRef = useRef(null) useEffect(() => { const onGridMove = (event: GridEvent) => { @@ -279,20 +279,29 @@ export const Grid = ({ setGridY(surfacePoint.y) } } else { - // Horizontal: keep the lattice anchored to world XZ (0,0); only the Y - // origin follows the surface height (floor / shelf top). Snap directly — - // the old lerp made the grid visibly drift up to a new floor height. - // Cursor uniform tracks the world cursor (mirrored on Z for the flat plane). + // Horizontal: lay the lattice on the frame the tools snap in — the + // active building's yaw and offset, so a turned house's walls sit square + // on it, or world XZ (0,0) for site work; only the Y origin follows the + // surface height (floor / shelf top). Snap directly — the old lerp made + // the grid visibly drift up to a new floor height. const targetY = surfacePoint ? surfacePoint.y : levelY // Visual mesh rides 1mm above the surface: at exactly the surface Y the // lattice is coplanar with the slab top and z-fights it. The event plane // (`gridY` → useGridEvents) stays at the true surface height. - gridMesh.position.set(0, targetY + 0.001, 0) - gridMesh.quaternion.copy(HORIZONTAL_QUATERNION) + layHorizontalGrid( + getActiveGridPose(), + targetY + 0.001, + gridMesh.position, + gridMesh.quaternion, + ) + // Cursor uniform → plane-local XY, as for the wall plane above. const world = lastWorldCursorRef.current if (world) { + invQuatRef.current.copy(gridMesh.quaternion).invert() const local = gridLocalPoint(gridMesh.parent, world, localCursorRef.current) - cursorPositionRef.current.set(local.x, -local.z) + .sub(gridMesh.position) + .applyQuaternion(invQuatRef.current) + cursorPositionRef.current.set(local.x, local.y) } if (lastGridYRef.current !== targetY) { lastGridYRef.current = targetY diff --git a/packages/editor/src/components/editor/group-move-3d.ts b/packages/editor/src/components/editor/group-move-3d.ts index 74cc625d31..5f9295a831 100644 --- a/packages/editor/src/components/editor/group-move-3d.ts +++ b/packages/editor/src/components/editor/group-move-3d.ts @@ -27,7 +27,6 @@ import useEditor, { import useInteractionScope from '../../store/use-interaction-scope' import { useFloorplanGroupDrag } from '../editor-2d/floorplan-group-move' import { suppressBoxSelectForPointer } from '../tools/select/box-select-state' -import { startGroupPickUp } from './group-actions' import { classifyParticipant, collectParticipants, @@ -46,8 +45,8 @@ import { swallowNextClick } from './handles/use-handle-drag' // 3D sibling of the 2D floorplan group move (and successor of the removed // group-move gizmo cross): pressing any selected element's body in a // multi-selection and dragging past the threshold slides the whole selection -// on the ground plane; a plain click (no drag) enters the group pick-up, -// parity with the single-item click-to-move. Shares the group participant +// on the ground plane; a plain click (no drag) only selects, never picks the +// group up. Shares the group participant // snapshot, welded junctions, snapping entry points, live override previews, // and single-undo commit with the 2D session. @@ -70,6 +69,8 @@ export function armGroupMove3d(args: { camera: Camera raycaster: Raycaster domElement: HTMLCanvasElement + /** A plain click keeps the selection (the group box) instead of selecting the pressed member. */ + keepSelectionOnClick?: boolean }): boolean { const { nodeId, clientX, clientY, pointerId, camera, raycaster, domElement } = args const { selectedIds, levelId } = useViewer.getState().selection @@ -330,14 +331,15 @@ export function armGroupMove3d(args: { const onUp = (e: PointerEvent) => { if (e.pointerId !== pointerId) return if (!session) { - // Plain click — enter the group pick-up, parity with the single-item - // click-to-move. Eat the click so the selection manager's click - // handling doesn't collapse the multi-selection underneath it. + // Plain click: no move. On a member the selection manager's click + // handling selects it, as a click on any other node would; on the group + // box the click is eaten so the selection stays, as in the plan. removeListeners() - swallowNextClick() - useViewer.getState().setInputDragging(true) - setTimeout(() => useViewer.getState().setInputDragging(false), 0) - startGroupPickUp() + if (args.keepSelectionOnClick) { + swallowNextClick() + useViewer.getState().setInputDragging(true) + setTimeout(() => useViewer.getState().setInputDragging(false), 0) + } return } swallowNextClick() diff --git a/packages/editor/src/components/editor/group-selection-box-3d.tsx b/packages/editor/src/components/editor/group-selection-box-3d.tsx index 6e33fe67ed..f3467e23ca 100644 --- a/packages/editor/src/components/editor/group-selection-box-3d.tsx +++ b/packages/editor/src/components/editor/group-selection-box-3d.tsx @@ -27,7 +27,7 @@ const BOX_PAD = 0.06 * the multi-selection's transformable participants that doubles as the * group's whole-volume drag * handle — move cursor across it, press-drag anywhere on it slides the group, - * a plain click picks it up. Holding a selection modifier passes the press + * a plain click keeps the selection. Holding a selection modifier passes the press * through so members inside can still be toggled. Rides the live drag delta * so it tracks the group mid-gesture. */ @@ -141,6 +141,7 @@ export function GroupSelectionBox3D() { camera, raycaster, domElement: gl.domElement, + keepSelectionOnClick: true, }) // Own the press: deeper hits (members, ground) must not also select. if (armed) event.stopPropagation() diff --git a/packages/editor/src/components/editor/index.tsx b/packages/editor/src/components/editor/index.tsx index 56e50c9fca..7e03bdb0d2 100644 --- a/packages/editor/src/components/editor/index.tsx +++ b/packages/editor/src/components/editor/index.tsx @@ -4,8 +4,10 @@ import { Icon } from '@iconify/react' import { acquireSceneReadOnlyLease, createI18n, + DEFAULT_UPPER_FLOOR_THICKNESS, getCatalogMaterialById, getLibraryMaterialIdFromRef, + getMaterialSwatch, getSceneMaterialIdFromRef, initSpaceDetectionSync, initSpatialGridSync, @@ -77,7 +79,7 @@ import useSessionGroups from '../../store/use-session-groups' import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout' import { PointAskLayer } from '../point-ask/point-ask-layer' import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system' -import { CeilingSystem } from '../systems/ceiling/ceiling-system' +import { CeilingSystem, CeilingTopsSync } from '../systems/ceiling/ceiling-system' import { RoofEditSystem } from '../systems/roof/roof-edit-system' import { SelectionAffordanceManager } from '../systems/selection-affordance-manager' import { StairEditSystem } from '../systems/stair/stair-edit-system' @@ -190,7 +192,10 @@ const EDITOR_DEFAULT_RENDER = { shading: 'solid' } as const */ function initializeEditorRuntime(): () => void { const unsubscribeSpatialGrid = initSpatialGridSync() - const unsubscribeSpaceDetection = initSpaceDetectionSync(useScene, useEditor) + // Floors a person draws over rooms below are born framed-floor thick; agent writes are not. + const unsubscribeSpaceDetection = initSpaceDetectionSync(useScene, useEditor, { + newUpperFloorThickness: DEFAULT_UPPER_FLOOR_THICKNESS, + }) const stopFinishScriptRefresh = initFinishScriptRefresh() initSFXBus() @@ -773,7 +778,17 @@ function getActivePaintMaterialSwatchImageUrl( const catalogId = getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined const catalogMaterial = getCatalogMaterialById(catalogId) - return catalogMaterial?.previewThumbnailUrl ?? catalogMaterial?.preset.maps.albedoMap + return catalogMaterial ? (getMaterialSwatch(catalogMaterial).imageUrl ?? undefined) : undefined +} + +/** Whether the badge's image is a white albedo its colour tints (a siding's profile). */ +function isActivePaintMaterialSwatchTinted(material: ActivePaintMaterial | null) { + if (material?.material?.texture?.url || getSceneMaterialIdFromRef(material?.materialPreset)) + return false + const catalogId = + getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined + const catalogMaterial = getCatalogMaterialById(catalogId) + return catalogMaterial ? getMaterialSwatch(catalogMaterial).tinted : false } function PaintCursorBadge({ @@ -781,6 +796,7 @@ function PaintCursorBadge({ state, swatchColor, swatchImageUrl, + swatchTinted = false, isEraser, isPicker = false, }: { @@ -788,6 +804,8 @@ function PaintCursorBadge({ state: PaintCursorBadgeState swatchColor: string swatchImageUrl?: string + /** Multiply the image by the colour (a tinted albedo). */ + swatchTinted?: boolean isEraser: boolean /** The eyedropper: the swatch is what a click would take. */ isPicker?: boolean @@ -861,6 +879,7 @@ function PaintCursorBadge({ backgroundImage: swatchImageUrl ? `url(${JSON.stringify(swatchImageUrl)})` : undefined, + backgroundBlendMode: swatchImageUrl && swatchTinted ? 'multiply' : undefined, opacity: state === 'aim' ? 0.55 : 1, }} /> @@ -1124,6 +1143,7 @@ function PaintCursorLayer({ const swatchMaterial = picking ? picked : activePaintMaterial const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials) const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials) + const swatchTinted = isActivePaintMaterialSwatchTinted(swatchMaterial) if (!active || !position) return null @@ -1139,6 +1159,7 @@ function PaintCursorLayer({ state={badgeState} swatchColor={swatchColor} swatchImageUrl={swatchImageUrl} + swatchTinted={swatchTinted} /> ) @@ -1367,6 +1388,7 @@ const ViewerCanvas = memo(function ViewerCanvas({ const viewId = layout.panes[pane] return (
view.id === viewId)} /> + {/* A restored pane's plugin is known only once the project has loaded: until + then it would read "This view isn't available". */} + {isSceneDataLoading && loadingOverlay}
) })} @@ -1866,6 +1891,7 @@ function EditorContent({ return ( <> + {!isLoading && isPreviewMode ? ( + {!isLoading && isPreviewMode ? ( , run: () => unknown /** * The selected room's arrows on the plan: one push/pull chevron per boundary - * span (on the far face, along the normal the handle supplies) and, for a - * mezzanine, one per plate edge. Mounted after every node's handles so a - * wall outline never takes the press. The drags are the 3D arrows' own. + * wall span (on the far face, along the normal the handle supplies), one per + * straight edge of invisible walls and, for a mezzanine, one per plate edge. + * Mounted after every node's handles so a wall outline never takes the press. + * The drags are the 3D arrows' own. */ export function RoomHandles2D({ levelId }: { levelId: string | null }) { const room = useRoomControls() @@ -352,3 +363,87 @@ export function RoomHandleDragPreview2D({ levelId }: { levelId: string | null })
) } + +/** + * The sole selected node's temporary dimensions while nothing is in progress, + * from its kind's `selectionDimensions` (a wall: the room widths its push + * shows and, for an exterior wall, the outside size, as the dimension-faces + * preference asks). Shared by the plan and the 3D view; typing into one + * drives the node. Recomputed only when the node itself or its level's rooms + * change (the room index keeps its records while unrelated nodes edit). + */ +export function useSelectedWallDimensions(): { + node: AnyNode + dimensions: readonly WallSelectionDimension[] +} | null { + const nodeId = useViewer((s) => + s.selection.selectedIds.length === 1 ? s.selection.selectedIds[0] : null, + ) + const node = useScene((s) => (nodeId ? s.nodes[nodeId as AnyNodeId] : undefined)) + const enabled = useEditor((s) => s.mode === 'select') + const idle = useInteractionScope((s) => s.scope.kind === 'idle') + const dragging = useRoomHandleDrag((s) => s.drag !== null) + const faces = useFloorplanAnnotationVisibility((s) => s.wallDimensionFaces) + const build = node + ? getFloorplanNodeExtension(nodeRegistry.get(node.type))?.selectionDimensions + : undefined + const rooms = useRoomRecords(build ? (node?.parentId ?? null) : null) + return useMemo(() => { + if (!node || !build || !enabled || !idle || dragging) return null + // `rooms` is the invalidation key: it changes when the level's room + // topology, slabs, ceilings or zones do. + if (!rooms) return null + const dimensions = build({ node, nodes: useScene.getState().nodes, faces }) + return dimensions.length ? { node, dimensions } : null + }, [build, dragging, enabled, faces, idle, node, rooms]) +} + +/** + * The selected wall's temporary dimensions on the plan, drawn as the push + * drag draws its room widths (indigo line, end dots). Each is a `dimension` + * owned by the wall, so a click on its value opens the plan's type-a-value + * box (`FloorplanDimensionEditOverlay`) and the typed size slides the wall. + */ +export function SelectedWallDimensions2D({ levelId }: { levelId: string | null }) { + const selection = useSelectedWallDimensions() + const context = useFloorplanRender() + const unit = useViewer((s) => s.unit) + const metricNotation = useViewer((s) => s.metricNotation) + const geometry = useMemo(() => { + if (!selection) return null + return { + kind: 'group', + children: selection.dimensions.map(({ from, to, distance }): FloorplanGeometry => { + const length = Math.hypot(to[0] - from[0], to[1] - from[1]) || 1 + return { + kind: 'dimension', + start: from, + end: to, + offsetNormal: [-(to[1] - from[1]) / length, (to[0] - from[0]) / length], + offsetDistance: 0, + extensionOvershoot: 0, + extensionStartGap: 0, + terminator: 'dot', + text: formatLinearMeasurement(distance, unit, metricNotation), + stroke: MEASUREMENT_FLOORPLAN_COLOR, + } + }), + } + }, [metricNotation, selection, unit]) + if (!selection || !geometry || selection.node.parentId !== levelId) return null + return ( + event.stopPropagation()} + > + + + ) +} diff --git a/packages/editor/src/components/editor/room-handles.tsx b/packages/editor/src/components/editor/room-handles.tsx index 86e7fe5de1..ed603f5db2 100644 --- a/packages/editor/src/components/editor/room-handles.tsx +++ b/packages/editor/src/components/editor/room-handles.tsx @@ -1,6 +1,7 @@ 'use client' import { + type AnyNodeId, clampRoomFloorHandle, type Point, roomDrawnFloor, @@ -32,6 +33,7 @@ import { publishStructuralElevationGuide, resolveStructuralElevationSnap, } from '../../lib/elevation-guides' +import { parseDimensionInput } from '../../lib/floorplan/dimension-drive' import { formatLinearMeasurement, MEASUREMENT_ACTIVE_COLOR } from '../../lib/measurements' import { commitRoomFloorElevation, roomFloorBase } from '../../lib/room-construction-commands' import { @@ -50,11 +52,16 @@ import { import type { RoomDimension } from '../../lib/room-push-dimensions' import type { RoomSelectionRecord } from '../../lib/room-selection' import useEditor, { isGridSnapActive, isMagneticSnapActive } from '../../store/use-editor' +import { + commitDimensionValue, + useDimensionEditAllowed, +} from '../editor-2d/renderers/floorplan-dimension-edit-overlay' import { suppressBoxSelectForPointer } from '../tools/select/box-select-state' import { ARROW_COLOR, ARROW_SCALE, HandleArrow } from './handles/handle-arrow' import { resolveResizeSnapValue } from './handles/resize-snap' import { Html } from './localized-html' import { formatMeasurement } from './measurement-pill' +import { useSelectedWallDimensions } from './room-handles-2d' import { WallPushArrow } from './wall-push-arrow' const PILL = @@ -295,8 +302,9 @@ export function ElevationArrows({ /** * A selected room's 3D handles: the floor elevation tracker at its centroid - * and one push/pull arrow outside every boundary wall. Rendered in the level - * frame by `RoomControls3D`, only while nothing else is in progress. + * and one push/pull arrow outside every boundary wall and every straight edge + * of invisible walls. Rendered in the level frame by `RoomControls3D`, only + * while nothing else is in progress. */ export function RoomHandles3D({ room }: { room: RoomSelectionRecord }) { const nodes = useScene((s) => s.nodes) @@ -307,7 +315,11 @@ export function RoomHandles3D({ room }: { room: RoomSelectionRecord }) { () => outlineCentroid([room.polygon as Point[], ...(room.holes as Point[][])]), [room.polygon, room.holes], ) - const handles = useMemo(() => roomPushHandles(nodes, room.spans), [nodes, room.spans]) + // Its invisible walls' arrows lie on its floor, where their dashed lines are drawn. + const handles = useMemo( + () => roomPushHandles(nodes, room.spans, elevation), + [nodes, room.spans, elevation], + ) // A mezzanine has no walls: its plate edges carry the push arrows instead. const edges = useMemo( () => (room.mezzanine ? mezzanineEdgeHandles(nodes, room.zoneId) : []), @@ -414,9 +426,21 @@ const DIMENSION_LABEL_OUTLINE = [ * above its room's floor and drawn over the walls, closed by cross ticks, with * the measurement's outlined label — apart from the moved-distance pill. */ -function RoomDimensionLines3D({ dimensions }: { dimensions: RoomDimension[] }) { +function RoomDimensionLines3D({ + dimensions, + hidden = false, + wallId, +}: { + dimensions: readonly RoomDimension[] + /** Hides the labels while keeping their drei `Html` roots mounted. */ + hidden?: boolean + /** The selected wall the dimensions run from: set, each value can be typed to move it. */ + wallId?: string +}) { const unit = useViewer((s) => s.unit) const metricNotation = useViewer((s) => s.metricNotation) + const editable = useDimensionEditAllowed() && !!wallId + const [editing, setEditing] = useState(null) const geometry = useMemo(() => { if (!dimensions.length) return null const positions: number[] = [] @@ -450,22 +474,157 @@ function RoomDimensionLines3D({ dimensions }: { dimensions: RoomDimension[] }) { dimension.elevation, (dimension.from[1] + dimension.to[1]) / 2, ]} - style={{ pointerEvents: 'none' }} + style={{ display: hidden ? 'none' : undefined, pointerEvents: 'none' }} zIndexRange={[20, 0]} > -
- {formatMeasurement(dimension.distance, unit, metricNotation)} -
+ {editable && wallId && !hidden && editing === dimension.key ? ( + setEditing(null)} + unit={unit} + wallId={wallId} + /> + ) : ( +
setEditing(dimension.key) : undefined} + onKeyDown={ + editable + ? (event) => { + if (event.key === 'Enter') setEditing(dimension.key) + } + : undefined + } + onPointerDown={editable ? (event) => event.stopPropagation() : undefined} + role={editable ? 'button' : undefined} + style={{ textShadow: DIMENSION_LABEL_OUTLINE }} + tabIndex={editable ? 0 : undefined} + title={editable ? 'Type a size to move the wall' : undefined} + > + {formatMeasurement(dimension.distance, unit, metricNotation)} +
+ )} ))} ) } +/** + * A temporary dimension's value, typed: Enter moves the selected wall so the + * dimension reads it (`commitDimensionValue`, one undo step), Esc or a click + * away leaves it. Feet-inches, metric and bare numbers parse as in the plan. + */ +function DimensionValueInput({ + dimension, + initial, + onClose, + unit, + wallId, +}: { + dimension: RoomDimension + initial: string + onClose: () => void + unit: 'metric' | 'imperial' + wallId: string +}) { + const [draft, setDraft] = useState(initial) + const [error, setError] = useState(null) + const commit = () => { + const nextLength = parseDimensionInput(draft, unit) + if (nextLength === null) { + setError(`Cannot read "${draft.trim()}"`) + return + } + const outcome = commitDimensionValue({ + target: { + ownerNodeId: wallId as AnyNodeId, + witnessStart: dimension.from, + witnessEnd: dimension.to, + value: dimension.distance, + }, + nextLength, + unit, + }) + if (outcome.ok) onClose() + else setError(outcome.reason) + } + return ( +
event.stopPropagation()} + > + { + setDraft(event.target.value) + setError(null) + }} + onFocus={(event) => event.target.select()} + onKeyDown={(event) => { + event.stopPropagation() + if (event.key === 'Enter') { + event.preventDefault() + commit() + } else if (event.key === 'Escape') { + event.preventDefault() + onClose() + } + }} + spellCheck={false} + value={draft} + /> + {error ? ( +
+ {error} +
+ ) : null} +
+ ) +} + +/** + * The sole selected wall's temporary dimensions in 3D, the plan's twin + * (`SelectedWallDimensions2D`): the room widths its push shows and the + * outside size, drawn as the push draws them, in the wall's level frame. + */ +export function SelectedWallDimensions3D() { + const selection = useSelectedWallDimensions() + const isFloorplanHovered = useEditor((s) => s.isFloorplanHovered) + const levelId = selection?.node.parentId ?? null + const root = useRef(null) + useFrame(() => { + const level = levelId ? sceneRegistry.nodes.get(levelId) : null + if (!root.current) return + root.current.visible = !!level && !isFloorplanHovered + if (level) { + level.updateWorldMatrix(true, false) + root.current.matrix.copy(level.matrixWorld) + } + }) + // The labels are DOM (drei `Html`); with no document (a headless + // renderer) there is nothing to draw them into. + if (!selection || typeof document === 'undefined') return null + // Hovering the plan hides the dimensions rather than unmounting them: drei + // `Html` unmounts its DOM root synchronously, which React reports as an error + // when it happens inside a render. + return ( + + + ) +} + /** A mezzanine edge's push arrow: the room push arrow's look, driving `setZoneEdges`. */ function MezzanineEdgeArrow({ handle, diff --git a/packages/editor/src/components/editor/room-highlight.tsx b/packages/editor/src/components/editor/room-highlight.tsx index eb50d1f747..c1e6f54718 100644 --- a/packages/editor/src/components/editor/room-highlight.tsx +++ b/packages/editor/src/components/editor/room-highlight.tsx @@ -25,6 +25,7 @@ import { describeZoneOutline, type RoomSelectionRecord } from '../../lib/room-se import useEditor from '../../store/use-editor' import useInteractionScope from '../../store/use-interaction-scope' import { RoomControls2D, RoomControls3D } from './room-controls' +import { SelectedWallDimensions3D } from './room-handles' /** * A zone reached as a zone (hovered or selected through its area or pill, not @@ -188,6 +189,7 @@ export function RoomHighlight3D() { return ( <> + {room ? : null} ) diff --git a/packages/editor/src/components/editor/room-labels.test.ts b/packages/editor/src/components/editor/room-labels.test.ts index f73ada9f36..7865aa7389 100644 --- a/packages/editor/src/components/editor/room-labels.test.ts +++ b/packages/editor/src/components/editor/room-labels.test.ts @@ -91,7 +91,7 @@ describe('room label pills', () => { } }) - test('step back for any other selection, a room, a zone, a unit, a tool, a gesture or site', () => { + test('step back for any other selection, a room, a zone, a unit, a tool, a gesture, site or the foundation view', () => { for (const change of [ { selectedTypes: ['wall'] }, { selectedTypes: ['level', 'item'] }, @@ -104,6 +104,7 @@ describe('room label pills', () => { { phase: 'site' }, { isCaptureMode: true }, { isThumbnailCapture: true }, + { foundationView: true }, ] satisfies Partial[]) { expect(roomLabelsVisible({ ...resting, ...change })).toBe(false) } diff --git a/packages/editor/src/components/editor/room-labels.tsx b/packages/editor/src/components/editor/room-labels.tsx index 3849ef2ef0..6fed8e51ee 100644 --- a/packages/editor/src/components/editor/room-labels.tsx +++ b/packages/editor/src/components/editor/room-labels.tsx @@ -71,12 +71,15 @@ export type RoomLabelVisibilityState = { focusedUnitId: string | null isCaptureMode: boolean isThumbnailCapture: boolean + /** The level list's Foundation row: no rooms in that view. */ + foundationView?: boolean } /** * Room pills are the resting view of a level: inside the building, select tool * idle, and nothing picked but the site, building or level itself. Anything - * selected, a unit being arranged, or a gesture in flight takes them away. + * selected, a unit being arranged, a gesture in flight, or the foundation view + * takes them away. */ export function roomLabelsVisible(state: RoomLabelVisibilityState): boolean { return ( @@ -86,6 +89,7 @@ export function roomLabelsVisible(state: RoomLabelVisibilityState): boolean { !state.room && !state.zoneId && !state.focusedUnitId && + !state.foundationView && !state.isCaptureMode && !state.isThumbnailCapture && state.selectedTypes.every((type) => type !== undefined && CONTEXT_TYPES.has(type)) @@ -123,6 +127,7 @@ function useRoomLabelsVisible() { const scopeIdle = useInteractionScope((s) => s.scope.kind === 'idle') const zoneId = useViewer((s) => s.selection.zoneId) const focusedUnitId = useViewer((s) => s.focusedUnitId) + const foundationView = useViewer((s) => s.foundationView) const selectedIds = useViewer((s) => s.selection.selectedIds) const selectedTypes = useScene( useShallow((s) => @@ -134,6 +139,7 @@ function useRoomLabelsVisible() { scopeIdle, zoneId, focusedUnitId, + foundationView, selectedTypes, isThumbnailCapture, }) diff --git a/packages/editor/src/components/editor/selection-manager.tsx b/packages/editor/src/components/editor/selection-manager.tsx index 1d099638e2..f9c0217170 100644 --- a/packages/editor/src/components/editor/selection-manager.tsx +++ b/packages/editor/src/components/editor/selection-manager.tsx @@ -15,7 +15,6 @@ import { type NodeEvent, nodeRegistry, type RoofEvent, - type RoofNode, type RoofSegmentEvent, type RoofSegmentNode, resolveLevelId, @@ -29,7 +28,11 @@ import { useScene, wallFaceScriptPaintRefusal, } from '@pascal-app/core' -import { getRoofMaterialArray, registerMaterialCacheCleanup, useViewer } from '@pascal-app/viewer' +import { + ROOF_HEEL_MATERIAL_INDEX, + registerMaterialCacheCleanup, + useViewer, +} from '@pascal-app/viewer' import { useThree } from '@react-three/fiber' import { useCallback, useEffect, useRef } from 'react' import { type BufferGeometry, Color, type Material, type Mesh, type Object3D, Vector3 } from 'three' @@ -43,7 +46,6 @@ import { canDirectMoveNode, canDirectRotateNode, pointerEventHitsEditorHandle, - resolveDirectManipulationNode, resolveDirectRotationDragDelta, resolveDirectRotationPatch, shouldStartDirectMoveDrag, @@ -57,7 +59,6 @@ import { import { selectionEnabled } from '../../lib/interaction/scope' import { type ActivePaintMaterial, - buildRoofSurfaceMaterialPatch, hasActivePaintMaterial, resolveActivePaintMaterialFromSelection, } from '../../lib/material-paint' @@ -90,7 +91,6 @@ import { paintScopeRole, paintSurfaceLabel, resolvePaintScopeTargets, - slotDisplayLabel, type WallPaintHit, } from '../../lib/paint-scope' import { @@ -248,9 +248,10 @@ function resolveStairMaterialTarget( function resolveRoofMaterialTarget( event: RoofEvent | RoofSegmentEvent, -): 'top' | 'edge' | 'wall' | null { +): 'top' | 'edge' | 'wall' | 'heel' | null { const materialIndex = getIntersectionMaterialIndex(getEventObject(event), event.faceIndex) if (materialIndex === 3) return 'top' + if (materialIndex === ROOF_HEEL_MATERIAL_INDEX) return 'heel' if (materialIndex === 0) return 'edge' if (materialIndex === 1 || materialIndex === 2) return 'wall' @@ -451,14 +452,6 @@ function resolveSelectModeNodeTarget(event: NodeEvent, drill = false): AnyNode { return event.node } -function previewMeshMaterial(mesh: Mesh, material: Material | Material[]): PaintPreviewCleanup { - const previousMaterial = mesh.material - mesh.material = material - return () => { - mesh.material = previousMaterial - } -} - function previewCursor(cursor: string): PaintPreviewCleanup { const previousCursor = document.body.style.cursor document.body.style.cursor = cursor @@ -467,33 +460,6 @@ function previewCursor(cursor: string): PaintPreviewCleanup { } } -function applyRoofPaintPreview( - node: RoofNode, - role: 'top' | 'edge' | 'wall', - material: ActivePaintMaterial, -): PaintPreviewCleanup | null { - const root = getRegisteredNodeObject(node.id) - const mesh = root?.getObjectByName('merged-roof') as Mesh | undefined - if (!mesh) return null - - const previewNode = { - ...node, - ...buildRoofSurfaceMaterialPatch(node, role, material.material, material.materialPreset), - } - const previewMaterial = getRoofMaterialArray( - previewNode, - useViewer.getState().shading, - useViewer.getState().textures, - useViewer.getState().colorPreset, - useViewer.getState().sceneTheme, - null, - useScene.getState().materials, - ) - if (!previewMaterial) return null - - return previewMeshMaterial(mesh, previewMaterial) -} - // Chimney + dormer paint dispatch lives on their NodeDefinition's // `capabilities.paint` (see packages/nodes/src/{chimney,dormer}/ // paint.ts). The generic registry-driven arm in this file consults @@ -892,9 +858,7 @@ export const SelectionManager = () => { // Registry-driven paint dispatch — kinds that declare // `capabilities.paint` route hover / click / preview through - // their definition. Wall, chimney, and dormer use this; legacy - // roof / stair / single-surface arms below stay until they - // migrate too. + // their definition (wall, chimney, dormer, roof, …). const paintCap = nodeRegistry.get(node.type)?.capabilities?.paint if (paintCap) { const materialIndex = getIntersectionMaterialIndex(getEventObject(event), event.faceIndex) @@ -1063,51 +1027,7 @@ export const SelectionManager = () => { } } - // A segment paints through its definition's `capabilities.paint`; the merged shell's hit is - // the roof's, and fans to it. - if (node.type === 'roof') { - const role = resolveRoofMaterialTarget(event as RoofEvent) - if (isPaintPicking()) return pickInteraction(node, role, event, null) - const compatible = role !== null && paintEnabled - return { - key: `roof:${node.id}:${role ?? 'unsupported'}:${eraser ? 'erase' : 'paint'}`, - hoveredId: node.id as AnyNodeId, - hoverMode: compatible ? (eraser ? 'erase-ready' : 'paint-ready') : 'paint-disabled', - // Roof isn't on the slot model (role-specific fields, custom commit), - // so it offers only the single surface — but still labels it. - paintHover: - compatible && role - ? { - scopes: ['single'], - slotLabel: slotDisplayLabel(node, role), - nodeNoun: 'roof', - } - : null, - apply: - compatible && role - ? () => { - useScene - .getState() - .updateNode( - node.id as AnyNodeId, - buildRoofSurfaceMaterialPatch( - node, - role, - paintSpec.material, - paintSpec.materialPreset, - ), - ) - } - : null, - preview: - compatible && role - ? () => applyRoofPaintPreview(node, role, paintSpec) - : () => previewCursor('not-allowed'), - } - } - - // Only `roof` reaches a legacy paint arm (above) — every - // other paintable kind declares `capabilities.paint` and returns from the + // Every paintable kind declares `capabilities.paint` and returns from the // registry-driven dispatch at the top of this function. const disabledNodeTypes = ['zone'] @@ -1478,8 +1398,8 @@ export const SelectionManager = () => { } }, [isCurveReshape, mode, movingNode, camera, raycaster, glDomElement, registryVersion]) - // Move cursor over the selected movable node: the visual cue that clicking it - // picks it up (replaces the removed move-cross gizmo). Reacts only when the + // Move cursor over the selected movable node: the visual cue that dragging it + // moves it (replaces the removed move-cross gizmo). Reacts only when the // hovered/selected node changes (not on every camera move) so it doesn't fight // the rotate/resize gizmos' own hover cursors. Clears only the cursor it owns. useEffect(() => { @@ -1521,7 +1441,7 @@ export const SelectionManager = () => { } }, [mode, glDomElement]) - // While a node is actively being moved (click-to-move / Move button, or a + // While a node is actively being moved (a drag, the Move button, or a // fresh preset placement), show a grabbing hand. Mode-independent: presets // move in build mode. Overrides the hover 'move' cursor (which bails while a // movingNode exists), and clears back to the canvas's custom cursor on drop. @@ -1741,9 +1661,9 @@ export const SelectionManager = () => { // Reset the handled flag after a short delay so the grid:click that the // SAME DOM click also raycasts is ignored (it fires synchronously, before // this 50ms macrotask). Scheduled here — right after the flag is set — so - // EVERY branch below clears it, including the click-to-move early return - // (which previously skipped the reset and left empty-click deselect stuck - // until the next normal select). + // EVERY branch below clears it, including the room early return (which + // would otherwise leave empty-click deselect stuck until the next + // normal select). setTimeout(() => { clickHandledRef.current = false }, 50) @@ -1778,35 +1698,9 @@ export const SelectionManager = () => { .endIf((sc) => sc.kind === 'reshaping' && sc.reshape === 'hole') } - // Click-to-move: clicking the already-selected sole movable node with - // no modifiers picks it up instead of re-selecting — the move-cross - // gizmo's old job, now on the node body. `setMovingNode` arms the - // registry move tool in click-to-commit mode, exactly like the floating - // Move button. The first (selecting) click can't hit this because the - // node isn't yet in `selectedIdsBeforeRouting`. - const nativeEvent = event.nativeEvent - const hasModifier = - nativeEvent.altKey || nativeEvent.shiftKey || isCommandModifier(nativeEvent) - const isAlreadySole = - selectedIdsBeforeRouting.length === 1 && selectedIdsBeforeRouting[0] === nodeToSelect.id - if ( - useEditor.getState().mode !== 'delete' && - !hasModifier && - isAlreadySole && - !expandSessionSelectionForNode(nodeToSelect.id) && - !getMovingNode() && - canDirectMoveNode(nodeToSelect) - ) { - // The move tool mounts before the browser's click that ends this - // gesture, which would otherwise commit the node where it stands. - swallowNextClick() - sfxEmitter.emit('sfx:item-pick') - const moveTarget = resolveDirectManipulationNode(nodeToSelect, useScene.getState().nodes) - useEditor.getState().setMovingNode(moveTarget as never) - useViewer.getState().setSelection({ selectedIds: [] }) - return - } - + // A repeated plain click only selects (or cycles, through + // `resolveCanvasSelectionNode` above): it never picks the node up. + // Moving takes an explicit drag, the Move action or its shortcut. activeStrategy.handleSelect( nodeToSelect, event.nativeEvent, diff --git a/packages/editor/src/components/editor/separator-end-grip.tsx b/packages/editor/src/components/editor/separator-end-grip.tsx new file mode 100644 index 0000000000..2c78638ead --- /dev/null +++ b/packages/editor/src/components/editor/separator-end-grip.tsx @@ -0,0 +1,74 @@ +'use client' + +import { type SeparatorNode, sceneRegistry } from '@pascal-app/core' +import { type ThreeEvent, useThree } from '@react-three/fiber' +import { useState } from 'react' +import { OrthographicCamera, Plane, Vector2, Vector3 } from 'three' +import { runSeparatorEndDrag } from '../../lib/separator-edit' +import { suppressBoxSelectForPointer } from '../tools/select/box-select-state' +import { HandleArrow } from './handles/handle-arrow' + +/** + * An invisible wall's 3D end grip, the twin of its end handle on the plan: a + * cube at the end, on the floor at `height`. Dragging it moves the end across + * that floor (`runSeparatorEndDrag`), the invisible walls joined there + * following. Rendered in the level frame. + */ +export function SeparatorEndGrip({ + separator, + endpoint, + height, + levelId, +}: { + separator: SeparatorNode + endpoint: 'start' | 'end' + height: number + levelId: string +}) { + const [hover, setHover] = useState(false) + const { camera, raycaster, gl } = useThree() + const zoom = camera instanceof OrthographicCamera ? 1 / camera.zoom : 1 + const [x, z] = separator[endpoint] + const onPointerDown = (event: ThreeEvent) => { + if (event.button !== 0) return + event.stopPropagation() + suppressBoxSelectForPointer(event) + const level = sceneRegistry.nodes.get(levelId) + if (!level) return + level.updateWorldMatrix(true, false) + const toLocal = level.matrixWorld.clone().invert() + const floor = new Plane().setFromNormalAndCoplanarPoint( + new Vector3(0, 1, 0).transformDirection(level.matrixWorld), + new Vector3(x, height, z).applyMatrix4(level.matrixWorld), + ) + runSeparatorEndDrag({ + separatorId: separator.id, + endpoint, + levelId, + toPlan: (clientX, clientY) => { + const rect = gl.domElement.getBoundingClientRect() + raycaster.setFromCamera( + new Vector2( + ((clientX - rect.left) / rect.width) * 2 - 1, + -((clientY - rect.top) / rect.height) * 2 + 1, + ), + camera, + ) + const hit = raycaster.ray.intersectPlane(floor, new Vector3()) + if (!hit) return null + const local = hit.applyMatrix4(toLocal) + return [local.x, local.z] + }, + }) + } + return ( + + ) +} diff --git a/packages/editor/src/components/editor/use-floorplan-background-placement.ts b/packages/editor/src/components/editor/use-floorplan-background-placement.ts index c62d584a28..11c6c96bc1 100644 --- a/packages/editor/src/components/editor/use-floorplan-background-placement.ts +++ b/packages/editor/src/components/editor/use-floorplan-background-placement.ts @@ -19,6 +19,7 @@ import { resolveGenericFloorplanGridEventPoint } from '../../lib/floorplan-grid- import { snapRegisteredDraftPoint } from '../../lib/registered-draft-snap' import { resolveSlabPlanPointSnap } from '../../lib/slab-plan-snap' import { commitWallPolygonDraft, wallPolygonDraftWalls } from '../../lib/wall-polygon-draft' +import { isWallRunFree, snapWallRunDraftPoint } from '../../lib/wall-run-snap' import useAlignmentGuides from '../../store/use-alignment-guides' import useEditor, { isAngleSnapActive, isMagneticSnapActive } from '../../store/use-editor' import usePlacementPreview from '../../store/use-placement-preview' @@ -52,6 +53,8 @@ type UseFloorplanBackgroundPlacementArgs = { wall: WallNode } | null floorplanOpeningLocalY: number + /** An open wall run's start and the direction of its last wall (null before one). */ + getWallRunContext: () => { runStart: WallPlanPoint | null; reference: WallPlanPoint | null } getSnappedFloorplanPoint: (point: WallPlanPoint) => WallPlanPoint handleCeilingItemPlacementClick: ( planPoint: WallPlanPoint, @@ -72,12 +75,9 @@ type UseFloorplanBackgroundPlacementArgs = { isZoneBuildActive: boolean levelId: string | null registryToolOwnsSnapping: boolean - roofDraftStart: WallPlanPoint | null setCursorPoint: React.Dispatch> setFenceDraftEnd: React.Dispatch> setFenceDraftStart: React.Dispatch> - setRoofDraftEnd: React.Dispatch> - setRoofDraftStart: React.Dispatch> snapWallDraftPoint: (args: { point: WallPlanPoint walls: WallNode[] @@ -86,6 +86,7 @@ type UseFloorplanBackgroundPlacementArgs = { bypassSnap?: boolean step?: number gridSnap?: (point: WallPlanPoint) => WallPlanPoint + captureWallBodies?: boolean }) => WallPlanPoint snapPolygonDraftPoint: (args: { point: WallPlanPoint @@ -95,10 +96,9 @@ type UseFloorplanBackgroundPlacementArgs = { toPoint2D: (point: WallPlanPoint) => { x: number; y: number } walls: WallNode[] /** - * Snap a building-local plan point to the world XZ grid at `step`. - * Injected so the hook doesn't have to know the building's rotation - * or position — used by wall / fence branches that snap at variable - * step. + * Snap a building-local plan point onto the building's grid lattice at + * `step` (the lattice the plan draws, which turns with the building). + * Injected — used by wall / fence branches that snap at variable step. */ worldGridSnap: (point: WallPlanPoint, step: number) => WallPlanPoint } @@ -115,6 +115,7 @@ export function useFloorplanBackgroundPlacement({ findClosestWallPoint, floorplanOpeningLocalY, getSnappedFloorplanPoint, + getWallRunContext, handleCeilingItemPlacementClick, handleCeilingPlacementPoint, handleSlabPlacementPoint, @@ -131,12 +132,9 @@ export function useFloorplanBackgroundPlacement({ isZoneBuildActive, levelId, registryToolOwnsSnapping, - roofDraftStart, setCursorPoint, setFenceDraftEnd, setFenceDraftStart, - setRoofDraftEnd, - setRoofDraftStart, snapWallDraftPoint, snapPolygonDraftPoint, toPoint2D, @@ -219,24 +217,14 @@ export function useFloorplanBackgroundPlacement({ if (isRoofBuildActive) { // Footprint placement (polygon context: grid / lines / off, no angle), // mode-driven to match the chip. Alt is force/free at commit time; - // alignment display/pull follows the active magnetic mode. + // alignment display/pull follows the active magnetic mode. The roof + // tool snaps the corner it commits and writes the draft rectangle from + // it, so the plan shows exactly the roof the click makes. const snappedPoint = alignFloorplanDraftPoint(getSnappedFloorplanPoint(planPoint), { applySnap: isMagneticSnapActive(), }) emitFloorplanGridEvent('click', snappedPoint, event) setCursorPoint(snappedPoint) - - if (roofFootprintSource !== 'draw') { - clearRoofPlacementDraft() - return true - } - - if (roofDraftStart) { - clearRoofPlacementDraft() - } else { - setRoofDraftStart(snappedPoint) - setRoofDraftEnd(snappedPoint) - } return true } @@ -254,8 +242,8 @@ export function useFloorplanBackgroundPlacement({ if (isFenceBuildActive) { // Fence draft: mode-driven (matches the chip), same as the move - // preview. `grid` snaps to the world XZ grid (rotation-safe via the - // `gridSnap` callback), `angles` locks 15° rays from the start, `lines` + // preview. `grid` snaps to the building's grid lattice (the `gridSnap` + // callback), `angles` locks 15° rays from the start, `lines` // pulls onto walls / fences / alignment, `off` is free. const fenceStep = getSegmentGridStep() const fenceAngleSnap = fenceDraftStart !== null && isAngleSnapActive() @@ -363,30 +351,44 @@ export function useFloorplanBackgroundPlacement({ // / draftEnd state in the floor plan would never update, leaving // the dashed-line draft preview invisible. if (isWallBuildActive) { - // Wall draft: mode-driven (matches the chip + the move-preview branch). - // `grid` snaps to the world XZ grid (rotation-safe via `gridSnap`), - // `angles` locks 15° rays from the start, `lines` pulls the endpoint - // onto existing wall corners / edges + alignment, `off` is free. - const wallStep = getSegmentGridStep() - const wallAngleSnap = draftStart !== null && isAngleSnapActive() - const wallSnapped = snapWallDraftPoint({ - point: planPoint, - walls: [...walls, ...wallPolygonDraftWalls()], - start: draftStart ?? undefined, - angleSnap: wallAngleSnap, - gridSnap: (p) => worldGridSnap(p, wallStep), - }) - const wallGridBase = worldGridSnap(planPoint, wallStep) - const wallLocked = wallSnapped[0] !== wallGridBase[0] || wallSnapped[1] !== wallGridBase[1] - let snappedPoint = wallSnapped - if (wallLocked) { - useAlignmentGuides.getState().clear() + // Wall draft, matching the move-preview branch. The first point is + // mode-driven (matches the chip): `grid` snaps to the building's grid + // lattice (`gridSnap`), `lines` pulls it onto existing wall corners / + // edges + alignment, `off` is free, and in every mode but `lines` an + // end aimed into a wall's body joins that wall. An open run's segment + // lands where the move showed it (`snapWallRunDraftPoint`). + let snappedPoint: WallPlanPoint + if (draftStart) { + const run = getWallRunContext() + snappedPoint = snapWallRunDraftPoint({ + point: planPoint, + start: draftStart, + runStart: run.runStart ?? draftStart, + reference: run.reference, + walls: [...walls, ...wallPolygonDraftWalls()], + free: isWallRunFree(event), + }).point } else { - // Alignment lines are shown in every mode; the pull applies only when - // magnetic ('lines') and the segment isn't angle-locked. - snappedPoint = alignFloorplanDraftPoint(wallSnapped, { - applySnap: isMagneticSnapActive() && !wallAngleSnap, + const wallStep = getSegmentGridStep() + const wallSnapped = snapWallDraftPoint({ + point: planPoint, + walls: [...walls, ...wallPolygonDraftWalls()], + gridSnap: (p) => worldGridSnap(p, wallStep), + captureWallBodies: true, }) + const wallGridBase = worldGridSnap(planPoint, wallStep) + const wallLocked = + wallSnapped[0] !== wallGridBase[0] || wallSnapped[1] !== wallGridBase[1] + snappedPoint = wallSnapped + if (wallLocked) { + useAlignmentGuides.getState().clear() + } else { + // Alignment lines are shown in every mode; the pull applies only when + // magnetic ('lines'). + snappedPoint = alignFloorplanDraftPoint(wallSnapped, { + applySnap: isMagneticSnapActive(), + }) + } } emitFloorplanGridEvent('click', snappedPoint, event) @@ -437,12 +439,12 @@ export function useFloorplanBackgroundPlacement({ activePolygonDraftPoints, ceilingDraftPoints, clearFencePlacementDraft, - clearRoofPlacementDraft, clearWallPlacementDraft, emitFloorplanGridEvent, fenceDraftStart, fences, findClosestWallPoint, + getWallRunContext, floorplanOpeningLocalY, getSnappedFloorplanPoint, handleCeilingItemPlacementClick, @@ -459,14 +461,10 @@ export function useFloorplanBackgroundPlacement({ isWallBuildActive, isZoneBuildActive, levelId, - roofDraftStart, registryToolOwnsSnapping, - roofFootprintSource, setCursorPoint, setFenceDraftEnd, setFenceDraftStart, - setRoofDraftEnd, - setRoofDraftStart, snapWallDraftPoint, snapPolygonDraftPoint, toPoint2D, diff --git a/packages/editor/src/components/editor/wall-move-side-handles.tsx b/packages/editor/src/components/editor/wall-move-side-handles.tsx index 34c7f8d9a0..e1ed7a6f39 100644 --- a/packages/editor/src/components/editor/wall-move-side-handles.tsx +++ b/packages/editor/src/components/editor/wall-move-side-handles.tsx @@ -70,6 +70,7 @@ import { NO_RAYCAST, useInvisibleHitAreaMaterial, } from './node-arrow-handles' +import { WallPullGrip } from './wall-pull-grip' import { WallPushArrow } from './wall-push-arrow' const HANDLE_OFFSET = 0.27 @@ -251,6 +252,9 @@ function WallMoveSideHandlesForWall({ wall }: { wall: WallNode }) { /> + {/* Past each end, the grip that pulls the next wall out of it. */} + + createPullArrowGeometry(), []) + const hitGeometry = useMemo(() => createEndpointHitAreaGeometry(PULL_HIT_RADIUS), []) + const hitMaterial = useInvisibleHitAreaMaterial() + const material = useMemo( + () => + new MeshBasicNodeMaterial({ + color: new Color(PULL_FILL), + side: DoubleSide, + transparent: true, + opacity: 0.95, + depthTest: false, + depthWrite: false, + toneMapped: false, + }), + [], + ) + useEffect(() => { + material.color.set(hover ? PULL_HOVER_FILL : PULL_FILL) + }, [hover, material]) + useEffect(() => () => geometry.dispose(), [geometry]) + useEffect(() => () => hitGeometry.dispose(), [hitGeometry]) + useEffect(() => () => material.dispose(), [material]) + // Warm the wall tool's module so the press starts drawing at once. + useEffect(() => { + void nodeRegistry + .get('wall') + ?.tool?.() + .catch(() => {}) + }, []) + useEffect( + () => () => { + hideGripTooltip() + if (document.body.style.cursor === 'grab') document.body.style.cursor = '' + }, + [], + ) + + if (!seed) return null + const [dx, dz] = seed.reference + const offset = PULL_ARROW_OFFSET * zoom + const position: [number, number, number] = [ + seed.start[0] + dx * offset, + PULL_FLOOR_LIFT, + seed.start[1] + dz * offset, + ] + const rotationY = Math.atan2(-dz, dx) + + const onPointerDown = (event: ThreeEvent) => { + if (event.button !== 0) return + event.stopPropagation() + suppressBoxSelectForPointer(event) + sfxEmitter.emit('sfx:item-pick') + setHover(false) + hideGripTooltip() + if (document.body.style.cursor === 'grab') document.body.style.cursor = '' + startWallPull({ wallId: wall.id, endpoint, origin: '3d' }) + } + + return ( + + + { + event.stopPropagation() + setHover(true) + showGripTooltip(event.nativeEvent.clientX, event.nativeEvent.clientY) + document.body.style.cursor = 'grab' + }} + onPointerLeave={(event) => { + event.stopPropagation() + setHover(false) + hideGripTooltip() + if (document.body.style.cursor === 'grab') document.body.style.cursor = '' + }} + scale={1} + /> + + + + ) +} diff --git a/packages/editor/src/components/editor/wall-snap-beacon-layer.tsx b/packages/editor/src/components/editor/wall-snap-beacon-layer.tsx index 1c9a8c55de..25ee75f111 100644 --- a/packages/editor/src/components/editor/wall-snap-beacon-layer.tsx +++ b/packages/editor/src/components/editor/wall-snap-beacon-layer.tsx @@ -16,6 +16,7 @@ import { } from '@pascal-app/core' import { useWallSnapIndicator, type WallSnapKind } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' +import { Html } from '@react-three/drei' import { useFrame } from '@react-three/fiber' import { memo, useMemo, useRef } from 'react' import { BoxGeometry, CircleGeometry, CylinderGeometry, type Group } from 'three' @@ -31,10 +32,14 @@ import { EDITOR_LAYER } from '../../lib/constants' * * endpoint (corner) → square midpoint → triangle * intersection → ✕ cross wall body (edge) → circle + * perpendicular → ⊥ corner projection guide → diamond + * grid length → small + * * Subscribes to the shared `useWallSnapIndicator` store (published by the wall * draft + endpoint-move tools). The vertical mouse pillar and the corner - * (endpoint) square are green; the other floor glyphs are indigo. + * (endpoint) square are green; the other floor glyphs are indigo. A point that + * carries a `label` (a wall pulled from a wall end) also names what it caught + * in a pill beside the foot of the pillar. * * The point carries only XZ (building-local plan coords); like the alignment * guides it's lifted to the active level's building-local Y each frame so it @@ -135,6 +140,23 @@ export const WallSnapBeaconLayer = memo(function WallSnapBeaconLayer() { renderOrder={1002} /> + {point.label ? ( + +
+ {point.label} +
+ + ) : null} ) }) @@ -295,6 +317,64 @@ function SnapMarker({ kind, x, z }: { kind: WallSnapKind; x: number; z: number } ) } + if (kind === 'perpendicular') { + // ⊥ — a base bar with an upright from its middle. + return ( + <> + + + + ) + } + if (kind === 'alignment') { + return ( + + ) + } + if (kind === 'grid') { + return ( + <> + + + + ) + } // 'wall' (edge / along-wall) → circle return ( { + const fresh = normalizePersistedEditorLayoutState(undefined) + expect(fresh.showRoofCeilings).toBe(true) + expect(fresh.showCeilings).toBe(false) + const turned = normalizePersistedEditorLayoutState({ + showRoofCeilings: false, + showCeilings: true, + }) + expect(turned.showRoofCeilings).toBe(false) + expect(turned.showCeilings).toBe(true) +}) + +test('the level’s ceilings setting draws the ceiling tops, stepping aside to place on a ceiling', async () => { + const previous = { + scene: useScene.getState(), + viewer: useViewer.getState(), + editor: useEditor.getState(), + scope: useInteractionScope.getState(), + } + const actGlobal = globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } + const previousAct = actGlobal.IS_REACT_ACT_ENVIRONMENT + actGlobal.IS_REACT_ACT_ENVIRONMENT = true + const makeCanvas = () => + Object.assign(new EventTarget(), { + getBoundingClientRect: () => ({ left: 0, top: 0, width: 100, height: 100 }), + }) as unknown as HTMLCanvasElement + const canvas = makeCanvas() + const previewCanvas = makeCanvas() + const root = createRoot(canvas) + const previewRoot = createRoot(previewCanvas) + const building = BuildingNode.parse({}) + const storey = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const roof = LevelNode.parse({ + parentId: building.id, + level: 1, + height: 3, + metadata: { role: 'roof' }, + }) + building.children = [storey.id, roof.id] + const ceiling = CeilingNode.parse({ + parentId: storey.id, + height: 2.7, + polygon: [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], + ], + }) + const select = (levelId: typeof storey.id) => + act(async () => + useViewer.setState({ + selection: { buildingId: building.id, levelId, zoneId: null, selectedIds: [] }, + }), + ) + const tops = () => useViewer.getState().showCeilingTops + try { + useScene.setState({ + nodes: { + [building.id]: building, + [storey.id]: storey, + [roof.id]: roof, + [ceiling.id]: ceiling, + }, + }) + useEditor.setState({ + ...normalizePersistedEditorLayoutState(undefined), + tool: null, + selectedItem: null, + isPreviewMode: false, + isCaptureMode: false, + }) + useInteractionScope.setState({ scope: { kind: 'idle' } }) + useViewer.setState({ showCeilingTops: false, hoveredId: null }) + await select(roof.id) + const configure = (target: typeof root, domElement: HTMLCanvasElement) => + target.configure({ + gl: { + domElement, + render() {}, + setSize() {}, + setPixelRatio() {}, + } as unknown as WebGLRenderer, + camera: Object.assign(new OrthographicCamera(), { manual: true }), + frameloop: 'never', + dpr: 1, + size: { width: 100, height: 100, top: 0, left: 0 }, + }) + await configure(root, canvas) + await act(async () => + root.render( + createElement(Fragment, null, createElement(CeilingTopsSync), createElement(CeilingSystem)), + ), + ) + expect(tops()).toBe(true) + + // A second viewer's ceiling system (the preview's) going away leaves the tops as set. + await configure(previewRoot, previewCanvas) + await act(async () => previewRoot.render(createElement(CeilingSystem))) + await act(async () => previewRoot.unmount()) + expect(tops()).toBe(true) + + await select(storey.id) + expect(tops()).toBe(false) + await act(async () => useEditor.getState().setShowCeilings(true)) + expect(tops()).toBe(true) + await act(async () => useEditor.setState({ tool: 'ceiling' })) + expect(tops()).toBe(false) + await act(async () => useEditor.setState({ tool: null, isPreviewMode: true })) + expect(tops()).toBe(false) + await act(async () => useEditor.setState({ isPreviewMode: false })) + expect(tops()).toBe(true) + + // The roof level keeps its own setting. + await select(roof.id) + await act(async () => useEditor.getState().setShowRoofCeilings(false)) + expect(tops()).toBe(false) + expect(useEditor.getState().showCeilings).toBe(true) + + await act(async () => useEditor.getState().setShowRoofCeilings(true)) + expect(tops()).toBe(true) + await act(async () => root.unmount()) + expect(tops()).toBe(false) + } finally { + useScene.setState(previous.scene, true) + useViewer.setState(previous.viewer, true) + useEditor.setState(previous.editor, true) + useInteractionScope.setState(previous.scope, true) + actGlobal.IS_REACT_ACT_ENVIRONMENT = previousAct + } +}) diff --git a/packages/editor/src/components/systems/ceiling/ceiling-system.tsx b/packages/editor/src/components/systems/ceiling/ceiling-system.tsx index 22607d1fc3..5e4a832b33 100644 --- a/packages/editor/src/components/systems/ceiling/ceiling-system.tsx +++ b/packages/editor/src/components/systems/ceiling/ceiling-system.tsx @@ -1,4 +1,4 @@ -import { type AnyNodeId, sceneRegistry, useScene } from '@pascal-app/core' +import { type AnyNodeId, sceneRegistry, servesAsRoofLevel, useScene } from '@pascal-app/core' import { isProceduralItem } from '@pascal-app/core/procedural-items' import { useViewer } from '@pascal-app/viewer' import { useEffect } from 'react' @@ -74,10 +74,59 @@ function setCeilingGridHighlighted(ceilingGrid: Mesh, highlighted: boolean) { delete userData[CEILING_GRID_HIGHLIGHT_MATERIAL_KEY] } -export const CeilingSystem = () => { +/** + * The "Ceilings" setting that applies on `levelId`: the roof level's, on until turned off; the + * floor plans', off until turned on. On, ceilings read as solid from above too. + */ +export function useCeilingsSetting(levelId: string | null | undefined): { + onRoofLevel: boolean + shown: boolean + setShown: (show: boolean) => void +} { + const onRoofLevel = useScene((state) => { + const level = levelId ? state.nodes[levelId as AnyNodeId] : undefined + return level?.type === 'level' && servesAsRoofLevel(state.nodes, level) + }) + const shown = useEditor((state) => (onRoofLevel ? state.showRoofCeilings : state.showCeilings)) + const setRoof = useEditor((state) => state.setShowRoofCeilings) + const setFloor = useEditor((state) => state.setShowCeilings) + return { onRoofLevel, shown, setShown: onRoofLevel ? setRoof : setFloor } +} + +function useIsCeilingToolActive(): boolean { const tool = useEditor((state) => state.tool) const selectedItem = useEditor((state) => state.selectedItem) const movingNode = useMovingNode() + return ( + tool === 'ceiling' || + selectedItem?.attachTo === 'ceiling' || + (movingNode?.type === 'item' && movingNode?.asset?.attachTo === 'ceiling') || + (isProceduralItem(movingNode) && movingNode.recipe.mounting?.attachTo === 'ceiling') + ) +} + +/** + * Whether the viewer draws ceiling tops (`showCeilingTops`), from the level's Ceilings setting. + * Mounted once by the editor, outside the canvases: the editor and preview viewers each mount a + * `CeilingSystem`, and two writers each turning the tops off on unmount raced. + */ +export const CeilingTopsSync = () => { + const activeLevelId = useViewer((state) => state.selection.levelId) + const ceilingsShown = useCeilingsSetting(activeLevelId).shown + const isPreviewMode = useEditor((state) => state.isPreviewMode) + const isCaptureMode = useEditor((state) => state.isCaptureMode) + const isCeilingToolActive = useIsCeilingToolActive() + // Placing on a ceiling looks through it from above, so the tops step aside; preview and + // captures keep the open look. + const showTops = ceilingsShown && !isCeilingToolActive && !isPreviewMode && !isCaptureMode + useEffect(() => { + useViewer.getState().setShowCeilingTops(showTops) + return () => useViewer.getState().setShowCeilingTops(false) + }, [showTops]) + return null +} + +export const CeilingSystem = () => { const selectedIds = useViewer((state) => state.selection.selectedIds) const activeLevelId = useViewer((state) => state.selection.levelId) const hoveredId = useViewer((state) => state.hoveredId) @@ -87,6 +136,7 @@ export const CeilingSystem = () => { // reveal is gated here, on the consumer. const inputDragging = useViewer((state) => state.inputDragging) const scopeIdle = useInteractionScope((state) => state.scope.kind === 'idle') + const isCeilingToolActive = useIsCeilingToolActive() useEffect(() => { const nodes = useScene.getState().nodes @@ -96,12 +146,6 @@ export const CeilingSystem = () => { const levelsToShowCeilings = new Set() - const isCeilingToolActive = - tool === 'ceiling' || - selectedItem?.attachTo === 'ceiling' || - (movingNode?.type === 'item' && movingNode?.asset?.attachTo === 'ceiling') || - (isProceduralItem(movingNode) && movingNode.recipe.mounting?.attachTo === 'ceiling') - if (isCeilingToolActive && activeLevelId) { levelsToShowCeilings.add(activeLevelId) } @@ -162,15 +206,6 @@ export const CeilingSystem = () => { } } }) - }, [ - tool, - selectedItem, - movingNode, - selectedIds, - activeLevelId, - hoveredId, - scopeIdle, - inputDragging, - ]) + }, [isCeilingToolActive, selectedIds, activeLevelId, hoveredId, scopeIdle, inputDragging]) return null } diff --git a/packages/editor/src/components/tools/item/placement-strategies.test.ts b/packages/editor/src/components/tools/item/placement-strategies.test.ts index 1adaf7b8ed..b96d7f87ca 100644 --- a/packages/editor/src/components/tools/item/placement-strategies.test.ts +++ b/packages/editor/src/components/tools/item/placement-strategies.test.ts @@ -1,6 +1,7 @@ -import { beforeEach, describe, expect, test } from 'bun:test' +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' import { BlockNode, + BuildingNode, type CeilingEvent, type CeilingNode, type GridEvent, @@ -11,7 +12,11 @@ import { type WallEvent, type WallNode, } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' import { BufferGeometry, Mesh, MeshBasicMaterial, type Object3D, Vector3 } from 'three' +import { GROUP_MOVE_DRAG_LABEL } from '../../../lib/contextual-help' +import useEditor from '../../../store/use-editor' +import useInteractionScope from '../../../store/use-interaction-scope' import { ceilingStrategy, faceHostStrategy, @@ -498,6 +503,64 @@ describe('floorStrategy.move', () => { expect(result.cursorPosition[1]).toBe(0) expect(result.nodeUpdate?.position?.[1]).toBe(0) }) + + describe('in grid mode', () => { + const originalSelection = useViewer.getState().selection + + beforeEach(() => { + // The group-move scope resolves to the item snap context without the + // node registry; grid mode at a 0.5 m step. + useInteractionScope + .getState() + .begin({ kind: 'handle-drag', nodeId: 'item_test', handle: GROUP_MOVE_DRAG_LABEL }) + useEditor.getState().setSnappingMode('item', 'grid') + useEditor.getState().setGridSnapStep(0.5) + }) + + afterEach(() => { + useInteractionScope.getState().end() + useEditor.getState().setSnappingMode('item', 'lines') + useViewer.setState({ selection: originalSelection } as never) + }) + + // A 0.5 m plant centres half a cell off the lattice, its edges on it. + test("inside a turned building it lands on the building's lattice, square to the house", () => { + const yaw = (17 * Math.PI) / 180 + const building = BuildingNode.parse({ position: [12.37, 0, -8.91], rotation: [0, yaw, 0] }) + useScene.setState((state) => ({ + ...state, + nodes: { ...state.nodes, [building.id]: building }, + })) + useViewer.setState({ + selection: { buildingId: building.id, levelId: null, zoneId: null, selectedIds: [] }, + } as never) + const local: [number, number] = [1.37, 2.61] + const cos = Math.cos(yaw) + const sin = Math.sin(yaw) + const event: GridEvent = { + position: [ + 12.37 + local[0] * cos + local[1] * sin, + 0, + -8.91 - local[0] * sin + local[1] * cos, + ], + localPosition: [local[0], 0, local[1]], + nativeEvent: {} as GridEvent['nativeEvent'], + } + + const result = floorStrategy.move(floorItemContext(), event) + expect(result?.nodeUpdate?.position?.[0]).toBeCloseTo(1.25, 9) + expect(result?.nodeUpdate?.position?.[2]).toBeCloseTo(2.75, 9) + }) + + test('outside any building it keeps the world grid', () => { + useViewer.setState({ + selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] }, + } as never) + const result = floorStrategy.move(floorItemContext(), makeGridEvent(5.37, 0, -3.12)) + expect(result?.nodeUpdate?.position?.[0]).toBeCloseTo(5.25, 9) + expect(result?.nodeUpdate?.position?.[2]).toBeCloseTo(-3.25, 9) + }) + }) }) describe('ceilingStrategy.move', () => { diff --git a/packages/editor/src/components/tools/item/placement-strategies.ts b/packages/editor/src/components/tools/item/placement-strategies.ts index d4e715a988..053be4c1b5 100644 --- a/packages/editor/src/components/tools/item/placement-strategies.ts +++ b/packages/editor/src/components/tools/item/placement-strategies.ts @@ -27,13 +27,12 @@ import { resolveSurfacePlacement, roofFacePointToSegment, sceneRegistry, - snapLocalXZInWorld, useScene, wouldCreateHostingCycle, } from '@pascal-app/core' import { Euler, Quaternion, Vector3 } from 'three' import { hasRoofFaceChildOverlap, resolveRoofWallHit } from '../../../lib/roof-wall-hit' -import { getActiveBuildingPose } from '../../../lib/world-grid-snap' +import { snapPlanPointToActiveGrid } from '../../../lib/world-grid-snap' import { itemEventToSurfaceHit, surfaceWorldNormalY } from '../shared/surface-hit' import { calculateItemRotation, @@ -73,17 +72,14 @@ export const floorStrategy = { const [dimX, , dimZ] = dims const rotY = ctx.draftItem?.rotation?.[1] ?? 0 const swapDims = Math.abs(Math.sin(rotY)) > 0.9 - // Snap on the world XZ grid (the grid the editor renders) so the - // item edges land on the visible grid even when the active building - // is rotated; then project the world point back into building-local - // for storage. Without this, a rotated building drags placement off - // the world grid. + // Snap on the lattice the grid is drawn on: a turned building's own + // lattice inside it (item edges square to the house), the world grid + // outside any building. // Snapping is governed by the active mode (snapToGrid returns raw in Off / // non-grid modes); Alt is force-place only and never bypasses snapping here. const dimensions = [swapDims ? dimZ : dimX, swapDims ? dimX : dimZ] as const - const [x, z] = snapLocalXZInWorld( + const [x, z] = snapPlanPointToActiveGrid( [event.localPosition[0], event.localPosition[2]], - getActiveBuildingPose(), (value, axis) => snapToGrid(value, dimensions[axis]), ) const y = ctx.gridPosition.y diff --git a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx index b258e3f224..241259a322 100644 --- a/packages/editor/src/components/tools/item/use-placement-coordinator.tsx +++ b/packages/editor/src/components/tools/item/use-placement-coordinator.tsx @@ -1008,9 +1008,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea // Floor grab-offset: the item tracks the grabbed point instead of snapping // its origin under the cursor. The offset is computed in building-local space - // (`event.localPosition`), but `floorStrategy.move` snaps on the WORLD grid - // (`event.position`), so the corrected local point is re-projected to a - // corrected world point and both frames carry the offset to stay consistent. + // (`event.localPosition`, which `floorStrategy.move` snaps), and the corrected + // local point is re-projected to a corrected world point so both frames carry + // the offset and stay consistent. const applyFloorGrabOffset = (event: GridEvent): GridEvent => { if (relativeFloorStart === null) return event const rawX = event.localPosition[0] diff --git a/packages/editor/src/components/tools/select/select-candidates.test.ts b/packages/editor/src/components/tools/select/select-candidates.test.ts index 0595e4ee4b..cf7fd3c09b 100644 --- a/packages/editor/src/components/tools/select/select-candidates.test.ts +++ b/packages/editor/src/components/tools/select/select-candidates.test.ts @@ -139,4 +139,49 @@ describe('selectable candidates', () => { }) expect(collectSelectableCandidateIds().sort()).toEqual(['item_pick', 'wall_pick']) }) + + test("a box leaves a room's automatic floor plate and ceiling to the room, keeping what hangs from it", () => { + const restore = nodeRegistry._snapshot() + try { + for (const kind of ['slab', 'ceiling', 'box-select-test-light'] as const) + if (!nodeRegistry.has(kind)) + registerNode({ + kind, + schemaVersion: 1, + schema: z.object({ type: z.literal(kind) }) as never, + category: 'structure', + defaults: () => ({}), + capabilities: { selectable: {} }, + } as AnyNodeDefinition) + useScene.setState({ + nodes: { + level_test: { + id: 'level_test', + type: 'level', + parentId: 'building_test', + children: ['slab_shared', 'slab_drawn', 'ceiling_auto'], + }, + slab_shared: { + id: 'slab_shared', + type: 'slab', + parentId: 'level_test', + boundary: 'auto', + plateRole: 'base', + }, + slab_drawn: { id: 'slab_drawn', type: 'slab', parentId: 'level_test' }, + ceiling_auto: { + id: 'ceiling_auto', + type: 'ceiling', + parentId: 'level_test', + boundary: 'auto', + children: ['light_hung'], + }, + light_hung: { id: 'light_hung', type: 'box-select-test-light', parentId: 'ceiling_auto' }, + } as unknown as Record, + }) + expect(collectSelectableCandidateIds().sort()).toEqual(['light_hung', 'slab_drawn']) + } finally { + restore() + } + }) }) diff --git a/packages/editor/src/components/tools/select/select-candidates.ts b/packages/editor/src/components/tools/select/select-candidates.ts index 40cb618aab..446d00b749 100644 --- a/packages/editor/src/components/tools/select/select-candidates.ts +++ b/packages/editor/src/components/tools/select/select-candidates.ts @@ -1,6 +1,7 @@ import { type AnyNode, type AnyNodeId, + isDerivedNode, isRegistrySelectable, type LevelNode, nodeRegistry, @@ -16,6 +17,14 @@ function isVisibleSelectableNode(node: AnyNode): boolean { return isRegistrySelectable(node.type) } +/** + * Derived construction (a room's automatic floor plate or ceiling) belongs to its rooms, so a box + * never takes it along: a box round a porch would otherwise catch the house's shared floor plate, + * and deleting the selection took the floor out from under every room on it. A click still selects + * one on its own. + */ +const boxSelectable = (node: AnyNode) => !isDerivedNode(node) + export function collectSelectableCandidateIds(): string[] { const { levelId } = useViewer.getState().selection const { nodes } = useScene.getState() @@ -43,7 +52,8 @@ export function collectSelectableCandidateIds(): string[] { ) return - if (isRegistrySelectable(node.type)) { + // A ceiling's hung lights and fans are still the box's; the ceiling is not. + if (isRegistrySelectable(node.type) && boxSelectable(node)) { addNode(node) } @@ -70,7 +80,8 @@ export function collectSelectableCandidateIds(): string[] { const buildingId = resolveBuildingForLevel(levelId as AnyNodeId, nodes) for (const node of Object.values(nodes)) { - if (!node || node.type === 'level' || !isVisibleSelectableNode(node)) continue + if (!node || node.type === 'level' || !isVisibleSelectableNode(node) || !boxSelectable(node)) + continue const def = nodeRegistry.get(node.type) const isBuildingScoped = def?.floorplanScope === 'building' diff --git a/packages/editor/src/components/tools/tool-manager.tsx b/packages/editor/src/components/tools/tool-manager.tsx index 5a21f1c966..a8871d22c0 100644 --- a/packages/editor/src/components/tools/tool-manager.tsx +++ b/packages/editor/src/components/tools/tool-manager.tsx @@ -222,6 +222,11 @@ export const ToolManager: React.FC = () => { | CeilingNode['id'] | undefined + // A foundation outline picked in the foundation view + const selectedFoundationId = selectedIds.find( + (id) => nodes[id as AnyNodeId]?.type === 'foundation', + ) + // Site boundary handles normally share one 2D/3D rule. Sculpt is the deliberate // 3D exception: the brush only owns this canvas, where PolygonEditor can hand // off its pointer before a boundary drag starts. @@ -274,6 +279,13 @@ export const ToolManager: React.FC = () => { ?.source ?? 'manual') === 'manual' && !isFloorplanDrivenReshape + // A foundation outline drags its corners and sides as a slab does + const showFoundationBoundaryEditor = + mode === 'select' && + isSoleSelection && + selectedFoundationId !== undefined && + !isFloorplanDrivenReshape + // Show zone boundary editor in select mode with a zone selected // Hide when editing a slab or ceiling to avoid overlapping handles const showZoneBoundaryEditor = @@ -368,6 +380,16 @@ export const ToolManager: React.FC = () => { ) : null })()} + {showFoundationBoundaryEditor && + selectedFoundationId && + (() => { + const Registry = getRegistryAffordanceTool('foundation', 'boundary-edit') + return Registry ? ( + + + + ) : null + })()} {showCeilingBoundaryEditor && selectedCeilingId && (() => { diff --git a/packages/editor/src/components/tools/wall/wall-drafting.test.ts b/packages/editor/src/components/tools/wall/wall-drafting.test.ts index 66cc2d1582..5021d0f3db 100644 --- a/packages/editor/src/components/tools/wall/wall-drafting.test.ts +++ b/packages/editor/src/components/tools/wall/wall-drafting.test.ts @@ -9,6 +9,7 @@ import { createTerrainField, DoorNode as DoorSchema, encodeTerrainField, + findOpenWallEnds, flattenPatch, GROUND_SUPPORT_ID, getFloorPlacedElevation, @@ -19,6 +20,7 @@ import { resolveTerrainWallConstructionOptions, runAsSingleSceneHistoryStep, SlabNode, + snapWorldXZToBuildingLocal, spatialGridManager, useScene, type WallNode, @@ -30,11 +32,15 @@ import { migrateRoomZones, } from '@pascal-app/core/scene-migrations' import { useViewer } from '@pascal-app/viewer' -import useEditor from '../../../store/use-editor' +import type { SnappingMode } from '../../../lib/snapping-mode' +import { snapBuildingLocalToWorldGrid } from '../../../lib/world-grid-snap' +import useEditor, { isAngleSnapActive, isMagneticSnapActive } from '../../../store/use-editor' import useInteractionScope from '../../../store/use-interaction-scope' import { createWallOnCurrentLevel, + getSegmentGridStep, resolveEndpointWallSplit, + snapWallDraftPoint, snapWallDraftPointDetailed, } from './wall-drafting' import type { WallPlanPoint } from './wall-snap-geometry' @@ -267,20 +273,42 @@ describe('createWallOnCurrentLevel', () => { for (const [start, end] of sides) { expect(createWallOnCurrentLevel(start, end)?.thickness).toBe(0.241) } + // Not foundation walls: they take the interior wall type of the Build panel. const partition = createWallOnCurrentLevel([4, 0], [4, 6]) - expect(partition?.assembly).toBeUndefined() + expect(partition?.assembly?.presetId).toBe('interior-2x4-drywall') const dangling = createWallOnCurrentLevel([0, 3], [3, 3]) - expect(dangling?.assembly).toBeUndefined() + expect(dangling?.assembly?.presetId).toBe('interior-2x4-drywall') // a bay on the outside of the east wall is on the outline again const bay = createWallOnCurrentLevel([8, 1], [11, 1]) expect(bay?.thickness).toBe(0.241) }) - test('the ground floor over it keeps the plain wall', () => { + test('the ground floor over it draws the exterior wall type, not a foundation wall', () => { seedBasement() select('level_main') const created = createWallOnCurrentLevel([0, 0], [4, 0]) - expect(created?.assembly).toBeUndefined() + expect(created?.assembly?.presetId).toBe('exterior-2x6-siding') + }) + + test('the Build panel wall types build what is drawn; plain draws a single layer', () => { + const previousTypes = useEditor.getState().wallTypes + try { + seedBasement() + select('level_main') + useEditor.getState().setWallType('exterior', 'exterior-2x6-stucco') + expect(createWallOnCurrentLevel([0, 0], [4, 0])?.assembly?.presetId).toBe( + 'exterior-2x6-stucco', + ) + useEditor.getState().setWallType('exterior', 'plain') + const plain = createWallOnCurrentLevel([0, 2], [4, 2]) + expect(plain?.assembly).toBeUndefined() + expect(plain?.thickness).toBeUndefined() + // An unknown id falls back to the default type. + useEditor.getState().setWallType('exterior', 'no-such-preset') + expect(useEditor.getState().wallTypes.exterior).toBe('exterior-2x6-siding') + } finally { + useEditor.setState({ wallTypes: previousTypes } as never) + } }) test('a thickness set in the tool defaults is kept, with no stack', () => { @@ -758,34 +786,53 @@ describe('createWallOnCurrentLevel', () => { expect(levelWalls()).toHaveLength(2) }) - test('genuine mid-wall endpoint still splits the host (T junction)', () => { + test('a mid-wall endpoint T-joins the host and leaves it whole', () => { const created = createWallOnCurrentLevel([2, 2], [2, 0]) expect(created?.end).toEqual([2, 0]) + const host = useScene.getState().nodes['wall_a' as AnyNodeId] as WallNode + expect([host.start, host.end]).toEqual([ + [0, 0], + [4, 0], + ]) + expect(levelWalls()).toHaveLength(2) + }) + + test('a wall drawn exactly over one built another way takes its place', () => { + // The seed wall is plain; the wall tool draws the Build panel's outside type. + const drawn = createWallOnCurrentLevel([0, 0], [4, 0]) + expect(drawn).not.toBeNull() expect(useScene.getState().nodes['wall_a' as AnyNodeId]).toBeUndefined() - const walls = levelWalls() - expect(walls).toHaveLength(3) - expect( - walls.some((wall) => wall.start[0] === 0 && wall.end[0] === 2 && wall.end[1] === 0), - ).toBe(true) - expect( - walls.some((wall) => wall.start[0] === 2 && wall.start[1] === 0 && wall.end[0] === 4), - ).toBe(true) + expect(levelWalls().map((wall) => wall.id)).toEqual([drawn!.id]) }) - test('exact duplicate segment is rejected', () => { + test('a wall drawn exactly over one built the same is refused', () => { + expect(createWallOnCurrentLevel([0, 0], [4, 0])).not.toBeNull() expect(createWallOnCurrentLevel([0, 0], [4, 0])).toBeNull() expect(levelWalls()).toHaveLength(1) }) - test('grid mode: endpoint resolved onto a wall body still splits the host', () => { + test('a wall drawn on along part of another takes that stretch over', () => { + const drawn = createWallOnCurrentLevel([2, 0], [6, 0]) + expect(drawn).not.toBeNull() + const walls = levelWalls() + // The seed wall keeps [0, 2]; nothing else runs along [2, 4]. + expect(walls).toHaveLength(2) + const seed = walls.find((wall) => wall.id !== drawn!.id)! + expect([seed.start, seed.end]).toEqual([ + [0, 0], + [2, 0], + ]) + }) + + test('grid mode: endpoint resolved onto a wall body joins it whole', () => { useEditor.getState().setSnappingMode('wall', 'grid') const created = createWallOnCurrentLevel([2, 2], [2, 0]) expect(created?.end).toEqual([2, 0]) - expect(useScene.getState().nodes['wall_a' as AnyNodeId]).toBeUndefined() - expect(levelWalls()).toHaveLength(3) + expect(useScene.getState().nodes['wall_a' as AnyNodeId]).toBeDefined() + expect(levelWalls()).toHaveLength(2) }) test('grid mode: endpoint beyond the connect radius is left alone (no residual snap)', () => { @@ -798,7 +845,7 @@ describe('createWallOnCurrentLevel', () => { expect(levelWalls()).toHaveLength(2) }) - test('mid-span split migrates the host attachments to the covering half', () => { + test("a T into a wall leaves the host's door on it, where it was", () => { const door = DoorSchema.parse({ position: [1, 1.05, 0], parentId: 'wall_a', @@ -809,12 +856,12 @@ describe('createWallOnCurrentLevel', () => { const created = createWallOnCurrentLevel([2, 2], [2, 0]) expect(created?.end).toEqual([2, 0]) - const walls = levelWalls() - const firstHalf = walls.find((wall) => wall.start[0] === 0 && wall.end[0] === 2) - expect(firstHalf).toBeDefined() - const migratedDoor = useScene.getState().nodes[door.id as AnyNodeId] - expect(migratedDoor?.parentId).toBe(firstHalf?.id) - expect(firstHalf?.children).toContain(door.id) + const kept = useScene.getState().nodes[door.id as AnyNodeId] as DoorSchema + expect(kept.parentId).toBe('wall_a') + expect(kept.position[0]).toBe(1) + expect((useScene.getState().nodes['wall_a' as AnyNodeId] as WallNode).children).toContain( + door.id, + ) }) test('a splitting commit lands as a single undo step', () => { @@ -826,15 +873,15 @@ describe('createWallOnCurrentLevel', () => { expect(useScene.temporal.getState().pastStates.length - before).toBe(1) }) - test('a crossing splits both the host and the inserted wall in one undo step', () => { + test('a crossing splits only the drawn wall, in one undo step: the host stays whole', () => { const before = useScene.temporal.getState().pastStates.length const created = createWallOnCurrentLevel([2, -2], [2, 2]) expect(created?.start).toEqual([2, 0]) expect(created?.end).toEqual([2, 2]) - expect(useScene.getState().nodes['wall_a' as AnyNodeId]).toBeUndefined() - expect(levelWalls()).toHaveLength(4) + expect(useScene.getState().nodes['wall_a' as AnyNodeId]).toBeDefined() + expect(levelWalls()).toHaveLength(3) expect(useScene.temporal.getState().pastStates.length - before).toBe(1) }) @@ -1095,4 +1142,237 @@ describe('snapWallDraftPointDetailed', () => { expect(freed.point).toEqual([2, 0]) expect(freed.snap).toBeNull() }) + + test('an end aimed into an L corner block joins the corner and reads as one', () => { + const south = { ...makeWall([0, 0], [4, 0], 'wall_south'), thickness: 0.2 } + const west = { ...makeWall([0, 4], [0, 0], 'wall_west'), thickness: 0.2 } + const result = snapWallDraftPointDetailed({ + point: [0.08, 0.08], + walls: [south, west], + start: [2, 3], + magnetic: false, + step: 0, + captureWallBodies: true, + }) + expect(result.point).toEqual([0, 0]) + expect(result.snap).toBe('endpoint') + expect(result.targetWallIds).toEqual(['wall_south', 'wall_west']) + }) + + // The 3D tool snaps to the floor below as a reference but can't join it: an + // end by a lower wall's face stays put, while its corner still snaps. + test('walls left out of bodyWalls never capture an end, but still snap corners', () => { + const lower = { ...makeWall([0, 0.4], [4, 0.4], 'wall_lower'), thickness: 0.2 } + const snap = (point: WallPlanPoint, bodyWalls?: WallNode[]) => + snapWallDraftPointDetailed({ + point, + walls: [lower], + start: [1, 3], + magnetic: false, + step: 0, + captureWallBodies: true, + bodyWalls, + }) + + const captured = snap([1, 0.5]) + expect(captured.snap).toBe('wall') + expect(captured.point[1]).toBeCloseTo(0.4, 12) + + const free = snap([1, 0.5], []) + expect(free.point).toEqual([1, 0.5]) + expect(free.snap).toBeNull() + + const corner = snap([0.02, 0.42], []) + expect(corner.point).toEqual([0, 0.4]) + expect(corner.snap).toBe('endpoint') + }) }) + +// The site planner sets a house on its lot turned and offset. Its walls are +// drawn square to the house, in building-local metres. An interior wall drawn +// across it has to meet both exterior walls exactly and stay square to the +// house however the building is turned, in 3D and on the plan alike. +const HOUSE_BUILDING_ID = 'building_lot' +// A 30' × 24' house of 8" walls, 2' × 1' off the building origin, so no wall +// line or face lies on the 0.5 m lattice. +const HOUSE_MIN: WallPlanPoint = [0.6096, 0.3048] +const HOUSE_MAX: WallPlanPoint = [9.7536, 7.62] +const EXTERIOR_THICKNESS = 0.2032 +// Where a builder aims an interior wall's end: just inside the exterior +// wall's interior face (1.18" off it). +const FACE_AIM = EXTERIOR_THICKNESS / 2 + 0.03 + +const BUILDING_POSES: Array<{ + label: string + yaw: number + position: [number, number, number] +}> = [ + { label: 'an unturned building at the origin', yaw: 0, position: [0, 0, 0] }, + { + label: 'a building turned 17° and offset on its lot', + yaw: (17 * Math.PI) / 180, + position: [12.37, 0, -8.91], + }, +] + +function seedHouse({ yaw, position }: { yaw: number; position: [number, number, number] }) { + const [x0, z0] = HOUSE_MIN + const [x1, z1] = HOUSE_MAX + const walls = [ + makeWall([x0, z0], [x1, z0], 'wall_south'), + makeWall([x1, z0], [x1, z1], 'wall_east'), + makeWall([x1, z1], [x0, z1], 'wall_north'), + makeWall([x0, z1], [x0, z0], 'wall_west'), + ].map((wall) => ({ ...wall, thickness: EXTERIOR_THICKNESS })) + const building = BuildingNode.parse({ + id: HOUSE_BUILDING_ID, + position, + rotation: [0, yaw, 0], + children: [LEVEL_ID], + }) + const level = LevelSchema.parse({ id: LEVEL_ID, parentId: building.id, level: 0, height: 2.5 }) + useScene.setState({ + nodes: Object.fromEntries([ + [building.id, building], + [level.id, { ...level, children: walls.map((wall) => wall.id) }], + ...walls.map((wall) => [wall.id, wall] as const), + ]), + rootNodeIds: [building.id], + dirtyNodes: new Set(), + collections: {}, + } as never) + useViewer.setState({ + selection: { + buildingId: HOUSE_BUILDING_ID, + levelId: LEVEL_ID, + zoneId: null, + selectedIds: [], + }, + } as never) +} + +for (const pose of BUILDING_POSES) { + describe(`an interior wall drawn across a house in ${pose.label}`, () => { + const [, z0] = HOUSE_MIN + const [, z1] = HOUSE_MAX + + beforeEach(() => { + useEditor.getState().setGridSnapStep(0.5) + // A reshaping-endpoint scope resolves to the 'wall' snap context + // without the node registry, as in `createWallOnCurrentLevel` above. + useInteractionScope + .getState() + .begin({ kind: 'reshaping', nodeId: 'wall_south', reshape: 'endpoint', driver: 'tool' }) + seedHouse(pose) + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() + }) + + afterEach(() => { + useEditor.getState().setSnappingMode('wall', 'grid') + }) + + const setMode = (mode: SnappingMode) => useEditor.getState().setSnappingMode('wall', mode) + + // The cursor as a grid event hands it over: a hit on the world plane, + // taken into the building's frame. + const cursorAt = (local: WallPlanPoint): WallPlanPoint => { + const cos = Math.cos(pose.yaw) + const sin = Math.sin(pose.yaw) + const worldX = pose.position[0] + local[0] * cos + local[1] * sin + const worldZ = pose.position[2] - local[0] * sin + local[1] * cos + return snapWorldXZToBuildingLocal(worldX, worldZ, pose.position, pose.yaw, 0).local + } + + // The 3D wall tool's snap call. + const snapIn3d = (point: WallPlanPoint, start?: WallPlanPoint, captureWallBodies = true) => + snapWallDraftPointDetailed({ + point, + walls: levelWalls(), + start, + angleSnap: start !== undefined && isAngleSnapActive(), + magnetic: isMagneticSnapActive(), + captureWallBodies, + }) + + // The plan's click: the same cursor through the plan's grid callback. + const snapOnPlan = (point: WallPlanPoint, start?: WallPlanPoint) => + snapWallDraftPoint({ + point, + walls: levelWalls(), + start, + angleSnap: start !== undefined && isAngleSnapActive(), + magnetic: isMagneticSnapActive(), + gridSnap: (p) => snapBuildingLocalToWorldGrid(p, getSegmentGridStep()), + captureWallBodies: true, + }) + + const partitionOpenEnds = (partition: WallNode) => + findOpenWallEnds(useScene.getState().nodes, LEVEL_ID).filter( + (end) => end.wallId === partition.id, + ) + + test('grid: both ends land on the exterior walls, square to the house, with no gap', () => { + setMode('grid') + const startAim = cursorAt([4.2, z0 + FACE_AIM]) + const start = snapIn3d(startAim) + expect(start.snap).toBe('wall') + expect(start.targetWallIds).toEqual(['wall_south']) + expect(start.point[0]).toBeCloseTo(4, 9) + expect(start.point[1]).toBeCloseTo(z0, 9) + + // The cursor drifts a little sideways on its way across the house. + const endAim = cursorAt([4.1, z1 - FACE_AIM]) + const end = snapIn3d(endAim, start.point) + expect(end.snap).toBe('wall') + expect(end.targetWallIds).toEqual(['wall_north']) + expect(end.point[0]).toBeCloseTo(start.point[0], 9) + expect(end.point[1]).toBeCloseTo(z1, 9) + + // The plan snaps the same cursor to the same points. + expect(snapOnPlan(startAim)).toEqual(start.point) + expect(snapOnPlan(endAim, start.point)).toEqual(end.point) + + const partition = createWallOnCurrentLevel(start.point, end.point) + expect(partition).not.toBeNull() + expect(partitionOpenEnds(partition!)).toEqual([]) + }) + + test('ortho holds the wall square to the house and lands it on the far wall', () => { + setMode('angles') + const startAim = cursorAt([4.2, z0 + FACE_AIM]) + const start = snapIn3d(startAim) + expect(start.point[0]).toBeCloseTo(4.2, 9) + expect(start.point[1]).toBeCloseTo(z0, 9) + + // 4° off square: the 15° lock squares it to the house. + const endAim = cursorAt([3.7, z1 - FACE_AIM]) + const end = snapIn3d(endAim, start.point) + expect(end.snap).toBe('wall') + expect(end.point[0]).toBeCloseTo(start.point[0], 9) + expect(end.point[1]).toBeCloseTo(z1, 9) + + const onPlan = snapOnPlan(endAim, start.point) + expect(onPlan[0]).toBeCloseTo(end.point[0], 9) + expect(onPlan[1]).toBeCloseTo(end.point[1], 9) + + const partition = createWallOnCurrentLevel(start.point, end.point) + expect(partition).not.toBeNull() + expect(partitionOpenEnds(partition!)).toEqual([]) + }) + + test('without the body snap the lattice stops the end short of the face: an open gap', () => { + setMode('grid') + const start = snapIn3d(cursorAt([4.2, z0 + FACE_AIM])) + const end = snapIn3d(cursorAt([4.1, z1 - FACE_AIM]), start.point, false) + // The lattice line under the cursor, 0.72" short of the north wall's face. + expect(end.snap).toBeNull() + expect(end.point[1]).toBeCloseTo(7.5, 9) + + const partition = createWallOnCurrentLevel(start.point, end.point) + const [open] = partitionOpenEnds(partition!) + expect(open?.reason).toBe('gap') + expect(open?.gap).toBeCloseTo(z1 - EXTERIOR_THICKNESS / 2 - 7.5, 3) + }) + }) +} diff --git a/packages/editor/src/components/tools/wall/wall-drafting.ts b/packages/editor/src/components/tools/wall/wall-drafting.ts index d38285b04b..535219d0b9 100644 --- a/packages/editor/src/components/tools/wall/wall-drafting.ts +++ b/packages/editor/src/components/tools/wall/wall-drafting.ts @@ -1,21 +1,30 @@ import { type AnyNodeId, DEFAULT_ANGLE_STEP, + finishWallEdit, + generateId, + planSeparatorInsertion, planWallInsertion, planWallSplitAtPoint, + railingWallDefaults, resolveWallConstruction, runAsSingleSceneHistoryStep, + type SeparatorNode, + sameWallConstruction, snapPointAlongAngleRay, useScene, type WallConstructionOptions, type WallNode, + wallsRunAlong, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { sfxEmitter } from '../../../lib/sfx-bus' import { resolveSnapFlags } from '../../../lib/snapping-mode' +import { getDrawnWallTypes, getWallLineKind } from '../../../lib/wall-draw-variant' import useEditor, { getActiveSnappingMode, isMagneticSnapActive } from '../../../store/use-editor' import { distanceSquared, + findWallBodySnapTarget, findWallSnapTarget, findWallSpecialPointSnap, WALL_CONNECT_SNAP_RADIUS, @@ -109,14 +118,30 @@ type SnapWallDraftArgs = { */ magnetic?: boolean /** - * Optional grid-snap override. Lets the caller route grid snapping - * through a world-XZ aligned snap (so a rotated building's draft - * lands on the visible grid). When omitted, falls back to the - * local-axis grid at `step`. + * Optional grid-snap override, in the caller's plan frame. When omitted, + * falls back to the building-local grid at `step` — the lattice the wall + * tools draft on, square to the house however the building is turned. */ gridSnap?: (point: WallPlanPoint) => WallPlanPoint /** Optional magnetic snap radii. Omitted means wall tools keep their defaults. */ snapRadii?: WallSnapRadii + /** + * The point is a wall end (drawn or dragged): in the non-magnetic modes an + * end aimed into another wall's drawn body, or within the connect radius of + * either face, lands on that wall's reference line — where the wall + * topology T-joins it — ahead of the grid / angle placement, keeping the + * segment's direction from `start` (see `findWallBodySnapTarget`). Without + * it the grid quantises an end aimed at a face to the nearest lattice point, + * a fraction short of the face, and the end stays open with a gap. + */ + captureWallBodies?: boolean + /** + * The walls `captureWallBodies` may join an end into; `walls` when omitted. + * A body capture T-joins the wall, so a caller that adds reference walls it + * can't join (the floor below) keeps them out of this list while they still + * serve the corner and edge snaps. + */ + bodyWalls?: WallNode[] } export function snapWallDraftPointDetailed(args: SnapWallDraftArgs): WallDraftSnapResult { @@ -131,6 +156,8 @@ export function snapWallDraftPointDetailed(args: SnapWallDraftArgs): WallDraftSn magnetic = true, gridSnap, snapRadii, + captureWallBodies = false, + bodyWalls = walls, } = args if (bypassSnap) return { point, snap: null, targetWallIds: [] } @@ -144,9 +171,8 @@ export function snapWallDraftPointDetailed(args: SnapWallDraftArgs): WallDraftSn } const step = overrideStep ?? getSegmentGridStep() - // The angle path snaps the distance ALONG the 15° ray — a scalar, the - // same in world and local frames — so the `gridSnap` world-grid override - // only applies when the angle lock is off. + // The angle path snaps the distance ALONG the 15° ray from `start`, so the + // `gridSnap` override only applies when the angle lock is off. const basePoint: WallPlanPoint = start && angleSnap ? [...snapPointAlongAngleRay(start, point, DEFAULT_ANGLE_STEP, step)] @@ -182,6 +208,30 @@ export function snapWallDraftPointDetailed(args: SnapWallDraftArgs): WallDraftSn } const connectSpecial = findWallSpecialPointSnap(basePoint, walls, ignoreWallIds, connectRadii) if (connectSpecial) return connectSpecial + // A wall end aimed into a wall's body joins it, whichever lattice point or + // ray station the mode picked near its face. + const bodySnap = captureWallBodies + ? findWallBodySnapTarget(point, bodyWalls, { + ignoreWallIds, + placed: basePoint, + start, + margin: WALL_CONNECT_SNAP_RADIUS, + }) + : null + if (bodySnap) { + // An end aimed into a corner block joins the corner, and reads as one. + const atCorner = bodyWalls.some( + (wall) => + !ignoreWallIds?.includes(wall.id) && + (distanceSquared(bodySnap, wall.start) <= 1e-12 || + distanceSquared(bodySnap, wall.end) <= 1e-12), + ) + return { + point: bodySnap, + snap: atCorner ? 'endpoint' : 'wall', + targetWallIds: wallIdsAtSnapPoint(bodySnap, walls, ignoreWallIds), + } + } const connectWall = findWallSnapTarget(basePoint, walls, { ignoreWallIds, radius: WALL_CONNECT_SNAP_RADIUS, @@ -205,6 +255,36 @@ export function isSegmentLongEnough(start: WallPlanPoint, end: WallPlanPoint): b return distanceSquared(start, end) >= WALL_MIN_LENGTH * WALL_MIN_LENGTH } +/** + * An invisible wall drawn `start` → `end` on the current level (the Invisible wall tile, a room + * divider): room separators, each end joined to a wall it lands on and broken + * either side of a wall it crosses (`planSeparatorInsertion`), as one undo step. Rooms close on + * it; a room it closes off outside the house becomes a porch. The last piece, for the run to go + * on from its end; null when it would be too short. + */ +export function createInvisibleWallOnCurrentLevel( + start: WallPlanPoint, + end: WallPlanPoint, +): SeparatorNode | null { + const currentLevelId = useViewer.getState().selection.levelId + const { nodes, applyNodeChanges } = useScene.getState() + if (!(currentLevelId && isSegmentLongEnough(start, end))) return null + const joinRadius = isMagneticSnapActive() ? WALL_JOIN_SNAP_RADIUS : WALL_CONNECT_SNAP_RADIUS + return runAsSingleSceneHistoryStep(useScene, () => { + const plan = planSeparatorInsertion(nodes, { + levelId: currentLevelId as AnyNodeId, + start, + end, + joinRadius, + mintId: () => generateId('separator'), + }) + if (!plan) return null + applyNodeChanges(plan.changes) + sfxEmitter.emit('sfx:structure-build') + return plan.separators.at(-1) ?? null + }) +} + export function createWallOnCurrentLevel( start: WallPlanPoint, end: WallPlanPoint, @@ -220,13 +300,37 @@ export function createWallOnCurrentLevel( const joinRadius = isMagneticSnapActive() ? WALL_JOIN_SNAP_RADIUS : WALL_CONNECT_SNAP_RADIUS return runAsSingleSceneHistoryStep(useScene, () => { - const result = planWallInsertion(nodes, { + // The Railing tile draws railings: a guard of their own, not the Build panel's wall types. + const railing = getWallLineKind() === 'railing' + const insertion = { levelId: currentLevelId as AnyNodeId, start, end, joinRadius, - wallDefaults: useEditor.getState().toolDefaults.wall ?? {}, - }) + wallDefaults: railing + ? railingWallDefaults() + : (useEditor.getState().toolDefaults.wall ?? {}), + wallTypes: railing ? undefined : getDrawnWallTypes(), + // A wall drawn into another meets its inside face and leaves it whole, + // as a framed partition does; the split tool breaks a wall on purpose. + keepHostsWhole: true, + } + let result = planWallInsertion(nodes, insertion) + if (!result.ok && result.reason === 'covered-existing-wall') { + // Drawn over walls built another way (an outside wall over an inside + // jog), the new wall takes their stretch over; over walls built the + // same, it would change nothing, so it stays refused. + const replacing = planWallInsertion(nodes, { ...insertion, replaceCovered: true }) + if ( + replacing.ok && + replacing.plan.insertedWalls.some((wall) => + wallsRunAlong(nodes, { ...wall, parentId: currentLevelId } as WallNode).some( + (covered) => !sameWallConstruction(covered, wall), + ), + ) + ) + result = replacing + } if (!result.ok) return null const { plan } = result @@ -237,20 +341,28 @@ export function createWallOnCurrentLevel( const sourceAlreadyUpdated = sourceUpdate ? plan.changes.update.some((operation) => operation.id === sourceUpdate.id) : false - applyNodeChanges({ - ...plan.changes, - update: plan.changes.update - .map((operation) => - sourceUpdate?.id === operation.id - ? { ...operation, data: { ...operation.data, ...sourceUpdate.data } } - : operation, - ) - .concat(sourceUpdate && !sourceAlreadyUpdated ? [sourceUpdate] : []), - create: plan.changes.create.map((operation) => ({ - ...operation, - node: finalizedWallsById.get(operation.node.id as WallNode['id']) ?? operation.node, - })), - }) + // A wall drawn along part of a parallel wall takes that stretch over, so + // drawing on from a wall never doubles the one it runs along. + applyNodeChanges( + finishWallEdit( + nodes, + { + ...plan.changes, + update: plan.changes.update + .map((operation) => + sourceUpdate?.id === operation.id + ? { ...operation, data: { ...operation.data, ...sourceUpdate.data } } + : operation, + ) + .concat(sourceUpdate && !sourceAlreadyUpdated ? [sourceUpdate] : []), + create: plan.changes.create.map((operation) => ({ + ...operation, + node: finalizedWallsById.get(operation.node.id as WallNode['id']) ?? operation.node, + })), + }, + { winnerIds: finalizedWalls.map((wall) => wall.id), mintId: () => generateId('wall') }, + ), + ) sfxEmitter.emit('sfx:structure-build') const terminalWall = finalizedWalls.at(-1)! diff --git a/packages/editor/src/components/tools/wall/wall-pull-snap.test.ts b/packages/editor/src/components/tools/wall/wall-pull-snap.test.ts new file mode 100644 index 0000000000..d7ccf92c6f --- /dev/null +++ b/packages/editor/src/components/tools/wall/wall-pull-snap.test.ts @@ -0,0 +1,274 @@ +import { describe, expect, test } from 'bun:test' +import type { WallNode } from '@pascal-app/core' +import { lockWallPullDirection, snapWallPullPoint, type WallPullSnapInput } from './wall-pull-snap' +import type { WallPlanPoint } from './wall-snap-geometry' + +function makeWall(start: WallPlanPoint, end: WallPlanPoint, id?: string): WallNode { + return { + object: 'node', + id: (id ?? `wall_${start.join('_')}_${end.join('_')}`) as WallNode['id'], + type: 'wall', + name: 'Wall', + parentId: null, + visible: true, + metadata: {}, + children: [], + start, + end, + thickness: 0.1, + frontSide: 'unknown', + backSide: 'unknown', + } as unknown as WallNode +} + +const deg = (degrees: number) => (degrees * Math.PI) / 180 +const polar = (from: WallPlanPoint, degrees: number, length: number): WallPlanPoint => [ + from[0] + Math.cos(deg(degrees)) * length, + from[1] + Math.sin(deg(degrees)) * length, +] + +function expectPoint(actual: WallPlanPoint, expected: WallPlanPoint) { + expect(actual[0]).toBeCloseTo(expected[0], 6) + expect(actual[1]).toBeCloseTo(expected[1], 6) +} + +// A 4 × 3 m room; the pull starts at the south wall's east end. +const ROOM = [ + makeWall([0, 0], [4, 0], 'south'), + makeWall([4, 0], [4, 3], 'east'), + makeWall([4, 3], [0, 3], 'north'), + makeWall([0, 3], [0, 0], 'west'), +] + +function pullFromRoom(point: WallPlanPoint, extra: Partial = {}) { + return snapWallPullPoint({ + point, + start: [4, 0], + runStart: [4, 0], + reference: [1, 0], + walls: ROOM, + gridStep: 0.5, + ...extra, + }) +} + +describe('lockWallPullDirection', () => { + test('holds straight on from the wall it comes from within about 7°', () => { + expect(lockWallPullDirection(polar([0, 0], 6, 3), [1, 0])).toEqual({ + direction: [1, 0], + lock: 'straight', + }) + expect(lockWallPullDirection(polar([0, 0], 8, 3), [1, 0]).lock).toBe('angle') + }) + + test('holds square to the wall it comes from, either side', () => { + const left = lockWallPullDirection(polar([0, 0], 85, 3), [1, 0]) + expect(left.lock).toBe('square') + expectPoint(left.direction, [0, 1]) + const right = lockWallPullDirection(polar([0, 0], -96, 3), [1, 0]) + expect(right.lock).toBe('square') + expectPoint(right.direction, [0, -1]) + }) + + test('holds straight on to a skewed wall, and the house axes beside it', () => { + const skew: WallPlanPoint = [Math.cos(deg(20)), Math.sin(deg(20))] + const straight = lockWallPullDirection(polar([0, 0], 23, 3), skew) + expect(straight.lock).toBe('straight') + expectPoint(straight.direction, skew) + const axis = lockWallPullDirection(polar([0, 0], 4, 3), skew) + expect(axis.lock).toBe('axis') + expectPoint(axis.direction, [1, 0]) + }) + + test('past the lock it steps by 15° on the house axes', () => { + for (const [cursor, expected] of [ + [40, 45], + [28, 30], + [52, 45], + [-118, -120], + ] as const) { + const { direction, lock } = lockWallPullDirection(polar([0, 0], cursor, 3), [1, 0]) + expect(lock).toBe('angle') + expectPoint(direction, polar([0, 0], expected, 1)) + } + }) +}) + +describe('snapWallPullPoint', () => { + test('a straight pull steps its length on the grid', () => { + const result = pullFromRoom([6.6, 0.2]) + expect(result.lock).toBe('straight') + expect(result.snap).toBe('grid') + expect(result.label).toBe('Grid') + expectPoint(result.point, [6.5, 0]) + }) + + test('a square pull holds 90° to the pulled wall', () => { + const result = pullFromRoom([4.25, -2.6]) + expect(result.lock).toBe('square') + expectPoint(result.point, [4, -2.5]) + }) + + test('further round it snaps to 15° steps, with a grid length along them', () => { + const result = pullFromRoom(polar([4, 0], -40, 3)) + expect(result.lock).toBe('angle') + // 3 m at 40° is 2.99 m along 45°: 3 m on the 0.5 m grid. + expectPoint(result.point, polar([4, 0], -45, 3)) + }) + + test('Alt is fully free', () => { + const cursor = polar([4, 0], -40, 3.13) + const result = pullFromRoom(cursor, { free: true }) + expect(result.lock).toBe('free') + expect(result.snap).toBeNull() + expectPoint(result.point, cursor) + }) + + test('the extension of the pulled wall is always drawn', () => { + const result = pullFromRoom([5.3, -1.9]) + const extension = result.guides.find((guide) => guide.kind === 'extension') + expect(extension?.from).toEqual([4, 0]) + expect(extension?.to[1]).toBeCloseTo(0, 9) + expect(extension?.to[0] ?? 0).toBeGreaterThan(5.3) + }) + + test('an existing wall end wins over the held direction', () => { + const result = snapWallPullPoint({ + point: [4.15, 3.2], + start: [7, 3], + runStart: [4, 0], + reference: [0, 1], + walls: [...ROOM, makeWall([4, 0], [7, 0], 'p1'), makeWall([7, 0], [7, 3], 'p2')], + gridStep: 0.5, + }) + expect(result.snap).toBe('endpoint') + expect(result.label).toBe('Endpoint') + expect(result.point).toEqual([4, 3]) + expect(result.targetWallIds.sort()).toEqual(['east', 'north']) + }) + + test('a held direction running into a wall lands on its line, square', () => { + // From the east of the room, back west at the north wall's height: the + // wall runs into the east wall at (4, 1.5), square to it. + const result = snapWallPullPoint({ + point: [4.2, 1.52], + start: [7, 1.5], + runStart: [7, -1], + reference: [0, 1], + walls: ROOM, + gridStep: 0.5, + }) + expect(result.snap).toBe('perpendicular') + expect(result.label).toBe('Perpendicular') + expectPoint(result.point, [4, 1.5]) + expect(result.targetWallIds).toEqual(['east']) + }) + + test('a run lines up with its start, then closes on it', () => { + // Pulled east out of a lone wall's end S = (0, 0): east to (5, 0), north + // to (5, 4), then west — the run's start guide catches x = 0 … + const source = makeWall([-4, 0], [0, 0], 'source') + const walls = [source, makeWall([0, 0], [5, 0], 'r1'), makeWall([5, 0], [5, 4], 'r2')] + const across = snapWallPullPoint({ + point: [0.1, 4.05], + start: [5, 4], + runStart: [0, 0], + reference: [0, 1], + walls, + gridStep: 0, + }) + expect(across.snap).toBe('alignment') + expect(across.label).toBe('In line with start') + expectPoint(across.point, [0, 4]) + const startGuide = across.guides.find((guide) => guide.kind === 'run-start') + expect(startGuide?.from).toEqual([0, 0]) + expect(startGuide?.to[0]).toBeCloseTo(0, 9) + expect(startGuide?.to[1] ?? 0).toBeGreaterThan(4) + // … and the last wall closes square back onto it. + const close = snapWallPullPoint({ + point: [0.12, 0.2], + start: [0, 4], + runStart: [0, 0], + reference: [-1, 0], + walls: [...walls, makeWall([5, 4], [0, 4], 'r3')], + gridStep: 0.5, + }) + expect(close.snap).toBe('endpoint') + expect(close.point).toEqual([0, 0]) + }) + + test('guides cross near the cursor: the crossing is marked and snaps', () => { + // The run's start S = (0, 0) and another wall's end P = (2, 6): S's + // vertical and P's horizontal cross at (0, 6). + const walls = [ + makeWall([-4, 0], [0, 0], 'source'), + makeWall([0, 0], [5, 0], 'r1'), + makeWall([5, 0], [5, 4], 'r2'), + makeWall([2, 6], [2, 9], 'p'), + ] + const near = snapWallPullPoint({ + point: [0.45, 5.6], + start: [5, 4], + runStart: [0, 0], + reference: [0, 1], + walls, + gridStep: 0.5, + }) + expect(near.intersections.some((point) => point[0] === 0 && point[1] === 6)).toBe(true) + expect(near.snap).not.toBe('intersection') + + const on = snapWallPullPoint({ + point: [0.1, 5.9], + start: [5, 4], + runStart: [0, 0], + reference: [0, 1], + walls, + gridStep: 0.5, + }) + expect(on.snap).toBe('intersection') + expect(on.label).toBe('Intersection') + expect(on.point).toEqual([0, 6]) + expect(on.guides.map((guide) => guide.kind).sort()).toEqual([ + 'alignment', + 'extension', + 'run-start', + ]) + const vertical = on.guides.find((guide) => guide.kind === 'run-start') + expect(vertical?.from).toEqual([0, 0]) + const horizontal = on.guides.find((guide) => guide.kind === 'alignment') + expect(horizontal?.from).toEqual([2, 6]) + }) + + test('a wall middle and a wall crossing snap too', () => { + const midpoint = pullFromRoom([2.1, 3.15], { start: [4, -2], reference: [0, -1] }) + expect(midpoint.snap).toBe('midpoint') + expect(midpoint.point).toEqual([2, 3]) + }) + + test("on a turned building the lock follows the house's axes, not the world's", () => { + // The house is turned 30° on its lot. Plan points are in its own frame, + // as both views hand them to the tools; a world cursor along the house's + // east side comes in along the house's X, and the held wall goes back out + // along it. + const yaw = deg(30) + const origin: WallPlanPoint = [12, -7] + const toWorld = ([x, z]: WallPlanPoint): WallPlanPoint => [ + origin[0] + x * Math.cos(yaw) + z * Math.sin(yaw), + origin[1] - x * Math.sin(yaw) + z * Math.cos(yaw), + ] + const toLocal = ([x, z]: WallPlanPoint): WallPlanPoint => { + const dx = x - origin[0] + const dz = z - origin[1] + return [dx * Math.cos(yaw) - dz * Math.sin(yaw), dx * Math.sin(yaw) + dz * Math.cos(yaw)] + } + const worldCursor = toWorld([6.6, 0.25]) + const result = pullFromRoom(toLocal(worldCursor)) + expect(result.lock).toBe('straight') + expectPoint(result.point, [6.5, 0]) + const worldStart = toWorld([4, 0]) + const worldEnd = toWorld(result.point) + const bearing = Math.atan2(worldEnd[1] - worldStart[1], worldEnd[0] - worldStart[0]) + // Along the house's X, which sits 30° off the world's. + expect(bearing).toBeCloseTo(-yaw, 9) + }) +}) diff --git a/packages/editor/src/components/tools/wall/wall-pull-snap.ts b/packages/editor/src/components/tools/wall/wall-pull-snap.ts new file mode 100644 index 0000000000..7174a23d9f --- /dev/null +++ b/packages/editor/src/components/tools/wall/wall-pull-snap.ts @@ -0,0 +1,563 @@ +// Pure snap geometry for a wall pulled from a selected wall's end (the "pull to +// continue" grip) and for the run it continues — no store / viewer / React +// deps, so it's unit-testable in isolation. Coordinates are XZ plan points in +// the building's own frame, the frame both views draft in: the building's axes +// are the plan's X and Z, so a turned house keeps its square. + +import { isCurvedWall, type WallNode } from '@pascal-app/core' +import { + distanceSquared, + findWallBodySnapTarget, + findWallSpecialPointSnap, + WALL_CONNECT_SNAP_RADIUS, + type WallPlanPoint, + wallIdsAtSnapPoint, +} from './wall-snap-geometry' + +/** + * What a pulled wall's end caught, as the snap marker names it: an existing + * wall's end or middle, a crossing (two walls, or two guides), a point on a + * wall (`perpendicular` when the new wall meets it square), a guide it lines up + * with, or a plain grid length. + */ +export type WallPullSnapKind = + | 'endpoint' + | 'midpoint' + | 'intersection' + | 'wall' + | 'perpendicular' + | 'alignment' + | 'grid' + +/** The words the snap marker shows for each kind. */ +export const WALL_PULL_SNAP_LABELS: Record = { + endpoint: 'Endpoint', + midpoint: 'Midpoint', + intersection: 'Intersection', + wall: 'On wall', + perpendicular: 'Perpendicular', + alignment: 'Aligned', + grid: 'Grid', +} + +/** + * How the wall's direction was held: straight on from the wall it comes from, + * square to it, along one of the house's axes, on a 15° step, or not at all. + */ +export type WallPullDirectionLock = 'straight' | 'square' | 'axis' | 'angle' | 'free' + +/** + * A dashed guide: the extension of the wall the new one comes from, a line + * through another wall's end along one of the house's axes, or the same through + * the run's start (so the run can close square back to it). + */ +export type WallPullGuideKind = 'extension' | 'alignment' | 'run-start' + +export type WallPullGuide = { + kind: WallPullGuideKind + from: WallPlanPoint + to: WallPlanPoint +} + +export type WallPullSnapResult = { + point: WallPlanPoint + snap: WallPullSnapKind | null + /** `WALL_PULL_SNAP_LABELS[snap]`, or "In line with start" for the run's start guide. */ + label: string | null + lock: WallPullDirectionLock + /** Walls whose geometry the end landed on, for the marker's wall highlight. */ + targetWallIds: string[] + guides: WallPullGuide[] + /** Where two guides cross near the cursor: markers, and snap targets. */ + intersections: WallPlanPoint[] +} + +/** Snap radii (m) measured from the cursor, or along the held direction for `wall` / `alignment`. */ +export type WallPullSnapRadii = { + endpoint: number + /** A wall's middle, or where two walls cross. */ + special: number + /** Where two guides cross. */ + intersection: number + /** The held direction landing on a wall's line, or the square foot on it. */ + wall: number + /** The held direction landing on a guide. */ + alignment: number + /** How near a guide line passes the cursor to be considered at all. */ + guide: number +} + +export const WALL_PULL_SNAP_RADII: WallPullSnapRadii = { + endpoint: 0.3, + special: 0.2, + intersection: 0.2, + wall: 0.25, + alignment: 0.15, + guide: 0.6, +} + +/** The direction holds straight on / square / on a house axis within this angle. */ +export const WALL_PULL_LOCK_TOLERANCE = (7 * Math.PI) / 180 +/** Past the lock the direction snaps to steps of this angle, on the house's axes. */ +export const WALL_PULL_ANGLE_STEP = Math.PI / 12 + +const PERPENDICULAR_SINE = Math.sin((1 * Math.PI) / 180) +const ON_LINE = 1e-6 +const INTERSECTION_SHOW_RADIUS = 0.75 +const EXTENSION_MIN_LENGTH = 1.5 +const EXTENSION_OVERSHOOT = 1 +const GUIDE_OVERSHOOT = 0.5 + +export type WallPullSnapInput = { + /** The raw cursor. */ + point: WallPlanPoint + /** Where the wall being drawn starts. */ + start: WallPlanPoint + /** Where the run started: the pulled wall end. */ + runStart: WallPlanPoint + /** + * Unit direction of the wall the new one comes from, pointing out of its end + * (the pulled wall's, then each placed wall's). Null draws on the house's + * axes and 15° steps only. + */ + reference: WallPlanPoint | null + /** The level's walls: snap targets and guide anchors. */ + walls: readonly WallNode[] + /** Length step along the held direction; 0 leaves lengths free. */ + gridStep: number + /** Alt, or snapping off: the end follows the cursor. */ + free?: boolean + radii?: Partial +} + +type GuideLine = { + kind: WallPullGuideKind + /** A point on the line: the anchor it is drawn from. */ + anchor: WallPlanPoint + /** Unit direction. */ + direction: WallPlanPoint +} + +function unit(vector: WallPlanPoint): WallPlanPoint | null { + const length = Math.hypot(vector[0], vector[1]) + return length > 1e-9 ? [vector[0] / length, vector[1] / length] : null +} + +function angleBetween(a: WallPlanPoint, b: WallPlanPoint): number { + return Math.abs(Math.atan2(a[0] * b[1] - a[1] * b[0], a[0] * b[0] + a[1] * b[1])) +} + +function samePoint(a: WallPlanPoint, b: WallPlanPoint, tolerance = 1e-6): boolean { + return distanceSquared(a, b) <= tolerance * tolerance +} + +/** + * The direction a pull from `start` toward `vector` holds: straight on from + * `reference` or square to it, or along a house axis, whichever is nearest + * within `WALL_PULL_LOCK_TOLERANCE`; otherwise the nearest 15° step on the + * house's axes. Folding back over the wall it comes from is not offered. + */ +export function lockWallPullDirection( + vector: WallPlanPoint, + reference: WallPlanPoint | null, +): { direction: WallPlanPoint; lock: WallPullDirectionLock } { + const along = unit(vector) + if (!along) return { direction: reference ?? [1, 0], lock: 'free' } + const candidates: Array<{ direction: WallPlanPoint; lock: WallPullDirectionLock }> = [] + const ref = reference ? unit(reference) : null + if (ref) { + candidates.push( + { direction: ref, lock: 'straight' }, + { direction: [-ref[1], ref[0]], lock: 'square' }, + { direction: [ref[1], -ref[0]], lock: 'square' }, + ) + } + for (const axis of [ + [1, 0], + [-1, 0], + [0, 1], + [0, -1], + ] as WallPlanPoint[]) { + candidates.push({ direction: axis, lock: 'axis' }) + } + let best: { direction: WallPlanPoint; lock: WallPullDirectionLock } | null = null + let bestAngle = Number.POSITIVE_INFINITY + for (const candidate of candidates) { + const angle = angleBetween(along, candidate.direction) + if (angle < bestAngle - 1e-9) { + best = candidate + bestAngle = angle + } + } + if (best && bestAngle <= WALL_PULL_LOCK_TOLERANCE) return best + const stepped = + Math.round(Math.atan2(along[1], along[0]) / WALL_PULL_ANGLE_STEP) * WALL_PULL_ANGLE_STEP + return { direction: [Math.cos(stepped), Math.sin(stepped)], lock: 'angle' } +} + +/** + * The points guides run through: every wall end on the level and the run's + * start, once each, leaving out the start of the wall being drawn (a guide + * through it is the direction lock itself). + */ +function guideAnchors( + walls: readonly WallNode[], + runStart: WallPlanPoint, + start: WallPlanPoint, +): Array<{ point: WallPlanPoint; runStart: boolean }> { + const anchors: Array<{ point: WallPlanPoint; runStart: boolean }> = [] + const add = (point: WallPlanPoint, isRunStart: boolean) => { + if (samePoint(point, start, 1e-4)) return + const existing = anchors.find((anchor) => samePoint(anchor.point, point, 1e-4)) + if (existing) { + existing.runStart ||= isRunStart + return + } + anchors.push({ point: [point[0], point[1]], runStart: isRunStart }) + } + add(runStart, true) + for (const wall of walls) { + add(wall.start, false) + add(wall.end, false) + } + return anchors +} + +/** + * The guide lines near the cursor: a vertical (constant X) line through each + * anchor whose X is within `radius` of the cursor's, and a horizontal one + * likewise. Several anchors on one line give one line, drawn from the anchor + * nearest the cursor; the run's start keeps its own lines. + */ +function nearbyGuideLines( + anchors: Array<{ point: WallPlanPoint; runStart: boolean }>, + cursor: WallPlanPoint, + radius: number, +): GuideLine[] { + const lines: GuideLine[] = [] + for (const axis of [0, 1] as const) { + // axis 0: vertical lines (constant X); axis 1: horizontal lines (constant Z). + const across = axis === 0 ? 1 : 0 + const byCoord = new Map() + for (const anchor of anchors) { + if (Math.abs(anchor.point[axis] - cursor[axis]) > radius) continue + const key = `${anchor.runStart ? 's' : 'a'}:${anchor.point[axis].toFixed(4)}` + const held = byCoord.get(key) + if ( + !held || + Math.abs(anchor.point[across] - cursor[across]) < + Math.abs(held.point[across] - cursor[across]) + ) { + byCoord.set(key, anchor) + } + } + for (const anchor of byCoord.values()) { + lines.push({ + kind: anchor.runStart ? 'run-start' : 'alignment', + anchor: anchor.point, + direction: axis === 0 ? [0, 1] : [1, 0], + }) + } + } + return lines +} + +/** Where two lines cross, or null when they run parallel. */ +function crossLines(a: GuideLine, b: GuideLine): WallPlanPoint | null { + const denom = a.direction[0] * b.direction[1] - a.direction[1] * b.direction[0] + if (Math.abs(denom) < 1e-9) return null + const dx = b.anchor[0] - a.anchor[0] + const dz = b.anchor[1] - a.anchor[1] + const t = (dx * b.direction[1] - dz * b.direction[0]) / denom + return [a.anchor[0] + a.direction[0] * t, a.anchor[1] + a.direction[1] * t] +} + +/** Station along the ray `start + direction · t` where it crosses `line`, or null. */ +function rayStationOnLine( + start: WallPlanPoint, + direction: WallPlanPoint, + line: GuideLine, +): number | null { + const denom = direction[0] * line.direction[1] - direction[1] * line.direction[0] + if (Math.abs(denom) < 1e-9) return null + const dx = line.anchor[0] - start[0] + const dz = line.anchor[1] - start[1] + const t = (dx * line.direction[1] - dz * line.direction[0]) / denom + return t > 1e-6 ? t : null +} + +function passesThrough(line: GuideLine, point: WallPlanPoint): boolean { + const dx = point[0] - line.anchor[0] + const dz = point[1] - line.anchor[1] + return Math.abs(dx * line.direction[1] - dz * line.direction[0]) <= 1e-4 +} + +/** A guide drawn from its anchor through `to` and a little past it. */ +function guideThrough(line: GuideLine, to: WallPlanPoint): WallPullGuide { + const dx = to[0] - line.anchor[0] + const dz = to[1] - line.anchor[1] + const along = dx * line.direction[0] + dz * line.direction[1] + const sign = along >= 0 ? 1 : -1 + return { + kind: line.kind, + from: [line.anchor[0], line.anchor[1]], + to: [ + to[0] + line.direction[0] * sign * GUIDE_OVERSHOOT, + to[1] + line.direction[1] * sign * GUIDE_OVERSHOOT, + ], + } +} + +function extensionGuide( + start: WallPlanPoint, + reference: WallPlanPoint, + cursor: WallPlanPoint, +): WallPullGuide { + const along = (cursor[0] - start[0]) * reference[0] + (cursor[1] - start[1]) * reference[1] + const length = Math.max(EXTENSION_MIN_LENGTH, along + EXTENSION_OVERSHOOT) + return { + kind: 'extension', + from: [start[0], start[1]], + to: [start[0] + reference[0] * length, start[1] + reference[1] * length], + } +} + +type WallLine = { wall: WallNode; ux: number; uz: number; length: number } + +function straightWallLines(walls: readonly WallNode[]): WallLine[] { + return walls.flatMap((wall) => { + if (isCurvedWall(wall)) return [] + const dx = wall.end[0] - wall.start[0] + const dz = wall.end[1] - wall.start[1] + const length = Math.hypot(dx, dz) + return length > 1e-9 ? [{ wall, ux: dx / length, uz: dz / length, length }] : [] + }) +} + +/** True when `segment` meets `wall` square (within 1°). */ +function meetsSquare(segment: WallPlanPoint, line: WallLine): boolean { + const along = unit(segment) + if (!along) return false + return Math.abs(along[0] * line.ux + along[1] * line.uz) <= PERPENDICULAR_SINE +} + +/** + * Snap the end of a pulled wall (or of a wall continuing the run). + * + * The direction holds straight on from the wall it comes from, square to it, or + * along a house axis within about 7°, and on 15° steps past that; the length + * steps by `gridStep` along it. Existing walls win over the held direction so a + * loop closes exactly: a wall end (the run's start among them) first, then a + * wall's middle or a crossing of two walls, then a crossing of two guides; then + * a wall the held direction runs into (or the square foot on a wall near the + * cursor); then a guide the held direction runs into, so the run lines up with + * its start or another wall's end; then the grid length. `free` (Alt, or + * snapping off) leaves the end at the cursor. + */ +export function snapWallPullPoint(input: WallPullSnapInput): WallPullSnapResult { + const { point: cursor, start, runStart, walls, gridStep } = input + const radii = { ...WALL_PULL_SNAP_RADII, ...input.radii } + const reference = input.reference ? unit(input.reference) : null + const extension = reference ? extensionGuide(start, reference, cursor) : null + const extensionLine: GuideLine | null = reference + ? { kind: 'extension', anchor: [start[0], start[1]], direction: reference } + : null + const baseGuides = extension ? [extension] : [] + const result = ( + point: WallPlanPoint, + snap: WallPullSnapKind | null, + lock: WallPullDirectionLock, + extra: Partial> = {}, + intersections: WallPlanPoint[] = [], + ): WallPullSnapResult => ({ + point: [point[0], point[1]], + snap, + label: extra.label ?? (snap ? WALL_PULL_SNAP_LABELS[snap] : null), + lock, + targetWallIds: extra.targetWallIds ?? [], + guides: [...baseGuides, ...(extra.guides ?? [])], + intersections, + }) + + const vector: WallPlanPoint = [cursor[0] - start[0], cursor[1] - start[1]] + if (input.free) return result(cursor, null, 'free') + if (Math.hypot(vector[0], vector[1]) < 1e-6) return result(start, null, 'free') + + // The held direction, and the length along it. + const { direction, lock } = lockWallPullDirection(vector, reference) + const along = vector[0] * direction[0] + vector[1] * direction[1] + const length = gridStep > 0 ? Math.round(along / gridStep) * gridStep : along + const held: WallPlanPoint = [start[0] + direction[0] * length, start[1] + direction[1] * length] + + const anchors = guideAnchors(walls, runStart, start) + const lines = nearbyGuideLines(anchors, cursor, radii.guide) + const allLines = extensionLine ? [extensionLine, ...lines] : lines + // Guide crossings near the cursor, each with the two lines through it. + const crossings: Array<{ point: WallPlanPoint; a: GuideLine; b: GuideLine }> = [] + for (let i = 0; i < allLines.length; i += 1) { + for (let j = i + 1; j < allLines.length; j += 1) { + const a = allLines[i]! + const b = allLines[j]! + const point = crossLines(a, b) + if (!point || samePoint(point, start, 1e-4)) continue + if (distanceSquared(point, cursor) > INTERSECTION_SHOW_RADIUS ** 2) continue + if (crossings.some((crossing) => samePoint(crossing.point, point, 1e-4))) continue + crossings.push({ point, a, b }) + } + } + const intersections = crossings.map((crossing) => crossing.point) + // The guides the final point sits on, then those through each crossing + // shown, drawn from their anchors — each line once. + const guidesThrough = (point: WallPlanPoint) => { + const drawn = new Set() + const guides: WallPullGuide[] = [] + for (const line of lines) { + if (!passesThrough(line, point) || samePoint(line.anchor, point, 1e-4)) continue + drawn.add(line) + guides.push(guideThrough(line, point)) + } + for (const crossing of crossings) { + for (const line of [crossing.a, crossing.b]) { + if (line.kind === 'extension' || drawn.has(line)) continue + drawn.add(line) + guides.push(guideThrough(line, crossing.point)) + } + } + return guides + } + const settle = ( + point: WallPlanPoint, + snap: WallPullSnapKind, + pointLock: WallPullDirectionLock, + extra: Partial> = {}, + ) => + result( + point, + snap, + pointLock, + { ...extra, guides: extra.guides ?? guidesThrough(point) }, + intersections, + ) + + // 1. A wall end, the run's start among them. + let endpoint: WallPlanPoint | null = null + let endpointDistance = radii.endpoint ** 2 + for (const anchor of anchors) { + const distance = distanceSquared(anchor.point, cursor) + if (distance <= endpointDistance) { + endpoint = anchor.point + endpointDistance = distance + } + } + if (endpoint) { + return settle(endpoint, 'endpoint', 'free', { + targetWallIds: wallIdsAtSnapPoint(endpoint, [...walls]), + }) + } + + // 2. A wall's middle, or where two walls cross. + const special = findWallSpecialPointSnap(cursor, [...walls], undefined, { + endpoint: 0, + midpoint: radii.special, + intersection: radii.special, + }) + if (special && special.snap !== 'endpoint' && !samePoint(special.point, start, 1e-4)) { + return settle(special.point, special.snap ?? 'midpoint', 'free', { + targetWallIds: special.targetWallIds, + }) + } + + // 3. Where two guides cross. + let crossing: (typeof crossings)[number] | null = null + let crossingDistance = radii.intersection ** 2 + for (const candidate of crossings) { + const distance = distanceSquared(candidate.point, cursor) + if (distance <= crossingDistance) { + crossing = candidate + crossingDistance = distance + } + } + if (crossing) return settle(crossing.point, 'intersection', 'free') + + // 4. A wall the held direction runs into: its body, its line near the held + // length, or the square foot on a wall line near the cursor. + const wallLines = straightWallLines(walls) + const onWall = (point: WallPlanPoint, pointLock: WallPullDirectionLock) => { + const targetWallIds = wallIdsAtSnapPoint(point, [...walls]) + const atEnd = walls.some( + (wall) => samePoint(point, wall.start, 1e-6) || samePoint(point, wall.end, 1e-6), + ) + const segment: WallPlanPoint = [point[0] - start[0], point[1] - start[1]] + const square = wallLines.some( + (line) => targetWallIds.includes(line.wall.id) && meetsSquare(segment, line), + ) + return settle(point, atEnd ? 'endpoint' : square ? 'perpendicular' : 'wall', pointLock, { + targetWallIds, + }) + } + if (length > ON_LINE) { + const body = findWallBodySnapTarget(cursor, [...walls], { + placed: held, + start, + margin: WALL_CONNECT_SNAP_RADIUS, + }) + if (body && !samePoint(body, start, 1e-4)) return onWall(body, lock) + } + let rayHit: WallPlanPoint | null = null + let rayHitGap = radii.wall + for (const line of wallLines) { + const denom = direction[0] * line.uz - direction[1] * line.ux + if (Math.abs(denom) < 1e-9) continue + const dx = line.wall.start[0] - start[0] + const dz = line.wall.start[1] - start[1] + const t = (dx * line.uz - dz * line.ux) / denom + const s = (dx * direction[1] - dz * direction[0]) / denom + if (!(t > 1e-4) || s <= 1e-6 || s >= line.length - 1e-6) continue + const gap = Math.abs(t - along) + if (gap <= rayHitGap) { + rayHit = [start[0] + direction[0] * t, start[1] + direction[1] * t] + rayHitGap = gap + } + } + if (rayHit) return onWall(rayHit, lock) + let foot: WallPlanPoint | null = null + let footDistance = radii.wall ** 2 + for (const line of wallLines) { + const station = + (start[0] - line.wall.start[0]) * line.ux + (start[1] - line.wall.start[1]) * line.uz + if (station <= 1e-6 || station >= line.length - 1e-6) continue + const point: WallPlanPoint = [ + line.wall.start[0] + line.ux * station, + line.wall.start[1] + line.uz * station, + ] + if (samePoint(point, start, 1e-4)) continue + const distance = distanceSquared(point, cursor) + if (distance <= footDistance) { + foot = point + footDistance = distance + } + } + if (foot) return onWall(foot, 'free') + + // 5. A guide the held direction runs into. + let aligned: { point: WallPlanPoint; line: GuideLine } | null = null + let alignedGap = radii.alignment + for (const line of lines) { + const t = rayStationOnLine(start, direction, line) + if (t === null) continue + const gap = Math.abs(t - along) + if (gap <= alignedGap) { + aligned = { point: [start[0] + direction[0] * t, start[1] + direction[1] * t], line } + alignedGap = gap + } + } + if (aligned) { + return settle(aligned.point, 'alignment', lock, { + label: aligned.line.kind === 'run-start' ? 'In line with start' : undefined, + }) + } + + // 6. The grid length along the held direction. + if (gridStep > 0) return settle(held, 'grid', lock) + return result(held, null, lock, { guides: guidesThrough(held) }, intersections) +} diff --git a/packages/editor/src/components/tools/wall/wall-snap-geometry.test.ts b/packages/editor/src/components/tools/wall/wall-snap-geometry.test.ts index b707fefcf4..b4809c1633 100644 --- a/packages/editor/src/components/tools/wall/wall-snap-geometry.test.ts +++ b/packages/editor/src/components/tools/wall/wall-snap-geometry.test.ts @@ -2,12 +2,18 @@ import { describe, expect, test } from 'bun:test' import type { WallNode } from '@pascal-app/core' import { chainEndJoinsExistingWall, + findWallBodySnapTarget, findWallSnapTarget, findWallSpecialPointSnap, type WallPlanPoint, } from './wall-snap-geometry' -function makeWall(start: WallPlanPoint, end: WallPlanPoint, id?: string): WallNode { +function makeWall( + start: WallPlanPoint, + end: WallPlanPoint, + id?: string, + overrides: Partial = {}, +): WallNode { return { object: 'node', id: (id ?? `wall_${start.join('_')}_${end.join('_')}`) as WallNode['id'], @@ -22,6 +28,7 @@ function makeWall(start: WallPlanPoint, end: WallPlanPoint, id?: string): WallNo thickness: 0.1, frontSide: 'unknown', backSide: 'unknown', + ...overrides, } as unknown as WallNode } @@ -129,3 +136,108 @@ describe('findWallSnapTarget (edge / along-wall)', () => { expect(findWallSnapTarget([1.2, 0.1], walls, { radius: 0.08 })).toBeNull() }) }) + +describe('findWallBodySnapTarget (a wall end aimed into a wall)', () => { + // An 8" wall centred on z = 0: its faces sit at z = ±0.1016. + const host = makeWall([0, 0], [4, 0], 'host', { thickness: 0.2032 }) + + test('an end inside the body or within the margin of a face lands on the reference line', () => { + expect(findWallBodySnapTarget([1.2, 0.05], [host])).toEqual([1.2, 0]) + expect(findWallBodySnapTarget([1.2, 0.14], [host])).toEqual([1.2, 0]) + expect(findWallBodySnapTarget([1.2, -0.14], [host])).toEqual([1.2, 0]) + expect(findWallBodySnapTarget([1.2, 0.16], [host])).toBeNull() + // Past the wall's ends the corner snap decides, not the body. + expect(findWallBodySnapTarget([4.05, 0.05], [host])).toBeNull() + }) + + test('the grid placement keeps its station along the wall, and captures on its own', () => { + // The cursor sits by the face; its lattice point is out of the band. + expect(findWallBodySnapTarget([1.23, 0.12], [host], { placed: [1.25, 0.25] })).toEqual([ + 1.25, 0, + ]) + // The cursor is out of the band; the lattice point fell into it. + expect(findWallBodySnapTarget([1.23, 0.3], [host], { placed: [1.25, 0.1] })).toEqual([1.25, 0]) + }) + + test('with a start, the end slides along the drafted segment onto the line', () => { + const start: WallPlanPoint = [1, 3] + const placed: WallPlanPoint = [1.3, 0.12] + const target = findWallBodySnapTarget(placed, [host], { start, placed }) + expect(target?.[1]).toBeCloseTo(0, 12) + // Still on the drafted line start → placed: the segment keeps its direction. + const cross = + (placed[0] - start[0]) * (target![1] - start[1]) - + (placed[1] - start[1]) * (target![0] - start[0]) + expect(cross).toBeCloseTo(0, 12) + }) + + test('a square segment stays square', () => { + const target = findWallBodySnapTarget([2, 0.13], [host], { start: [2, 3] }) + expect(target?.[0]).toBe(2) + expect(target?.[1]).toBeCloseTo(0, 12) + }) + + test('walls the segment runs alongside, or starts on, are left alone', () => { + expect(findWallBodySnapTarget([2, 0.06], [host], { start: [0.5, 0.05] })).toBeNull() + expect(findWallBodySnapTarget([1.1, 0.12], [host], { start: [1, 0] })).toBeNull() + }) + + test('a justified wall captures across its whole body, measured from its faces', () => { + // Justified to face a: the body runs from the line (z = 0) to z = 0.2032. + const justified = makeWall([0, 0], [4, 0], 'justified', { + thickness: 0.2032, + justification: 'a', + }) + expect(findWallBodySnapTarget([1.2, 0.24], [justified])).toEqual([1.2, 0]) + expect(findWallBodySnapTarget([1.2, -0.04], [justified])).toEqual([1.2, 0]) + expect(findWallBodySnapTarget([1.2, -0.08], [justified])).toBeNull() + }) + + test('an end already on the line stays put', () => { + const onLine: WallPlanPoint = [1.234, 0] + expect(findWallBodySnapTarget(onLine, [host], { start: [1.5, 3], placed: [1.5, 0.5] })).toEqual( + onLine, + ) + }) + + test('picks the wall whose body the end is closest to', () => { + // 0.05 off `near`'s face, 0.018 off the host's. + const near = makeWall([0, 0.22], [4, 0.22], 'near', { thickness: 0.1 }) + expect(findWallBodySnapTarget([1, 0.12], [near, host])).toEqual([1, 0]) + }) + + test('an end in the corner block of an L joins the corner, not an 8 cm stub', () => { + // Two 0.2 m walls meeting at the origin: the corner block is the 0.2 m + // square around it. + const l = [ + makeWall([0, 0], [4, 0], 'south', { thickness: 0.2 }), + makeWall([0, 4], [0, 0], 'west', { thickness: 0.2 }), + ] + expect(findWallBodySnapTarget([0.08, 0.08], l)).toEqual([0, 0]) + // The block's outer quadrant lies in one wall's body only, and joins too. + expect(findWallBodySnapTarget([0.05, -0.05], l)).toEqual([0, 0]) + // Aimed into the block from across the room. + expect(findWallBodySnapTarget([0.08, 0.08], l, { start: [2, 3] })).toEqual([0, 0]) + // Past the other wall's face, the end T-joins as before. + expect(findWallBodySnapTarget([0.3, 0.05], l)).toEqual([0.3, 0]) + }) + + test('an end in the corner block of a T joins the junction', () => { + // The host split at the junction, as the topology leaves a T-join. + const t = [ + makeWall([0, 0], [2, 0], 'left', { thickness: 0.2 }), + makeWall([2, 0], [4, 0], 'right', { thickness: 0.2 }), + makeWall([2, 0], [2, 4], 'stem', { thickness: 0.2 }), + ] + expect(findWallBodySnapTarget([2.08, 0.08], t)).toEqual([2, 0]) + expect(findWallBodySnapTarget([1.92, 0.06], t)).toEqual([2, 0]) + // A host left whole, the stem ending on its interior, from either wall's side. + const whole = makeWall([0, 0], [4, 0], 'whole', { thickness: 0.2 }) + const stem = makeWall([2, 0], [2, 4], 'stem', { thickness: 0.2 }) + expect(findWallBodySnapTarget([2.08, 0.08], [whole, stem])).toEqual([2, 0]) + expect(findWallBodySnapTarget([2.08, 0.08], [stem, whole])).toEqual([2, 0]) + // Clear of the corner block each wall T-joins as before. + expect(findWallBodySnapTarget([2.4, 0.08], [whole, stem])).toEqual([2.4, 0]) + expect(findWallBodySnapTarget([2.06, 0.3], [whole, stem])).toEqual([2, 0.3]) + }) +}) diff --git a/packages/editor/src/components/tools/wall/wall-snap-geometry.ts b/packages/editor/src/components/tools/wall/wall-snap-geometry.ts index 7ef593bc43..8b88b2110e 100644 --- a/packages/editor/src/components/tools/wall/wall-snap-geometry.ts +++ b/packages/editor/src/components/tools/wall/wall-snap-geometry.ts @@ -6,6 +6,7 @@ import { getWallCurveFrameAt, getWallCurveLength, + getWallFaceOffsets, isCurvedWall, type WallNode, } from '@pascal-app/core' @@ -124,6 +125,142 @@ export function findWallSnapTarget( return bestTarget } +// A drafted segment closer than this to a wall's direction (sine of the angle +// between them) runs alongside it: the wall topology stacks such walls rather +// than T-joining them, so the body snap leaves them alone. Two walls meeting +// closer than this aren't a corner either. +const WALL_BODY_SNAP_MIN_SINE = 0.5 + +/** A straight wall's reference line as a frame, with the faces its body spans. */ +type WallBodyFrame = { + wall: WallNode + length: number + ux: number + uz: number + faces: { a: number; b: number } + /** Offset along the wall's left normal, the side its face offsets are measured on. */ + across: (q: WallPlanPoint) => number + /** Station along the reference line from `start`. */ + along: (q: WallPlanPoint) => number +} + +function wallBodyFrame(wall: WallNode): WallBodyFrame | null { + if (isCurvedWall(wall)) return null + const [x1, z1] = wall.start + const length = Math.hypot(wall.end[0] - x1, wall.end[1] - z1) + if (length < 1e-9) return null + const ux = (wall.end[0] - x1) / length + const uz = (wall.end[1] - z1) / length + return { + wall, + length, + ux, + uz, + faces: getWallFaceOffsets(wall), + across: (q) => (q[1] - z1) * ux - (q[0] - x1) * uz, + along: (q) => (q[0] - x1) * ux + (q[1] - z1) * uz, + } +} + +/** + * The corner a join at `target` on `host`'s reference line falls into: where + * `host` meets another straight wall, at an L or a T, with `target` inside that + * wall's drawn body. A T-join there would split `host` into a stub buried in + * the corner block, so the end joins the junction instead. A shared end comes + * back exactly; walls meeting at a shallow angle aren't a corner. + */ +function cornerJunctionAt( + target: WallPlanPoint, + host: WallBodyFrame, + frames: WallBodyFrame[], +): WallPlanPoint | null { + for (const other of frames) { + if (other === host) continue + if (Math.abs(host.ux * other.uz - host.uz * other.ux) < WALL_BODY_SNAP_MIN_SINE) continue + const offset = other.across(target) + if (offset < other.faces.b || offset > other.faces.a) continue + const ends = [host.wall.start, host.wall.end, other.wall.start, other.wall.end] + const crossing = segmentIntersection(ends[0]!, ends[1]!, ends[2]!, ends[3]!) + if (!crossing) continue + const sharedEnd = ends.find((end) => distanceSquared(end, crossing) <= 1e-12) + return sharedEnd ? [sharedEnd[0], sharedEnd[1]] : crossing + } + return null +} + +/** + * Where a drawn wall end joins a wall it is aimed into: the point on a straight + * wall's reference line (`start` → `end`, the line the wall topology T-joins a + * partition end on, whatever the wall's justification) for an end inside that + * wall's drawn body or within `margin` of either face. + * + * `point` is the raw cursor and `placed` its grid / angle placement; either one + * inside a body captures, so the grid can't leave an end a fraction short of a + * face. Given the drafted segment's `start`, the end slides along start → + * `placed` onto the line, so a square or angle-locked segment keeps its + * direction, and a wall the segment runs alongside (or starts on) is skipped. + * Without a start the end drops square onto the line, keeping its position + * along the wall. A join that lands inside a corner block, where the wall + * meets another at an L or a T, takes the junction itself (`cornerJunctionAt`). + * Curved walls are left to the edge snap (`findWallSnapTarget`). + */ +export function findWallBodySnapTarget( + point: WallPlanPoint, + walls: WallNode[], + options?: { + ignoreWallIds?: string[] + placed?: WallPlanPoint + start?: WallPlanPoint + margin?: number + }, +): WallPlanPoint | null { + const ignored = new Set(options?.ignoreWallIds ?? []) + const placed = options?.placed ?? point + const start = options?.start + const margin = options?.margin ?? WALL_CONNECT_SNAP_RADIUS + const frames = walls.flatMap((wall) => { + const frame = ignored.has(wall.id) ? null : wallBodyFrame(wall) + return frame ? [frame] : [] + }) + let best: WallPlanPoint | null = null + let bestOutside = Number.POSITIVE_INFINITY + + for (const frame of frames) { + const { length, ux, uz, faces, across, along } = frame + const [x1, z1] = frame.wall.start + const onWall = (q: WallPlanPoint) => along(q) > 0 && along(q) < length + const outsideBody = (q: WallPlanPoint) => + onWall(q) ? Math.max(0, faces.b - across(q), across(q) - faces.a) : Number.POSITIVE_INFINITY + const outside = Math.min(outsideBody(point), outsideBody(placed)) + if (outside > margin || outside >= bestOutside) continue + + let target: WallPlanPoint | null + if (Math.abs(across(point)) <= 1e-9 && onWall(point)) { + // Already on the line: a joined end snapped again (the plan view hands + // the 3D tool its resolved point) stays put. + target = [point[0], point[1]] + } else if (start) { + const dx = placed[0] - start[0] + const dz = placed[1] - start[1] + const run = Math.hypot(dx, dz) + if (run < 1e-9 || Math.abs(dx * uz - dz * ux) / run < WALL_BODY_SNAP_MIN_SINE) continue + const startAcross = across(start) + const t = startAcross / (startAcross - across(placed)) + if (!(t > 1e-6)) continue + target = [start[0] + dx * t, start[1] + dz * t] + if (!onWall(target)) continue + } else { + const station = along(placed) + if (!(station > 0 && station < length)) continue + target = [x1 + ux * station, z1 + uz * station] + } + best = cornerJunctionAt(target, frame, frames) ?? target + bestOutside = outside + } + + return best +} + /** * Wall ids that contain an already-resolved snap point. * diff --git a/packages/editor/src/components/tools/zone/zone-tool.tsx b/packages/editor/src/components/tools/zone/zone-tool.tsx index 9f8f26797f..7f48b4b4c8 100644 --- a/packages/editor/src/components/tools/zone/zone-tool.tsx +++ b/packages/editor/src/components/tools/zone/zone-tool.tsx @@ -184,9 +184,9 @@ export const ZoneTool: React.FC = () => { updateLines() } - // World-grid snap projected into building-local; rotated buildings - // used to pull the snap off the visible grid lines. Grid quantize only - // in grid mode; off / lines / angles leave the raw cursor. + // Snap on the building's own grid lattice, the one its walls are drafted + // on (square to a turned house). Grid quantize only in grid mode; off / + // lines / angles leave the raw cursor. const gridPointOf = (event: GridEvent): [number, number] => isGridSnapActive() ? snapWorldXZForActiveBuilding( diff --git a/packages/editor/src/components/ui/action-menu/structure-tools.tsx b/packages/editor/src/components/ui/action-menu/structure-tools.tsx index 34aa72d24e..8d37aab62e 100644 --- a/packages/editor/src/components/ui/action-menu/structure-tools.tsx +++ b/packages/editor/src/components/ui/action-menu/structure-tools.tsx @@ -20,6 +20,9 @@ export const tools: ToolConfig[] = [ { id: 'roof', iconSrc: '/icons/roof.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.gableRoof', message: 'Gable Roof' } }, { id: 'fence', iconSrc: '/icons/fence.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.fence', message: 'Fence' } }, { id: 'column', iconSrc: '/icons/column.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.column', message: 'Column' } }, + { id: 'beam', iconSrc: '/icons/beam.svg', label: /* i18n */ { id: 'editor.actionMenu.structureTools.beam', message: 'Beam' } }, + { id: 'foundation', iconSrc: '/icons/floor.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.foundation', message: 'Foundation' } }, + { id: 'footing', iconSrc: '/icons/floor.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.footing', message: 'Footing' } }, { id: 'elevator', iconSrc: '/icons/elevator.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.elevator', message: 'Elevator' } }, { id: 'slab', iconSrc: '/icons/floor.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.slab', message: 'Slab' } }, { id: 'ceiling', iconSrc: '/icons/ceiling.webp', label: /* i18n */ { id: 'editor.actionMenu.structureTools.ceiling', message: 'Ceiling' } }, diff --git a/packages/editor/src/components/ui/action-menu/view-toggles.test.ts b/packages/editor/src/components/ui/action-menu/view-toggles.test.ts new file mode 100644 index 0000000000..a24dc742d5 --- /dev/null +++ b/packages/editor/src/components/ui/action-menu/view-toggles.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, test } from 'bun:test' +import { adjacentWallsToggle } from './view-toggles' + +describe('walls below and above button', () => { + test('on the top storey it turns on only the walls below', () => { + expect(adjacentWallsToggle(false, { below: 'level_0', above: null })).toEqual({ below: true }) + }) + + test('on the ground floor it turns on only the walls above', () => { + expect(adjacentWallsToggle(false, { below: null, above: 'level_1' })).toEqual({ above: true }) + }) + + test('between two levels it turns both sides on, then both off', () => { + const levels = { below: 'level_0', above: 'level_2' } + expect(adjacentWallsToggle(false, levels)).toEqual({ below: true, above: true }) + expect(adjacentWallsToggle(true, levels)).toEqual({ below: false, above: false }) + }) +}) diff --git a/packages/editor/src/components/ui/action-menu/view-toggles.tsx b/packages/editor/src/components/ui/action-menu/view-toggles.tsx index f525d49ecb..fe826ad8c5 100644 --- a/packages/editor/src/components/ui/action-menu/view-toggles.tsx +++ b/packages/editor/src/components/ui/action-menu/view-toggles.tsx @@ -1,6 +1,7 @@ 'use client' import { + type AnyNode, type AnyNodeId, type BuildingNode, type GuideNode, @@ -11,15 +12,31 @@ import { usePascalI18n, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' -import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2, Waypoints } from 'lucide-react' +import { + Check, + ChevronDown, + Eye, + EyeOff, + Layers2, + Plus, + SquareDashed, + Trash2, + Waypoints, +} from 'lucide-react' import { useCallback, useRef, useState } from 'react' import { useShallow } from 'zustand/react/shallow' import { createLocalGuideImage } from '../../../lib/local-guide-image' import { cn } from '../../../lib/utils' import useEditor from '../../../store/use-editor' import { useUploadStore } from '../../../store/use-upload' +import { + ADJACENT_WALL_COLORS, + adjacentLevelIds, + useWallsBelowSetting, +} from '../../editor-2d/floorplan-adjacent-walls-layer' import { SliderControl } from '../controls/slider-control' import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover' +import { Switch } from '../primitives/switch' import { ActionButton } from './action-button' import { useActionMenuPopupSide } from './placement' import { localizedLevelName } from '../../../i18n/level-name' @@ -963,6 +980,152 @@ function ReferenceFloorControl() { ) } +// ── Walls below / above control ───────────────────────────────────────────── + +function useAdjacentLevelNames() { + const i18n = usePascalI18n() + const levelId = useViewer((s) => s.selection.levelId) + return useScene( + useShallow((state) => { + const nodes = state.nodes as Record + const ids = adjacentLevelIds(nodes, levelId) + const name = (id: string | null) => { + const level = id ? nodes[id] : undefined + return level?.type === 'level' ? localizedLevelName(level, i18n, i18n.getLocale()) : null + } + return { below: name(ids.below), above: name(ids.above) } + }), + ) +} + +/** + * What the "Walls below / above" button turns each side to: the sides that have a level all on, or + * all off when one shows. A side with no level keeps its setting, which persists for the levels + * that have one: turning the walls below on from the top storey must not trace walls above later. + */ +export function adjacentWallsToggle( + active: boolean, + levels: { below: string | null; above: string | null }, +): { below?: boolean; above?: boolean } { + return { + ...(levels.below ? { below: !active } : {}), + ...(levels.above ? { above: !active } : {}), + } +} + +/** + * "Walls below / above": the plan traces the walls of the neighbouring levels as dashed outlines, + * blue for the level below (on a roof level, the top storey), magenta for the level above. + */ +function AdjacentWallsControl() { + const popupSide = useActionMenuPopupSide() + const levelId = useViewer((s) => s.selection.levelId) + const { shown: showBelow, setShown: setShowBelow } = useWallsBelowSetting(levelId) + const showAbove = useEditor((state) => state.showWallsAbove) + const setShowAbove = useEditor((state) => state.setShowWallsAbove) + const names = useAdjacentLevelNames() + const [isOpen, setIsOpen] = useState(false) + const active = (showBelow && !!names.below) || (showAbove && !!names.above) + const available = !!(names.below || names.above) + const rows = [ + { + key: 'below', + label: 'Walls below', + level: names.below, + checked: showBelow, + set: setShowBelow, + color: ADJACENT_WALL_COLORS.below, + }, + { + key: 'above', + label: 'Walls above', + level: names.above, + checked: showAbove, + set: setShowAbove, + color: ADJACENT_WALL_COLORS.above, + }, + ] as const + + return ( + +
+ { + const next = adjacentWallsToggle(active, names) + if (next.below !== undefined) setShowBelow(next.below) + if (next.above !== undefined) setShowAbove(next.above) + }} + size="icon" + variant="ghost" + > + + + + + +
+ +
+
+

Walls below and above

+

+ Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof + against the walls under it. +

+
+ {rows.map((row) => ( + + ))} +
+
+
+ ) +} + // ── Riser diagram control ──────────────────────────────────────────────────── function RiserControl() { @@ -1003,6 +1166,7 @@ export function ViewToggles() { + ) diff --git a/packages/editor/src/components/ui/build-panel/build-panel.tsx b/packages/editor/src/components/ui/build-panel/build-panel.tsx index 4a6c3f77ed..94d8706205 100644 --- a/packages/editor/src/components/ui/build-panel/build-panel.tsx +++ b/packages/editor/src/components/ui/build-panel/build-panel.tsx @@ -1,13 +1,41 @@ 'use client' -import { type PascalI18n, usePascalI18n } from '@pascal-app/core' +import { + type MessageDescriptor, + type PascalI18n, + usePascalI18n, + WALL_FINISH_LIBRARY_REF, + wallAssemblyCladding, + wallCladdingProducts, + wallPaintProducts, +} from '@pascal-app/core' import { ChevronRight } from 'lucide-react' import { type ComponentPropsWithoutRef, type ReactNode, useEffect, useState } from 'react' +import { messageWithSlot } from '../../../i18n/message-slot' +import { armWallLineTool, armWallTypeTool, withToolShortcut } from '../../../lib/tool-shortcuts' import { cn } from '../../../lib/utils' import { + setWallTypeOverride, + useWallLineKind, + useWallTypeOverridePlacement, WALL_DRAW_VARIANTS, type WallDrawVariant, + type WallLineKind, } from '../../../lib/wall-draw-variant' +import { + availableWallType, + deleteProjectWallType, + resolveWallType, + useProjectWallTypes, + WALL_LINE_TOOLS, + WALL_TYPE_TOOLS, + type WallTypePlacement, + wallTypeOptions, + wallTypePlacementOf, +} from '../../../lib/wall-type' +import useEditor from '../../../store/use-editor' +import { WallFinishPicker, WallTypeActions, WallTypeSelect } from './wall-type-controls' +import { type WallTypeEditorRequest, WallTypeEditorDialog } from './wall-type-editor-dialog' /** * The Build panel's grouped layout (Rooms / Add to rooms / Outdoor / Advanced), @@ -213,24 +241,49 @@ export function BuildPanelCaption({ caption, lead }: { caption: string; lead?: s } /** - * The Rooms group: one large tile per wall drawing variant, and the armed - * variant's caption. `activeVariant` is null while the wall tool is not armed. + * The Rooms group: one large tile per wall drawing variant, then the Exterior + * wall and Interior wall tiles (single walls of one type, `WALL_TYPE_TOOLS`), the + * Invisible wall tile (`WALL_LINE_TOOLS`), and the armed tile's caption. + * `activeVariant` is null while the wall tool is not armed. */ export function BuildPanelRoomsSection({ activeVariant, onHover, onSelect, + onSelectWallTool, + onSelectWallLineTool, tileProps, }: { activeVariant: WallDrawVariant | null onHover?: (variant: WallDrawVariant) => void onSelect: (variant: WallDrawVariant) => void + /** + * Arms the Exterior or Interior wall tool the way the host arms its tiles; + * absent = as its shortcut does (`armWallTypeTool`). + */ + onSelectWallTool?: (placement: WallTypePlacement) => void + /** Arms the Invisible wall (or Railing) tool the way the host arms its tiles; absent = `armWallLineTool`. */ + onSelectWallLineTool?: (kind: WallLineKind) => void /** Extra attributes per tile (e.g. a host's guide-target hook). */ tileProps?: (variant: WallDrawVariant) => Record }) { const i18n = usePascalI18n() - const active = WALL_DRAW_VARIANTS.find((variant) => variant.id === activeVariant) + // Exterior and Interior wall draw the Walls shape with one type: their tile is lit, not Walls. + const forced = useWallTypeOverridePlacement() + const lineKind = useWallLineKind() + const wallTool = activeVariant + ? WALL_TYPE_TOOLS.find((tool) => tool.placement === forced) + : undefined + const lineTool = activeVariant ? WALL_LINE_TOOLS.find((tool) => tool.kind === lineKind) : undefined + const litVariant = wallTool || lineTool ? null : activeVariant + const active = WALL_DRAW_VARIANTS.find((variant) => variant.id === litVariant) const activeMessages = active ? roomVariantMessages(active.id, i18n) : null + // The Exterior / Interior wall and line tools draw like Walls: their caption opens with its lead. + const toolCaption = wallTool + ? wallTypeToolMessages(wallTool.placement, i18n).caption + : lineTool + ? wallLineToolMessages(lineTool.kind, i18n).caption + : null return ( onSelect(variant.id)} + onClick={() => { + setWallTypeOverride(null) + onSelect(variant.id) + }} onMouseEnter={() => onHover?.(variant.id)} size="lg" title={messages.title} @@ -258,9 +314,48 @@ export function BuildPanelRoomsSection({ /> ) })} + {WALL_TYPE_TOOLS.map((tool) => { + const messages = wallTypeToolMessages(tool.placement, i18n) + return ( + { + if (onSelectWallTool) onSelectWallTool(tool.placement) + else armWallTypeTool(tool.placement) + }} + onMouseEnter={() => onHover?.('walls')} + size="lg" + title={withToolShortcut(messages.title, tool.shortcut)} + /> + ) + })} + {WALL_LINE_TOOLS.map((tool) => { + const messages = wallLineToolMessages(tool.kind, i18n) + return ( + { + if (onSelectWallLineTool) onSelectWallLineTool(tool.kind) + else armWallLineTool(tool.kind) + }} + onMouseEnter={() => onHover?.('walls')} + size="lg" + title={messages.title} + /> + ) + })} + ) } @@ -302,11 +398,12 @@ function roomVariantMessages(variant: WallDrawVariant, i18n: PascalI18n) { title: i18n._({ id: 'editor.buildPanel.rooms.walls.title', message: 'Walls' }), lead: i18n._({ id: 'editor.buildPanel.rooms.walls.lead', - message: 'Draw one wall at a time.', + message: 'Click each corner;', }), caption: i18n._({ id: 'editor.buildPanel.rooms.walls.caption', - message: '{lead} Close a loop and it becomes a room; leave it open for partitions.', + message: + '{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room.', values: { lead: CAPTION_LEAD_MARKER }, }), } @@ -328,3 +425,250 @@ function roomVariantMessages(variant: WallDrawVariant, i18n: PascalI18n) { }), } } + +/** + * The Exterior and Interior wall tiles (`WALL_TYPE_TOOLS`): their name, tooltip, and the caption + * while one is armed, after the Walls lead. + */ +function wallTypeToolMessages(placement: WallTypePlacement, i18n: PascalI18n) { + if (placement === 'interior') { + return { + label: i18n._({ id: 'editor.buildPanel.rooms.interiorWall.label', message: 'Interior wall' }), + title: i18n._({ + id: 'editor.buildPanel.rooms.interiorWall.title', + message: 'Interior walls: every wall drawn takes the interior type and paint', + }), + caption: i18n._({ + id: 'editor.buildPanel.rooms.interiorWall.caption', + message: + '{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run.', + values: { lead: CAPTION_LEAD_MARKER }, + }), + } + } + return { + label: i18n._({ id: 'editor.buildPanel.rooms.exteriorWall.label', message: 'Exterior wall' }), + title: i18n._({ + id: 'editor.buildPanel.rooms.exteriorWall.title', + message: 'Exterior walls: every wall drawn takes the exterior type and cladding', + }), + caption: i18n._({ + id: 'editor.buildPanel.rooms.exteriorWall.caption', + message: + '{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run.', + values: { lead: CAPTION_LEAD_MARKER }, + }), + } +} + +/** + * The Invisible wall and Railing tiles (`WALL_LINE_TOOLS`): their name, tooltip, and the caption + * while one is armed, after the Walls lead. + */ +function wallLineToolMessages(kind: WallLineKind, i18n: PascalI18n) { + if (kind === 'railing') { + return { + label: i18n._({ id: 'editor.buildPanel.rooms.railing.label', message: 'Railing' }), + title: i18n._({ + id: 'editor.buildPanel.rooms.railing.title', + message: + 'Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel.', + }), + caption: i18n._({ + id: 'editor.buildPanel.rooms.railing.caption', + message: + '{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run.', + values: { lead: CAPTION_LEAD_MARKER }, + }), + } + } + return { + label: i18n._({ + id: 'editor.buildPanel.rooms.invisibleWall.label', + message: 'Invisible wall', + }), + title: i18n._({ + id: 'editor.buildPanel.rooms.invisibleWall.title', + message: + 'Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall.', + }), + caption: i18n._({ + id: 'editor.buildPanel.rooms.invisibleWall.caption', + message: + '{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run.', + values: { lead: CAPTION_LEAD_MARKER }, + }), + } +} + +const WALL_TYPE_ROWS: ReadonlyArray<{ placement: WallTypePlacement; label: MessageDescriptor }> = [ + { + placement: 'exterior', + label: /* i18n */ { id: 'editor.buildPanel.wallTypes.exterior', message: 'Exterior' }, + }, + { + placement: 'interior', + label: /* i18n */ { id: 'editor.buildPanel.wallTypes.interior', message: 'Interior' }, + }, +] + +/** The cladding picker's name for each kind: what a builder calls the stuff on the wall. */ +const CLADDING_LABEL: Record = { + siding: /* i18n */ { id: 'editor.buildPanel.wallTypes.siding', message: 'Siding' }, + 'fiber-cement': /* i18n */ { + id: 'editor.buildPanel.wallTypes.fiberCement', + message: 'Fiber cement', + }, + stucco: /* i18n */ { id: 'editor.buildPanel.wallTypes.stucco', message: 'Stucco' }, + brick: /* i18n */ { id: 'editor.buildPanel.wallTypes.brick', message: 'Brick' }, + stone: /* i18n */ { id: 'editor.buildPanel.wallTypes.stone', message: 'Stone' }, +} + +/** A cladding kind the picker has no name for. */ +const CLADDING_FALLBACK_LABEL = /* i18n */ { + id: 'editor.buildPanel.wallTypes.cladding', + message: 'Cladding', +} + +/** + * The wall types Rectangle, Polygon and Walls draw with, picked before drawing + * and kept with the editor preferences: the exterior type for the outline and + * open runs, the interior type for walls drawn inside a room. Each list holds + * the types that build its kind of wall, the project's own among them, and + * "New wall type…"; the pencil edits a project type or duplicates any. Beside + * them, the finishes new walls get: the exterior type's cladding product and + * the paint on every drywall face. While the Exterior or Interior wall tool is + * armed only its row shows, as that tool's settings. + */ +export function BuildPanelWallTypes() { + const i18n = usePascalI18n() + const wallTypes = useEditor((state) => state.wallTypes) + const wallFinishes = useEditor((state) => state.wallFinishes) + const setWallType = useEditor((state) => state.setWallType) + const setWallCladding = useEditor((state) => state.setWallCladding) + const setWallPaint = useEditor((state) => state.setWallPaint) + const projectTypes = useProjectWallTypes() + const [editing, setEditing] = useState(null) + // Set by the Exterior / Interior wall tools: every wall takes that type. + const forced = useWallTypeOverridePlacement() + const rows = forced ? WALL_TYPE_ROWS.filter((row) => row.placement === forced) : WALL_TYPE_ROWS + return ( +
+ {rows.map(({ placement, label }) => { + const id = availableWallType(wallTypes[placement], placement, projectTypes) + const type = resolveWallType(id, projectTypes) + const cladding = type ? wallAssemblyCladding(type.assembly) : undefined + const claddingProducts = wallCladdingProducts(cladding) + return ( +
+
+ setWallType(placement, next)} + onNew={() => setEditing({ mode: 'new', placement })} + options={wallTypeOptions(placement, projectTypes, id)} + value={id} + /> + deleteProjectWallType(id) : undefined} + onDuplicate={ + type + ? () => + setEditing({ + mode: 'duplicate', + placement, + label: type.label, + assembly: type.assembly, + }) + : undefined + } + onEdit={ + type?.project + ? () => + setEditing({ + mode: 'edit', + placement, + id, + label: type.label, + assembly: type.assembly, + }) + : undefined + } + /> +
+ {placement === 'exterior' && cladding && claddingProducts.length > 0 ? ( + setWallCladding(cladding, ref)} + products={claddingProducts} + value={wallFinishes.cladding[cladding] ?? WALL_FINISH_LIBRARY_REF[cladding] ?? ''} + /> + ) : null} + {placement === 'interior' ? ( + + ) : null} +
+ ) + })} +

+ {forced + ? messageWithSlot( + i18n, + forced === 'interior' + ? /* i18n */ { + id: 'editor.buildPanel.wallTypes.forcedInterior', + message: + '{drawing} Every wall takes the interior type until you pick a shape or leave the tool.', + } + : /* i18n */ { + id: 'editor.buildPanel.wallTypes.forcedExterior', + message: + '{drawing} Every wall takes the exterior type until you pick a shape or leave the tool.', + }, + 'drawing', + + {forced === 'interior' + ? i18n._({ + id: 'editor.buildPanel.wallTypes.drawingInterior', + message: 'Drawing: Interior walls.', + }) + : i18n._({ + id: 'editor.buildPanel.wallTypes.drawingExterior', + message: 'Drawing: Exterior walls.', + })} + , + ) + : i18n._({ + id: 'editor.buildPanel.wallTypes.hint', + message: + 'New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint.', + })} +

+ setEditing(null)} + onSaved={(type) => setWallType(wallTypePlacementOf(type.assembly), type.id)} + request={editing} + /> +
+ ) +} diff --git a/packages/editor/src/components/ui/build-panel/wall-type-controls.tsx b/packages/editor/src/components/ui/build-panel/wall-type-controls.tsx new file mode 100644 index 0000000000..34ae567755 --- /dev/null +++ b/packages/editor/src/components/ui/build-panel/wall-type-controls.tsx @@ -0,0 +1,245 @@ +'use client' + +import { + getCatalogMaterialById, + getLibraryMaterialIdFromRef, + getMaterialSwatch, + type MaterialCatalogItem, + toLibraryMaterialRef, +} from '@pascal-app/core' +import { ChevronDown, Copy, Pencil, Plus, Trash2 } from 'lucide-react' +import { Fragment, useState } from 'react' +import { materialSwatchStyle } from '../../../lib/material-swatch-style' +import { cn } from '../../../lib/utils' +import { + newWallTypeBlockedReason, + useProjectWallTypes, + type WallTypeId, + type WallTypeOption, +} from '../../../lib/wall-type' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '../primitives/dropdown-menu' +import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover' + +const TRIGGER_CLASS = + 'group flex h-9 w-full min-w-0 items-center justify-between gap-2 rounded-lg border border-border/50 bg-black/4 px-3 text-xs transition-colors hover:bg-black/8 data-[state=open]:bg-black/8 dark:bg-[#2C2C2E] dark:data-[state=open]:bg-[#3e3e3e] dark:hover:bg-[#3e3e3e]' + +/** + * One placement's wall type list, grouped (Presets, This project, Other), with + * "New wall type…" at the bottom: the Build panel's Exterior or Interior pick. + * "New wall type…" is off, and says why, once the project keeps as many types as + * it can (`newWallTypeBlockedReason`). + */ +export function WallTypeSelect({ + className, + label, + onChange, + onNew, + options, + value, +}: { + className?: string + label: string + onChange: (id: WallTypeId) => void + onNew: () => void + options: readonly WallTypeOption[] + value: WallTypeId +}) { + const current = options.find((option) => option.id === value)?.label + const blocked = newWallTypeBlockedReason(useProjectWallTypes()) + return ( + + + + + + onChange(next)} value={value}> + {options.map((option, index) => ( + + {option.group !== options[index - 1]?.group ? ( + <> + {index > 0 ? : null} + + {option.group} + + + ) : null} + {option.label} + + ))} + + + + + New wall type… + + {blocked ? ( +

+ {blocked} +

+ ) : null} +
+
+ ) +} + +/** + * What can be done with the picked type: a project type is edited or deleted in + * place; any type, a preset too, can be duplicated into a new project type while + * the project has room for one (`newWallTypeBlockedReason`). + */ +export function WallTypeActions({ + label, + onDelete, + onDuplicate, + onEdit, +}: { + label: string + /** Absent for a preset or the plain wall, which belong to no project. */ + onDelete?: () => void + /** Absent for the plain wall, which has no layers to start from. */ + onDuplicate?: () => void + onEdit?: () => void +}) { + const projectTypes = useProjectWallTypes() + const blocked = onDuplicate ? newWallTypeBlockedReason(projectTypes) : null + return ( + + + + + + {label} + + + Edit layers… + + + + Duplicate as a new type… + + {onDelete ? ( + + + Delete from this project + + ) : null} + {blocked ? ( +

+ {blocked} +

+ ) : onEdit ? null : ( +

+ Presets stay as cited. Duplicate one to change its layers. +

+ )} +
+
+ ) +} + +function swatchOf(ref: string): { label: string; style: ReturnType } { + const item = getCatalogMaterialById(getLibraryMaterialIdFromRef(ref) ?? undefined) + if (!item) return { label: ref.startsWith('#') ? ref.toUpperCase() : 'Default', style: {} } + return { label: item.label, style: materialSwatchStyle(getMaterialSwatch(item)) } +} + +/** + * A finish new walls are drawn with: the swatch and name of the pick, and a grid of + * the products it can be (a cladding kind's, or the flat paint colours). + */ +export function WallFinishPicker({ + label, + onChange, + products, + value, +}: { + label: string + onChange: (ref: string) => void + products: readonly MaterialCatalogItem[] + value: string +}) { + const [open, setOpen] = useState(false) + const swatch = swatchOf(value) + return ( + + + + + +
{label}
+
+ {products.map((item) => { + const ref = toLibraryMaterialRef(item.id) + return ( + + ) + })} +
+
+
+ ) +} diff --git a/packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx b/packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx new file mode 100644 index 0000000000..03904bed67 --- /dev/null +++ b/packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx @@ -0,0 +1,543 @@ +'use client' + +import { + type Assembly, + type AssemblyLayer, + BRICK_AIR_SPACE, + BRICK_VENEER, + FIBER_CEMENT, + GYPSUM_HALF, + GYPSUM_SHEATHING, + newWallTypeId, + SIDING_LAP, + STONE_VENEER_UNVERIFIED, + STUCCO_3_COAT, + WALL_TYPE_CORES, + type WallTypeCore, + type WallTypeDefinition, + WSP_SHEATHING, + wallTypeAssembly, + wallTypeCoreOf, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { ArrowDown, ArrowUp, Plus, X } from 'lucide-react' +import { useEffect, useMemo, useState } from 'react' +import { formatLayerThickness, parseLayerThickness } from '../../../lib/layer-thickness' +import type { LinearUnit } from '../../../lib/measurements' +import { cn } from '../../../lib/utils' +import { + saveProjectWallType, + WALL_TYPE_LAYER_LIMIT, + WALL_TYPE_SAVE_FAILURE, + type WallTypePlacement, + type WallTypeSaveResult, + wallTypePlacementOf, +} from '../../../lib/wall-type' +import { Button } from '../primitives/button' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '../primitives/dialog' + +/** What the editor opens on: a blank type for a placement, a project type to edit, or a type to copy. */ +export type WallTypeEditorRequest = + | { mode: 'new'; placement: WallTypePlacement } + | { mode: 'edit'; placement: WallTypePlacement; id: string; label: string; assembly: Assembly } + | { mode: 'duplicate'; placement: WallTypePlacement; label: string; assembly: Assembly } + +/** The roles a layer takes in the editor; `core` is the one framing layer. */ +type DraftRole = 'finish' | 'air' | 'sheathing' | 'insulation' | 'core' | 'lining' + +type DraftLayer = { + key: number + role: DraftRole + material: string + thickness: number +} + +const IN = 0.0254 + +const ROLE_LABEL: Record = { + finish: 'Finish', + air: 'Air space', + sheathing: 'Sheathing', + insulation: 'Insulation', + core: 'Framing core', + lining: 'Lining', +} + +/** + * Each role's materials, as the construction kinds the inspector and the framers + * read, and the cited thickness one starts at (the `wall-assembly.ts` constants; + * insulation is the product's own, typed in). + */ +const LAYER_MATERIALS: Record< + Exclude, + ReadonlyArray<{ value: string; label: string; thickness: number }> +> = { + finish: [ + { value: 'siding', label: 'Lap siding', thickness: SIDING_LAP }, + { value: 'fiber-cement', label: 'Fiber cement', thickness: FIBER_CEMENT }, + { value: 'stucco', label: 'Stucco', thickness: STUCCO_3_COAT }, + { value: 'brick', label: 'Brick veneer', thickness: BRICK_VENEER }, + { value: 'stone', label: 'Stone veneer', thickness: STONE_VENEER_UNVERIFIED }, + ], + air: [{ value: 'air', label: 'Air space', thickness: BRICK_AIR_SPACE }], + sheathing: [ + { value: 'osb', label: 'OSB', thickness: WSP_SHEATHING }, + { value: 'plywood', label: 'Plywood', thickness: WSP_SHEATHING }, + { value: 'gypsum', label: 'Gypsum sheathing', thickness: GYPSUM_SHEATHING }, + ], + insulation: [ + { value: 'rigid-foam', label: 'Rigid foam', thickness: 1 * IN }, + { value: 'mineral-wool', label: 'Mineral wool board', thickness: 1.5 * IN }, + ], + lining: [ + { value: 'drywall', label: 'Drywall', thickness: GYPSUM_HALF }, + { value: 'plaster', label: 'Plaster', thickness: STUCCO_3_COAT }, + ], +} + +const ADDABLE_ROLES: ReadonlyArray> = [ + 'finish', + 'air', + 'sheathing', + 'insulation', + 'lining', +] + +const DEFAULT_CORE: Record = { + exterior: WALL_TYPE_CORES.find((core) => core.id === 'wood-2x6-16')!, + interior: WALL_TYPE_CORES.find((core) => core.id === 'wood-2x4-16')!, +} + +let nextKey = 1 + +function layerOf(role: Exclude, material?: string): DraftLayer { + const options = LAYER_MATERIALS[role] + const option = options.find((entry) => entry.value === material) ?? options[0]! + return { key: nextKey++, role, material: option.value, thickness: option.thickness } +} + +function coreLayer(core: WallTypeCore): DraftLayer { + return { key: nextKey++, role: 'core', material: core.id, thickness: core.thickness } +} + +function draftRoleOf(layer: AssemblyLayer): DraftRole { + if (layer.core) return 'core' + switch (layer.role) { + case 'finish': + case 'air': + case 'sheathing': + case 'insulation': + case 'lining': + return layer.role + default: + return 'sheathing' + } +} + +/** The editor's layers for a request: a stack to start from, or a typical one for the placement. */ +function initialLayers(request: WallTypeEditorRequest): DraftLayer[] { + if (request.mode === 'new') { + return request.placement === 'exterior' + ? [ + layerOf('finish', 'siding'), + layerOf('sheathing', 'osb'), + coreLayer(DEFAULT_CORE.exterior), + layerOf('lining', 'drywall'), + ] + : [layerOf('lining', 'drywall'), coreLayer(DEFAULT_CORE.interior), layerOf('lining', 'drywall')] + } + const core = wallTypeCoreOf(request.assembly) ?? DEFAULT_CORE[request.placement] + const layers = request.assembly.layers.map((layer): DraftLayer => { + const role = draftRoleOf(layer) + if (role === 'core') return coreLayer(core) + return { + key: nextKey++, + role, + material: layer.material ?? LAYER_MATERIALS[role][0]!.value, + thickness: layer.thickness, + } + }) + return layers.some((layer) => layer.role === 'core') ? layers : [...layers, coreLayer(core)] +} + +/** Where a new layer of `role` goes: a finish outermost, sheathing and insulation just outside the core, a lining innermost. */ +function insertIndex(layers: readonly DraftLayer[], role: DraftRole): number { + const coreIndex = layers.findIndex((layer) => layer.role === 'core') + if (role === 'finish') return 0 + if (role === 'air') { + const finish = layers.findIndex((layer) => layer.role === 'finish') + return finish < 0 ? 0 : finish + 1 + } + if (role === 'lining') return layers.length + if (role === 'insulation') { + const sheathing = layers.findIndex((layer) => layer.role === 'sheathing') + return sheathing >= 0 && sheathing < coreIndex ? sheathing : coreIndex + } + return coreIndex +} + +/** The saved stack: each layer as an assembly layer, the core with its framing size and spacing. */ +function draftAssembly(id: string, layers: readonly DraftLayer[]): Assembly { + return wallTypeAssembly( + id, + layers.map((layer): Omit => { + if (layer.role === 'core') { + const core = WALL_TYPE_CORES.find((entry) => entry.id === layer.material)! + return { + role: 'structure', + core: true, + material: core.material, + thickness: core.thickness, + ...(core.spacing !== undefined ? { spacing: core.spacing } : {}), + } + } + return { + role: layer.role, + thickness: layer.thickness, + ...(layer.role === 'air' ? {} : { material: layer.material }), + } + }), + ) +} + +const FIELD_CLASS = + 'h-8 w-full min-w-0 rounded-md border border-border bg-background px-2 text-xs text-foreground outline-none focus:border-primary' + +function ThicknessField({ + metres, + onCommit, + unit, +}: { + metres: number + onCommit: (metres: number) => void + unit: LinearUnit +}) { + const [draft, setDraft] = useState(null) + return ( + { + if (draft != null) { + const parsed = parseLayerThickness(draft, unit) + if (parsed != null && parsed > 0) onCommit(parsed) + } + setDraft(null) + }} + onChange={(event) => setDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') event.currentTarget.blur() + if (event.key === 'Escape') { + setDraft(null) + event.currentTarget.blur() + } + }} + spellCheck={false} + value={draft ?? formatLayerThickness(metres, unit)} + /> + ) +} + +const TITLE: Record = { + new: 'New wall type', + edit: 'Edit wall type', + duplicate: 'Duplicate wall type', +} + +/** + * The wall type editor: a name and the + * layers from the outside in — finish, air space, sheathing, insulation, lining — + * around exactly one framing core, whose size and spacing are what the framers stud + * the wall at. The total updates as layers change; saving keeps the type with the + * project (`saveProjectWallType`) and lists it under Exterior when it has a cladding + * side, Interior otherwise. + */ +export function WallTypeEditorDialog({ + onClose, + onSaved, + request, +}: { + onClose: () => void + onSaved: (type: WallTypeDefinition) => void + request: WallTypeEditorRequest | null +}) { + const unit = useViewer((state) => state.unit) + const [name, setName] = useState('') + const [layers, setLayers] = useState([]) + const [failure, setFailure] = useState | null>(null) + + useEffect(() => { + if (!request) return + setLayers(initialLayers(request)) + setName( + request.mode === 'new' + ? request.placement === 'exterior' + ? 'My exterior wall' + : 'My interior wall' + : request.mode === 'duplicate' + ? `${request.label} (copy)` + : request.label, + ) + setFailure(null) + }, [request]) + + const total = layers.reduce((sum, layer) => sum + layer.thickness, 0) + const placement = useMemo( + () => (layers.length > 0 ? wallTypePlacementOf(draftAssembly('custom-draft', layers)) : null), + [layers], + ) + const trimmed = name.trim() + const canSave = trimmed.length > 0 && layers.every((layer) => layer.thickness > 0) + // A wall type's stack holds `WALL_TYPE_LAYER_LIMIT` layers at most; past it the type would not save. + const full = layers.length >= WALL_TYPE_LAYER_LIMIT + const fullReason = `A wall type holds at most ${WALL_TYPE_LAYER_LIMIT} layers.` + + const update = (key: number, patch: Partial) => + setLayers((current) => current.map((layer) => (layer.key === key ? { ...layer, ...patch } : layer))) + const move = (index: number, by: -1 | 1) => + setLayers((current) => { + const target = index + by + if (target < 0 || target >= current.length) return current + const next = [...current] + ;[next[index], next[target]] = [next[target]!, next[index]!] + return next + }) + + const save = () => { + if (!(request && canSave)) return + const id = request.mode === 'edit' ? request.id : newWallTypeId() + const type: WallTypeDefinition = { + id, + label: trimmed.slice(0, 60), + assembly: draftAssembly(id, layers), + } + const result = saveProjectWallType(type) + if (result !== 'saved') { + setFailure(result) + return + } + onSaved(type) + onClose() + } + + return ( + { + if (!open) onClose() + }} + open={request !== null} + > + + + {request ? TITLE[request.mode] : 'Wall type'} + + List the layers from the outside in, around one framing core. Walls already drawn + keep their layers; walls drawn from now on use the saved type. + + + + + +
+
+ Layer + Material + Thickness + +
+
+ Outside +
+ {layers.map((layer, index) => ( +
+ {layer.role === 'core' ? ( + {ROLE_LABEL.core} + ) : ( + + )} + {layer.role === 'core' ? ( + + ) : ( + + )} + {layer.role === 'core' ? ( + + {formatLayerThickness(layer.thickness, unit)} + + ) : ( + update(layer.key, { thickness })} + unit={unit} + /> + )} +
+ + + +
+
+ ))} +
+ Inside +
+
+ Add + {ADDABLE_ROLES.map((role) => ( + + ))} + {full ? ( + + {fullReason} Remove one to add another. + + ) : null} +
+
+ +
+ + {placement === 'exterior' + ? 'Lists under Exterior: it has a cladding side.' + : 'Lists under Interior: both faces match.'} + + + Total {formatLayerThickness(total, unit)} + +
+ {failure ? ( +

{WALL_TYPE_SAVE_FAILURE[failure]}

+ ) : null} + + + + + +
+
+ ) +} diff --git a/packages/editor/src/components/ui/command-palette/editor-commands.tsx b/packages/editor/src/components/ui/command-palette/editor-commands.tsx index 4c5d7c83b0..d292091502 100644 --- a/packages/editor/src/components/ui/command-palette/editor-commands.tsx +++ b/packages/editor/src/components/ui/command-palette/editor-commands.tsx @@ -37,6 +37,7 @@ import { } from 'lucide-react' import { useEffect } from 'react' import { addStoreyOnTop } from '../../../lib/add-storey' +import { offerExteriorWallCopy } from '../level-wall-copy-prompt' import { getHistoryCommandState, runRedo, runUndo } from '../../../lib/history' import { deleteLevelWithFallbackSelection } from '../../../lib/level-selection' import { getSavedSceneDocument } from '../../../lib/scene' @@ -205,6 +206,7 @@ export function EditorCommands() { if (!building) return const newLevel = addStoreyOnTop(building.id) useViewer.getState().setSelection({ levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) }), }, { diff --git a/packages/editor/src/components/ui/controls/finish-swatch.tsx b/packages/editor/src/components/ui/controls/finish-swatch.tsx index 18f4e8f694..e03ff33d85 100644 --- a/packages/editor/src/components/ui/controls/finish-swatch.tsx +++ b/packages/editor/src/components/ui/controls/finish-swatch.tsx @@ -4,6 +4,7 @@ import { type AnyNode, getCatalogMaterialById, getLibraryMaterialIdFromRef, + getMaterialSwatch, getSceneMaterialIdFromRef, nodeRegistry, type PascalI18n, @@ -11,10 +12,11 @@ import { useScene, } from '@pascal-app/core' import { useState } from 'react' +import { materialSwatchStyle } from '../../../lib/material-swatch-style' import { Popover, PopoverContent, PopoverTrigger } from '../primitives/popover' import { MaterialPicker } from './material-picker' -type Swatch = { label: string; color: string | null; image: string | null } +type Swatch = { label: string; color: string | null; image: string | null; tinted?: boolean } /** How a finish reference reads in a panel: its catalog tile, its colour, or its scene material. */ function useFinishSwatch(ref: string | undefined, fallback: string): Swatch { @@ -33,15 +35,19 @@ function describe( i18n: PascalI18n, ) { const library = getCatalogMaterialById(getLibraryMaterialIdFromRef(ref) ?? undefined) - if (library) + if (library) { + const swatch = getMaterialSwatch(library) return { label: library.label, - color: library.previewColor ?? null, - image: library.previewThumbnailUrl ?? null, + color: swatch.color, + image: swatch.imageUrl, + tinted: swatch.tinted, } + } const sceneId = getSceneMaterialIdFromRef(ref) const scene = sceneId ? materials[sceneId as keyof typeof materials] : undefined - if (scene) return { label: scene.name, color: scene.material.properties?.color ?? null, image: null } + if (scene) + return { label: scene.name, color: scene.material.properties?.color ?? null, image: null } if (ref.startsWith('#')) return { label: ref.toUpperCase(), color: ref, image: null } return { label: defaultLabel(i18n), color: null, image: null } } @@ -101,17 +107,22 @@ export function FinishSwatch({ - {swatch.image && } - + style={materialSwatchStyle({ + color: swatch.color ?? 'transparent', + imageUrl: swatch.image, + tinted: swatch.tinted, + })} + /> {swatch.label} { - const live = useScene.getState().nodes[node.id as keyof ReturnType['nodes']] + const live = + useScene.getState().nodes[ + node.id as keyof ReturnType['nodes'] + ] if (live) paintSurface(live, role, ref) setOpen(false) }} diff --git a/packages/editor/src/components/ui/controls/material-picker.tsx b/packages/editor/src/components/ui/controls/material-picker.tsx index 0c6dc0be10..b36117ff94 100644 --- a/packages/editor/src/components/ui/controls/material-picker.tsx +++ b/packages/editor/src/components/ui/controls/material-picker.tsx @@ -1,8 +1,9 @@ 'use client' -import { type MaterialTarget, toLibraryMaterialRef } from '@pascal-app/core' +import { getMaterialSwatch, type MaterialTarget, toLibraryMaterialRef } from '@pascal-app/core' import { Plus } from 'lucide-react' -import { useMaterialCatalogModel, type MaterialSourceFilter } from '../../../lib/material-catalog-model' +import { useMaterialCatalogModel } from '../../../lib/material-catalog-model' +import { materialSwatchStyle } from '../../../lib/material-swatch-style' import { triggerSFX } from '../../../lib/sfx-bus' export type { MaterialSourceFilter } from '../../../lib/material-catalog-model' @@ -35,7 +36,21 @@ export function MaterialPicker({ onCreateMaterialRequest, selectOnCategoryChange = true, }: MaterialPickerProps) { - const { selectedCategory, setSelectedCategory, sourceFilter, setSourceFilter, visibleSourceFilters, availableCategories, catalogItems, select: handleCatalogSelect } = useMaterialCatalogModel(selectedMaterialPreset, onSelectMaterialPreset, disabled, selectOnCategoryChange) + const { + selectedCategory, + setSelectedCategory, + sourceFilter, + setSourceFilter, + visibleSourceFilters, + availableCategories, + catalogItems, + select: handleCatalogSelect, + } = useMaterialCatalogModel( + selectedMaterialPreset, + onSelectMaterialPreset, + disabled, + selectOnCategoryChange, + ) return (
triggerSFX('sfx:menu-hover')} type="button" > -
- {item.previewThumbnailUrl ? ( - {item.label} - ) : ( -
- )} -
+
{item.label} diff --git a/packages/editor/src/components/ui/floating-level-selector.tsx b/packages/editor/src/components/ui/floating-level-selector.tsx index 46a45d2425..9eea0e689c 100644 --- a/packages/editor/src/components/ui/floating-level-selector.tsx +++ b/packages/editor/src/components/ui/floating-level-selector.tsx @@ -23,6 +23,9 @@ import { type AnyNodeId, type BuildingNode, DEFAULT_LEVEL_HEIGHT, + type FoundationBadge, + foundationBadge, + type GroundSeatRule, getStoredLevelHeight, groundSeat, LevelNode, @@ -56,6 +59,7 @@ import { } from '@pascal-app/core/building' import { formatNumber, + type MessageDescriptor, roofLevelOrderBreach, servesAsRoofLevel, useLocale, @@ -64,14 +68,17 @@ import { import { authoredLevelName, localizedLevelName } from '../../i18n/level-name' import { messageWithSlot } from '../../i18n/message-slot' import { addStoreyBelow, addStoreyOnTop, setGroundFloor } from '../../lib/add-storey' +import { groundSeatAction } from '../../lib/building-foundation' import { deleteLevelWithFallbackSelection } from '../../lib/level-selection' import { unitMemberLevels, leaveUnitFocus } from '../../lib/units' +import { enterFoundationView } from '../../lib/foundation-view' import { useLinearDisplay } from '../../lib/use-linear-display' import { cn } from '../../lib/utils' import { useIsMobile } from '../../hooks/use-mobile' import { ActionButton } from './controls/action-button' import { SliderControl } from './controls/slider-control' import { LevelDuplicateDialog } from './level-duplicate-dialog' +import { LevelWallCopyPrompt, offerExteriorWallCopy } from './level-wall-copy-prompt' import { Dialog, DialogContent, @@ -473,13 +480,16 @@ type GroundLineState = { seated: boolean atFoundationTop: boolean offset: number + rule: GroundSeatRule + askedIn: number } /** * "Ground · +0.20": where the stack meets grade, read as the ground floor's datum over the ground * (the top of the foundation, 8 in over it once seated). The levels under it are below grade. When * the datum stands outside the seat (a stack that lifted it, or sank it) the line turns amber and - * a click seats the building. + * a click seats the building, by its foundation's own rule (`groundSeat`: a slab's or a raised + * floor's top, a basement's walls' top, over the high side of the ground). */ function GroundLine({ ground, onSeat }: { ground: GroundLineState; onSeat: () => void }) { const i18n = usePascalI18n() @@ -491,6 +501,8 @@ function GroundLine({ ground, onSeat }: { ground: GroundLineState; onSeat: () => values: { offset: `${over > 0 ? '+' : ''}${over.toFixed(precision)}` }, }) const distance = `${Math.abs(over).toFixed(precision)} ${displayUnit}` + // What the click does, by the foundation's own seat rule. + const action = groundSeatAction(ground.rule, ground.askedIn) return (
ground.offset > 0 ? i18n._({ id: 'editor.floatingLevelSelector.groundFloorOverGrade', - message: - 'The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade', - values: { distance }, + message: 'The ground floor stands {distance} over grade. {action}', + values: { distance, action }, }) : i18n._({ id: 'editor.floatingLevelSelector.groundFloorUnderGrade', - message: - 'The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade', - values: { distance }, + message: 'The ground floor stands {distance} under grade. {action}', + values: { distance, action }, }) } type="button" @@ -537,6 +547,95 @@ function GroundLine({ ground, onSeat }: { ground: GroundLineState; onSeat: () => ) } +// ── The foundation, under the stack: a view of the building's, never a level ─ + +/** The badge's kind as the row shows it; `data-foundation-badge` keeps core's value. */ +const FOUNDATION_BADGE_LABELS: Record = { + Slab: /* i18n */ { id: 'editor.floatingLevelSelector.foundationBadgeSlab', message: 'Slab' }, + Raised: /* i18n */ { id: 'editor.floatingLevelSelector.foundationBadgeRaised', message: 'Raised' }, + Piles: /* i18n */ { id: 'editor.floatingLevelSelector.foundationBadgePiles', message: 'Piles' }, + Basement: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgeBasement', + message: 'Basement', + }, + Off: /* i18n */ { id: 'editor.floatingLevelSelector.foundationBadgeOff', message: 'Off' }, +} + +const FOUNDATION_BADGE_TITLES: Record = { + Slab: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgeSlabTitle', + message: 'Slab on grade', + }, + Raised: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgeRaisedTitle', + message: 'Raised floor on a stem wall', + }, + Piles: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgePilesTitle', + message: 'Grade beam on piles', + }, + Basement: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgeBasementTitle', + message: 'Basement walls cut into the ground', + }, + Off: /* i18n */ { + id: 'editor.floatingLevelSelector.foundationBadgeOffTitle', + message: 'No foundation drawn: click to set one up', + }, +} + +/** + * "Foundation" under the lowest level and the ground line: the building's foundation, its kind on + * the badge. A click shows its plan, the house hidden over it in 3D, and its settings in the panel. + * Not a level: never dragged, nothing inserted beside it. + */ +export function FoundationRow({ + badge, + isSelected, + onSelect, +}: { + badge: FoundationBadge + isSelected: boolean + onSelect: () => void +}) { + const i18n = usePascalI18n() + return ( + + ) +} + // ── Unit focus chip: name + × to end focus, only while a unit is focused ──── function UnitFocusChip() { @@ -582,6 +681,8 @@ export function FloatingLevelSelector() { const selectedBuildingId = useViewer((s) => s.selection.buildingId) const levelId = useViewer((s) => s.selection.levelId) const setSelection = useViewer((s) => s.setSelection) + const foundationView = useViewer((s) => s.foundationView) + const setFoundationView = useViewer((s) => s.setFoundationView) const createNode = useScene((s) => s.createNode) const applyNodeChanges = useScene((s) => s.applyNodeChanges) const updateNodes = useScene((s) => s.updateNodes) @@ -637,6 +738,8 @@ export function FloatingLevelSelector() { seated: seat.seated, atFoundationTop: seat.atFoundationTop, offset: seat.offset, + rule: seat.rule, + askedIn: seat.askedIn, } : null }), @@ -646,6 +749,14 @@ export function FloatingLevelSelector() { setGroundFloor(levelId as LevelNode['id']) }, []) + const badge = useScene((state) => + resolvedBuildingId ? foundationBadge(state.nodes, resolvedBuildingId) : 'Off', + ) + + const handleSelectFoundation = useCallback(() => { + if (resolvedBuildingId) enterFoundationView(resolvedBuildingId) + }, [resolvedBuildingId]) + const topIsRoofLevel = useScene((state) => { const top = levels.at(-1) return top ? servesAsRoofLevel(state.nodes, top) : false @@ -655,12 +766,14 @@ export function FloatingLevelSelector() { if (!resolvedBuildingId) return const newLevel = addStoreyOnTop(resolvedBuildingId) setSelection({ buildingId: resolvedBuildingId, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) }, [resolvedBuildingId, setSelection]) const handleAddBelow = useCallback(() => { if (!resolvedBuildingId) return const newLevel = addStoreyBelow(resolvedBuildingId) setSelection({ buildingId: resolvedBuildingId, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) }, [resolvedBuildingId, setSelection]) const handleInsertBetween = useCallback( @@ -684,6 +797,7 @@ export function FloatingLevelSelector() { .map((l) => ({ id: l.id as AnyNodeId, data: { level: l.level + 1 } as Partial })), }) setSelection({ buildingId: resolvedBuildingId, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id, lower.id) }, [resolvedBuildingId, levels, applyNodeChanges, setSelection], ) @@ -794,18 +908,6 @@ export function FloatingLevelSelector() { )} - {/* Floating + at bottom edge */} - {!draggingLevelId && ( - - )} - {/* Level list */}
{reversedLevels.map((level, i) => { - const isSelected = level.id === levelId + const isSelected = level.id === levelId && !foundationView const sortedIndex = levels.indexOf(level) const showGapBelow = i < reversedLevels.length - 1 @@ -845,7 +947,8 @@ export function FloatingLevelSelector() { onRequestDelete={() => setDeletingLevel(level)} onSetGround={() => handleSetGround(level.id)} onSelect={() => { - if (!isSelected) markPerfAction('level-switch', level.id) + if (level.id !== levelId) markPerfAction('level-switch', level.id) + setFoundationView(false) setSelection( resolvedBuildingId ? { buildingId: resolvedBuildingId, levelId: level.id } @@ -854,11 +957,27 @@ export function FloatingLevelSelector() { }} /> - {showGapBelow && !draggingLevelId && ( + {!draggingLevelId && ( + // Between two levels, a level inserted there; under the lowest, one added + // below it (over the foundation, which is no level).
+
{/* Delete confirmation dialog */} diff --git a/packages/editor/src/components/ui/helpers/helper-manager.test.tsx b/packages/editor/src/components/ui/helpers/helper-manager.test.tsx index 2c678b3581..4fb362c697 100644 --- a/packages/editor/src/components/ui/helpers/helper-manager.test.tsx +++ b/packages/editor/src/components/ui/helpers/helper-manager.test.tsx @@ -5,7 +5,12 @@ import { snapContextOf } from '../../../lib/snapping-mode' import { cornerHintRows } from '../../../store/use-corner-snap-hint' import { TooltipProvider } from '../primitives/tooltip' import { ContextualHelperPanel } from './contextual-helper-panel' -import { mezzanineHints, openingHints, ROOM_DIVIDE_HINTS } from './helper-manager' +import { + mezzanineHints, + openingHints, + ROOM_DIVIDE_HINTS, + toolHintsOwnPlacing, +} from './helper-manager' describe('Divide HUD', () => { test('uses the wall draft snapping context', () => { @@ -133,3 +138,34 @@ describe('Cut opening HUD', () => { expect(english(mezzanineHints('rectangle'))).toContain('Draw the mezzanine') }) }) + +// The roof tool holds its draft in a placing scope, so the HUD read "Move roof · Left click: Place" +// while a click set a footprint corner; the tool's own hints say what the click does. +describe('a registry tool holding its draft', () => { + const placing = (driver: 'registry-tool' | 'move-tool', nodeType = 'roof') => ({ + kind: 'placing' as const, + node: { id: 'roof_draft', type: nodeType } as never, + nodeId: 'roof_draft', + nodeType, + view: '3d' as const, + pressDrag: false, + driver, + }) + + test("shows the armed tool's hints", () => { + expect(toolHintsOwnPlacing(placing('registry-tool'), 'roof', 3)).toBe(true) + }) + + test('leaves a move, a catalog placement or a tool with no hints to the move rows', () => { + expect(toolHintsOwnPlacing(placing('move-tool'), 'roof', 3)).toBe(false) + expect(toolHintsOwnPlacing(placing('registry-tool', 'item'), 'roof', 3)).toBe(false) + expect(toolHintsOwnPlacing(placing('registry-tool'), 'roof', 0)).toBe(false) + expect( + toolHintsOwnPlacing( + { kind: 'moving', node: { id: 'r', type: 'roof' } as never, nodeId: 'r', nodeType: 'roof', view: '3d' }, + 'roof', + 3, + ), + ).toBe(false) + }) +}) diff --git a/packages/editor/src/components/ui/helpers/helper-manager.tsx b/packages/editor/src/components/ui/helpers/helper-manager.tsx index 663ba7dd0d..60457a82da 100644 --- a/packages/editor/src/components/ui/helpers/helper-manager.tsx +++ b/packages/editor/src/components/ui/helpers/helper-manager.tsx @@ -42,7 +42,7 @@ import { terrainHudTitle, toolHudTitle, } from '../../../lib/hud-title' -import type { ReshapeKind } from '../../../lib/interaction/scope' +import type { InteractionScope, ReshapeKind } from '../../../lib/interaction/scope' import { type MezzanineShape, useMezzanineDraft, @@ -294,6 +294,25 @@ export const MEZZANINE_GESTURE_HINTS: Record 0 + ) +} + export function HelperManager() { const i18n = usePascalI18n() const mode = useEditor((s) => s.mode) @@ -546,7 +565,10 @@ export function HelperManager() { ) } - if (movingNode) { + if ( + movingNode && + !toolHintsOwnPlacing(scope, tool, tool ? (nodeRegistry.get(tool)?.toolHints?.length ?? 0) : 0) + ) { // A fresh placement (e.g. a positioned preset like a shelf) advertises its // once/repeat continuation, exactly like the GLB item tool — but an existing // node being *moved* is not a placement, so it gets no continuation chip. diff --git a/packages/editor/src/components/ui/level-wall-copy-prompt.tsx b/packages/editor/src/components/ui/level-wall-copy-prompt.tsx new file mode 100644 index 0000000000..0813f0b3f8 --- /dev/null +++ b/packages/editor/src/components/ui/level-wall-copy-prompt.tsx @@ -0,0 +1,128 @@ +'use client' + +import { + type AnyNode, + type AnyNodeId, + getLevelAbove, + getLevelBelow, + getLevelDisplayName, + useScene, +} from '@pascal-app/core' +import { buildExteriorWallCopy, levelExteriorWalls } from '@pascal-app/core/building' +import { useViewer } from '@pascal-app/viewer' +import { create } from 'zustand' +import { getDrawnWallTypes } from '../../lib/wall-draw-variant' + +type Offer = { levelId: string; sourceLevelId: string; direction: 'below' | 'above' } + +/** + * The one open "Copy exterior walls?" offer, for the level just added. While it is open the plan + * traces the source level's walls on the new one; that trace is the offer's, never the saved + * Walls below / above preference, and it goes with the offer. + */ +export const useLevelWallCopyOffer = create<{ + offer: Offer | null + setOffer: (offer: Offer | null) => void +}>((set) => ({ offer: null, setOffer: (offer) => set({ offer }) })) + +let stopWatchingLevel: (() => void) | null = null + +/** Opens `offer` (or closes the open one); picking another level closes it. */ +function setOffer(offer: Offer | null) { + stopWatchingLevel?.() + stopWatchingLevel = null + useLevelWallCopyOffer.getState().setOffer(offer) + if (!offer) return + stopWatchingLevel = useViewer.subscribe((state) => { + if (state.selection.levelId !== offer.levelId) setOffer(null) + }) +} + +/** + * After a level is added next to `sourceLevelId` (by default the level under it, else the one over + * it, for a basement), and selected: when that level has an outline, offer to copy its exterior + * walls up or down, tracing them dashed on the plan while the offer is open. + */ +export function offerExteriorWallCopy(levelId: string, sourceLevelId?: string) { + const nodes = useScene.getState().nodes as Record + const graph = nodes as Record + sourceLevelId ??= (getLevelBelow(levelId, graph) ?? getLevelAbove(levelId, graph))?.id + const source = sourceLevelId ? nodes[sourceLevelId] : undefined + const level = nodes[levelId] + if (!sourceLevelId || source?.type !== 'level' || level?.type !== 'level') return setOffer(null) + const direction = source.level < level.level ? 'below' : 'above' + setOffer( + levelExteriorWalls(nodes, sourceLevelId).length ? { levelId, sourceLevelId, direction } : null, + ) +} + +/** Closes the open offer without copying. */ +export function dismissExteriorWallCopy() { + setOffer(null) +} + +/** Copies the offered walls in place, as one undo step of its own. */ +export function acceptExteriorWallCopy(offer: Offer): number { + const walls = buildExteriorWallCopy({ + nodes: useScene.getState().nodes as Record, + fromLevelId: offer.sourceLevelId, + toLevelId: offer.levelId, + // Foundation walls copied above grade are built as the Build panel's exterior walls. + exteriorConstruction: getDrawnWallTypes().exterior, + }) + if (walls.length) useScene.getState().applyNodeChanges({ create: walls }) + setOffer(null) + return walls.length +} + +/** + * "Copy exterior walls from the level below?" — a small card under the level list, never in the + * way: it goes when answered or when another level is picked, and hides once the level has walls. + */ +export function LevelWallCopyPrompt() { + const offer = useLevelWallCopyOffer((state) => state.offer) + const activeLevelId = useViewer((state) => state.selection.levelId) + const sourceName = useScene((state) => { + const source = offer ? state.nodes[offer.sourceLevelId as never] : undefined + return source?.type === 'level' ? getLevelDisplayName(source) : null + }) + const levelHasWalls = useScene((state) => { + const level = offer ? state.nodes[offer.levelId as AnyNodeId] : undefined + return ( + level?.type === 'level' && + level.children.some((id) => state.nodes[id as AnyNodeId]?.type === 'wall') + ) + }) + if (!offer || !sourceName || activeLevelId !== offer.levelId || levelHasWalls) return null + return ( +
+

+ Copy exterior walls from the level {offer.direction}? +

+

+ {sourceName}’s outside walls, in place, so this level lines up with it. +

+
+ + +
+
+ ) +} diff --git a/packages/editor/src/components/ui/panels/building-foundation-section.tsx b/packages/editor/src/components/ui/panels/building-foundation-section.tsx new file mode 100644 index 0000000000..cdb911ea0d --- /dev/null +++ b/packages/editor/src/components/ui/panels/building-foundation-section.tsx @@ -0,0 +1,1485 @@ +'use client' + +import { + BUILDING_FOUNDATION_LIMITS, + type BasementReport, + type BuildingFoundation, + type BuildingFoundationDimension, + type BuildingFoundationRecord, + type BuildingNode, + buildingFoundationOf, + DEFAULT_RAISED_FLOOR_BUILD_UP, + type DesignedFoundationReport, + DRYWALL_THICKNESSES, + designedFoundationReport, + FLOOR_BUILD_UP_PRESETS, + FLOORING_THICKNESS, + FOUNDATION_SILL_IN, + FRAMING_SPACINGS, + type FloorBuildUp, + type FloorBuildUpPreset, + type FloorStructure, + type FlooringKind, + floorBuildUpThickness, + floorStructureDepth, + I_JOIST_DEPTH, + type IJoistDepth, + type InteriorFinishKind, + LUMBER_DEPTH, + type NominalLumber, + type PanelKind, + PLASTER_THICKNESS, + SLAB_EDGE_TAPER_IN, + SUBFLOOR_THICKNESSES, + slabEdgeTaper, + useScene, + WOOD_PLANK_THICKNESS, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { Check } from 'lucide-react' +import { useRef, useState } from 'react' +import { + applyBuildingFoundation, + foundationAdvice, + standsOnBasement, +} from '../../../lib/building-foundation' +import { + editedFloorBuildUp, + type FloorLayerKey, + floorStructureAs, + renameFloorLayer, + switchFloorLayer, +} from '../../../lib/floor-footprints' +import { formatLayerDepth, formatLayerThickness } from '../../../lib/layer-thickness' +import { formatLinearMeasurement, type LinearUnit } from '../../../lib/measurements' +import { cn } from '../../../lib/utils' +import { MetricControl } from '../controls/metric-control' +import { SegmentedControl } from '../controls/segmented-control' +import { SelectControl } from '../controls/select-control' +import { ToggleControl } from '../controls/toggle-control' +import { Button } from '../primitives/button' + +const IN = 0.0254 + +type Field = { key: BuildingFoundationDimension | 'edgeTaperIn'; label: string; hint?: string } + +/** + * A slab edge's 45° taper as core pours it, metres: as the record sets it, else the rule's for the + * edge's depth under the slab (its bottom under the pad, which is held the slab's top over grade). + */ +export function slabEdgeTaperOf(foundation: BuildingFoundation): number { + const depth = + (foundation.topAboveGradeIn + foundation.edgeDepthIn - foundation.slabThicknessIn) * IN + return slabEdgeTaper( + Math.max(0, depth), + foundation.edgeTaperIn === null ? null : foundation.edgeTaperIn * IN, + ) +} + +/** The numbers each kind of foundation is sized by, in the order it is built, top down. */ +function fieldsFor(foundation: BuildingFoundation): Field[] { + const piles = foundation.support === 'piles' + const raised = foundation.type === 'raised' + const floor: Field = { + key: 'ffAboveGradeIn', + label: 'Finish floor over grade', + hint: 'Measured over the highest grade under the outside walls. The foundation grows down the slope from there.', + } + const top: Field = { + key: 'topAboveGradeIn', + label: raised ? 'Stem top over grade' : 'Slab top over grade', + hint: raised + ? 'IRC R317.1 keeps wood framing 8 in off the ground.' + : 'The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in).', + } + // Where the floor's layers go (`RaisedFloorFraming`): the depth is theirs and the sill's. + const framing: Field = { + key: 'floorFramingDepthIn', + label: 'Floor framing', + hint: 'Floor top down to the stem: flooring, subfloor, joists, sill.', + } + // A slab's grade is held at its edge all round (fill on the low side), so only a raised floor steps. + const steps: Field[] = + raised && foundation.stepped + ? [ + ...(piles + ? [] + : [ + { + key: 'maxStemIn', + label: 'Tallest stem before stepping', + hint: 'A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it.', + } as Field, + ]), + { key: 'stepHeightIn', label: 'Step height' }, + { + key: 'minStepRunIn', + label: 'Least step run', + hint: 'IRC R403.1.5: the top of a footing is level; it steps where the ground falls.', + }, + ] + : [] + const crawl: Field = { + key: 'crawlClearanceIn', + label: 'Crawl space under joists', + hint: 'Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it.', + } + if (piles) + return [ + floor, + top, + ...(raised ? [framing, crawl] : [{ key: 'slabThicknessIn', label: 'Slab' } as Field]), + { key: 'gradeBeamWidthIn', label: 'Grade beam width' }, + { key: 'gradeBeamDepthIn', label: 'Grade beam depth' }, + { + key: 'footingDepthIn', + label: 'Least depth under grade', + hint: 'The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further.', + }, + { key: 'pileDiameterIn', label: 'Pile diameter', hint: 'Piles are sized by the engineer.' }, + { key: 'pileSpacingIn', label: 'Pile spacing' }, + { key: 'pileLengthIn', label: 'Pile length' }, + ...steps, + ] + if (raised) + return [ + floor, + top, + framing, + crawl, + { key: 'stemThicknessIn', label: 'Stem wall', hint: 'Table R404.1.2: 6 in for one storey, 8 in for more.' }, + { + key: 'footingWidthIn', + label: 'Footing width', + hint: 'Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys.', + }, + { key: 'footingThicknessIn', label: 'Footing thickness' }, + { + key: 'footingDepthIn', + label: 'Footing under grade', + hint: 'IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line.', + }, + ...steps, + ] + return [ + floor, + top, + { key: 'slabThicknessIn', label: 'Slab', hint: 'IRC R506.1 asks 3½ in at least.' }, + { key: 'edgeDepthIn', label: 'Edge under grade', hint: 'IRC R403.1.4: at least 12 in, or the frost line.' }, + { key: 'edgeWidthIn', label: 'Edge width' }, + { + key: 'edgeTaperIn', + label: 'Taper', + hint: 'The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider.', + }, + ...steps, + ] +} + +/** + * The numbers a house over a basement is sized by: how far its walls' top stands over grade, and + * the walls, footing and slab it pours (none where the basement's walls are its own: they stand on + * their own footing, its floor its own plates). + */ +function basementFieldsFor(ownWalls: boolean): Field[] { + const top: Field = { + key: 'basementTopAboveGradeIn', + label: 'Basement wall top over grade', + hint: 'Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement.', + } + if (ownWalls) return [top] + return [ + top, + { + key: 'basementWallThicknessIn', + label: 'Basement wall', + hint: 'Poured concrete: 8 in for most basements (IRC Table R404.1.2(1)).', + }, + { + key: 'footingWidthIn', + label: 'Footing width', + hint: 'Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys.', + }, + { key: 'footingThicknessIn', label: 'Footing thickness' }, + { key: 'slabThicknessIn', label: 'Basement slab', hint: 'IRC R506.1 asks 3½ in at least.' }, + ] +} + +/** One number of the record, in the panel's length unit; a scrub previews in the field, a release writes it. */ +function FoundationNumber({ + field, + value, + min, + onCommit, +}: { + field: Field + value: number + min: number + onCommit: (value: number) => void +}) { + const [draft, setDraft] = useState(null) + // Foundation sizes read in inches on an imperial plan (feet would show 8 in as 0.67). + const imperial = useViewer((state) => state.unit) === 'imperial' + const [low, high] = + field.key === 'edgeTaperIn' ? SLAB_EDGE_TAPER_IN : BUILDING_FOUNDATION_LIMITS[field.key] + const toShown = (inches: number) => (imperial ? inches : inches * IN) + const fromShown = (shown: number) => (imperial ? shown : shown / IN) + return ( +
+ setDraft(next)} + onCommit={(next) => { + setDraft(null) + onCommit(Math.round(fromShown(next) * 100) / 100) + }} + precision={imperial ? 2 : 3} + restoreOnCommit={false} + step={imperial ? 0.5 : 0.5 * IN} + unit={imperial ? 'in' : 'm'} + value={draft ?? toShown(value)} + /> +
+ ) +} + +/** + * The slab edge's taper: as the record sets it, or auto, the rule's for the edge's depth + * (`slabEdgeTaperOf`). A number typed sets it; Auto hands it back to the rule. + */ +function EdgeTaperNumber({ + field, + foundation, + onCommit, +}: { + field: Field + foundation: BuildingFoundation + onCommit: (edgeTaperIn: number | undefined) => void +}) { + const auto = foundation.edgeTaperIn === null + return ( +
+ + {!auto && ( + + )} +
+ ) +} + +/** A spacing on centre, as the roof's framing reads: `16" o.c.` or `406 mm o.c.`. */ +function formatSpacing(metres: number, unit: LinearUnit): string { + if (unit === 'imperial') return `${formatLayerThickness(metres, unit)} o.c.` + return `${Math.round(metres * 1000)} mm o.c.` +} + +const FLOORING_OPTIONS: { label: string; value: FlooringKind }[] = [ + { label: 'Hardwood', value: 'hardwood' }, + { label: 'Engineered wood', value: 'engineered-wood' }, + { label: 'Laminate', value: 'laminate' }, + { label: 'Vinyl plank', value: 'vinyl-plank' }, + { label: 'Ceramic tile', value: 'ceramic-tile' }, + { label: 'Carpet', value: 'carpet' }, +] + +const PANEL_OPTIONS: { label: string; value: PanelKind }[] = [ + { label: 'Plywood', value: 'plywood' }, + { label: 'OSB', value: 'osb' }, +] + +const CEILING_OPTIONS: { label: string; value: InteriorFinishKind }[] = [ + { label: 'Drywall', value: 'drywall' }, + { label: 'Plaster', value: 'plaster' }, + { label: 'Wood plank', value: 'wood-plank' }, +] + +const CEILING_THICKNESS: Record = { + drywall: DRYWALL_THICKNESSES[0]!, + plaster: PLASTER_THICKNESS, + 'wood-plank': WOOD_PLANK_THICKNESS, +} + +const labelOf = (options: { label: string; value: T }[], value: T) => + options.find((option) => option.value === value)?.label ?? value + +/** A floor's joist sizes: lumber by its nominal size and dressed depth, I-joists by their depth. */ +function structureSizeOptions(system: FloorStructure['system'], unit: LinearUnit) { + return system === 'joists' + ? (Object.keys(LUMBER_DEPTH) as NominalLumber[]).map((size) => ({ + label: `${size} (${formatLayerThickness(LUMBER_DEPTH[size], unit)})`, + value: size as string, + })) + : (Object.keys(I_JOIST_DEPTH) as IJoistDepth[]).map((size) => ({ + label: `${formatLayerThickness(I_JOIST_DEPTH[size], unit)} I-joist`, + value: size as string, + })) +} + +/** + * One layer of a floor as a section draws it, top down: what it reads as, its thickness in metres, + * and that thickness written out. + */ +export type SectionLayer = { key: string; name: string; thickness: number; size: string } + +/** + * A framed floor's layers as its section draws them, top down, each named as the person named its + * material, else by its kind; `sill` adds the sill a raised floor's joists bear on. + */ +export function floorSectionLayers( + buildUp: FloorBuildUp, + unit: LinearUnit, + sill = false, +): SectionLayer[] { + const layer = (key: string, name: string, thickness: number): SectionLayer => ({ + key, + name, + thickness, + size: formatLayerThickness(thickness, unit), + }) + const { flooring, sheathing, structure, ceiling } = buildUp + return [ + ...(flooring + ? [layer('flooring', flooring.name ?? labelOf(FLOORING_OPTIONS, flooring.kind), flooring.thickness)] + : []), + ...(sheathing + ? [ + layer( + 'sheathing', + sheathing.name ?? `${labelOf(PANEL_OPTIONS, sheathing.kind)} subfloor`, + sheathing.thickness, + ), + ] + : []), + layer( + 'joists', + structure.system === 'joists' ? `${structure.size} joists` : 'I-joists', + floorStructureDepth(structure), + ), + ...(ceiling + ? [ + layer( + 'ceiling', + ceiling.name ?? `${labelOf(CEILING_OPTIONS, ceiling.kind)} ceiling`, + ceiling.thickness, + ), + ] + : []), + ...(sill ? [layer('sill', 'Sill', FOUNDATION_SILL_IN * IN)] : []), + ] +} + +/** The layers' fills, alike in the floor plate's section and the foundation's: wood tones, a pale ceiling board, a treated sill. */ +export const SECTION_LAYER_FILL: Record = { + flooring: 'fill-[#8b5e34]', + sheathing: 'fill-[#dcc08f]', + joists: 'fill-[#c9a46b]', + framing: 'fill-[#c9a46b]', + ceiling: 'fill-[#ececec]', + sill: 'fill-[#a3b18a]', +} + +/** A name as a section's callout shows it: a long one cut short so it stays in the drawing. */ +export const calloutName = (name: string) => (name.length > 22 ? `${name.slice(0, 21)}…` : name) + +/** + * Heights for a section's callouts: each as near its own height as it can be with `gap` between + * neighbours (a run of thin layers fans out round its middle), within `low`..`high`. In the order + * given. + */ +export function stackLabels( + wanted: readonly number[], + gap: number, + low: number, + high: number, +): number[] { + const order = wanted.map((y, index) => ({ y, index })).sort((a, b) => a.y - b.y) + const runs: { top: number; ys: number[] }[] = [] + for (const { y } of order) { + let run = { top: y, ys: [y] } + // A label too close under the run above joins it, the run centred on the heights it holds. + while (runs.length > 0) { + const above = runs[runs.length - 1]! + if (above.top + above.ys.length * gap <= run.top + 1e-9) break + runs.pop() + const ys = [...above.ys, ...run.ys] + const middle = ys.reduce((sum, value) => sum + value, 0) / ys.length + run = { top: middle - ((ys.length - 1) * gap) / 2, ys } + } + runs.push(run) + } + const placed = runs.flatMap((run) => run.ys.map((_, k) => run.top + k * gap)) + const last = placed.length - 1 + for (let k = last; k >= 0; k--) placed[k] = Math.min(placed[k]!, high - (last - k) * gap) + for (let k = 0; k <= last; k++) placed[k] = Math.max(placed[k]!, low + k * gap) + const result = new Array(wanted.length) + order.forEach(({ index }, k) => { + result[index] = placed[k]! + }) + return result +} + +/** + * A section through the foundation where the ground is highest (the side that sets the floor), + * drawn to scale: the grade, the wall on it, and the slab with its turned-down edge, or the stem + * wall on its footing under the floor framing, or the grade beam on a pile. A raised floor's + * framing is drawn layer by layer down to its sill, the crawl space dug out under the joists, and + * the footing where core lays it: as deep under the crawl space's dirt as under the grade outside. + */ +export function FoundationSectionDrawing({ + foundation, + length, + framing, +}: { + foundation: BuildingFoundation + length: (meters: number) => string + /** A raised floor's framing, top down to its sill (`floorSectionLayers`); absent, it is one band. */ + framing?: readonly SectionLayer[] +}) { + const raised = foundation.type === 'raised' + const piles = foundation.support === 'piles' + const m = (inches: number) => inches * IN + // Metres, the grade at 0, up positive; the outside face at x 0, the building to the right. + const floor = m(foundation.ffAboveGradeIn) + const top = raised ? floor - m(foundation.floorFramingDepthIn) : floor + // The joists bear on the sill on the stem's top. The crawl space under them is dug out to its + // clearance where that falls under the grade (where the ground already stands lower it stays). + const underside = top + m(FOUNDATION_SILL_IN) + const crawl = raised ? Math.min(0, underside - m(foundation.crawlClearanceIn)) : 0 + // A footing's bottom is its depth under the lower of the grade outside and the crawl space's dirt + // (IRC R403.1.4), as core lays it; a grade beam's is under the grade. + const bottom = piles + ? Math.min(top - m(foundation.gradeBeamDepthIn), -m(foundation.footingDepthIn)) + : raised + ? crawl - m(foundation.footingDepthIn) + : -m(foundation.edgeDepthIn) + const pileShown = piles ? Math.min(m(foundation.pileLengthIn), 0.6) : 0 + const lowest = Math.min(bottom - pileShown, crawl) + const wallHeight = 0.35 + const span = floor + wallHeight - lowest + const [width, height, pad] = [280, 170, 14] + const scale = Math.min((height - pad * 2) / span, 260) + const x0 = 70 + const sx = (x: number) => x0 + x * scale + const sy = (y: number) => pad + (floor + wallHeight - y) * scale + const right = width - 8 + // A raised floor is drawn this far into the house; its callouts stand clear past it. + const reach = 0.75 + const labelX = sx(reach) + (raised ? 10 : 6) + const wallWidth = m(5.5) + const perimeter = m(piles ? foundation.gradeBeamWidthIn : foundation.stemThicknessIn) + const footingInside = m(foundation.stemThicknessIn) / 2 + m(foundation.footingWidthIn) / 2 + const concrete = 'fill-[#9a9a9a] stroke-current' + const shapes: Array<{ key: string; points: Array<[number, number]>; className: string }> = [] + /** A callout; `from` (metres across) leads a line to it from the end of the layer it names. */ + const labels: Array<{ key: string; y: number; text: string; from?: number }> = [] + + if (piles) { + const beam = m(foundation.gradeBeamWidthIn) + const beamBottom = bottom + shapes.push({ + key: 'beam', + className: concrete, + points: [ + [0, beamBottom], + [beam, beamBottom], + [beam, top], + [0, top], + ], + }) + const pile = m(foundation.pileDiameterIn) + const px = beam / 2 - pile / 2 + shapes.push({ + key: 'pile', + className: concrete, + points: [ + [px, beamBottom - pileShown], + [px + pile, beamBottom - pileShown], + [px + pile, beamBottom], + [px, beamBottom], + ], + }) + labels.push({ key: 'beam', y: (top + beamBottom) / 2, text: `Grade beam ${length(beam)} × ${length(top - beamBottom)}` }) + labels.push({ + key: 'pile', + y: beamBottom - pileShown / 2, + text: `Pile ${length(pile)} @ ${length(m(foundation.pileSpacingIn))}, ${length(m(foundation.pileLengthIn))} deep`, + }) + } else if (raised) { + const stem = m(foundation.stemThicknessIn) + const footing = m(foundation.footingWidthIn) + const thick = m(foundation.footingThicknessIn) + const fx = stem / 2 - footing / 2 + shapes.push({ + key: 'footing', + className: concrete, + points: [ + [fx, bottom], + [fx + footing, bottom], + [fx + footing, bottom + thick], + [fx, bottom + thick], + ], + }) + shapes.push({ + key: 'stem', + className: concrete, + points: [ + [0, bottom + thick], + [stem, bottom + thick], + [stem, top], + [0, top], + ], + }) + labels.push({ key: 'stem', y: (top + Math.max(0, bottom + thick)) / 2, text: `Stem wall ${length(stem)}` }) + labels.push({ key: 'footing', y: bottom + thick / 2, text: `Footing ${length(footing)} × ${length(thick)}` }) + } else { + // The edge's inside face plumb from its square bottom, then in at 45° to the slab's underside. + const slab = m(foundation.slabThicknessIn) + const edge = m(foundation.edgeWidthIn) + const rise = floor - slab - bottom + const taper = Math.min(rise, slabEdgeTaperOf(foundation)) + shapes.push({ + key: 'slab', + className: concrete, + points: [ + [0, bottom], + [edge, bottom], + [edge, floor - slab - taper], + [edge + taper, floor - slab], + [(right - x0) / scale, floor - slab], + [(right - x0) / scale, floor], + [0, floor], + ], + }) + labels.push({ key: 'slab', y: floor - slab / 2, text: `Slab ${length(slab)}` }) + labels.push({ key: 'taper', y: floor - slab - taper / 2, text: `Taper ${length(taper)} at 45°` }) + labels.push({ key: 'edge', y: bottom + 0.04, text: `Edge ${length(edge)} wide` }) + } + // The wall stands on the subfloor; the finished floor runs inside it. + let wallBase = floor + if (raised) { + const layers = framing ?? [ + { key: 'framing', name: 'Floor framing', thickness: floor - top, size: length(floor - top) }, + ] + // Top down from the floor: the sill only under the wall, on the stem (or beam) it is bolted to. + let y = floor + for (const layer of layers) { + if (layer.thickness <= 0) continue + const from = layer.key === 'flooring' ? wallWidth : 0 + const to = layer.key === 'sill' ? Math.min(wallWidth, perimeter) : reach + if (layer.key === 'flooring') wallBase = y - layer.thickness + shapes.push({ + key: layer.key, + className: `${SECTION_LAYER_FILL[layer.key] ?? SECTION_LAYER_FILL.framing} stroke-current`, + points: [ + [from, y - layer.thickness], + [to, y - layer.thickness], + [to, y], + [from, y], + ], + }) + labels.push({ + key: layer.key, + y: y - layer.thickness / 2, + text: `${calloutName(layer.name)} ${layer.size}`, + from: to, + }) + y -= layer.thickness + } + } + shapes.push({ + key: 'wall', + className: 'fill-muted stroke-current', + points: [ + [0, wallBase], + [wallWidth, wallBase], + [wallWidth, floor + wallHeight], + [0, floor + wallHeight], + ], + }) + + type Dim = { key: string; from: number; to: number; text: string; x: number; extend?: number } + // A footing's depth is read from the lower ground beside it: in the crawl space where that is dug + // out under the grade, chained under the crawl's own height. + const inCrawl = raised && !piles && crawl < 0 + const depthTo = raised && !piles ? crawl : 0 + const depth = { key: 'depth', from: bottom, to: depthTo, text: `${length(depthTo - bottom)} down` } + const outside = [ + { key: 'floor', from: 0, to: floor, text: `${length(floor)} FF` }, + ...(raised ? [{ key: 'top', from: 0, to: top, text: `${length(top)} top` }] : []), + ...(inCrawl ? [] : [depth]), + ] + const crawlX = (sx(perimeter) + sx(reach)) / 2 + const dims: Dim[] = [ + ...outside.map((dim, index) => ({ ...dim, x: 10 + index * 18 })), + ...(raised + ? [{ key: 'crawl', from: crawl, to: underside, text: `${length(underside - crawl)} crawl`, x: crawlX }] + : []), + ...(inCrawl ? [{ ...depth, x: crawlX, extend: footingInside }] : []), + ] + const toPath = (points: Array<[number, number]>) => + points.map(([x, y]) => `${sx(x).toFixed(1)},${sy(y).toFixed(1)}`).join(' ') + const placed = stackLabels( + labels.map((label) => sy(label.y)), + 9, + 8, + height - 6, + ) + + return ( +
+ + {/* Grade: outside, and under a slab the ground under the house. */} + + {Array.from({ length: 6 }, (_, i) => ( + + ))} + {raised && ( + // The crawl space's dirt, inside the stem: dug out to its clearance under the joists. + + + {Array.from( + { length: Math.max(0, Math.floor((sx(reach) - sx(perimeter) - 4) / 9)) }, + (_, i) => ( + + ), + )} + + )} + {shapes.map((shape) => ( + + ))} + {dims.map((dim) => ( + + {dim.extend !== undefined && ( + + )} + + + + + {dim.text} + + + ))} + {labels.map((label, index) => ( + + {label.from !== undefined && ( + + )} + + {label.text} + + + ))} + +
+ At the high side of the ground, to scale +
+
+ ) +} + +/** + * A section through a house's basement wall where the ground is highest, drawn to scale: the grade + * outside meeting the wall where it lies, the poured wall from its footing up to the sill and the + * floor framing it carries `basementTopAboveGradeIn` over that grade, the slab inside at the + * basement's floor, and no ground inside the wall (dug out to the slab's underside). Its depth is + * the basement's own, read off `basement` (the house as it stands); a basement with walls of its + * own draws them as one. + */ +export function BasementSectionDrawing({ + foundation, + report, + basement, + length, +}: { + foundation: BuildingFoundation + report: DesignedFoundationReport + basement: BasementReport + length: (meters: number) => string +}) { + const m = (inches: number) => inches * IN + // Metres, the grade at 0, up positive; the wall's outside face at x 0, the basement to the right. + const top = m(foundation.basementTopAboveGradeIn) + const floor = top - (basement.wallTopY - report.floorTopY) + const framed = top + Math.max(m(FOUNDATION_SILL_IN), basement.groundFloorTopY - basement.wallTopY) + const slab = m(foundation.slabThicknessIn) + const slabBottom = floor - (basement.ownWalls ? 0 : slab) + const thick = m(foundation.footingThicknessIn) + const bottom = slabBottom - thick + const wall = m(foundation.basementWallThicknessIn) + const footing = Math.max(wall, m(foundation.footingWidthIn)) + const houseWall = 0.35 + const [width, height, pad] = [280, 220, 14] + const span = framed + houseWall - bottom + const scale = Math.min((height - pad * 2) / span, 260) + const x0 = 70 + const sx = (x: number) => x0 + x * scale + const sy = (y: number) => pad + (framed + houseWall - y) * scale + const right = width - 8 + const reach = 0.75 + const labelX = sx(reach) + 10 + const concrete = 'fill-[#9a9a9a] stroke-current' + const box = (x: number, y: number, w: number, h: number): Array<[number, number]> => [ + [x, y], + [x + w, y], + [x + w, y + h], + [x, y + h], + ] + const fx = wall / 2 - footing / 2 + const shapes = [ + { key: 'footing', className: concrete, points: box(fx, bottom, footing, thick) }, + { key: 'basement-wall', className: concrete, points: box(0, slabBottom, wall, top - slabBottom) }, + ...(basement.ownWalls + ? [] + : [{ key: 'slab', className: concrete, points: box(wall, slabBottom, reach - wall, slab) }]), + { + key: 'sill', + className: `${SECTION_LAYER_FILL.sill} stroke-current`, + points: box(0, top, wall, m(FOUNDATION_SILL_IN)), + }, + { + key: 'framing', + className: `${SECTION_LAYER_FILL.framing} stroke-current`, + points: box(0, top + m(FOUNDATION_SILL_IN), reach, framed - top - m(FOUNDATION_SILL_IN)), + }, + { key: 'wall', className: 'fill-muted stroke-current', points: box(0, framed, m(5.5), houseWall) }, + ] + const labels = [ + { key: 'framing', y: (top + framed) / 2, text: `Floor framing and sill ${length(framed - top)}` }, + { + key: 'basement-wall', + y: (top + floor) / 2, + text: basement.ownWalls ? 'Basement wall (its own)' : `Basement wall ${length(wall)}`, + }, + ...(basement.ownWalls ? [] : [{ key: 'slab', y: floor - slab / 2, text: `Slab ${length(slab)}` }]), + { key: 'footing', y: bottom + thick / 2, text: `Footing ${length(footing)} × ${length(thick)}` }, + ] + const placed = stackLabels( + labels.map((label) => sy(label.y)), + 9, + 8, + height - 6, + ) + const dims = [ + { key: 'top', from: 0, to: top, text: `${length(top)} top` }, + { key: 'deep', from: floor, to: 0, text: `${length(-floor)} deep` }, + { key: 'down', from: bottom, to: 0, text: `${length(-bottom)} down` }, + ].map((dim, index) => ({ ...dim, x: 10 + index * 18 })) + const toPath = (points: Array<[number, number]>) => + points.map(([x, y]) => `${sx(x).toFixed(1)},${sy(y).toFixed(1)}`).join(' ') + + return ( +
+ + {/* Grade: outside the wall only, meeting it where it lies; inside it is dug out. */} + + {Array.from({ length: 6 }, (_, i) => ( + + ))} + {shapes.map((shape) => ( + + ))} + + {dims.map((dim) => ( + + + + + + {dim.text} + + + ))} + {labels.map((label, index) => ( + + {label.text} + + ))} + +
+ At the high side of the ground, to scale; the dashed line is the grade +
+
+ ) +} + +/** A select over standard sizes; a stored size off the list stays selectable, so it reads back as it is. */ +function SizeSelect({ + label, + value, + sizes, + format, + onChange, +}: { + label: string + value: number + sizes: readonly number[] + format: (value: number) => string + onChange: (value: number) => void +}) { + const key = (size: number) => size.toFixed(6) + const listed = sizes.some((size) => Math.abs(size - value) < 1e-6) + const choices = listed ? sizes : [value, ...sizes] + const current = choices.find((size) => Math.abs(size - value) < 1e-6) ?? value + return ( + { + const picked = choices.find((size) => key(size) === next) + if (picked !== undefined) onChange(picked) + }} + options={choices.map((size) => ({ label: format(size), value: key(size) }))} + value={key(current)} + /> + ) +} + +/** + * A free layer thickness in the panel's length unit, as the foundation's sizes read (inches on an + * imperial plan, metres otherwise), in 8ths of an inch; a scrub previews, a release writes. + */ +function LayerThickness({ + layer, + metres, + min, + max, + unit, + onPreview, + onCommit, +}: { + layer: string + metres: number + min: number + max: number + unit: LinearUnit + onPreview: (metres: number) => void + onCommit: (metres: number) => void +}) { + const imperial = unit === 'imperial' + const toShown = (value: number) => (imperial ? value / IN : value) + const fromShown = (shown: number) => Math.round((imperial ? shown * IN : shown) * 1e6) / 1e6 + return ( +
+ onPreview(fromShown(next))} + onCommit={(next) => onCommit(fromShown(next))} + precision={3} + restoreOnCommit={false} + step={imperial ? 0.125 : 0.125 * IN} + unit={imperial ? 'in' : 'm'} + value={toShown(metres)} + /> +
+ ) +} + +/** + * A layer's title line: its switch (absent for the joists, which are always there), and once on + * the name of its material, typed in place over the kind's (cleared, it reads as the kind again). + */ +function FloorLayerHead({ + layer, + title, + on, + onSwitch, + name, + kind, + onRename, +}: { + layer: string + title: string + on: boolean + onSwitch?: (on: boolean) => void + name?: string + /** The kind's own name, what the layer reads as unnamed. */ + kind?: string + onRename?: (name: string) => void +}) { + const [draft, setDraft] = useState(null) + const dropped = useRef(false) + const commit = () => { + if (!dropped.current && draft !== null && draft.trim() !== (name ?? '')) onRename?.(draft) + dropped.current = false + setDraft(null) + } + return ( +
+ {onSwitch ? ( + + ) : ( + + )} + + {title} + + {!on ? ( + Off + ) : onRename ? ( + setDraft(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') event.currentTarget.blur() + if (event.key === 'Escape') { + dropped.current = true + event.currentTarget.blur() + } + }} + placeholder={kind} + spellCheck={false} + title="Name the material" + type="text" + value={draft ?? name ?? ''} + /> + ) : null} +
+ ) +} + +const CUSTOM = 'custom' + +/** + * A framed floor's layers, top down, as a roof plane's read: a preset, then each layer's product + * and size. The flooring, the subfloor sheathing, the joists and (under a storey's floor) the + * ceiling hung from them. A layer can be switched off (it goes; back on, it is `defaults`' again) + * and its material named; the joists are always there. Each change is the whole build-up, + * `onCommit`; a thickness scrub calls `onPreview` on the way. Props only, so it renders anywhere. + */ +export function FloorBuildUpFields({ + buildUp, + defaults, + presets, + ceiling = true, + unit, + onCommit, + onPreview, +}: { + buildUp: FloorBuildUp + /** The floor a layer switched back on comes back as. */ + defaults: FloorBuildUp + presets: readonly FloorBuildUpPreset[] + /** Whether a ceiling hangs under it (a storey's floor; not one over a crawl space). */ + ceiling?: boolean + unit: LinearUnit + onCommit: (buildUp: FloorBuildUp) => void + onPreview?: (buildUp: FloorBuildUp) => void +}) { + // A scrub shows its build-up here until it lands. + const [draft, setDraft] = useState(null) + const shown = draft ?? buildUp + const commit = (next: FloorBuildUp) => { + setDraft(null) + onCommit(next) + } + // A change that changes nothing (a scrub let go where it started) keeps the preset; it still + // lands, so a preview it began is closed. + const edit = (change: Partial) => { + const next = editedFloorBuildUp(buildUp, change) + const { presetId: _, ...layers } = buildUp + commit(JSON.stringify(next) === JSON.stringify(layers) ? buildUp : next) + } + const preview = (change: Partial) => { + const next = editedFloorBuildUp(buildUp, change) + setDraft(next) + onPreview?.(next) + } + const head = (layer: FloorLayerKey, title: string, kind: string | undefined) => ( + commit(renameFloorLayer(buildUp, layer, name))} + onSwitch={(on) => commit(switchFloorLayer(buildUp, layer, on, defaults))} + title={title} + /> + ) + const preset = presets.some((entry) => entry.id === buildUp.presetId) + ? buildUp.presetId! + : CUSTOM + const { flooring, sheathing, structure } = shown + const hung = ceiling ? shown.ceiling : undefined + + return ( +
+ { + // A copy: picked again, a preset still lands (an older floor takes its layers). + const picked = presets.find((entry) => entry.id === id) + if (picked) commit({ ...picked.buildUp }) + }} + options={[ + ...(preset === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ...presets.map((entry) => ({ label: entry.label, value: entry.id })), + ]} + value={preset} + /> + {head('flooring', 'Flooring', flooring && labelOf(FLOORING_OPTIONS, flooring.kind))} + {flooring && ( + <> + edit({ flooring: { kind, thickness: FLOORING_THICKNESS[kind] } })} + options={FLOORING_OPTIONS} + value={flooring.kind} + /> + edit({ flooring: { ...flooring, thickness } })} + onPreview={(thickness) => preview({ flooring: { ...flooring, thickness } })} + unit={unit} + /> + + )} + {head('sheathing', 'Subfloor sheathing', sheathing && labelOf(PANEL_OPTIONS, sheathing.kind))} + {sheathing && ( + <> + edit({ sheathing: { kind, thickness: sheathing.thickness } })} + options={PANEL_OPTIONS} + value={sheathing.kind} + /> + formatLayerThickness(value, unit)} + label="Thickness" + onChange={(thickness) => edit({ sheathing: { ...sheathing, thickness } })} + sizes={SUBFLOOR_THICKNESSES} + value={sheathing.thickness} + /> + + )} + + + edit({ structure: floorStructureAs(structure, system) }) + } + options={[ + { label: 'Lumber', value: 'joists' }, + { label: 'I-joists', value: 'i-joists' }, + ]} + value={structure.system} + /> + edit({ structure: { ...structure, size } as FloorStructure })} + options={structureSizeOptions(structure.system, unit)} + value={structure.size as string} + /> + formatSpacing(value, unit)} + label="Spacing" + onChange={(spacing) => edit({ structure: { ...structure, spacing } })} + sizes={FRAMING_SPACINGS} + value={structure.spacing} + /> + {ceiling && head('ceiling', 'Ceiling', hung && labelOf(CEILING_OPTIONS, hung.kind))} + {hung && ( + <> + edit({ ceiling: { kind, thickness: CEILING_THICKNESS[kind] } })} + options={CEILING_OPTIONS} + value={hung.kind} + /> + {hung.kind === 'drywall' && ( + formatLayerThickness(value, unit)} + label="Thickness" + onChange={(thickness) => edit({ ceiling: { ...hung, thickness } })} + sizes={DRYWALL_THICKNESSES} + value={hung.thickness} + /> + )} + + )} +
+ ) +} + +/** A raised floor's presets: the floor presets less their ceilings (the default storey floor is the crawl space's less its ceiling, so it goes). */ +const RAISED_FLOOR_PRESETS: readonly FloorBuildUpPreset[] = FLOOR_BUILD_UP_PRESETS.slice(1).map( + ({ buildUp: { ceiling: _, ...buildUp }, ...preset }) => ({ ...preset, buildUp }), +) + +/** + * A raised floor's framing over its crawl space: the floor's layers (no ceiling under them), then + * the depth they and the sill make, which sets the stem's top under the floor. A record that only + * set the depth keeps it until a layer is set. + */ +export function RaisedFloorFraming({ + foundation, + unit, + onCommit, +}: { + foundation: BuildingFoundation + unit: LinearUnit + onCommit: (floorBuildUp: FloorBuildUp) => void +}) { + const framing = foundation.floorFramingDepthIn * IN + const layered = floorBuildUpThickness(foundation.floorBuildUp) + FOUNDATION_SILL_IN * IN + // Written only when it changes: an older record's depth stays until a layer does. + const commitChanged = (next: FloorBuildUp) => { + if (JSON.stringify(next) !== JSON.stringify(foundation.floorBuildUp)) onCommit(next) + } + return ( +
+

+ Floor framing +

+ +

+ Floor framing {formatLayerDepth(framing, unit)}, sill{' '} + {formatLayerThickness(FOUNDATION_SILL_IN * IN, unit)} included +

+ {Math.abs(framing - layered) > 1e-4 && ( +

+ Its layers make {formatLayerDepth(layered, unit)} with the sill; the framing follows them + once a layer is set. +

+ )} +
+ ) +} + +/** + * The building's foundation (`metadata.foundation`): switched on, the engine draws it under every + * outside wall, sized as set here, and the floor is seated over the high side of the ground; off, + * the generic one is drawn. Type, support and every size, then what the foundation as drawn + * misses or how it stands, with the fix where there is one. Over a basement, the basement's walls + * in their place: how far their top stands over grade (the house seated by it) and what they pour. + * `heading` off, the panel it sits in is titled "Foundation" already; a string names the section + * otherwise. + */ +export function BuildingFoundationSection({ + building, + heading = true, +}: { + building: BuildingNode + heading?: boolean | string +}) { + const nodes = useScene((state) => state.nodes) + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation) + const foundation = buildingFoundationOf(nodes, building.id) + if (!foundation) return null + const report: DesignedFoundationReport | null = foundation.enabled + ? designedFoundationReport(nodes, building.id) + : null + const apply = (patch: BuildingFoundationRecord) => applyBuildingFoundation(building.id, patch) + const advice = report ? foundationAdvice(report, length) : [] + // Over a basement the type and its sizes wait for the basement to go: its walls' are shown. + const basement = report?.basement + // A raised floor is drawn layer by layer once its framing is its layers' (an older record that + // only set a depth draws it as one band). + const layered = + Math.abs( + floorBuildUpThickness(foundation.floorBuildUp) / IN + + FOUNDATION_SILL_IN - + foundation.floorFramingDepthIn, + ) < 1e-3 + + return ( +
+ {heading && ( +

+ {heading === true ? 'Foundation' : heading} +

+ )} + apply({ enabled })} + /> + {foundation.enabled && basement && report && ( + <> +

+ The house stands on its basement, cut into the ground: the ground round it is left as + it lies.{' '} + {basement.ownWalls + ? 'Its walls are the basement’s own, on their footing.' + : 'Its walls are poured under the house’s outside walls.'}{' '} + Delete the basement and it stands on its{' '} + {foundation.type === 'raised' ? 'raised floor' : 'slab'} again. +

+ + {basementFieldsFor(basement.ownWalls).map((field) => ( + apply({ [field.key]: value })} + value={foundation[field.key as BuildingFoundationDimension]} + /> + ))} + + )} + {foundation.enabled && !basement && ( + <> + apply({ type })} + options={[ + { label: 'Slab on grade', value: 'slab' }, + { label: 'Raised floor', value: 'raised' }, + ]} + value={foundation.type} + /> + apply({ support })} + options={[ + { label: foundation.type === 'raised' ? 'Stem wall and footing' : 'Turned-down edge', value: 'footing' }, + { label: 'Grade beam on piles', value: 'piles' }, + ]} + value={foundation.support} + /> + + {fieldsFor(foundation).map((field) => + field.key === 'floorFramingDepthIn' ? ( + // The floor's layers write the record's build-up; the depth (and the house's seat + // over the ground) follows them in the same step. + apply({ floorBuildUp })} + unit={unit} + /> + ) : field.key === 'edgeTaperIn' ? ( + apply({ edgeTaperIn })} + /> + ) : ( + apply({ [field.key]: value })} + value={foundation[field.key]} + /> + ), + )} + {foundation.type === 'raised' && ( + apply({ stepped })} + /> + )} + {!report && ( +

+ {standsOnBasement(nodes, building.id) + ? 'The house stands on a basement: its walls are the foundation walls, so only their footing is drawn.' + : 'Close the outside walls into a loop to draw the foundation under them.'} +

+ )} + + )} + {foundation.enabled && + advice.map((item) => ( +
+

+ {item.text} +

+ {item.fix && ( + + )} +
+ ))} +
+ ) +} diff --git a/packages/editor/src/components/ui/panels/deck-boards-fields.tsx b/packages/editor/src/components/ui/panels/deck-boards-fields.tsx new file mode 100644 index 0000000000..774c7bba5d --- /dev/null +++ b/packages/editor/src/components/ui/panels/deck-boards-fields.tsx @@ -0,0 +1,107 @@ +'use client' + +import { + type AnyNodeId, + DECK_BOARD_GAPS, + DECK_BOARD_SIZES, + DECK_BOARD_STOCK, + type DeckBoardDirection, + type DeckBoardSize, + deckBoardSettings, + deckJoistSpacingLimit, + inchFractionLabel, + setZoneIntent, + useScene, +} from '@pascal-app/core' +import type { ReactNode } from 'react' +import { useShallow } from 'zustand/react/shallow' +import { applyRoomPlan } from '../../../lib/room-structure-commands' +import { SegmentedControl } from '../controls/segmented-control' + +const IN = 0.0254 + +const BOARD_OPTIONS = DECK_BOARD_SIZES.map((value) => ({ + label: DECK_BOARD_STOCK[value].label, + value, +})) + +const GAP_OPTIONS = DECK_BOARD_GAPS.map((gap, index) => ({ + label: `${inchFractionLabel(gap)} in`, + value: String(index), +})) + +const RUN_OPTIONS: ReadonlyArray<{ label: string; value: DeckBoardDirection }> = [ + { label: 'Along house', value: 'parallel' }, + { label: 'Diagonal', value: 'diagonal' }, + { label: 'Out from house', value: 'perpendicular' }, +] + +const RUN_HINTS: Record = { + parallel: 'along the house, on joists square to it', + diagonal: 'at 45° to the house', + perpendicular: 'out from the house, on joists along it, carried by beams square to the house', +} + +function Field({ label, children }: { label: string; children: ReactNode }) { + return ( +
+ {label} + {children} +
+ ) +} + +/** + * A deck's boards (`decking`, through core's `setZoneIntent`): their stock, the gap between them + * and the way they run against the house. The engine lays them over the deck's frame, and the + * frame follows: joists 12 in on centre under diagonal 5/4 or composite boards (IRC R507.7), and + * along the house on beams square to it under boards run out from it. + */ +export function DeckBoardsFields({ zoneId }: { zoneId: string }) { + const deck = useScene( + useShallow((scene) => { + const zone = scene.nodes[zoneId as AnyNodeId] + if (zone?.type !== 'zone' || zone.roomType !== 'deck') return null + return deckBoardSettings(zone) + }), + ) + if (!deck) return null + const set = (decking: NonNullable[1]['patch']['decking']>) => + applyRoomPlan(setZoneIntent(useScene.getState().nodes, { zoneId, patch: { decking } })) + const stock = DECK_BOARD_STOCK[deck.boardSize] + const gap = DECK_BOARD_GAPS.findIndex((option) => Math.abs(option - deck.gap) < 1e-6) + const spacing = Math.min(16 * IN, deckJoistSpacingLimit(deck)) + return ( +
+

Deck boards

+ + set({ boardSize })} + options={BOARD_OPTIONS} + value={deck.boardSize} + /> + + + set({ gap: DECK_BOARD_GAPS[Number(index)] })} + options={GAP_OPTIONS} + value={String(Math.max(gap, 0))} + /> + + + set({ direction })} + options={[...RUN_OPTIONS]} + value={deck.direction} + /> + +

+ {`${stock.label} boards ${inchFractionLabel(stock.thickness)} × ${inchFractionLabel(stock.width)} in, ${inchFractionLabel(deck.gap)} in apart, ${RUN_HINTS[deck.direction]}; joists ${Math.round(spacing / IN)} in on centre.`} +

+
+ ) +} diff --git a/packages/editor/src/components/ui/panels/floor-build-up.test.tsx b/packages/editor/src/components/ui/panels/floor-build-up.test.tsx new file mode 100644 index 0000000000..318886a88e --- /dev/null +++ b/packages/editor/src/components/ui/panels/floor-build-up.test.tsx @@ -0,0 +1,444 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + BuildingNode, + buildingFoundationOf, + createZone, + DEFAULT_FLOOR_BUILD_UP, + DEFAULT_RAISED_FLOOR_BUILD_UP, + floorBuildUpThickness, + generateId, + initSpaceDetectionSync, + LevelNode, + resolveBuildingFoundation, + type SlabNode, + useScene, +} from '@pascal-app/core' +import { createElement } from 'react' +import { renderToStaticMarkup } from 'react-dom/server' +import { applyBuildingFoundation } from '../../../lib/building-foundation' +import { + applyFloorBuildUp, + beginFootprintHeightPreview, + editedFloorBuildUp, + floorStructureAs, + footprintHeightValue, + levelFootprints, + renameFloorLayer, + switchFloorLayer, +} from '../../../lib/floor-footprints' +import { formatLayerThickness } from '../../../lib/layer-thickness' +import { applyRoomPlan } from '../../../lib/room-structure-commands' +import { installImmediateAnimationFrames } from '../../../test-utils/immediate-animation-frames' +import { + FoundationSectionDrawing, + floorSectionLayers, + RaisedFloorFraming, + slabEdgeTaperOf, + stackLabels, +} from './building-foundation-section' +import { FloorPlateBuildUp, FloorSection } from './floor-foundation-panel' + +const IN = 0.0254 +const GROUND = 'level_build_up_ground' +const UPPER = 'level_build_up_upper' +const BUILDING = 'building_build_up' +let stop = () => {} +let restoreFrames = () => {} +const nodes = () => useScene.getState().nodes as Record +const past = () => useScene.temporal.getState().pastStates.length +// Rendered text with its quotes back (the inch marks are escaped in markup). +const render = (element: Parameters[0]) => + renderToStaticMarkup(element).replaceAll('"', '"') + +const rect = (x0: number, z0: number, x1: number, z1: number): [number, number][] => [ + [x0, z0], + [x1, z0], + [x1, z1], + [x0, z1], +] +const room = (levelId: string, polygon: [number, number][], name: string) => + applyRoomPlan(createZone(nodes(), { levelId, polygon, enclose: true, mintId: generateId, name })) + +beforeEach(() => { + restoreFrames = installImmediateAnimationFrames() + const building = BuildingNode.parse({ id: BUILDING, children: [GROUND, UPPER] }) + const ground = LevelNode.parse({ id: GROUND, parentId: building.id, level: 0, name: 'Ground floor' }) + const upper = LevelNode.parse({ id: UPPER, parentId: building.id, level: 1, name: 'Floor 1' }) + useScene.setState({ + nodes: { [building.id]: building, [ground.id]: ground, [upper.id]: upper }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + materials: {}, + collections: {}, + readOnly: false, + }) + useScene.temporal.getState().resume() + useScene.temporal.getState().clear() + stop = initSpaceDetectionSync(useScene, { getState: () => ({ spaces: {}, setSpaces: () => {} }) }) + room(GROUND, rect(0, 0, 8, 5), 'Living') + room(UPPER, rect(0, 0, 8, 5), 'Bedroom') +}) +afterEach(() => { + stop() + restoreFrames() +}) + +const plateOf = (levelId: string, name: string) => + levelFootprints(nodes(), levelId).find((plate) => + plate.zoneIds?.some((id) => nodes()[id]?.name === name), + ) as SlabNode +const bedroom = () => plateOf(UPPER, 'Bedroom') +const top = (plate: SlabNode) => plate.floorHeight ?? plate.elevation +const underside = (plate: SlabNode) => top(plate) - plate.thickness +/** The bedroom floor on the default build-up, history cleared. */ +const framedBedroom = () => { + expect(applyFloorBuildUp(bedroom().id, DEFAULT_FLOOR_BUILD_UP)).toBeNull() + useScene.temporal.getState().clear() + return bedroom() +} + +describe('Floor build-up rows (the floor plate panel)', () => { + test('a framed floor lists its layers top down, with the thickness they make', () => { + const markup = render( + createElement(FloorPlateBuildUp, { + buildUp: DEFAULT_FLOOR_BUILD_UP, + matches: true, + onCommit: () => {}, + thickness: floorBuildUpThickness(DEFAULT_FLOOR_BUILD_UP), + unit: 'imperial', + }), + ) + const order = ['flooring', 'sheathing', 'joists', 'ceiling'].map((layer) => + markup.indexOf(`data-floor-layer="${layer}"`), + ) + expect(order.every((at) => at >= 0)).toBe(true) + expect([...order].sort((a, b) => a - b)).toEqual(order) + expect(markup).toContain('Hardwood on 2x12 @ 16"') + expect(markup).toContain('Floor thickness 13-1/8"') + // Every layer switches but the joists, which are always there. + for (const layer of ['flooring', 'sheathing', 'ceiling']) + expect(markup).toContain(`data-floor-layer-switch="${layer}"`) + expect(markup).not.toContain('data-floor-layer-switch="joists"') + // Unnamed, a layer's material reads as its kind. + expect(markup).toMatch(/data-floor-layer-name="flooring"[^>]*placeholder="Hardwood"/) + expect(markup).not.toContain('data-floor-build-up-follows') + }) + + test('an older plate shows the default layers and says its thickness follows them', () => { + const markup = render( + createElement(FloorPlateBuildUp, { + buildUp: DEFAULT_FLOOR_BUILD_UP, + matches: false, + onCommit: () => {}, + thickness: 14 * IN, + unit: 'imperial', + }), + ) + expect(markup).toContain('Floor thickness 14"') + expect(markup).toContain('Its layers make 13-1/8"') + }) + + test('a layer switched off goes and lowers the floor, its underside held, in one undo step', () => { + const plate = framedBedroom() + expect(plate.thickness).toBeCloseTo(floorBuildUpThickness(DEFAULT_FLOOR_BUILD_UP), 6) + const off = switchFloorLayer(plate.buildUp!, 'ceiling', false) + expect(off.ceiling).toBeUndefined() + expect(off.presetId).toBeUndefined() + expect(applyFloorBuildUp(plate.id, off)).toBeNull() + const after = bedroom() + expect(after.buildUp?.ceiling).toBeUndefined() + expect(after.thickness).toBeCloseTo(plate.thickness - 0.5 * IN, 6) + expect(underside(after)).toBeCloseTo(underside(plate), 6) + expect(top(after)).toBeCloseTo(top(plate) - 0.5 * IN, 6) + expect(past()).toBe(1) + // The row reads off. + const markup = render( + createElement(FloorPlateBuildUp, { + buildUp: after.buildUp!, + matches: true, + onCommit: () => {}, + thickness: after.thickness, + unit: 'imperial', + }), + ) + expect(markup).toMatch(/data-floor-layer="ceiling" data-on="false"/) + expect(markup).toContain('Floor thickness 12-5/8"') + // One undo takes back the layers and the thickness together. + useScene.temporal.getState().undo() + expect(bedroom().buildUp?.ceiling).toEqual(DEFAULT_FLOOR_BUILD_UP.ceiling) + expect(bedroom().thickness).toBeCloseTo(plate.thickness, 6) + }) + + test('a layer switched back on is the default floor’s again', () => { + const plate = framedBedroom() + const off = switchFloorLayer(plate.buildUp!, 'sheathing', false) + expect(applyFloorBuildUp(plate.id, off)).toBeNull() + const on = switchFloorLayer(bedroom().buildUp!, 'sheathing', true) + expect(on.sheathing).toEqual(DEFAULT_FLOOR_BUILD_UP.sheathing) + expect(applyFloorBuildUp(plate.id, on)).toBeNull() + expect(bedroom().thickness).toBeCloseTo(plate.thickness, 6) + }) + + test('a joist pick sets the depth: the top moves, the underside stays', () => { + const plate = framedBedroom() + const iJoists = editedFloorBuildUp(plate.buildUp!, { + structure: floorStructureAs(plate.buildUp!.structure, 'i-joists'), + }) + // The nearest I-joist to a 2x12. + expect(iJoists.structure).toMatchObject({ system: 'i-joists', size: '11-7/8' }) + const fourteen = editedFloorBuildUp(iJoists, { structure: { ...iJoists.structure, size: '14' } }) + expect(applyFloorBuildUp(plate.id, fourteen)).toBeNull() + const after = bedroom() + expect(after.thickness).toBeCloseTo((13.125 - 11.25 + 14) * IN, 6) + expect(underside(after)).toBeCloseTo(underside(plate), 6) + expect(footprintHeightValue(nodes(), after)).toBeCloseTo(top(plate) + 2.75 * IN, 6) + // Back to lumber, the nearest size to 14 in is a 2x12. + expect(floorStructureAs(fourteen.structure, 'joists')).toMatchObject({ size: '2x12' }) + }) + + test('a material name stays with its layer and keeps the preset; cleared, it reads as its kind', () => { + const plate = framedBedroom() + const named = renameFloorLayer(plate.buildUp!, 'flooring', ' Brazilian cherry ') + expect(named.flooring?.name).toBe('Brazilian cherry') + expect(named.presetId).toBe(DEFAULT_FLOOR_BUILD_UP.presetId) + expect(applyFloorBuildUp(plate.id, named)).toBeNull() + expect(bedroom().buildUp?.flooring?.name).toBe('Brazilian cherry') + expect(bedroom().thickness).toBeCloseTo(plate.thickness, 6) + const markup = render( + createElement(FloorPlateBuildUp, { + buildUp: bedroom().buildUp!, + matches: true, + onCommit: () => {}, + thickness: plate.thickness, + unit: 'imperial', + }), + ) + expect(markup).toMatch(/data-floor-layer-name="flooring"[^>]*value="Brazilian cherry"/) + const drawn = floorSectionLayers(bedroom().buildUp!, 'imperial') + expect(drawn[0]).toMatchObject({ key: 'flooring', name: 'Brazilian cherry', size: '3/4"' }) + const cleared = renameFloorLayer(named, 'flooring', '') + expect(cleared.flooring).toEqual(DEFAULT_FLOOR_BUILD_UP.flooring) + expect(floorSectionLayers(cleared, 'imperial')[0]!.name).toBe('Hardwood') + }) + + test('a thickness scrub previews live and lands with the layers as one undo step', () => { + const plate = framedBedroom() + const thicker = editedFloorBuildUp(plate.buildUp!, { + flooring: { ...plate.buildUp!.flooring!, thickness: 1.5 * IN }, + }) + const session = beginFootprintHeightPreview(plate.id) + expect(session.preview(underside(plate) + floorBuildUpThickness(thicker))).toBeNull() + // Live: the plate moves, the layers wait for the release, history untouched. + expect(bedroom().thickness).toBeCloseTo(plate.thickness + 0.75 * IN, 6) + expect(bedroom().buildUp).toEqual(plate.buildUp) + expect(past()).toBe(0) + expect(applyFloorBuildUp(plate.id, thicker, { session })).toBeNull() + expect(bedroom().thickness).toBeCloseTo(plate.thickness + 0.75 * IN, 6) + expect(bedroom().buildUp?.flooring?.thickness).toBeCloseTo(1.5 * IN, 9) + expect(past()).toBe(1) + useScene.temporal.getState().undo() + expect(bedroom().thickness).toBeCloseTo(plate.thickness, 6) + expect(bedroom().buildUp).toEqual(plate.buildUp) + }) + + test('a floor on the ground is no framed floor: refused, nothing written', () => { + const living = plateOf(GROUND, 'Living') + expect(applyFloorBuildUp(living.id, DEFAULT_FLOOR_BUILD_UP)).toBeString() + expect((nodes()[living.id] as SlabNode).buildUp).toBeUndefined() + expect((nodes()[living.id] as SlabNode).thickness).toBe(living.thickness) + }) +}) + +describe('Raised floor framing (the foundation section)', () => { + test('flooring, subfloor and joists, no ceiling, then the framing they and the sill make', () => { + const foundation = resolveBuildingFoundation({ type: 'raised' }, 1) + const markup = render( + createElement(RaisedFloorFraming, { foundation, onCommit: () => {}, unit: 'imperial' }), + ) + for (const layer of ['flooring', 'sheathing', 'joists']) + expect(markup).toContain(`data-floor-layer="${layer}"`) + expect(markup).not.toContain('data-floor-layer="ceiling"') + expect(markup).toContain('data-floor-layer-switch="flooring"') + expect(markup).toContain('data-floor-layer-switch="sheathing"') + expect(markup).not.toContain('data-floor-layer-switch="joists"') + expect(markup).toContain('Floor framing 14-1/8", sill 1-1/2" included') + expect(markup).toContain('Over a crawl space, 2x12 @ 16"') + expect(markup).not.toContain('data-floor-framing-follows') + }) + + test('a layer switched off lowers the framing; an older record keeps its depth until a layer is set', () => { + const off = switchFloorLayer( + DEFAULT_RAISED_FLOOR_BUILD_UP, + 'flooring', + false, + DEFAULT_RAISED_FLOOR_BUILD_UP, + ) + expect(resolveBuildingFoundation({ type: 'raised', floorBuildUp: off }, 1).floorFramingDepthIn).toBeCloseTo( + 13.375, + 4, + ) + const older = resolveBuildingFoundation({ type: 'raised', floorFramingDepthIn: 16 }, 1) + expect(older.floorFramingDepthIn).toBe(16) + const markup = render( + createElement(RaisedFloorFraming, { foundation: older, onCommit: () => {}, unit: 'imperial' }), + ) + expect(markup).toContain('Floor framing 16", sill 1-1/2" included') + expect(markup).toContain('data-floor-framing-follows') + }) + + test('the layers written through the panel’s apply set the framing, names kept', () => { + const tile = editedFloorBuildUp(DEFAULT_RAISED_FLOOR_BUILD_UP, { + flooring: { kind: 'ceramic-tile', thickness: 0.5 * IN, name: 'Saltillo' }, + structure: { ...DEFAULT_RAISED_FLOOR_BUILD_UP.structure, size: '2x10' }, + }) + expect(applyBuildingFoundation(BUILDING, { type: 'raised', floorBuildUp: tile })).toBe(true) + const foundation = buildingFoundationOf(nodes(), BUILDING)! + expect(foundation.floorFramingDepthIn).toBeCloseTo(0.5 + 0.625 + 9.25 + 1.5, 4) + expect(foundation.floorBuildUp.flooring?.name).toBe('Saltillo') + }) +}) + +describe('Section drawings', () => { + const length = (meters: number) => `${Math.round((meters / IN) * 100) / 100} in` + + test('a raised floor: its layers to the sill, the crawl dug out under the joists, the footing under that', () => { + const foundation = resolveBuildingFoundation({ type: 'raised' }, 1) + const markup = render( + createElement(FoundationSectionDrawing, { + foundation, + framing: floorSectionLayers(foundation.floorBuildUp, 'imperial', true), + length, + }), + ) + for (const part of ['flooring', 'sheathing', 'joists', 'sill', 'stem', 'footing', 'wall']) + expect(markup).toContain(`data-section-part="${part}"`) + expect(markup).toContain('Hardwood 3/4"') + expect(markup).toContain('Sill 1-1/2"') + // The joists' underside is the stem's top (8 in over grade) and the sill: 9.5 in; the dirt + // 18 in under that is 8.5 in under the grade, and the footing's bottom 12 in under the dirt. + expect(markup).toContain('data-section-crawl="-0.2159"') + expect(markup).toContain('>18 in crawl<') + expect(markup).toContain('>12 in down<') + const dirt = Number(/data-section-crawl="[^"]*">]*?y1="([\d.]+)"/.exec(markup)![1]) + const footing = /data-section-part="footing" points="([^"]+)"/.exec(markup)![1]! + const footingBottom = Math.max(...footing.split(' ').map((point) => Number(point.split(',')[1]))) + expect(footingBottom).toBeGreaterThan(dirt + 10) + // The depth is measured from the dirt down to the footing's bottom. + const depth = markup.slice(markup.indexOf('data-section-dim="depth"')) + const spans = [...depth.matchAll(/y1="([\d.]+)" y2="([\d.]+)"/g)].map((match) => [ + Number(match[1]), + Number(match[2]), + ]) + const [from, to] = spans[1]! + expect(from).toBeCloseTo(footingBottom, 0) + expect(to).toBeCloseTo(dirt, 0) + }) + + test("a slab's edge is plumb inside from its square bottom, then tapers in at 45°: half its depth under the slab, 6 to 12 in", () => { + /** The slab's outline as drawn, in the drawing's points, and the markup round it. */ + const drawn = (record: Parameters[0]) => { + const markup = render( + createElement(FoundationSectionDrawing, { + foundation: resolveBuildingFoundation(record, 1), + length, + }), + ) + const points = /data-section-part="slab" points="([^"]+)"/ + .exec(markup)![1]! + .split(' ') + .map((point) => point.split(',').map(Number) as [number, number]) + return { markup, points } + } + const { markup, points } = drawn(null) + // 16 in under the slab's underside: 8 in plumb over the bottom, then 8 in at 45°. + expect(markup).toContain('>Taper 8 in at 45°<') + expect(markup).toContain('>Edge 12 in wide<') + const [bottomLeft, bottomRight, plumbTop, taperTop] = points + expect(bottomRight![1]).toBeCloseTo(bottomLeft![1], 0) + expect(plumbTop![0]).toBeCloseTo(bottomRight![0], 0) + expect(taperTop![0] - plumbTop![0]).toBeCloseTo(plumbTop![1] - taperTop![1], 0) + expect(bottomRight![1] - plumbTop![1]).toBeCloseTo(taperTop![0] - plumbTop![0], 0) + // Deeper, the edge grows plumb: 30 in under the slab tapers 12 in. A taper set stands. + expect(drawn({ edgeDepthIn: 26 }).markup).toContain('>Taper 12 in at 45°<') + expect(drawn({ edgeTaperIn: 10 }).markup).toContain('>Taper 10 in at 45°<') + expect(slabEdgeTaperOf(resolveBuildingFoundation({ edgeDepthIn: 4 }, 1)) / IN).toBeCloseTo(6, 9) + }) + + test('a layer switched off is not drawn; a slab has no crawl; an older raised record is one band', () => { + const off = switchFloorLayer( + DEFAULT_RAISED_FLOOR_BUILD_UP, + 'flooring', + false, + DEFAULT_RAISED_FLOOR_BUILD_UP, + ) + const raised = resolveBuildingFoundation({ type: 'raised', floorBuildUp: off }, 1) + const withoutFlooring = render( + createElement(FoundationSectionDrawing, { + foundation: raised, + framing: floorSectionLayers(raised.floorBuildUp, 'imperial', true), + length, + }), + ) + expect(withoutFlooring).not.toContain('data-section-part="flooring"') + expect(withoutFlooring).toContain('data-section-part="sheathing"') + const slab = render( + createElement(FoundationSectionDrawing, { + foundation: resolveBuildingFoundation(null, 1), + length, + }), + ) + expect(slab).not.toContain('data-section-crawl') + expect(slab).not.toContain('crawl<') + expect(slab).toContain('data-section-part="slab"') + const older = render( + createElement(FoundationSectionDrawing, { + foundation: resolveBuildingFoundation({ type: 'raised', floorFramingDepthIn: 16 }, 1), + length, + }), + ) + expect(older).toContain('data-section-part="framing"') + expect(older).toContain('data-section-crawl') + }) + + test('the floor plate draws as its layers top down; a layer that is off is not drawn', () => { + const section = (layers = floorSectionLayers(DEFAULT_FLOOR_BUILD_UP, 'imperial')) => + render( + createElement(FloorSection, { + layers, + raised: false, + supported: true, + thickness: floorBuildUpThickness(DEFAULT_FLOOR_BUILD_UP), + thicknessLabel: '13-1/8"', + }), + ) + const markup = section() + const order = [ + 'Floor top', + 'Hardwood · 3/4"', + 'Plywood subfloor · 5/8"', + '2x12 joists · 11-1/4"', + 'Drywall ceiling · 1/2"', + 'Walls below', + ].map((label) => markup.indexOf(`>${label}<`)) + expect(order.every((at) => at >= 0)).toBe(true) + expect([...order].sort((a, b) => a - b)).toEqual(order) + for (const layer of ['flooring', 'sheathing', 'joists', 'ceiling']) + expect(markup).toContain(`data-section-layer="${layer}"`) + expect(markup).toContain('Not to scale') + const exposed = section( + floorSectionLayers(switchFloorLayer(DEFAULT_FLOOR_BUILD_UP, 'ceiling', false), 'imperial'), + ) + expect(exposed).not.toContain('data-section-layer="ceiling"') + expect(exposed).not.toContain('Drywall ceiling') + }) + + test('callouts keep their order, a crowded run fanned round its middle, within bounds', () => { + expect(stackLabels([10, 11, 12, 50], 9, 0, 100)).toEqual([2, 11, 20, 50]) + expect(stackLabels([0, 1], 9, 5, 100)).toEqual([5, 14]) + expect(stackLabels([30, 10], 9, 0, 100)).toEqual([30, 10]) + expect(formatLayerThickness(0.75 * IN, 'imperial')).toBe('3/4"') + expect(formatLayerThickness(11.25 * IN, 'imperial')).toBe('11-1/4"') + expect(formatLayerThickness(0.5 * IN, 'metric')).toBe('12.7 mm') + }) +}) diff --git a/packages/editor/src/components/ui/panels/floor-foundation-panel.tsx b/packages/editor/src/components/ui/panels/floor-foundation-panel.tsx index 98c45d2731..8efa5bec6c 100644 --- a/packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +++ b/packages/editor/src/components/ui/panels/floor-foundation-panel.tsx @@ -3,8 +3,16 @@ import { type AnyNode, type AnyNodeId, + DEFAULT_FLOOR_BUILD_UP, + defaultI18n, + FLOOR_BUILD_UP_PRESETS, + type FloorBuildUp, type FloorFoundationPatch, + floorBuildUpMatches, + floorBuildUpOf, + floorBuildUpThickness, floorFootprintSupportClass, + formatList, upperFloorHeightControl, getLevelElevations, getStoredLevelHeight, @@ -22,6 +30,7 @@ import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name' import { localizedLevelName } from '../../../i18n/level-name' import { useRef, useState } from 'react' import { + applyFloorBuildUp, applyFloorFoundation, beginFootprintHeightPreview, beginFootprintThicknessPreview, @@ -36,11 +45,22 @@ import { THICK_FLOOR_HINT, thickFloorAdvice, } from '../../../lib/floor-footprints' +import { buildingOfPlate, floorHeldByDesignedFoundation } from '../../../lib/building-foundation' import { formatLinearMeasurement } from '../../../lib/measurements' +import { formatLayerDepth } from '../../../lib/layer-thickness' import { FinishSwatch } from '../controls/finish-swatch' import { MetricControl } from '../controls/metric-control' import { SegmentedControl } from '../controls/segmented-control' import { Button } from '../primitives/button' +import { + BuildingFoundationSection, + calloutName, + FloorBuildUpFields, + floorSectionLayers, + SECTION_LAYER_FILL, + type SectionLayer, + stackLabels, +} from './building-foundation-section' import { PanelWrapper } from './panel-wrapper' /** Unpainted finishes, as the plate renders them (nodes' slab slot defaults). */ @@ -52,8 +72,9 @@ const FOUNDATION_DEFAULT = 'library:concrete-raw' * is built. On the ground: the floor top, the slab, the foundation when the * footprint is raised on one, the ground — the slab always sits on the * foundation (or the ground), so the top is their sum. Upstairs: the floor - * plate resting on the walls of the storey below. Drawn to a readable scale, - * not to size. + * plate resting on the walls of the storey below, drawn as its layers when it + * has them (flooring, sheathing, joists, ceiling; a layer that is off is not + * drawn). Drawn to a readable scale, not to size. */ export function FloorSection({ supported, @@ -65,6 +86,7 @@ export function FloorSection({ topLabel, thickness, thicknessLabel, + layers, }: { /** Upstairs (or over a basement): it rests on the walls below, never on a foundation. */ supported: boolean @@ -80,11 +102,20 @@ export function FloorSection({ topLabel?: string thickness: number thicknessLabel: string + /** Upstairs: the plate's layers, top down (`floorSectionLayers`), drawn in place of one band. */ + layers?: readonly SectionLayer[] }) { const i18n = usePascalI18n() + const locale = useLocale() const base = 100 const foundation = !supported && raised ? Math.min(40, 14 + foundationHeight * 30) : 0 - const band = Math.max(7, Math.min(16, thickness * 60)) + // Not to scale: a thin layer still reads, and the joists take most of the plate. + const drawn = supported ? (layers ?? []).filter((layer) => layer.thickness > 0) : [] + const layerHeight = (layer: SectionLayer) => Math.max(3, Math.min(20, layer.thickness * 70)) + const band = + drawn.length > 0 + ? drawn.reduce((sum, layer) => sum + layerHeight(layer), 0) + : Math.max(7, Math.min(16, thickness * 60)) // Upstairs a lift moves the plate off its default position (on the walls below). const upstairsLift = supported && Math.abs(lift) > 0.005 @@ -100,28 +131,46 @@ export function FloorSection({ const measured = supported ? Math.abs(lift) > 0.005 : true const measureLabel = supported ? liftLabel : (topLabel ?? '') const left = 64 - const right = 164 + // The layers' names need more room beside the plate than one band's. + const right = drawn.length > 0 ? 136 : 164 + const bands: { layer: SectionLayer; y: number; height: number }[] = [] + for (const layer of drawn) { + const last = bands[bands.length - 1] + bands.push({ layer, y: last ? last.y + last.height : bandTop, height: layerHeight(layer) }) + } const labels: { y: number; text: string; key: string }[] = [ { key: 'top', y: bandTop - 9, text: i18n._({ id: 'editor.panels.floorFoundationPanel.section.floorTop', message: 'Floor top' }), }, - { - key: 'plate', - y: bandTop + band / 2, - text: supported - ? i18n._({ - id: 'editor.panels.floorFoundationPanel.section.floorPlate', - message: 'Floor plate · {thickness}', - values: { thickness: thicknessLabel }, - }) - : i18n._({ - id: 'editor.panels.floorFoundationPanel.section.slab', - message: 'Slab · {thickness}', - values: { thickness: thicknessLabel }, + ...(bands.length > 0 + ? bands.map(({ layer, y, height }) => ({ + key: `layer-${layer.key}`, + y: y + height / 2, + text: i18n._({ + id: 'editor.panels.floorFoundationPanel.section.layer', + message: '{layer} · {size}', + values: { layer: calloutName(layer.name), size: layer.size }, }), - }, + })) + : [ + { + key: 'plate', + y: bandTop + band / 2, + text: supported + ? i18n._({ + id: 'editor.panels.floorFoundationPanel.section.floorPlate', + message: 'Floor plate · {thickness}', + values: { thickness: thicknessLabel }, + }) + : i18n._({ + id: 'editor.panels.floorFoundationPanel.section.slab', + message: 'Slab · {thickness}', + values: { thickness: thicknessLabel }, + }), + }, + ]), ...(foundation ? [ { @@ -145,12 +194,37 @@ export function FloorSection({ : i18n._({ id: 'editor.panels.floorFoundationPanel.section.ground', message: 'Ground' }), }, ] + // Thin layers' names fan out round them, each led back to its layer. + const placed = stackLabels( + labels.map((label) => label.y), + 11, + 6, + 113, + ) return (
0 + ? i18n._({ + id: 'editor.panels.floorFoundationPanel.sectionLayers', + message: 'Floor plate {thickness} thick: {layers}; resting on the walls below', + values: { + thickness: thicknessLabel, + layers: formatList( + drawn.map((layer) => + i18n._({ + id: 'editor.panels.floorFoundationPanel.sectionLayerSize', + message: '{layer} {size}', + values: { layer: layer.name, size: layer.size }, + }), + ), + locale, + ), + }, + }) + : i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn', message: 'Floor plate {thicknessLabel} thick, resting on the walls below', values: { thicknessLabel } }) : raised ? i18n._({ id: 'editor.panels.floorFoundationPanel.sectionRaised', @@ -229,14 +303,33 @@ export function FloorSection({ y={bandBottom} /> )} - + {bands.length > 0 ? ( + + {bands.map(({ layer, y, height }) => ( + + ))} + + ) : ( + + )} {/* The floor top. */} )} - {labels.map((label) => ( + {labels.map((label, index) => ( - {label.text} @@ -304,10 +395,37 @@ export function FloorSection({ ) } +/** Upstairs: "Floor top +9'2"", the sign written only over the ground (a length prints its own minus). */ +export function upperFloorTopLabel( + top: number, + length: (meters: number) => string, + i18n: PascalI18n = defaultI18n, +): string { + return top > 0 + ? i18n._({ + id: 'editor.panels.floorFoundationPanel.floorTopAbove', + message: 'Floor top +{height}', + values: { height: length(top) }, + }) + : i18n._({ + id: 'editor.panels.floorFoundationPanel.floorTopValue', + message: 'Floor top {height}', + values: { height: length(top) }, + }) +} + type Notice = { message: string - /** The footprints to move together, and the change, when that would be accepted. */ - together: { ids: string[]; patch: FloorFoundationPatch; lower: boolean } | null + /** + * The footprints to move together, and the change, when that would be accepted; with the floor + * layers that asked for it, written to this plate as it moves. + */ + together: { + ids: string[] + patch: FloorFoundationPatch + lower: boolean + buildUp?: FloorBuildUp + } | null } export function moveTogetherLabel(count: number, lower: boolean, i18n: PascalI18n) { @@ -388,12 +506,77 @@ export function floorFoundationModel(nodes: Record, plate: Slab } } +/** + * A framed floor plate's "Floor build-up": its layers top down (`FloorBuildUpFields`), then the + * thickness they make, read-only. A plate saved before its layers were kept shows the default + * floor's and says its thickness follows them on the first change. Props only, so it renders + * outside the editor's stores. + */ +export function FloorPlateBuildUp({ + buildUp, + matches, + thickness, + unit, + onCommit, + onPreview, +}: { + buildUp: FloorBuildUp + /** Whether the plate's thickness is its layers' sum. */ + matches: boolean + thickness: number + unit: 'metric' | 'imperial' + onCommit: (buildUp: FloorBuildUp) => void + onPreview?: (buildUp: FloorBuildUp) => void +}) { + const i18n = usePascalI18n() + return ( +
+

+ {i18n._({ id: 'editor.panels.floorFoundationPanel.floorBuildUp', message: 'Floor build-up' })} +

+ +

+ {i18n._({ + id: 'editor.panels.floorFoundationPanel.floorThicknessValue', + message: 'Floor thickness {thickness}', + values: { thickness: formatLayerDepth(thickness, unit) }, + })} +

+ {!matches && ( +

+ {i18n._({ + id: 'editor.panels.floorFoundationPanel.buildUpFollows', + message: + 'Its layers make {thickness}; the thickness follows them on the first change.', + values: { thickness: formatLayerDepth(floorBuildUpThickness(buildUp), unit) }, + })} +

+ )} +
+ ) +} + /** * "Floor & foundation" for one footprint (the house, a shed, an upper storey), * in one column in the order it is built: the section, the slab (thickness, - * edge finish), then what is under the floor — on a ground-bearing footprint - * only — and the floor top that follows. Scrubbing the foundation height moves - * the building live and lands as one undo step. + * edge finish) or upstairs the floor's layers, then what is under the floor — + * on a ground-bearing footprint only — and the floor top that follows. + * Scrubbing the foundation height, or a layer's thickness, moves the building + * live and lands as one undo step. */ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClose: () => void }) { const i18n = usePascalI18n() @@ -415,7 +598,11 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos // A refused change says why, in the panel's words, never as an error; when // only the shared-storey rule refused it, it offers to move every footprint // under that storey together (checked once the change settles, not per frame). - const report = (message: string | null, patch?: FloorFoundationPatch) => { + const report = ( + message: string | null, + patch?: FloorFoundationPatch, + buildUp?: FloorBuildUp, + ) => { if (!message) return setNotice(null) const current = useScene.getState().nodes const together = patch ? footprintsToMoveTogether(current, plate.id, patch) : null @@ -429,7 +616,10 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos ? patch.foundationHeight < footprintHeightValue(current, latest) : patch?.floorHeight == null || patch.floorHeight < (latest.floorHeight ?? latest.elevation) - setNotice({ message, together: together && patch ? { ids: together, patch, lower } : null }) + setNotice({ + message, + together: together && patch ? { ids: together, patch, lower, buildUp } : null, + }) } const heightPatchFor = (value: number) => { const current = useScene.getState().nodes @@ -447,9 +637,30 @@ export function FloorFoundationPanel({ node, onClose }: { node: SlabNode; onClos const message = session.commit(value) report(message, message ? heightPatchFor(value) : undefined) } - // Upstairs the underside stays on the walls below: the thickness is the one - // control, and it moves the floor top (and the storeys above) with it. + // Upstairs the underside stays on the walls below: the floor's layers make + // its thickness, and a change to them moves the floor top (and the storeys + // above) with it. A scrub previews live through the same height session. const underside = height - plate.thickness + const buildUp = floorBuildUpOf(plate) + const previewBuildUp = (next: FloorBuildUp) => + previewHeight(underside + floorBuildUpThickness(next)) + const commitBuildUp = (next: FloorBuildUp) => { + const session = scrub.current + scrub.current = null + // Let go where it started, nothing changes (an older plate keeps its own thickness) and a + // preview it began is taken back. + if (next === buildUp) { + session?.cancel() + return report(null) + } + const message = applyFloorBuildUp(plate.id, next, { session }) + const top = underside + floorBuildUpThickness(next) + report(message, message ? heightPatchFor(top) : undefined, message ? next : undefined) + } + // On the ground the building's foundation is set here too; switched on, it is what the floor + // stands on, in place of the footprint's own "raised on a foundation". + const building = underFloor ? buildingOfPlate(nodes, plate) : null + const designed = floorHeldByDesignedFoundation(nodes, plate) return ( + {!designed && ( + )}

- {upper ? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlate', message: 'Floor plate' }) : i18n._({ id: 'editor.panels.floorFoundationPanel.slab', message: 'Slab' })} + {upper + ? i18n._({ id: 'editor.panels.floorFoundationPanel.floorPlate', message: 'Floor plate' }) + : designed + ? i18n._({ id: 'editor.panels.floorFoundationPanel.floor', message: 'Floor' }) + : i18n._({ id: 'editor.panels.floorFoundationPanel.slab', message: 'Slab' })}

- {upper ? ( -
- previewHeight(underside + next)} - onCommit={(next) => commitHeight(underside + next)} - precision={2} - restoreOnCommit={false} - step={0.01} - unit="m" - value={plate.thickness} - /> -
+ {designed ? null : upper ? ( + ) : (
- {i18n._({ - id: 'editor.panels.floorFoundationPanel.floorTopAbove', - message: 'Floor top +{height}', - values: { height: length(floorTop) }, - })} + {upperFloorTopLabel(floorTop, length, i18n)}

)} )}
- {underFloor && ( + {building && } + {underFloor && !designed && (

{i18n._({ id: 'editor.panels.floorFoundationPanel.underTheFloor', message: 'Under the floor' })}

{ - const { ids, patch } = notice.together! - report(applyFloorFoundation(ids, patch)) + const { ids, patch, buildUp: layers } = notice.together! + report( + layers + ? applyFloorBuildUp(plate.id, layers, { together: ids }) + : applyFloorFoundation(ids, patch), + ) }} size="sm" type="button" diff --git a/packages/editor/src/components/ui/panels/floor-opening-panel.tsx b/packages/editor/src/components/ui/panels/floor-opening-panel.tsx index ca9b412197..bb2997d87e 100644 --- a/packages/editor/src/components/ui/panels/floor-opening-panel.tsx +++ b/packages/editor/src/components/ui/panels/floor-opening-panel.tsx @@ -100,11 +100,17 @@ export function FloorOpeningPanel({ openingId }: { openingId: string }) {

{owned - ? i18n._({ - id: 'editor.panels.floorOpeningPanel.areaOwned', - message: '{area} · Shape follows its owner.', - values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) }, - }) + ? opening.source === 'stair' + ? i18n._({ + id: 'editor.panels.floorOpeningPanel.areaStairResize', + message: '{area} · Drag its sides or corners to resize it; it moves with the stair.', + values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) }, + }) + : i18n._({ + id: 'editor.panels.floorOpeningPanel.areaOwned', + message: '{area} · Shape follows its owner.', + values: { area: formatAreaLabel(area([{ outer: opening.polygon, holes: [] }]), unit, 2) }, + }) : i18n._({ id: 'editor.panels.floorOpeningPanel.areaDragCorners', message: '{area} · Drag its corners to change the shape.', diff --git a/packages/editor/src/components/ui/panels/foundation-view-panel.tsx b/packages/editor/src/components/ui/panels/foundation-view-panel.tsx new file mode 100644 index 0000000000..7db4b261f3 --- /dev/null +++ b/packages/editor/src/components/ui/panels/foundation-view-panel.tsx @@ -0,0 +1,275 @@ +'use client' + +import { + type AnyNodeId, + buildingFoundationOf, + type FoundationNode, + foundationViewOf, + nodeRegistry, + type ToolOption, + traceFoundationOutlines, + useScene, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { useMemo, useState, useSyncExternalStore } from 'react' +import { + editFoundationOutline, + followWallsForFoundation, + houseFoundationOutlines, + traceFoundationFromWalls, +} from '../../../lib/foundation-view' +import { triggerSFX } from '../../../lib/sfx-bus' +import { cn } from '../../../lib/utils' +import useEditor from '../../../store/use-editor' +import { MetricControl } from '../controls/metric-control' +import { SegmentedControl } from '../controls/segmented-control' +import { Button } from '../primitives/button' +import { BuildingFoundationSection } from './building-foundation-section' +import { PanelWrapper } from './panel-wrapper' + +const IN = 0.0254 +const ACTION = 'rounded-full' +const HEADING = 'font-medium text-muted-foreground text-xs' +const NOTE = 'px-0.5 text-muted-foreground text-xs' + +/** How many footings are drawn on the level footings go on. */ +function useFootingCount(buildingId: string): number { + return useScene((state) => { + const { levelId } = foundationViewOf(state.nodes, buildingId) + const level = levelId ? state.nodes[levelId as AnyNodeId] : undefined + if (level?.type !== 'level') return 0 + return level.children.filter((id) => state.nodes[id as AnyNodeId]?.type === 'footing').length + }) +} + +/** + * Where the house's foundation outline comes from: the outside walls (it moves with them), or an + * outline of its own to drag, which "Follow the walls" gives up again. Outlines poured on their own + * (a porch slab) are not the house's and stay either way. + */ +function OutlineSection({ buildingId }: { buildingId: string }) { + const nodes = useScene((state) => state.nodes) + const own = useMemo(() => houseFoundationOutlines(nodes, buildingId).length, [nodes, buildingId]) + const traceable = useMemo( + () => traceFoundationOutlines(nodes, buildingId).length > 0, + [nodes, buildingId], + ) + return ( +

+

Outline

+ + source === 'own' ? editFoundationOutline(buildingId) : followWallsForFoundation(buildingId) + } + options={[ + { label: 'Follow the walls', value: 'walls' }, + { + label: ( + 0 ? undefined : 'Close the outside walls into a loop first, or draw one'}> + Edit it myself + + ), + value: 'own', + disabled: own === 0 && !traceable, + }, + ]} + value={own > 0 ? 'own' : 'walls'} + /> +

+ {own === 0 + ? 'Traced from the outside walls: it moves when they do.' + : 'Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be.'} +

+ {own > 0 && traceable && ( + + )} +
+ ) +} + +const NO_SUBSCRIBE = () => () => {} +const NO_VALUE = () => '' + +/** + * The Foundation tool's "Draw" choices, from the registry (the house's outline, a slab or raised + * floor of its own, a strip or pad footing): each arms the tool that draws it, and the one armed is + * lit, with what it draws and how. + */ +function DrawSection({ buildingId }: { buildingId: string }) { + const option: ToolOption | undefined = nodeRegistry + .get('foundation') + ?.toolOptions?.find((candidate) => candidate.id === 'draw') + const armed = useEditor( + (state) => state.mode === 'build' && (state.tool === 'foundation' || state.tool === 'footing'), + ) + const value = useSyncExternalStore( + option?.subscribe ?? NO_SUBSCRIBE, + option?.value ?? NO_VALUE, + option?.value ?? NO_VALUE, + ) + const footings = useFootingCount(buildingId) + if (!option) return null + const active = armed ? option.choices.find((choice) => choice.value === value) : undefined + return ( +
+

Draw

+
+ {option.choices.map((choice) => { + const selected = choice === active + return ( + + ) + })} +
+

+ {active + ? `${active.description}.${active.value === 'strip' || active.value === 'pad' ? '' : ' Click its corners; click the first again, or press Enter, to close it.'}` + : footings === 0 + ? 'Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts.' + : `${footings} ${footings === 1 ? 'footing' : 'footings'} drawn: click one in the plan to size or move it.`} +

+
+ ) +} + +const CONSTRUCTIONS: { label: string; value: FoundationNode['construction'] }[] = [ + { label: 'The house’s', value: 'building' }, + { label: 'Slab', value: 'slab' }, + { label: 'Raised floor', value: 'raised' }, +] + +const CONSTRUCTION_NOTES: Record = { + building: 'The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline.', + slab: 'A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round.', + raised: 'A raised floor on a stem wall of its own.', +} + +/** A picked foundation outline: what it pours, how far its top sits under the house's floor, and deleting it. */ +function PickedOutlineSection({ outline }: { outline: FoundationNode }) { + const imperial = useViewer((state) => state.unit) === 'imperial' + const [draft, setDraft] = useState(null) + const update = (data: Partial) => + useScene.getState().updateNode(outline.id as AnyNodeId, data) + const toShown = (meters: number) => (imperial ? meters / IN : meters) + const fromShown = (shown: number) => (imperial ? shown * IN : shown) + return ( +
+

{outline.name || 'This outline'}

+ + update(construction === 'building' ? { construction, drop: 0 } : { construction }) + } + options={CONSTRUCTIONS} + value={outline.construction} + /> +

{CONSTRUCTION_NOTES[outline.construction]}

+ {outline.construction !== 'building' && ( +
+ setDraft(next)} + onCommit={(next) => { + setDraft(null) + update({ drop: Math.max(0, fromShown(next)) }) + }} + precision={imperial ? 2 : 3} + restoreOnCommit={false} + step={imperial ? 0.5 : 0.5 * IN} + unit={imperial ? 'in' : 'm'} + value={draft ?? toShown(outline.drop)} + /> +
+ )} + +
+ ) +} + +/** + * The building's foundation: the panel of the level list's Foundation row (nothing picked in the + * foundation view), and of a picked foundation outline, whose own settings come first. Where the + * house's outline comes from (the outside walls, or one of its own to drag), what else to draw + * (slabs and raised floors of their own, strip and pad footings), then the house's foundation + * settings (on or off, type, support, section, sizes, advice). Closing it leaves the view, or + * `onClose` (an outline's panel lets the outline go). + */ +export function FoundationViewPanel({ onClose }: { onClose?: () => void } = {}) { + const buildingId = useViewer((state) => state.selection.buildingId) + const pickedId = useViewer((state) => + state.selection.selectedIds.length === 1 ? state.selection.selectedIds[0] : null, + ) + const setFoundationView = useViewer((state) => state.setFoundationView) + const building = useScene((state) => { + const node = buildingId ? state.nodes[buildingId as AnyNodeId] : undefined + return node?.type === 'building' ? node : null + }) + const picked = useScene((state) => { + const node = pickedId ? state.nodes[pickedId as AnyNodeId] : undefined + return node?.type === 'foundation' ? node : null + }) + const on = useScene((state) => + building ? buildingFoundationOf(state.nodes, building.id)?.enabled === true : false, + ) + if (!building) return null + + return ( + setFoundationView(false))} + title="Foundation" + > +
+ {picked && } + {on && } + {on && } + +
+
+ ) +} diff --git a/packages/editor/src/components/ui/panels/multi-field-value.test.ts b/packages/editor/src/components/ui/panels/multi-field-value.test.ts index dddd6c3d1a..eeefa0c2ac 100644 --- a/packages/editor/src/components/ui/panels/multi-field-value.test.ts +++ b/packages/editor/src/components/ui/panels/multi-field-value.test.ts @@ -15,6 +15,7 @@ import { import { buildMultiNodePatches, commitMultiNodeFields, + fieldTargetIds, fieldVisibleForAll, reduceFieldValue, reduceHeightBoundMode, @@ -57,6 +58,32 @@ describe('reduceFieldValue', () => { expect(reduceFieldValue(['a', 'b'], 'thickness', nodes)).toEqual({ kind: 'mixed' }) }) + test('an omitted value reads as the field default, not as zero', () => { + const nodes = { + a: makeNode('a', 'wall'), + b: makeNode('b', 'wall', { thickness: 0.1 }), + c: makeNode('c', 'wall', { thickness: 0.2 }), + } + expect(reduceFieldValue(['a', 'b'], 'thickness', nodes, 0.1)).toEqual({ + kind: 'same', + value: 0.1, + }) + expect(reduceFieldValue(['a', 'c'], 'thickness', nodes, 0.1)).toEqual({ kind: 'mixed' }) + }) + + test('a field that names the nodes it applies to edits only those', () => { + const nodes = { + a: makeNode('a', 'wall', { thickness: 0.2 }), + b: makeNode('b', 'wall', { thickness: 0.18, assembly: { layers: [] } }), + c: makeNode('c', 'wall', { thickness: 0.2 }), + } + const plain = (node: AnyNode) => !(node as { assembly?: unknown }).assembly + const targets = fieldTargetIds(['a', 'b', 'c'], plain, nodes) + expect(targets).toEqual(['a', 'c']) + expect(reduceFieldValue(targets, 'thickness', nodes)).toEqual({ kind: 'same', value: 0.2 }) + expect(fieldTargetIds(['a', 'b'], undefined, nodes)).toEqual(['a', 'b']) + }) + test('vec3 compares elementwise', () => { const nodes = { a: makeNode('a', 'item', { position: [1, 0, 2] }), diff --git a/packages/editor/src/components/ui/panels/multi-field-value.ts b/packages/editor/src/components/ui/panels/multi-field-value.ts index 11fa7828d1..2af38176c8 100644 --- a/packages/editor/src/components/ui/panels/multi-field-value.ts +++ b/packages/editor/src/components/ui/panels/multi-field-value.ts @@ -20,17 +20,23 @@ function fieldValuesEqual(a: unknown, b: unknown): boolean { return false } +/** + * The value every node holds for `key`, or mixed. `fallback` is what a node + * that omits the key shows (the field's default), so an absent value and an + * explicit default read as the same. + */ export function reduceFieldValue( nodeIds: readonly string[], key: string, nodes: Readonly>, + fallback?: unknown, ): ReducedFieldValue { let seen = false let shared: unknown for (const id of nodeIds) { const node = nodes[id] if (!node) continue - const value = (node as Record)[key] + const value = (node as Record)[key] ?? fallback if (!seen) { seen = true shared = value @@ -78,6 +84,19 @@ export function fieldVisibleForAll( return true } +/** The selected nodes a field edits: every one, unless it names those it `appliesTo`. */ +export function fieldTargetIds( + nodeIds: readonly T[], + appliesTo: ((node: AnyNode) => boolean) | undefined, + nodes: Readonly>, +): T[] { + if (!appliesTo) return [...nodeIds] + return nodeIds.filter((id) => { + const node = nodes[id] + return node ? appliesTo(node) : false + }) +} + export function firstNumericFieldValue( nodeIds: readonly string[], key: string, diff --git a/packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx b/packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx index 407bc0a80e..151388919c 100644 --- a/packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx +++ b/packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx @@ -11,15 +11,17 @@ import { import { useViewer } from '@pascal-app/viewer' import { useCallback, useMemo } from 'react' import { useShallow } from 'zustand/react/shallow' -import { getNodePanelModel } from '../../../lib/panel-rows' +import { getNodePanelModel, type MultiPanelControl } from '../../../lib/panel-rows' import { selectionMatchesSessionGroup } from '../../../lib/session-groups' import useSessionGroups from '../../../store/use-session-groups' import { PanelSection } from '../controls/panel-section' import { SegmentedControl } from '../controls/segmented-control' +import { SelectControl } from '../controls/select-control' import { SliderControl } from '../controls/slider-control' import { resolveUniqueSelectionIds } from './homogeneous-selection' import { commitMultiNodeFields, + fieldTargetIds, fieldVisibleForAll, firstNumericFieldValue, firstVec3FieldValue, @@ -88,6 +90,13 @@ export function MultiParametricInspector({ footer }: { footer?: React.ReactNode ) : null} ))} + {hasKindControls ? ( + group.label)} + nodeIds={nodeIds} + nodeType={nodeType} + /> + ) : null}
@@ -95,10 +104,20 @@ export function MultiParametricInspector({ footer }: { footer?: React.ReactNode ) } +function useMultiKindControls(nodeIds: AnyNodeId[], nodeType: AnyNode['type']) { + const nodes = useScene((s) => s.nodes) + return useMemo(() => { + const model = getNodePanelModel(nodeRegistry.get(nodeType)) + if (!model?.multiControls) return [] + const selection = nodeIds.flatMap((id) => (nodes[id] ? [nodes[id]!] : [])) + return model.multiControls({ selection, nodes }) + }, [nodeIds, nodeType, nodes]) +} + /** * The kind's own multi-selection controls for one section (the wall's - * Reference), from its panel model — the same segmented control the single - * panel shows, mixed when the selection disagrees. + * Reference), from its panel model — the same control the single panel + * shows, mixed when the selection disagrees. */ function MultiKindControls({ nodeIds, @@ -109,32 +128,84 @@ function MultiKindControls({ nodeType: AnyNode['type'] section: string }) { - const nodes = useScene((s) => s.nodes) - const controls = useMemo(() => { - const model = getNodePanelModel(nodeRegistry.get(nodeType)) - if (!model?.multiControls) return [] - const selection = nodeIds.flatMap((id) => (nodes[id] ? [nodes[id]!] : [])) - return model.multiControls({ selection, nodes }).filter((control) => control.section === section) - }, [nodeIds, nodeType, nodes, section]) + const controls = useMultiKindControls(nodeIds, nodeType).filter( + (control) => control.section === section, + ) return ( <> {controls.map((control) => ( -
-
- {control.label} -
- ({ ...option }))} - value={control.value ?? ''} - /> -
+ + ))} + + ) +} + +/** The kind's controls whose section is none of the parametrics groups, each section its own. */ +function MultiKindSections({ + groupTitles, + nodeIds, + nodeType, +}: { + groupTitles: readonly string[] + nodeIds: AnyNodeId[] + nodeType: AnyNode['type'] +}) { + const controls = useMultiKindControls(nodeIds, nodeType).filter( + (control) => !groupTitles.includes(control.section), + ) + const sections = [...new Set(controls.map((control) => control.section))] + return ( + <> + {sections.map((section) => ( + + {controls + .filter((control) => control.section === section) + .map((control) => ( + + ))} + ))} ) } +function MultiKindControlView({ control }: { control: MultiPanelControl }) { + if (control.kind === 'readout') { + return ( +
+ + {control.label} + + + {control.detail ? `${control.value} · ${control.detail}` : control.value} + +
+ ) + } + if (control.kind === 'select') { + return ( + ({ ...option }))} + value={control.value ?? ''} + /> + ) + } + return ( +
+
{control.label}
+ ({ ...option }))} + value={control.value ?? ''} + /> +
+ ) +} + function MultiGroupFields({ defaultExpanded, title, @@ -154,9 +225,16 @@ function MultiGroupFields({ }) { const genericFields = fields.filter((field) => field.kind !== 'custom') const anyVisible = useScene((s) => - genericFields.some((field) => - fieldVisibleForAll(nodeIds, (field as { visibleIf?: (n: AnyNode) => boolean }).visibleIf, s.nodes), - ), + genericFields.some((field) => { + const { visibleIf, appliesTo } = field as { + visibleIf?: (n: AnyNode) => boolean + appliesTo?: (n: AnyNode) => boolean + } + return ( + fieldVisibleForAll(nodeIds, visibleIf, s.nodes) && + fieldTargetIds(nodeIds, appliesTo, s.nodes).length > 0 + ) + }), ) if ((genericFields.length === 0 || !anyVisible) && !children) return null return ( @@ -195,7 +273,7 @@ function MultiGroupFields({ function MultiFieldRenderer({ field, - nodeIds, + nodeIds: selectedIds, parametrics, }: { field: ParamField @@ -203,11 +281,21 @@ function MultiFieldRenderer({ parametrics: ParametricDescriptor }) { const key = String(field.key) + // A field that names the nodes it `appliesTo` reads and edits only those. + const appliesTo = (field as { appliesTo?: (n: AnyNode) => boolean }).appliesTo + const nodeIds = useScene( + useShallow((s) => fieldTargetIds(selectedIds, appliesTo, s.nodes)), + ) const visible = useScene((s) => fieldVisibleForAll(nodeIds, (field as { visibleIf?: (n: AnyNode) => boolean }).visibleIf, s.nodes), ) - const reduced = useScene(useShallow((s) => reduceFieldValue(nodeIds, key, s.nodes))) - const numericOrigin = useScene((s) => firstNumericFieldValue(nodeIds, key, s.nodes)) + const fallback = field.kind === 'number' ? field.default : undefined + const reduced = useScene( + useShallow((s) => reduceFieldValue(nodeIds, key, s.nodes, fallback)), + ) + const numericOrigin = useScene((s) => + firstNumericFieldValue(nodeIds, key, s.nodes, fallback ?? 0), + ) const vecOrigin = useScene(useShallow((s) => firstVec3FieldValue(nodeIds, key, s.nodes))) const preview = useCallback( @@ -223,7 +311,7 @@ function MultiFieldRenderer({ [nodeIds, parametrics], ) - if (!visible) return null + if (!visible || nodeIds.length === 0) return null if (field.kind === 'vec3') { return ( diff --git a/packages/editor/src/components/ui/panels/panel-manager.tsx b/packages/editor/src/components/ui/panels/panel-manager.tsx index fcb5d4caea..3602065f4b 100644 --- a/packages/editor/src/components/ui/panels/panel-manager.tsx +++ b/packages/editor/src/components/ui/panels/panel-manager.tsx @@ -25,6 +25,7 @@ import { useViewer } from '@pascal-app/viewer' import { useCallback, useEffect, useMemo, useState } from 'react' import { useSelectedRoom, useZoneRoom } from '../../../hooks/use-selected-room' import { FloorOpeningPanel } from './floor-opening-panel' +import { FoundationViewPanel } from './foundation-view-panel' import { OpenRoomPanel, RoomPanel } from './room-panel' import { useIsMobile } from '../../../hooks/use-mobile' import { shouldShowEditingControls } from '../../../lib/interaction/overlay-policy' @@ -172,6 +173,21 @@ function MobilePanelLayer({ ) } +/** The foundation view's panel on a phone: its sheet open while the view is; closing it leaves the view. */ +function MobileFoundationLayer() { + const setFoundationView = useViewer((s) => s.setFoundationView) + return ( + setFoundationView(false)} + open={true} + title="Foundation" + > + + + ) +} + function MobileMultiPanelLayer({ breakdown, panel, @@ -225,6 +241,7 @@ export function PanelManager({ const selectedIds = useViewer((s) => s.selection.selectedIds) const selectedZoneId = useViewer((s) => s.selection.zoneId) const setSelection = useViewer((s) => s.setSelection) + const foundationView = useViewer((s) => s.foundationView) const selectedReferenceId = useEditor((s) => s.selectedReferenceId) const readOnly = useScene((s) => s.readOnly) // Only subscribe to the *type* of the single-selected node — string primitive @@ -307,6 +324,12 @@ export function PanelManager({ ) } + // The level list's Foundation row with nothing picked: the foundation's settings. A footing + // picked in the view shows its own panel. + if (foundationView && selectedIds.length === 0 && !selectedReferenceId) { + return isMobile ? : + } + if (isMobile) { if (selectedReferenceId) { return } /> diff --git a/packages/editor/src/components/ui/panels/parametric-field-control.tsx b/packages/editor/src/components/ui/panels/parametric-field-control.tsx index d06c6a6e98..c234e270b4 100644 --- a/packages/editor/src/components/ui/panels/parametric-field-control.tsx +++ b/packages/editor/src/components/ui/panels/parametric-field-control.tsx @@ -28,7 +28,7 @@ export function ParametricFieldControl({ switch (field.kind) { case 'number': { - const num = typeof value === 'number' ? value : 0 + const num = typeof value === 'number' ? value : (field.default ?? 0) const step = field.step ?? 0.01 const precision = precisionForStep(step) return ( diff --git a/packages/editor/src/components/ui/panels/parametric-inspector.tsx b/packages/editor/src/components/ui/panels/parametric-inspector.tsx index 266b268095..c045cac7e2 100644 --- a/packages/editor/src/components/ui/panels/parametric-inspector.tsx +++ b/packages/editor/src/components/ui/panels/parametric-inspector.tsx @@ -314,10 +314,13 @@ function FieldRenderer({ field, nodeId, onUpdate }: FieldRendererProps) { // visibleIf may consult other fields on the node — subscribe to its boolean // result so we re-evaluate when relevant. const visible = useScene((s) => { - const visibleIf = (field as { visibleIf?: (n: AnyNode) => boolean }).visibleIf - if (!visibleIf) return true + const { visibleIf, appliesTo } = field as { + visibleIf?: (n: AnyNode) => boolean + appliesTo?: (n: AnyNode) => boolean + } + if (!(visibleIf || appliesTo)) return true const n = s.nodes[nodeId] - return n ? visibleIf(n as AnyNode) : false + return n ? (visibleIf?.(n as AnyNode) ?? true) && (appliesTo?.(n as AnyNode) ?? true) : false }) if (!visible) return null diff --git a/packages/editor/src/components/ui/panels/room-construction-rows.tsx b/packages/editor/src/components/ui/panels/room-construction-rows.tsx index 0063e6314b..93658087b9 100644 --- a/packages/editor/src/components/ui/panels/room-construction-rows.tsx +++ b/packages/editor/src/components/ui/panels/room-construction-rows.tsx @@ -3,12 +3,22 @@ import { type AnyNode, type AnyNodeId, + BEAM_SIZE_PRESETS, + buildingFoundationOf, floorFootprintSupportClass, formatList, floorPlateAtGroundContact, + getLevelElevations, MIN_GROUND_FLOOR_THICKNESS, MIN_SLAB_THICKNESS, + type MessageDescriptor, + type OutdoorRoomType, + type PorchFramingSettings, + porchFramingSettings, + porchPartOf, roomDrawnFloor, + roomFoundationOf, + setZoneIntent, type PascalI18n, useLocale, usePascalI18n, @@ -19,6 +29,7 @@ import { Check, ChevronDown, ChevronRight } from 'lucide-react' import Image from 'next/image' import { type ReactNode, useMemo, useState } from 'react' import { useShallow } from 'zustand/react/shallow' +import { floorHeldByDesignedFoundation } from '../../../lib/building-foundation' import { startCeilingEdit } from '../../../lib/ceiling-edit-session' import { applyRoomFloorConstruction, @@ -54,7 +65,9 @@ import { import { ownFloorElevationBounds } from '../../../lib/room-handle-drag' import type { RoomSelectionRecord } from '../../../lib/room-selection' import { cn } from '../../../lib/utils' +import { applyRoomPlan } from '../../../lib/room-structure-commands' import { MetricControl } from '../controls/metric-control' +import { SegmentedControl } from '../controls/segmented-control' import { DropdownMenu, DropdownMenuContent, @@ -67,6 +80,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip' import { localizedFloorFootprintName } from '../../../i18n/floor-footprint-name' import { messageWithSlot } from '../../../i18n/message-slot' import { nodeLabel } from '../../../i18n/node-messages' +import { DeckBoardsFields } from './deck-boards-fields' import { RoomOpeningsField } from './room-openings-field' import { ShapeChoice } from './shape-choice' import { displayRoomName } from '../../../lib/room-name-messages' @@ -396,7 +410,8 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) { const summary = useScene( useShallow((scene) => { const plate = roomFloorOwner(scene.nodes, zoneId) ?? roomFootprint(scene.nodes, zoneId) - if (!plate) return { id: '', name: '', drawn: false, lift: 0, supported: false } + if (!plate) + return { id: '', name: '', drawn: false, lift: 0, supported: false, designed: false } if (plate.plateRole !== 'base') return { id: plate.id as string, @@ -404,6 +419,7 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) { drawn: true, lift: 0, supported: false, + designed: false, } return { id: plate.id as string, @@ -422,7 +438,12 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) { : i18n._({ id: 'editor.panels.roomConstructionRows.lowered', message: 'Lowered {lift}', values: { lift } }) : summary.supported ? i18n._({ id: 'editor.panels.roomConstructionRows.onTheWallsBelow', message: 'On the walls below' }) - : i18n._({ id: 'editor.panels.roomConstructionRows.onTheGround', message: 'On the ground' }) + : summary.designed + ? i18n._({ + id: 'editor.panels.roomConstructionRows.onTheFoundation', + message: 'On the foundation', + }) + : i18n._({ id: 'editor.panels.roomConstructionRows.onTheGround', message: 'On the ground' }) if (noFloor) return ( @@ -431,27 +452,349 @@ function SitsOn({ zoneId, noFloor }: { zoneId: string; noFloor: boolean }) { ) if (!summary.id) return null return ( - + + + ) +} + +/** + * "Raised floor | Slab": what the room's floor stands on in a raised house (`floor.foundation`, + * through core's `setZoneIntent`): on the house's raised floor, or on a slab on grade of its own + * (a garage, which its name picks), its floor coming down onto it in the same step. Shown where + * the building's designed foundation is a raised floor, or the room already chose. + */ +export function RoomFoundationChoice({ zoneId }: { zoneId: string }) { + const i18n = usePascalI18n() + const choice = useScene( + useShallow((scene) => { + const zone = scene.nodes[zoneId as AnyNodeId] + if (zone?.type !== 'zone') return null + const resolved = roomFoundationOf(scene.nodes, zone) + const building = resolved && zone.parentId ? buildingOfLevel(scene.nodes, zone.parentId) : null + const type = building ? buildingFoundationOf(scene.nodes, building)?.type : null + if (!resolved || (type !== 'raised' && zone.floor?.foundation === undefined)) return null + return { value: resolved, chosen: zone.floor?.foundation !== undefined } + }), + ) + if (!choice) return null + return ( +
+ + applyRoomPlan( + setZoneIntent(useScene.getState().nodes, { zoneId, patch: { floor: { foundation } } }), + ) + } + options={[ + { + label: i18n._({ + id: 'editor.panels.roomConstructionRows.raisedFloor', + message: 'Raised floor', + }), + value: 'raised', }, - 'name', - {summary.name}, - )} - - - + { + label: i18n._({ id: 'editor.panels.roomConstructionRows.slab', message: 'Slab' }), + value: 'slab', + }, + ]} + value={choice.value} + /> +
+ ) +} + +const ROOM_TYPE_OPTIONS: ReadonlyArray<{ + label: MessageDescriptor + value: 'room' | OutdoorRoomType +}> = [ + { + label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypeRoom', message: 'Room' }, + value: 'room', + }, + { + label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypePorch', message: 'Porch' }, + value: 'porch', + }, + { + label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypeDeck', message: 'Deck' }, + value: 'deck', + }, + { + label: /* i18n */ { id: 'editor.panels.roomConstructionRows.roomTypePatio', message: 'Patio' }, + value: 'patio', + }, +] + +const ROOM_TYPE_HINTS: Record<'room' | OutdoorRoomType, MessageDescriptor> = { + room: /* i18n */ { + id: 'editor.panels.roomConstructionRows.roomTypeRoomHint', + message: 'An ordinary room inside the house.', + }, + porch: /* i18n */ { + id: 'editor.panels.roomConstructionRows.roomTypePorchHint', + message: + 'Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s.', + }, + deck: /* i18n */ { + id: 'editor.panels.roomConstructionRows.roomTypeDeckHint', + message: + 'Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling.', + }, + patio: /* i18n */ { + id: 'editor.panels.roomConstructionRows.roomTypePatioHint', + message: 'Outdoors and open: a concrete slab 1½ in down from the house, no ceiling.', + }, +} + +/** + * "Room | Porch | Deck | Patio": the room type (`roomType`, through core's + * `setZoneIntent`). A porch, deck or patio is outdoors whatever covers it, steps its floor down + * from the house's and takes its floor and ceiling defaults in the same step; each stays + * adjustable below. + */ +export function RoomTypeChoice({ zoneId }: { zoneId: string }) { + const i18n = usePascalI18n() + const value = useScene((scene) => { + const zone = scene.nodes[zoneId as AnyNodeId] + return zone?.type === 'zone' ? (zone.roomType ?? 'room') : null + }) + if (!value) return null + return ( +
+

+ {i18n._({ id: 'editor.panels.roomConstructionRows.roomType', message: 'Room type' })} +

+ + applyRoomPlan( + setZoneIntent(useScene.getState().nodes, { + zoneId, + patch: { roomType: next === 'room' ? null : next }, + }), + ) + } + options={ROOM_TYPE_OPTIONS.map((option) => ({ + label: i18n._(option.label), + value: option.value, + }))} + value={value} + /> +

{i18n._(ROOM_TYPE_HINTS[value])}

+
+ ) +} + +const POST_SIZE_OPTIONS: ReadonlyArray<{ + label: string + value: PorchFramingSettings['postSize'] +}> = [ + { label: '4x4', value: '4x4' }, + { label: '6x6', value: '6x6' }, + { label: '8x8', value: '8x8' }, +] + +type PorchFramingMode = 'off' | 'auto' | 'manual' + +/** + * A covered porch's posts and beam (`porchFraming`, through core's `setZoneIntent`), and a deck's + * or patio's once switched on, to carry a roof over it (Off by default): while Automatic the + * structure kernel keeps a post on each outside corner, more at most the spacing apart, and a beam + * along each open side under the ceiling. A size or the spacing rebuilds them; a post or beam + * edited by hand turns the room to Manual, and Automatic rebuilds them again. + */ +export function PorchFramingFields({ zoneId }: { zoneId: string }) { + const i18n = usePascalI18n() + const porch = useScene( + useShallow((scene) => { + const zone = scene.nodes[zoneId as AnyNodeId] + if (zone?.type !== 'zone') return null + const optional = zone.roomType === 'deck' || zone.roomType === 'patio' + if (!(optional || (zone.roomType === 'porch' && zone.hasCeiling !== false))) return null + const level = zone.parentId ? scene.nodes[zone.parentId as AnyNodeId] : undefined + let posts = 0 + let beams = 0 + for (const id of level?.type === 'level' ? level.children : []) { + const node = scene.nodes[id as AnyNodeId] + if (porchPartOf(node)?.porchOf !== zoneId) continue + if (node?.type === 'column') posts++ + else beams++ + } + const auto = zone.porchFraming?.auto + const mode: PorchFramingMode = + auto === false ? 'manual' : optional && auto !== true ? 'off' : 'auto' + return { + mode, + optional, + room: zone.roomType ?? 'porch', + ...porchFramingSettings(zone), + posts, + beams, + } + }), ) + const [spacing, setSpacing] = useState(null) + if (!porch) return null + const set = (porchFraming: NonNullable[1]['patch']['porchFraming']>) => + applyRoomPlan(setZoneIntent(useScene.getState().nodes, { zoneId, patch: { porchFraming } })) + const beam = BEAM_SIZE_PRESETS.find((preset) => preset.id === porch.beamSize) + return ( +
+

+ {i18n._({ id: 'editor.panels.roomConstructionRows.postsAndBeam', message: 'Posts and beam' })} +

+ + set({ auto: next === 'off' ? null : next === 'auto' }) + } + options={[ + ...(porch.optional + ? [ + { + label: i18n._({ + id: 'editor.panels.roomConstructionRows.framingOff', + message: 'Off', + }), + value: 'off' as const, + }, + ] + : []), + { + label: i18n._({ + id: 'editor.panels.roomConstructionRows.framingAutomatic', + message: 'Automatic', + }), + value: 'auto' as const, + }, + { + label: i18n._({ + id: 'editor.panels.roomConstructionRows.framingManual', + message: 'Manual', + }), + value: 'manual' as const, + }, + ]} + value={porch.mode} + /> +

+ {porch.mode === 'off' + ? i18n._({ + id: 'editor.panels.roomConstructionRows.framingOffHint', + message: + 'None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}.', + values: { room: porch.room }, + }) + : porch.mode === 'auto' + ? i18n._({ + id: 'editor.panels.roomConstructionRows.framingAutoHint', + message: + '{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}.', + values: { + posts: porch.posts, + postCount: String(porch.posts), + beams: porch.beams, + beamCount: String(porch.beams), + room: porch.room, + }, + }) + : i18n._({ + id: 'editor.panels.roomConstructionRows.framingManualHint', + message: 'Kept as you edited them. Automatic rebuilds them from these settings.', + })} +

+ {porch.mode !== 'off' && ( + <> + + set({ postSize })} + options={[...POST_SIZE_OPTIONS]} + value={porch.postSize} + /> + + { + setSpacing(null) + if (Math.abs(value - porch.postSpacing) > 1e-6) set({ postSpacing: value }) + }} + precision={2} + step={0.05} + unit="m" + value={spacing ?? porch.postSpacing} + /> + + + + + + + {BEAM_SIZE_PRESETS.map((preset) => ( + { + if (preset.id !== porch.beamSize) set({ beamSize: preset.id }) + }} + > + {preset.label} + {preset.id === porch.beamSize && } + + ))} + + + + + )} +
+ ) +} + +/** The building a level stands in. */ +function buildingOfLevel(nodes: Record, levelId: string): string | null { + return getLevelElevations(nodes).get(levelId as AnyNodeId)?.buildingId ?? null } /** @@ -520,7 +863,7 @@ export function RoomFloorConstructionField({ zoneId }: { zoneId: string }) { * How a footprint stands: on the ground, its foundation height ("Raised 0.30 m" * or on the ground); upstairs, its lift above resting on the walls below. */ -function footprintStanding( +export function footprintStanding( nodes: Record, plate: import('@pascal-app/core').SlabNode, ) { @@ -528,6 +871,8 @@ function footprintStanding( return { supported, lift: supported ? floorLift(nodes, plate) : foundationHeight(nodes, plate), + // The building's designed foundation carries a floor on the ground. + designed: floorHeldByDesignedFoundation(nodes, plate), } } @@ -733,6 +1078,9 @@ export function RoomConstructionRows({ room }: { room: RoomSelectionRecord }) { return (
+ + +

{i18n._({ id: 'editor.panels.roomConstructionRows.builtWith', message: 'Built with' })}

@@ -100,9 +101,7 @@ export function WallPaintRegionList({ wallId }: { wallId: string }) {
) })} - {capNotice && ( -
{capNotice}
- )} + {capNotice &&
{capNotice}
}
diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx index 7c328634bd..85116b532c 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx @@ -73,6 +73,7 @@ import { Switch } from './../../../../../components/ui/primitives/switch' import { cn } from './../../../../../lib/utils' import { refusedObjectsNotice } from './../../../../../components/editor/group-actions' import { addStoreyBelow, addStoreyOnTop } from './../../../../../lib/add-storey' +import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt' import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection' import { bringBuildArtifacts } from './../../../../../lib/scene-clipboard' import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor' @@ -423,10 +424,12 @@ function FloorSettingsSection() { const addFloor = () => { const newLevel = addStoreyOnTop(building.id) setSelection({ buildingId: building.id, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) } const addBasement = () => { const newLevel = addStoreyBelow(building.id) setSelection({ buildingId: building.id, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) } return ( diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx index 58bff43ee0..d02dfe79bf 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx @@ -12,9 +12,10 @@ import { ShortcutToken, shortcutDisplayValue, } from './../../../../../components/ui/primitives/shortcut-token' +import { TOOL_SHORTCUT_KEYS } from './../../../../../lib/tool-shortcuts' type Shortcut = { - keys: string[] + keys: readonly string[] action: string note?: string } @@ -31,8 +32,8 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { keys: ['1'], action: 'Switch to Site' }, { keys: ['2'], action: 'Switch to Building' }, { keys: ['\\'], action: 'Open or close the split view' }, - { keys: ['F'], action: 'Open the items catalog with the item tool' }, - { keys: ['Z'], action: 'Draw a zone' }, + { keys: TOOL_SHORTCUT_KEYS.item, action: 'Open the items catalog with the item tool' }, + { keys: TOOL_SHORTCUT_KEYS.zone, action: 'Draw a zone' }, { keys: ['Cmd/Ctrl', 'Arrow Up'], action: 'Select next level in the active building', @@ -47,10 +48,12 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { title: 'Modes & History', shortcuts: [ - { keys: ['V'], action: 'Switch to Select mode' }, - { keys: ['B'], action: 'Switch to Build mode' }, - { keys: ['M'], action: 'Activate the last measurement tool' }, - { keys: ['X'], action: 'Switch to Delete mode' }, + { keys: TOOL_SHORTCUT_KEYS.select, action: 'Switch to Select mode' }, + { keys: TOOL_SHORTCUT_KEYS.wall, action: 'Switch to Build mode, drawing a rectangle room' }, + { keys: TOOL_SHORTCUT_KEYS.measurement, action: 'Activate the last measurement tool' }, + { keys: TOOL_SHORTCUT_KEYS.paint, action: 'Switch to Paint mode' }, + { keys: TOOL_SHORTCUT_KEYS.terrain, action: 'Sculpt the ground (terrain)' }, + { keys: TOOL_SHORTCUT_KEYS.delete, action: 'Switch to Delete mode' }, { keys: ['Esc'], action: 'Cancel the active tool and return to Select mode', @@ -62,6 +65,33 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { keys: ['Cmd/Ctrl', 'S'], action: 'Save' }, ], }, + { + title: 'Build Tools', + shortcuts: [ + { + keys: TOOL_SHORTCUT_KEYS['exterior-wall'], + action: 'Draw exterior walls', + note: 'Draws with the Walls shape, each wall with the exterior wall type picked in the Build panel. E alone still opens or closes the selected door or window.', + }, + { + keys: TOOL_SHORTCUT_KEYS['interior-wall'], + action: 'Draw interior walls', + note: 'Draws with the Walls shape, each wall with the interior wall type picked in the Build panel.', + }, + { + keys: TOOL_SHORTCUT_KEYS.door, + action: 'Place a door', + note: 'D alone pans the view. While placing, the door swings toward the side of the wall the cursor is on; R flips it.', + }, + { keys: TOOL_SHORTCUT_KEYS.window, action: 'Place a window', note: 'W alone pans the view.' }, + { keys: TOOL_SHORTCUT_KEYS.stair, action: 'Place stairs', note: 'S alone pans the view.' }, + { + keys: TOOL_SHORTCUT_KEYS.roof, + action: 'Draw a roof', + note: "R alone still rotates or flips the selection, and while placing it stays the placement's own R.", + }, + ], + }, { title: 'Selection', shortcuts: [ @@ -78,8 +108,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { keys: ['Cmd/Ctrl', 'V'], action: 'Paste and place copied objects', - note: - 'Carries a preview under the cursor. Click to place it, or press Escape to cancel.', + note: 'Carries a preview under the cursor. Click to place it, or press Escape to cancel.', }, { keys: ['Cmd/Ctrl', 'Left click'], @@ -92,10 +121,9 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ note: 'In the scene graph, Shift-click selects the visible range like a file browser.', }, { - keys: ['Left click'], + keys: ['Left drag'], action: 'Move the whole multi-selection', - note: - 'With 2+ objects selected, in 2D and 3D alike: drag the selection (or its dashed box) to slide it; click it to pick it up and place with the next click.', + note: 'With 2+ objects selected, in 2D and 3D alike: drag the selection (or its dashed box) to slide it. A click alone never starts a move; use Move in the action menu to pick it up.', }, { keys: ['R', 'T'], @@ -105,14 +133,23 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { keys: ['Cmd/Ctrl', 'G'], action: 'Group the multi-selection (session only)', - note: - 'Editor-only. Plain click a member later to reselect the whole group. Not saved with the project.', + note: 'Editor-only. Plain click a member later to reselect the whole group. Not saved with the project.', }, { keys: ['Cmd/Ctrl', 'Shift', 'G'], action: 'Ungroup the session selection', note: 'Keeps the current selection; only dissolves the session group.', }, + { + keys: ['Arrow keys'], + action: 'Nudge the selected wall 1 in (1 cm in metric)', + note: 'In the floor plan. Stops where it lines up with a parallel wall; joined walls follow.', + }, + { + keys: ['Tab'], + action: 'Select the next object under the pointer', + note: 'In the floor plan, for walls that overlap or hide under another. Shift+Tab goes back.', + }, { keys: ['Esc'], action: 'Clear the selection', @@ -177,7 +214,8 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ }, { keys: ['E'], - action: 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed', + action: + 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed', }, { keys: ['Shift'], @@ -192,7 +230,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { keys: ['W', 'A', 'S', 'D'], action: 'Pan camera', - note: 'Moves in screen space, similar to dragging the camera view.', + note: 'Moves in screen space, similar to dragging the camera view. With Shift, W, S and D place a window, stairs and a door instead.', }, { keys: ['Middle click'], @@ -208,7 +246,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ }, ] -function ShortcutKeys({ keys }: { keys: string[] }) { +function ShortcutKeys({ keys }: { keys: readonly string[] }) { return (
{keys.map((key, index) => ( diff --git a/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx b/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx index 0626fb399e..e4cc2e5c36 100644 --- a/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx @@ -58,6 +58,7 @@ import { type LevelDuplicatePreset, } from '@pascal-app/core/building' import { addStoreyOnTop } from './../../../../../lib/add-storey' +import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt' import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection' import { formatAreaLabel, @@ -1068,6 +1069,7 @@ const LevelsSection = memo(function LevelsSection({ const handleAddLevel = () => { const newLevel = addStoreyOnTop(building.id) setSelection({ buildingId: building.id, levelId: newLevel.id }) + offerExteriorWallCopy(newLevel.id) } return ( diff --git a/packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx b/packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx index 2b9ca1c07b..ae7c3c1bc2 100644 --- a/packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx @@ -2,8 +2,9 @@ import { type AnyNodeId, usePascalI18n, useScene } from '@pascal-app/core' import { Pencil } from 'lucide-react' import { memo, useCallback, useEffect, useRef, useState } from 'react' import { displayRoomName } from './../../../../../lib/room-name-messages' +import { renameRoom } from './../../../../../lib/room-structure-commands' import { cn } from './../../../../../lib/utils' -import { commitTreeRoomRename } from './../../../panels/room-name-options' +import { commitRoomNameEdit } from './../../../panels/room-name-options' interface InlineRenameFieldProps { name: string | undefined @@ -24,7 +25,10 @@ type InlineRenameInputProps = Omit editableName?: (name: string | undefined) => string | undefined } -/** Renames a scene node through `updateNode`. */ +/** + * Renames a scene node through `updateNode`; a room through its intent (`renameRoom`), so a name + * carries what it asks of the room (a garage stands on a slab) as the room panel's does. + */ export const InlineRenameInput = memo(function InlineRenameInput({ nodeId, editableName, @@ -33,9 +37,11 @@ export const InlineRenameInput = memo(function InlineRenameInput({ const updateNode = useScene((s) => s.updateNode) const stored = useScene((s) => s.nodes[nodeId]?.name) const name = editableName ? editableName(stored) : stored + const isRoom = useScene((s) => s.nodes[nodeId]?.type === 'zone') const handleRename = useCallback( - (next: string | undefined) => updateNode(nodeId, { name: next }), - [nodeId, updateNode], + (next: string | undefined) => + isRoom ? renameRoom(nodeId, next ?? '') : updateNode(nodeId, { name: next }), + [isRoom, nodeId, updateNode], ) return }) @@ -44,6 +50,8 @@ export const InlineRenameInput = memo(function InlineRenameInput({ * Renames a room or zone from the layer tree. The field edits the name in the * UI language, as the room panel shows it, and stores what an edit there * stores: a catalog name stays English ("Salon 3" on a Lounge stores "Lounge 3"). + * Like the room panel's, it writes through the room's intent (`renameRoom`), so + * a name carries what it asks of the room (a garage stands on a slab). */ export const RoomRenameInput = memo(function RoomRenameInput({ nodeId, @@ -52,7 +60,14 @@ export const RoomRenameInput = memo(function RoomRenameInput({ const i18n = usePascalI18n() const stored = useScene((s) => s.nodes[nodeId]?.name) const handleRename = useCallback( - (next: string | undefined) => commitTreeRoomRename(nodeId, next, i18n), + (next: string | undefined) => { + if (next === undefined) { + renameRoom(nodeId, '') + return + } + const name = useScene.getState().nodes[nodeId]?.name ?? '' + commitRoomNameEdit(nodeId, name, displayRoomName(name, i18n), next, i18n) + }, [nodeId, i18n], ) return ( diff --git a/packages/editor/src/hooks/use-keyboard.test.ts b/packages/editor/src/hooks/use-keyboard.test.ts index 606535ed82..55bc7f1404 100644 --- a/packages/editor/src/hooks/use-keyboard.test.ts +++ b/packages/editor/src/hooks/use-keyboard.test.ts @@ -18,6 +18,13 @@ import { useSidebarStore } from '../components/ui/primitives/sidebar' import { runRedo, runUndo } from '../lib/history' import { meshEditScope } from '../lib/interaction/scope' import { setSidebarTabIds } from '../lib/sidebar-panel' +import { + armToolShortcut, + subscribeWallTypeOverrideReset, + toolShortcutForKey, + withToolShortcut, +} from '../lib/tool-shortcuts' +import { getWallTypeOverride } from '../lib/wall-draw-variant' import useEditor from '../store/use-editor' import useInteractionScope from '../store/use-interaction-scope' import { @@ -164,6 +171,14 @@ describe('rotation shortcut ownership', () => { expect(isToolOwnedRotation()).toBe(true) }) + test('leaves R and T, and so Shift+R, to the stair and elevator placement tools', () => { + useEditor.getState().armToolMode({ mode: 'build', tool: 'stair' }) + expect(isToolOwnedRotation()).toBe(true) + + useEditor.getState().armToolMode({ mode: 'build', tool: 'elevator' }) + expect(isToolOwnedRotation()).toBe(true) + }) + test('does not reserve R for the wall tool: its shape is picked in the Build panel', () => { useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' }) @@ -473,3 +488,100 @@ describe('B and P open their panel', () => { expect(useEditor.getState().activeSidebarPanel).toBe('build') }) }) + +describe('drafting tool shortcuts', () => { + const press = (code: string, mods: Partial = {}) => + toolShortcutForKey({ + code, + shiftKey: false, + metaKey: false, + ctrlKey: false, + altKey: false, + repeat: false, + ...mods, + }) + + afterEach(() => { + setSidebarTabIds([]) + useInteractionScope.getState().endIf(() => true) + useEditor.getState().setContinuation('wall', 'room') + useEditor.getState().armToolMode({ mode: 'select' }) + }) + + test('keep the bare keys they would collide with: pan, rotate and operate', () => { + expect(press('KeyD', { shiftKey: true })).toBe('door') + expect(press('KeyW', { shiftKey: true })).toBe('window') + expect(press('KeyS', { shiftKey: true })).toBe('stair') + expect(press('KeyR', { shiftKey: true })).toBe('roof') + expect(press('KeyE', { shiftKey: true })).toBe('exterior-wall') + expect(press('KeyI')).toBe('interior-wall') + for (const code of ['KeyD', 'KeyW', 'KeyS', 'KeyR', 'KeyE', 'KeyA']) + expect(press(code)).toBeNull() + expect(press('KeyD', { shiftKey: true, ctrlKey: true })).toBeNull() + expect(press('KeyD', { shiftKey: true, repeat: true })).toBeNull() + }) + + test('stand down while a gesture holds Shift', () => { + useInteractionScope.getState().begin({ kind: 'box-select' }) + expect(press('KeyD', { shiftKey: true })).toBeNull() + expect(press('KeyI')).toBe('interior-wall') + }) + + test('arm their tool with the Build panel showing, and name their key in tooltips', () => { + setSidebarTabIds(['build', 'items']) + useEditor.getState().setActiveSidebarPanel('items') + for (const [id, tool] of [ + ['door', 'door'], + ['window', 'window'], + ['stair', 'stair'], + ['roof', 'roof'], + ] as const) { + armToolShortcut(id) + expect(useEditor.getState().mode).toBe('build') + expect(useEditor.getState().tool).toBe(tool) + expect(useEditor.getState().activeSidebarPanel).toBe('build') + } + expect(withToolShortcut('Door', 'door')).toBe('Door (Shift+D)') + expect(withToolShortcut('Wall', 'wall')).toBe('Wall (B)') + expect(withToolShortcut('Fence', 'fence')).toBe('Fence') + }) + + test('exterior and interior walls draw single walls of one type until the wall tool is left', () => { + const unsubscribe = subscribeWallTypeOverrideReset() + try { + armToolShortcut('interior-wall') + expect(useEditor.getState().tool).toBe('wall') + expect(useEditor.getState().getContinuation('wall')).toBe('single') + expect(getWallTypeOverride()).toBe('interior') + armToolShortcut('exterior-wall') + expect(getWallTypeOverride()).toBe('exterior') + armWallToolFromShortcut() + expect(getWallTypeOverride()).toBeNull() + armToolShortcut('interior-wall') + useEditor.getState().armToolMode({ mode: 'select' }) + expect(getWallTypeOverride()).toBeNull() + } finally { + unsubscribe() + } + }) + + test('leave the saved Rooms shape as it was once their walls end', () => { + const unsubscribe = subscribeWallTypeOverrideReset() + try { + useEditor.getState().setContinuation('wall', 'rectangle') + armToolShortcut('interior-wall') + armToolShortcut('exterior-wall') + expect(useEditor.getState().getContinuation('wall')).toBe('single') + useEditor.getState().armToolMode({ mode: 'select' }) + expect(useEditor.getState().getContinuation('wall')).toBe('rectangle') + + // A shape picked while drawing is the user's choice, and stays. + armToolShortcut('interior-wall') + useEditor.getState().setContinuation('wall', 'room') + useEditor.getState().armToolMode({ mode: 'select' }) + expect(useEditor.getState().getContinuation('wall')).toBe('room') + } finally { + unsubscribe() + } + }) +}) diff --git a/packages/editor/src/hooks/use-keyboard.ts b/packages/editor/src/hooks/use-keyboard.ts index a6b2cf9c19..34bc9c0c38 100644 --- a/packages/editor/src/hooks/use-keyboard.ts +++ b/packages/editor/src/hooks/use-keyboard.ts @@ -34,12 +34,19 @@ import { isActive } from '../lib/interaction/scope' import { paintRegionModeActive } from '../lib/paint-region-mode' import { popRoomSelection } from '../lib/room-selection-commands' import { copySelectedNodesToEditorClipboard } from '../lib/scene-clipboard' +import { cycleSelectionAtPointer, trackSelectionCyclePointer } from '../lib/selection-cycle' import { sfxEmitter } from '../lib/sfx-bus' import { openSidebarPanel } from '../lib/sidebar-panel' import { activeSiteNode, clampBrushRadius } from '../lib/terrain-sculpt' +import { + armToolShortcut, + subscribeWallTypeOverrideReset, + toolShortcutForKey, +} from '../lib/tool-shortcuts' import { isTypingTarget } from '../lib/typing-target' import { leaveUnitFocus } from '../lib/units' import { selectWallDrawVariant } from '../lib/wall-draw-variant' +import { isWallNudgeKey, nudgeSelectedWall } from '../lib/wall-nudge' import useDeleteConfirmation from '../store/use-delete-confirmation' import useEditor, { getActiveContinuationContext, getActiveSnapContext } from '../store/use-editor' import useInteractionScope, { @@ -49,6 +56,14 @@ import useInteractionScope, { import { groupCurrentSelection, ungroupCurrentSelection } from '../store/use-session-groups' import { activeViewLayout } from '../store/view-layout' +const pickKeyFields = (e: KeyboardEvent) => ({ + code: e.code, + shiftKey: e.shiftKey, + metaKey: e.metaKey, + ctrlKey: e.ctrlKey, + altKey: e.altKey, +}) + export function hasSceneKeyboardFocus(): boolean { const layout = activeViewLayout(useEditor.getState()) return isSceneView(layout.panes[layout.focus]) @@ -257,6 +272,8 @@ export const isToolOwnedRotation = () => { (editor.tool === 'door' || editor.tool === 'window' || editor.tool === 'roof' || + editor.tool === 'stair' || + editor.tool === 'elevator' || // The item tool is mounted for the build mode, but it only owns R/T // when a catalog item is actually selected and a placement draft can // exist. Without this check, selecting an existing item in the 2D plan @@ -312,6 +329,7 @@ export const useKeyboard = ({ if (disabled) { return } + trackSelectionCyclePointer() // True while an active placement tool owns R/T. Door/window tools flip the // draft, item / lean-to placement rotates its draft, and the roof tool turns @@ -467,6 +485,19 @@ export const useKeyboard = ({ } } + // Drafting tools on a key: I, and Shift with D / W / S / R / E where the + // bare key pans, rotates or operates (lib/tool-shortcuts). An armed + // placement keeps Shift+R as its own R. + const toolShortcut = isVersionPreviewMode + ? null + : toolShortcutForKey({ ...pickKeyFields(e), repeat: false }) + if (toolShortcut && !(toolShortcut === 'roof' && isToolOwnedRotation())) { + e.preventDefault() + // A held chord repeats: arm once, and never let a repeat fall through to bare R / E. + if (!e.repeat) armToolShortcut(toolShortcut) + return + } + if (e.key === 'Escape') { e.preventDefault() @@ -585,6 +616,31 @@ export const useKeyboard = ({ } } } + } else if (isWallNudgeKey(e.key) && !e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey) { + // Bare arrows nudge a selected wall an inch in the floor plan. + const editor = useEditor.getState() + if ( + isVersionPreviewMode || + editor.mode !== 'select' || + useInteractionScope.getState().scope.kind !== 'idle' || + editor.isFirstPersonMode + ) { + return + } + if (nudgeSelectedWall(e.key)) e.preventDefault() + } else if (e.key === 'Tab' && !e.metaKey && !e.ctrlKey && !e.altKey) { + // Tab steps the selection to the next object under the pointer in the + // floor plan (Shift+Tab back): a wall hidden under another is reachable. + const editor = useEditor.getState() + if ( + isVersionPreviewMode || + editor.mode !== 'select' || + useInteractionScope.getState().scope.kind !== 'idle' || + editor.isFirstPersonMode + ) { + return + } + if (cycleSelectionAtPointer(e.shiftKey)) e.preventDefault() } else if ( (e.key === 'r' || e.key === 'R') && !e.metaKey && @@ -600,7 +656,7 @@ export const useKeyboard = ({ // open/close toggle lives on E. Windows still use R to toggle // their open/closed state. // - // Skipped while an item, door, window, or roof placement owns rotation. + // Skipped while a placement tool (item, door, window, roof, stair…) owns rotation. // The user can still have a node selected during placement; without this // guard both the draft and the selection would rotate. // @@ -847,7 +903,9 @@ export const useKeyboard = ({ window.addEventListener('keydown', handleSessionGroupKeyDown, true) window.addEventListener('keydown', handleKeyDown) window.addEventListener('keyup', handleKeyUp) + const unsubscribeWallTypeOverride = subscribeWallTypeOverrideReset() return () => { + unsubscribeWallTypeOverride() window.removeEventListener('keydown', handleSessionGroupKeyDown, true) window.removeEventListener('keydown', handleKeyDown) window.removeEventListener('keyup', handleKeyUp) diff --git a/packages/editor/src/hooks/use-selected-room.ts b/packages/editor/src/hooks/use-selected-room.ts index 547772ac0f..3e042e0e0b 100644 --- a/packages/editor/src/hooks/use-selected-room.ts +++ b/packages/editor/src/hooks/use-selected-room.ts @@ -1,8 +1,9 @@ 'use client' -import { type AnyNode, type AnyNodeId, useScene } from '@pascal-app/core' +import { type AnyNode, type AnyNodeId, nodeRegistry, useScene } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' import { useCallback, useSyncExternalStore } from 'react' +import { getFloorplanNodeExtension } from '../lib/floorplan/floorplan-extension' import { selectionEnabled } from '../lib/interaction/scope' import { type RoomKey, @@ -111,13 +112,18 @@ export function resolveEditorRoomHit( return room?.key ?? null } -/** Rooms pick inside the building; from site a click picks the whole building. */ +/** + * Rooms pick inside the building; from site a click picks the whole building. The foundation view + * shows no rooms: a click there picks a footing or nothing. + */ export function roomPickingEnabled() { const editor = useEditor.getState() + const viewer = useViewer.getState() return ( editor.phase !== 'site' && editor.mode === 'select' && - !useViewer.getState().focusedUnitId && + !viewer.focusedUnitId && + !viewer.foundationView && selectionEnabled(useInteractionScope.getState().scope) ) } @@ -134,8 +140,18 @@ export function hoverRoomFromHit( return !!room } +/** + * The room a plan hit picks first. A kind whose plan extension declares + * `planHitSelectsNode` (a wall: a thin band between two rooms whose interiors + * are clickable in their own right) keeps the hit: picking a room by which + * half of the band the pointer landed on made thin partitions take several + * clicks. 3D keeps them room-first, where a room's floor is often hidden + * behind its walls. + */ export function resolvePlanRoomHit(nodeId: string | null, point: readonly [number, number]) { const node = nodeId ? useScene.getState().nodes[nodeId as AnyNodeId] : null + if (node && getFloorplanNodeExtension(nodeRegistry.get(node.type))?.planHitSelectsNode) + return null const levelId = node?.parentId ?? useViewer.getState().selection.levelId return levelId ? resolveEditorRoomHit(node ?? null, levelId, point, '2d') : null } diff --git a/packages/editor/src/index.tsx b/packages/editor/src/index.tsx index 4f0d004463..d5dbdd26c5 100644 --- a/packages/editor/src/index.tsx +++ b/packages/editor/src/index.tsx @@ -127,12 +127,15 @@ export { } from './components/editor/node-arrow-handles' export { QuickMeasurementCard } from './components/editor/quick-measurement-card' export { SelectionManager } from './components/editor/selection-manager' +export { SeparatorEndGrip } from './components/editor/separator-end-grip' export { type SnapshotCameraData, ThumbnailGenerator, } from './components/editor/thumbnail-generator' export { PLUGINS_VIEW_ID } from './components/editor/views/use-editor-views' export { WallMoveSideHandles } from './components/editor/wall-move-side-handles' +export { WallPushArrow } from './components/editor/wall-push-arrow' +export { useWallsBelowSetting } from './components/editor-2d/floorplan-adjacent-walls-layer' export { useFloorplanRender } from './components/editor-2d/floorplan-render-context' export { FloorplanDimensionRenderer } from './components/editor-2d/renderers/floorplan-dimension-renderer' export { @@ -153,6 +156,7 @@ export { buildSvgArrowHeadPoints, getArcPlanPoint, } from './components/editor-2d/svg-paths' +export { useCeilingsSetting } from './components/systems/ceiling/ceiling-system' export type { SelectionAffordanceHistoryApi, SelectionAffordanceInteractionApi, @@ -183,7 +187,10 @@ export { usePlacementCoordinator, } from './components/tools/item/use-placement-coordinator' export { MoveRegistryNodeTool } from './components/tools/registry/move-registry-node-tool' -export { useRegistryToolContext } from './components/tools/registry-tool-context' +export { + RegistryToolProvider, + useRegistryToolContext, +} from './components/tools/registry-tool-context' export { CursorSphere } from './components/tools/shared/cursor-sphere' export { DRAFT_LABEL_Y_OFFSET, @@ -224,6 +231,7 @@ export { export { preloadRegistryToolModules, ToolManager } from './components/tools/tool-manager' export { chainEndJoinsExistingWall, + createInvisibleWallOnCurrentLevel, createWallOnCurrentLevel, findWallSnapTarget, getSegmentGridStep, @@ -241,6 +249,21 @@ export { type WallPlanPoint, type WallSnapRadii, } from './components/tools/wall/wall-drafting' +export { + lockWallPullDirection, + snapWallPullPoint, + WALL_PULL_ANGLE_STEP, + WALL_PULL_LOCK_TOLERANCE, + WALL_PULL_SNAP_LABELS, + WALL_PULL_SNAP_RADII, + type WallPullDirectionLock, + type WallPullGuide, + type WallPullGuideKind, + type WallPullSnapInput, + type WallPullSnapKind, + type WallPullSnapRadii, + type WallPullSnapResult, +} from './components/tools/wall/wall-pull-snap' // `ToolbarLeft` / `ToolbarRight` alias `ViewerToolbarLeft` / `ViewerToolbarRight` // (same components) under the names headless hosts compose their toolbars with. export { @@ -286,6 +309,7 @@ export { CATALOG_ITEMS } from './components/ui/item-catalog/catalog-items' // A base plate's panel (one connected footprint's floor and foundation); the // slab kind's panel hands base plates to it. export { FloorFoundationPanel } from './components/ui/panels/floor-foundation-panel' +export { FoundationViewPanel } from './components/ui/panels/foundation-view-panel' export { resolveHomogeneousSelection, resolveUniqueSelectionIds, @@ -534,6 +558,7 @@ export { type FloorplanSchedule, type FloorplanToolContext, type FloorplanToolMode, + type FloorplanWallDimensionFaces, floorplanGeometryMetadata, getFloorplanNodeExtension, readFloorplanContext, @@ -610,6 +635,15 @@ export { streetCore, streetEdgeNames, } from './lib/floorplan/site-plan' +export { + armFootingTool, + armFoundationTool, + editFoundationOutline, + enterFoundationView, + followWallsForFoundation, + houseFoundationOutlines, + traceFoundationFromWalls, +} from './lib/foundation-view' export { commitFreshPlacementSubtree, createFreshPlacementSubtree, @@ -663,6 +697,11 @@ export { scopeNodeId, } from './lib/interaction/scope' export { isEditableKeyboardTarget } from './lib/keyboard-pan' +export { + formatLayerDepth, + formatLayerThickness, + parseLayerThickness, +} from './lib/layer-thickness' export { selectPlacedNode } from './lib/level-selection' // Lot drop-in — address → parcel → streets → front edge → setbacks, on the // site node, through the parcel provider the host sets. @@ -757,6 +796,7 @@ export { } from './lib/paint-scope' export { getNodePanelModel, + type MultiPanelControl, type NodePanelModel, PANEL_MODEL_EXTENSION, type PanelAction, @@ -817,7 +857,13 @@ export { clearRoofDuplicateMetadata, duplicateRoofSubtree } from './lib/roof-dup // kind-owned door / window tools in `@pascal-app/nodes` and the item // placement coordinator's roof-wall strategy. export { hasRoofFaceChildOverlap, type RoofWallHit, resolveRoofWallHit } from './lib/roof-wall-hit' -export { WALL_PUSH_AFFORDANCE, type WallPushArrowPayload } from './lib/room-handle-drag' +export { + selectedWallDimensions, + separatorFloorElevation, + separatorPushHandles, + WALL_PUSH_AFFORDANCE, + type WallPushArrowPayload, +} from './lib/room-handle-drag' export { displayRoomName } from './lib/room-name-messages' export type { RoomKey, RoomSelectionRecord } from './lib/room-selection' export { popRoomSelection, selectRoom } from './lib/room-selection-commands' @@ -835,6 +881,11 @@ export { remapSceneMaterialRefs, sceneMaterialUsageCounts, } from './lib/scene-material-refs' +export { + beginSeparatorEndDrag, + runSeparatorEndDrag, + type SeparatorEndDrag, +} from './lib/separator-edit' export { movementSfxStepKey } from './lib/sfx/movement-tick' export { emitDeleteSFX, triggerSFX } from './lib/sfx-bus' export { playSFX, type SFXName, type SFXPlaybackOptions } from './lib/sfx-player' @@ -893,6 +944,12 @@ export { sculptFieldForSite, terrainPointInsideSite, } from './lib/terrain-sculpt' +export { + armWallLineTool, + armWallTypeTool, + wallVariantTooltip, + withToolShortcut, +} from './lib/tool-shortcuts' export { isTypingTarget } from './lib/typing-target' export { enterUnitFocus } from './lib/units' export { exportSceneToUsdz, type UsdzExportOptions } from './lib/usdz-export' @@ -912,11 +969,31 @@ export { type ViewerSource, } from './lib/viewer-selection' export { + getDrawnWallTypes, getWallDrawVariant, + getWallLineKind, + getWallTypeOverride, + INVISIBLE_WALL_DRAFT_THICKNESS, + invisibleWallDraftSnapLines, selectWallDrawVariant, + setWallLineKind, + setWallTypeOverride, + useWallDraftThickness, useWallDrawVariant, + useWallLineKind, + useWallSegmentDraftThickness, + useWallTypeOverridePlacement, type WallDrawVariant, + type WallLineKind, } from './lib/wall-draw-variant' +export { + nearestWallParallelTarget, + nudgeWallPosition, + publishWallParallelGuide, + WALL_PARALLEL_SNAP_RADIUS, + type WallParallelTarget, + wallParallelTargets, +} from './lib/wall-parallel-snap' export { createWallPointerTracker, wallPointerInParent, @@ -929,6 +1006,44 @@ export { startWallPolygonDraft, wallPolygonDraftWalls, } from './lib/wall-polygon-draft' +export { + endWallPullRun, + releaseWallPullDrag, + startWallPull, + useWallPullRun, + WALL_PULL_AFFORDANCE, + WALL_PULL_GRIP_TITLE, + type WallPullGripPayload, + type WallPullRun, + wallPullSeed, + wallPullTypePatch, +} from './lib/wall-pull' +export { + isWallRunFree, + publishWallRunFeedback, + snapWallRunDraftPoint, + wallRunDirection, +} from './lib/wall-run-snap' +export { + availableWallType, + DEFAULT_WALL_FINISHES, + DEFAULT_WALL_TYPES, + deleteProjectWallType, + PLAIN_WALL_TYPE, + resolveWallType, + saveProjectWallType, + useProjectWallTypes, + WALL_LINE_TOOLS, + WALL_TYPE_OPTIONS, + WALL_TYPE_TOOLS, + type WallFinishes, + type WallTypeId, + type WallTypePlacement, + type WallTypes, + wallTypeOptions, + wallTypePatch, + wallTypePlacementOf, +} from './lib/wall-type' export { writeWindowChanges } from './lib/window-changes' export { closeWindowOpenState, @@ -945,7 +1060,11 @@ export { snapWorldXZForActiveBuilding, } from './lib/world-grid-snap' export { subscribeCameraPose } from './store/camera-pose-store' -export { default as useAlignmentGuides } from './store/use-alignment-guides' +export { + type DraftGuideLine, + type DraftGuides, + default as useAlignmentGuides, +} from './store/use-alignment-guides' export { default as useAudio } from './store/use-audio' export { type CameraHintAction, useCameraHintFocus } from './store/use-camera-hint-focus' export { type CommandAction, useCommandRegistry } from './store/use-command-registry' diff --git a/packages/editor/src/lib/add-storey.test.ts b/packages/editor/src/lib/add-storey.test.ts index 9336d27346..a48ad6f3c2 100644 --- a/packages/editor/src/lib/add-storey.test.ts +++ b/packages/editor/src/lib/add-storey.test.ts @@ -3,13 +3,18 @@ import { type AnyNode, type AnyNodeId, BuildingNode, + designedFoundationReport, + groundSeat, LevelNode, levelFloorWorldY, levelGradeStanding, RoofNode, + SlabNode, useScene, + WallNode, } from '@pascal-app/core' import { addStoreyBelow, addStoreyOnTop, setGroundFloor } from './add-storey' +import { deleteLevelWithFallbackSelection } from './level-selection' // The editor's "add a floor" on a roofed house: the new storey goes under the roof, not over it. const BUILDING = 'building_add-storey' as BuildingNode['id'] @@ -59,7 +64,8 @@ describe('addStoreyOnTop', () => { }) }) -// The editor's "add a basement": under the lowest storey, the building staying where it stands. +// The editor's "add a basement": under the lowest storey, the building staying where it stands +// while no foundation is designed under it (no walls drawn). describe('addStoreyBelow', () => { beforeEach(() => { useScene.temporal.getState().clear() @@ -120,6 +126,102 @@ describe('addStoreyBelow', () => { expect(useScene.getState().nodes[basement.id as AnyNodeId]).toBeUndefined() expect(floorOf('level_ground')).toBe(0) }) + + test('deleted again, even made taller first, it leaves the floors where they stand, in one undo step', () => { + const basement = addStoreyBelow(BUILDING) + useScene + .getState() + .applyNodeChanges({ update: [{ id: basement.id, data: { height: 2.7432 } }] }) + const standing = [floorOf('level_ground'), floorOf('level_roof')] + const steps = useScene.temporal.getState().pastStates.length + deleteLevelWithFallbackSelection(basement.id as AnyNodeId) + expect(useScene.getState().nodes[basement.id as AnyNodeId]).toBeUndefined() + expect([floorOf('level_ground'), floorOf('level_roof')]).toEqual(standing) + expect(useScene.temporal.getState().pastStates).toHaveLength(steps + 1) + useScene.temporal.getState().undo() + expect(useScene.getState().nodes[basement.id as AnyNodeId]).toBeDefined() + expect([floorOf('level_ground'), floorOf('level_roof')]).toEqual(standing) + }) +}) + +// A house drawn with walls stands on its designed foundation (on by default): a basement added +// under it seats it on the basement, and deleting the basement seats it on its slab again. +describe('addStoreyBelow on a designed foundation', () => { + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + beforeEach(() => { + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() + seed() + const walls = corners.map((start, index) => + WallNode.parse({ + id: `wall_${index}`, + parentId: 'level_ground', + start, + end: corners[(index + 1) % 4], + thickness: 0.15, + }), + ) + const plate = SlabNode.parse({ + id: 'slab_ground', + parentId: 'level_ground', + plateRole: 'base', + polygon: corners, + elevation: 0.05, + thickness: 0.05, + }) + const nodes = useScene.getState().nodes + useScene.setState({ + nodes: { + ...nodes, + level_ground: { + ...nodes['level_ground' as AnyNodeId], + children: [...walls.map((wall) => wall.id), plate.id], + }, + [plate.id]: plate, + ...Object.fromEntries(walls.map((wall) => [wall.id, wall])), + } as never, + }) + useScene.temporal.getState().clear() + }) + + const report = () => designedFoundationReport(useScene.getState().nodes, BUILDING)! + const heightY = () => + (useScene.getState().nodes[BUILDING as AnyNodeId] as BuildingNode).position[1] + + test('seats the house with the basement walls’ top 8 in over grade, in the same undo step', () => { + expect(report().basement).toBeUndefined() + const steps = useScene.temporal.getState().pastStates.length + const basement = addStoreyBelow(BUILDING) + expect(useScene.temporal.getState().pastStates).toHaveLength(steps + 1) + const seated = report() + expect(seated.levelId).toBe(basement.id) + expect(seated.basement!.wallTopY - seated.highestGradeY).toBeCloseTo(8 * 0.0254, 6) + expect(heightY()).toBeGreaterThan(0.3) + expect(groundSeat(useScene.getState().nodes, BUILDING)).toMatchObject({ + rule: 'basement', + seated: true, + }) + useScene.temporal.getState().undo() + expect(heightY()).toBe(0) + expect(useScene.getState().nodes[basement.id as AnyNodeId]).toBeUndefined() + }) + + test('deleted again, the house comes down onto its slab, seated 8 in over grade', () => { + const basement = addStoreyBelow(BUILDING) + deleteLevelWithFallbackSelection(basement.id as AnyNodeId) + const slab = report() + expect(slab.basement).toBeUndefined() + expect(slab.floorTopY - slab.highestGradeY).toBeCloseTo(8 * 0.0254, 6) + expect(groundSeat(useScene.getState().nodes, BUILDING)).toMatchObject({ + rule: 'slab', + atFoundationTop: true, + }) + }) }) // The editor's "Set as ground floor": the owner's mansion, whose stack stood Lower Ground at grade. diff --git a/packages/editor/src/lib/building-foundation.test.ts b/packages/editor/src/lib/building-foundation.test.ts new file mode 100644 index 0000000000..6fbbb779a7 --- /dev/null +++ b/packages/editor/src/lib/building-foundation.test.ts @@ -0,0 +1,434 @@ +import { afterEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + type BuildingFoundationRecord, + BuildingNode, + createTerrainField, + createZone, + type DesignedFoundationReport, + designedFoundationReport, + encodeTerrainField, + generateId, + genericFoundation, + groundFloorConstruction, + initSpaceDetectionSync, + LevelNode, + resolveBuildingFoundation, + SiteNode, + type SlabNode, + useScene, + WallNode, +} from '@pascal-app/core' +import { + floorFoundationModel, + upperFloorTopLabel, +} from '../components/ui/panels/floor-foundation-panel' +import { footprintStanding } from '../components/ui/panels/room-construction-rows' +import { installImmediateAnimationFrames } from '../test-utils/immediate-animation-frames' +import { + applyBuildingFoundation, + floorHeldByDesignedFoundation, + foundationAdvice, + groundSeatAction, + standsOnBasement, +} from './building-foundation' +import { applyFloorFoundation, levelFootprints } from './floor-footprints' +import { applyRoomPlan } from './room-structure-commands' + +const IN = 0.0254 +const inches = (meters: number) => `${Math.round((meters / IN) * 100) / 100} in` +// A raised floor's least finish floor over grade: the stem's top 8 in over it, the default framing +// on that (¾ in hardwood, ⅝ in subfloor, 2x12 joists, 1½ in sill: 14⅛ in). +const RAISED_FLOOR_IN = 8 + 14.125 + +/** A foundation on flat ground (grade at 0), its floor seated as the record asks. */ +function report( + record: BuildingFoundationRecord, + storeys = 1, + extra: Partial = {}, +): DesignedFoundationReport { + const foundation = resolveBuildingFoundation({ enabled: true, ...record }, storeys) + const floorTopY = foundation.ffAboveGradeIn * IN + const stemTopY = + foundation.type === 'raised' ? floorTopY - foundation.floorFramingDepthIn * IN : null + return { + buildingId: 'building_a', + levelId: 'level_g', + foundation, + floorTopY, + highestGradeY: 0, + lowestGradeY: 0, + stemTopY, + bottomY: -foundation.footingDepthIn * IN, + steps: 0, + tallestStep: 0, + piles: 0, + cripples: 0, + tallestCripple: 0, + crawlY: stemTopY === null ? null : stemTopY + 1.5 * IN - foundation.crawlClearanceIn * IN, + ...extra, + } +} + +const keys = (advice: ReturnType) => advice.map((item) => item.key) + +test('a foundation to code on flat ground has nothing to warn of', () => { + expect(keys(foundationAdvice(report({ type: 'slab' }), inches))).toEqual([]) +}) + +test('a footing or slab edge under 12 in of grade is warned of, with the fix', () => { + const raised = foundationAdvice(report({ type: 'raised', footingDepthIn: 6 }), inches) + const depth = raised.find((item) => item.key === 'depth') + expect(depth?.text).toContain('6 in under grade') + expect(depth?.fix?.patch).toEqual({ footingDepthIn: 12 }) + const slab = foundationAdvice(report({ type: 'slab', edgeDepthIn: 0 }), inches) + expect(slab.find((item) => item.key === 'depth')?.fix?.patch).toEqual({ edgeDepthIn: 12 }) + // Piles are engineered: no depth asked of the grade beam here. + const piles = report({ type: 'raised', support: 'piles', footingDepthIn: 6 }) + expect(keys(foundationAdvice(piles, inches))).not.toContain('depth') +}) + +test('a footing narrower than Table R403.1(1) asks for the storeys is warned of', () => { + const two = foundationAdvice(report({ type: 'raised', footingWidthIn: 12 }, 2), inches) + const width = two.find((item) => item.key === 'footing-width') + expect(width?.text).toContain('2 storeys ask 15 in') + expect(width?.fix?.patch).toEqual({ footingWidthIn: 15 }) + expect(keys(foundationAdvice(report({ type: 'raised' }, 2), inches))).not.toContain( + 'footing-width', + ) +}) + +test('the step note counts one side and names the tallest step', () => { + const even = foundationAdvice( + report({ type: 'raised' }, 1, { steps: 2, tallestStep: 8 * IN, lowestGradeY: -0.5 }), + inches, + ).find((item) => item.key === 'steps') + expect(even?.text).toContain('steps 2 times along its steepest side, 8 in a step') + const merged = foundationAdvice( + report({ type: 'raised' }, 1, { steps: 1, tallestStep: 16 * IN, lowestGradeY: -0.5 }), + inches, + ).find((item) => item.key === 'steps') + expect(merged?.text).toContain('up to 16 in a step') +}) + +test('a floor the ground has risen over reads as under it', () => { + const sunk = report({ type: 'slab' }, 1, { highestGradeY: 1, lowestGradeY: 1 }) + const seat = foundationAdvice(sunk, inches).find((item) => item.key === 'seat') + expect(seat?.text).toContain('stands 31.37 in under the high side') +}) + +test('a house on a basement with walls says so; one standing at grade does not', () => { + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const walls = corners.map((start, index) => + WallNode.parse({ + id: `wall_b_${index}`, + parentId: 'level_b', + start, + end: corners[(index + 1) % 4], + }), + ) + const scene = (y: number) => + Object.fromEntries( + [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + position: [0, y, 0], + children: ['level_b', 'level_g'], + }), + LevelNode.parse({ + id: 'level_b', + parentId: 'building_a', + level: 0, + height: 3, + children: walls.map((wall) => wall.id), + }), + LevelNode.parse({ id: 'level_g', parentId: 'building_a', level: 1 }), + ...walls, + ].map((node) => [node.id, node]), + ) as Record + // The lower floor 2.9 m under the ground, the one over it at grade. + expect(standsOnBasement(scene(-2.9), 'building_a')).toBe(true) + expect(standsOnBasement(scene(0), 'building_a')).toBe(false) +}) + +test('the ground round a slab is filled and cut to hold it 8 in under the top; deep fill asks for a stem wall', () => { + const said = (seated: DesignedFoundationReport, key: string) => + foundationAdvice(seated, inches).find((item) => item.key === key)?.text + expect(said(report({ type: 'slab', ffAboveGradeIn: 30 }), 'grading')).toBe( + 'The ground is held 8 in under the slab’s top on every side: filled 22 in.', + ) + const slope = report({ type: 'slab' }, 1, { lowestGradeY: -0.6 }) + expect(said(slope, 'grading')).toBe( + 'The ground is held 8 in under the slab’s top on every side: filled up to 23.62 in on the low side.', + ) + expect(said(slope, 'exposed')).toBeUndefined() + expect( + said(report({ type: 'slab' }, 1, { highestGradeY: 0.1, lowestGradeY: -0.1 }), 'grading'), + ).toBe( + 'The ground is held 8 in under the slab’s top on every side: filled up to 3.94 in on the low side, cut 3.94 in on the high side.', + ) + expect(said(report({ type: 'slab' }, 1, { lowestGradeY: -0.8 }), 'exposed')).toBe( + 'That is 31.5 in of fill on the low side. A raised floor on a stem wall suits this slope.', + ) + expect(said(report({ type: 'slab', ffAboveGradeIn: 40 }), 'exposed')).toBe( + 'That is 32 in of fill. A raised floor on a stem wall suits a floor this high.', + ) +}) + +test('a raised floor notes its crawl space dug out, the ground cut round it, and its cripple walls', () => { + const said = (seated: DesignedFoundationReport, key: string) => + foundationAdvice(seated, inches).find((item) => item.key === key)?.text + // The stem's top 8 in over flat ground: the joists 9.5 in over it, the crawl dug 8.5 in. + const raised = report({ type: 'raised' }) + expect(said(raised, 'crawl')).toBe( + 'Crawl space: dug out up to 8.5 in for 18 in under the joists (IRC R317.1).', + ) + expect(said(raised, 'top')).toBeUndefined() + expect(said(report({ type: 'raised' }, 1, { highestGradeY: 4 * IN }), 'top')).toBe( + 'The ground is cut 4 in on the high side to hold the stem wall’s top 8 in over it (IRC R317.1). Raising the floor cuts less.', + ) + expect( + said(report({ type: 'raised' }, 1, { cripples: 3, tallestCripple: 16 * IN }), 'cripple'), + ).toBe( + 'Past 36 in of stem out of the ground the stem drops to 8 in over grade: 3 cripple walls over it, up to 16 in tall.', + ) + expect(said(report({ type: 'slab' }), 'crawl')).toBeUndefined() +}) + +test('over a basement the panel speaks of its walls’ seat, its dig and a downhill wall out of the ground', () => { + const said = (seated: DesignedFoundationReport) => + Object.fromEntries(foundationAdvice(seated, inches).map((item) => [item.key, item.text])) + // Flat ground at 0, the slab's underside 2.8 m under it. + const basement = (wallTopY: number, extra: Partial = {}) => + report({}, 1, { + levelId: 'level_b', + floorTopY: -2.7, + stemTopY: wallTopY, + crawlY: null, + basement: { + groundLevelId: 'level_g', + outlineLevelId: 'level_g', + ownWalls: false, + wallTopY, + groundFloorTopY: wallTopY + 14.125 * IN, + excavationY: -2.8, + }, + ...extra, + }) + expect(said(basement(8 * IN))).toEqual({ + excavation: + 'Cut 110.24 in into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.', + }) + // Added under a house at grade: the framing over it in the ground until the house is seated. + const sunk = foundationAdvice(basement(-10 * IN), inches) + expect(sunk.map((item) => item.key)).toEqual(['seat', 'excavation']) + expect(sunk[0]!.text).toBe( + 'The basement wall’s top stands 10 in under the high side of the ground; the foundation is set for 8 in over it.', + ) + expect(sunk[0]!.fix).toEqual({ label: 'Seat it at 8 in', patch: {} }) + expect(said(basement(8 * IN, { lowestGradeY: -1 })).daylight).toBe( + 'The ground falls 39.37 in across the house: the downhill walls stand up to 47.37 in out of it.', + ) + const low = basement(4 * IN, { + foundation: resolveBuildingFoundation({ enabled: true, basementTopAboveGradeIn: 4 }, 1), + }) + expect(foundationAdvice(low, inches).map((item) => item.key)).toEqual([ + 'basement-top', + 'excavation', + ]) +}) + +test('the ground line says which rule its click seats the house by', () => { + expect(groundSeatAction('basement', 8)).toBe( + 'Seat the basement wall’s top 8 in over the high side of the ground', + ) + expect(groundSeatAction('raised', 22.125)).toBe( + 'Seat the raised floor’s top 22.13 in over the high side of the ground', + ) + expect(groundSeatAction('slab', 8)).toBe( + 'Seat the slab’s top 8 in over the high side of the ground', + ) + expect(groundSeatAction('construction', 7.99)).toBe('Seat the ground floor 7.99 in over grade') +}) + +test('a floor top under the ground prints one minus, not "+-"', () => { + expect(upperFloorTopLabel(-5 * IN, inches)).toBe('Floor top -5 in') + expect(upperFloorTopLabel(110 * IN, inches)).toBe('Floor top +110 in') +}) + +describe('switching the foundation on from the floor panel', () => { + let stop = () => {} + let restore = () => {} + afterEach(() => { + stop() + restore() + }) + const nodes = () => useScene.getState().nodes as Record + + /** The site's ground rising 10 cm a metre to the east, `drop` metres lower all over. */ + function slope(drop = 0) { + const field = createTerrainField({ cols: 41, rows: 41, spacing: 1, origin: [-20, -20] }) + const heights = new Int16Array(field.heights) + for (let row = 0; row < 41; row++) + for (let col = 0; col < 41; col++) heights[row * 41 + col] = (col - 20) * 10 - drop * 100 + return encodeTerrainField({ ...field, heights }) + } + + /** A one-room house drawn as the editor draws it, its floor plate derived from the room. */ + function drawHouse({ + terrain, + rotated, + }: { + terrain?: ReturnType + rotated?: boolean + }) { + restore = installImmediateAnimationFrames() + const site = SiteNode.parse({ id: 'site_f', children: ['building_f'], terrain }) + const building = BuildingNode.parse({ + id: 'building_f', + parentId: site.id, + children: ['level_f'], + ...(rotated ? { position: [2, 0, -3], rotation: [0, 0.6, 0] } : {}), + }) + const level = LevelNode.parse({ + id: 'level_f', + parentId: building.id, + level: 0, + name: 'Ground floor', + }) + useScene.setState({ + nodes: { [site.id]: site, [building.id]: building, [level.id]: level }, + rootNodeIds: [site.id], + dirtyNodes: new Set(), + materials: {}, + collections: {}, + readOnly: false, + }) + useScene.temporal.getState().resume() + stop = initSpaceDetectionSync(useScene, { + getState: () => ({ spaces: {}, setSpaces: () => {} }), + }) + const polygon: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + applyRoomPlan( + createZone(nodes(), { + levelId: level.id, + polygon, + enclose: true, + mintId: generateId, + name: 'Living', + }), + ) + useScene.temporal.getState().clear() + } + const plate = () => levelFootprints(nodes(), 'level_f')[0] as SlabNode + const seat = () => designedFoundationReport(nodes() as Record, 'building_f')! + const over = () => seat().floorTopY - seat().highestGradeY + /** The floor panel and the Room panel still read the house's ground floor. */ + const expectGroundFloor = (designed: boolean) => { + expect(floorFoundationModel(nodes(), plate())).toMatchObject({ + upper: false, + underFloor: true, + preset: 'ground', + }) + expect(footprintStanding(nodes(), plate())).toMatchObject({ supported: false, designed }) + // The record's finish floor is the floor's height: the 3D height handle steps aside. + expect(floorHeldByDesignedFoundation(nodes(), plate())).toBe(designed) + } + + test('the ground floor stays a ground floor through type, support, sizes, off and undo', () => { + drawHouse({}) + expect(applyBuildingFoundation('building_f', { enabled: true })).toBe(true) + expectGroundFloor(true) + // The default: a 4 in slab, its top 8 in over grade. + expect(seat().foundation.type).toBe('slab') + expect(over()).toBeCloseTo(8 * IN, 6) + const slab = genericFoundation(nodes() as Record).find( + (member) => member.kind === 'slab', + )! + expect(slab.bottomY).toBeCloseTo(seat().floorTopY - 4 * IN, 6) + + applyBuildingFoundation('building_f', { type: 'raised' }) + expectGroundFloor(true) + expect(over()).toBeCloseTo(RAISED_FLOOR_IN * IN, 6) + applyBuildingFoundation('building_f', { support: 'piles' }) + expect( + genericFoundation(nodes() as Record).some((m) => m.kind === 'pile'), + ).toBe(true) + applyBuildingFoundation('building_f', { ffAboveGradeIn: 30 }) + expectGroundFloor(true) + expect(over()).toBeCloseTo(30 * IN, 6) + + applyBuildingFoundation('building_f', { enabled: false }) + expectGroundFloor(false) + useScene.temporal.getState().undo() + expectGroundFloor(true) + useScene.temporal.getState().redo() + expectGroundFloor(false) + }) + + // The plate's own raise stayed under the designed foundation: the seat lowered the house by it, + // and past 0.3 m the floor read as a basement and the Foundation section, off switch and all, went. + for (const raise of [0.3, 0.5]) + test(`a floor raised ${raise} m on its own comes down onto the designed foundation, in one step`, () => { + drawHouse({}) + expect(applyFloorFoundation(plate().id, { foundationHeight: raise })).toBeNull() + useScene.temporal.getState().clear() + applyBuildingFoundation('building_f', { enabled: true }) + expectGroundFloor(true) + expect(groundFloorConstruction(nodes(), plate()).foundationHeight).toBe(0) + expect(footprintStanding(nodes(), plate()).lift).toBe(0) + expect(over()).toBeCloseTo(8 * IN, 6) + expect((nodes().building_f as BuildingNode).position[1]).toBeGreaterThan(0) + useScene.temporal.getState().undo() + expect((nodes().building_f as BuildingNode).position[1]).toBe(0) + expect(groundFloorConstruction(nodes(), plate()).foundationHeight).toBeCloseTo(raise, 6) + }) + + // Switched on with the outside walls open, nothing seated the floor, yet its thickness and + // height were hidden as the foundation's. + test('with the record on and nothing designed, the floor keeps its own controls', () => { + drawHouse({}) + const wall = Object.values(nodes()).find((node) => node.type === 'wall')! + useScene.getState().deleteNode(wall.id as AnyNodeId) + applyBuildingFoundation('building_f', { enabled: true }) + expect(designedFoundationReport(nodes() as Record, 'building_f')).toBeNull() + expect(floorHeldByDesignedFoundation(nodes(), plate())).toBe(false) + expect(footprintStanding(nodes(), plate()).designed).toBe(false) + }) + + for (const rotated of [false, true]) + test(`on a slope${rotated ? ', the house turned,' : ''} the first seat stands the floor over the high side`, () => { + drawHouse({ terrain: slope(), rotated }) + applyBuildingFoundation('building_f', { enabled: true }) + expectGroundFloor(true) + expect(over()).toBeCloseTo(8 * IN, 6) + expect(keys(foundationAdvice(seat(), inches))).not.toContain('seat') + applyBuildingFoundation('building_f', { type: 'raised' }) + expect(over()).toBeCloseTo(RAISED_FLOOR_IN * IN, 6) + }) + + // The house's high side 1 m under the datum: the seat moves the building down onto the ground. + test('on ground lower than the datum the first seat moves the house down to it', () => { + drawHouse({ terrain: slope(2) }) + applyBuildingFoundation('building_f', { enabled: true }) + expect((nodes().building_f as BuildingNode).position[1]).toBeLessThan(0) + expectGroundFloor(true) + expect(over()).toBeCloseTo(8 * IN, 6) + applyBuildingFoundation('building_f', { type: 'raised' }) + expectGroundFloor(true) + expect(over()).toBeCloseTo(RAISED_FLOOR_IN * IN, 6) + }) +}) diff --git a/packages/editor/src/lib/building-foundation.ts b/packages/editor/src/lib/building-foundation.ts new file mode 100644 index 0000000000..2721e7cb2c --- /dev/null +++ b/packages/editor/src/lib/building-foundation.ts @@ -0,0 +1,268 @@ +import { + type AnyNode, + type AnyNodeId, + type BasementReport, + type BuildingFoundationRecord, + type BuildingNode, + buildingFoundationDefaults, + type DesignedFoundationReport, + designedFoundationReport, + type GroundSeatRule, + getLevelElevations, + groundLevelOf, + levelGradeStanding, + type SlabNode, + upperFloorHeightControl, + useScene, +} from '@pascal-app/core' +import { buildBuildingFoundationChanges } from '@pascal-app/core/building' + +const IN = 0.0254 + +/** + * The Foundation panel's edit: the record patched and, with it on, the building seated over the + * high side of the ground (`buildBuildingFoundationChanges`), in one undo step. `false` for an id + * that is no building. + */ +export function applyBuildingFoundation(buildingId: string, patch: BuildingFoundationRecord) { + const scene = useScene.getState() + const built = buildBuildingFoundationChanges({ nodes: scene.nodes, buildingId, patch }) + if (!built) return false + scene.applyNodeChanges({ update: built.update }) + return true +} + +/** The building a footprint's plate stands in. */ +export function buildingOfPlate( + nodes: Record, + plate: Pick, +): BuildingNode | null { + const buildingId = plate.parentId + ? getLevelElevations(nodes as Record).get(plate.parentId as AnyNodeId) + ?.buildingId + : undefined + const building = buildingId ? nodes[buildingId] : undefined + return building?.type === 'building' ? building : null +} + +/** + * Whether a ground floor's height is the designed foundation's: it is drawn under this plate's + * level, and its finish floor over grade (`ffAboveGradeIn`) seats the floor, so the plate's own + * "raised on a foundation" height is no control of it. With the record on but nothing designed + * (open outside walls, a basement under the house) the plate keeps its own controls. + */ +export function floorHeldByDesignedFoundation( + nodes: Record, + plate: SlabNode, +): boolean { + if (upperFloorHeightControl(nodes, plate) !== null) return false + const building = buildingOfPlate(nodes, plate) + if (!building) return false + const report = designedFoundationReport(nodes as Record, building.id) + // A basement's floor keeps its own controls: the record seats the basement walls' top. + return report?.levelId === plate.parentId && !report.basement +} + +/** + * Whether the building stands on a basement with walls: a level under its ground floor, all of it + * below grade. Its walls are the foundation walls, standing on their footing. + */ +export function standsOnBasement(nodes: Record, buildingId: string): boolean { + const scene = nodes as Record + const ground = groundLevelOf(scene, buildingId) + if (!ground) return false + const elevations = getLevelElevations(scene) + return Object.values(scene).some( + (node) => + node.type === 'level' && + elevations.get(node.id)?.buildingId === buildingId && + node.level < ground.level.level && + node.children.some((id) => scene[id as AnyNodeId]?.type === 'wall') && + levelGradeStanding(scene, node.id) === 'below-grade', + ) +} + +/** + * What the level panel's ground line does when its click seats the building, by the rule that + * seats it (`groundSeat`): the construction's floor height over grade, or the designed + * foundation's own over the high side of the ground. `askedIn` in inches. + */ +export function groundSeatAction(rule: GroundSeatRule, askedIn: number): string { + const asked = `${Math.round(askedIn * 100) / 100} in` + switch (rule) { + case 'basement': + return `Seat the basement wall’s top ${asked} over the high side of the ground` + case 'raised': + return `Seat the raised floor’s top ${asked} over the high side of the ground` + case 'piles': + return `Seat the floor’s top ${asked} over the high side of the ground` + case 'slab': + return `Seat the slab’s top ${asked} over the high side of the ground` + default: + return `Seat the ground floor ${asked} over grade` + } +} + +/** One line under the foundation's settings: a rule it misses (with the fix) or a note on how it stands. */ +export type FoundationAdvice = { + key: string + tone: 'warning' | 'note' + text: string + fix?: { label: string; patch: BuildingFoundationRecord } +} + +/** + * What the panel says about the designed foundation as it stands: the floor off the height the + * record seats it at (the ground or the house moved since), the ground cut round a stem wall to + * hold it 8 in out, sizes under the code's least, the crawl space dug out under the joists, the + * steps down a slope and the cripple walls over them, how much the ground round a slab is filled + * and cut to hold it 8 in under the slab's top. + */ +export function foundationAdvice( + report: DesignedFoundationReport, + length: (meters: number) => string, +): FoundationAdvice[] { + if (report.basement) return basementAdvice(report, report.basement, length) + const { foundation } = report + const advice: FoundationAdvice[] = [] + const floorOver = report.floorTopY - report.highestGradeY + const asked = foundation.ffAboveGradeIn * IN + if (Math.abs(floorOver - asked) > 0.5 * IN) + advice.push({ + key: 'seat', + tone: 'warning', + text: `The floor stands ${length(Math.abs(floorOver))} ${floorOver < 0 ? 'under' : 'over'} the high side of the ground; the foundation is set for ${length(asked)} over it.`, + fix: { label: `Seat it at ${length(asked)}`, patch: {} }, + }) + const least = foundation.topAboveGradeIn * IN + // The ground is cut down round the stem wall's top (a slab's, filled and cut, below). + const cutOver = report.stemTopY === null ? 0 : report.highestGradeY - (report.stemTopY - least) + if (cutOver > 0.5 * IN) + advice.push({ + key: 'top', + tone: 'note', + text: `The ground is cut ${length(cutOver)} on the high side to hold the stem wall’s top ${length(least)} over it (IRC R317.1). Raising the floor cuts less.`, + fix: { label: 'Raise the floor', patch: { ffAboveGradeIn: foundation.ffMinIn } }, + }) + const footingWidthIn = buildingFoundationDefaults(foundation.storeys).footingWidthIn + const piles = foundation.support === 'piles' + const raised = foundation.type === 'raised' + const depth = raised ? foundation.footingDepthIn : foundation.edgeDepthIn + if (!piles && depth < 12) + advice.push({ + key: 'depth', + tone: 'warning', + text: `The ${raised ? 'footing' : 'slab edge'}’s bottom is ${length(depth * IN)} under grade: keep it ${length(12 * IN)} at least, or the frost line (IRC R403.1.4).`, + fix: { + label: `Set it ${length(12 * IN)} deep`, + patch: raised ? { footingDepthIn: 12 } : { edgeDepthIn: 12 }, + }, + }) + if (raised && !piles && foundation.footingWidthIn < footingWidthIn) + advice.push({ + key: 'footing-width', + tone: 'warning', + text: `The footing is ${length(foundation.footingWidthIn * IN)} wide: ${foundation.storeys === 1 ? 'one storey asks' : `${foundation.storeys} storeys ask`} ${length(footingWidthIn * IN)} on 1500 psf soil (IRC Table R403.1(1)).`, + fix: { label: `Widen it to ${length(footingWidthIn * IN)}`, patch: { footingWidthIn } }, + }) + if (report.stemTopY !== null && report.crawlY !== null) { + // The crawl space is dug out to its clearance under the joists, where the ground stands higher. + const dug = report.highestGradeY - report.crawlY + const clearance = foundation.crawlClearanceIn * IN + if (dug > 0.5 * IN) + advice.push({ + key: 'crawl', + tone: 'note', + text: `Crawl space: dug out up to ${length(dug)} for ${length(clearance)} under the joists (IRC R317.1).`, + }) + } + if (report.cripples > 0) + advice.push({ + key: 'cripple', + tone: 'note', + text: `Past ${length(foundation.maxStemIn * IN)} of stem out of the ground the stem drops to ${length(least)} over grade: ${report.cripples} ${report.cripples === 1 ? 'cripple wall' : 'cripple walls'} over it, up to ${length(report.tallestCripple)} tall.`, + }) + if (report.steps > 0) { + const step = foundation.stepHeightIn * IN + const riser = + report.tallestStep > step + 0.25 * IN + ? `up to ${length(report.tallestStep)} a step` + : `${length(step)} a step` + advice.push({ + key: 'steps', + tone: 'note', + text: `The ground falls ${length(report.highestGradeY - report.lowestGradeY)} across the house: the ${report.stemTopY === null ? 'slab edge' : 'footing'} steps ${report.steps} ${report.steps === 1 ? 'time' : 'times'} along its steepest side, ${riser}.`, + }) + } + if (report.stemTopY === null) { + // The ground round a slab is graded to its edge, `least` under its top, on every side. + const pad = report.floorTopY - least + const fill = pad - report.lowestGradeY + const cut = report.highestGradeY - pad + const sloped = report.highestGradeY - report.lowestGradeY > 0.5 * IN + const works = [ + fill > 0.5 * IN && + (sloped ? `filled up to ${length(fill)} on the low side` : `filled ${length(fill)}`), + cut > 0.5 * IN && (sloped ? `cut ${length(cut)} on the high side` : `cut ${length(cut)}`), + ].filter(Boolean) + if (works.length > 0) + advice.push({ + key: 'grading', + tone: 'note', + text: `The ground is held ${length(least)} under the slab’s top on every side: ${works.join(', ')}.`, + }) + if (fill > 24 * IN) + advice.push({ + key: 'exposed', + tone: 'warning', + text: `That is ${length(fill)} of fill${sloped ? ' on the low side' : ''}. A raised floor on a stem wall suits ${sloped ? 'this slope' : 'a floor this high'}.`, + fix: { label: 'Raise it on a stem wall', patch: { type: 'raised' } }, + }) + } + return advice +} + +/** + * What the panel says about a house over a basement: its walls' top off the height the record + * seats it at (the house not moved since the basement went in, or the ground since), a top under + * the 8 in that keeps the floor framing off the ground, how deep the basement is cut in, and how + * far a downhill wall stands out of the ground (a daylight basement). + */ +function basementAdvice( + report: DesignedFoundationReport, + basement: BasementReport, + length: (meters: number) => string, +): FoundationAdvice[] { + const { foundation } = report + const advice: FoundationAdvice[] = [] + const over = basement.wallTopY - report.highestGradeY + const asked = foundation.basementTopAboveGradeIn * IN + if (Math.abs(over - asked) > 0.5 * IN) + advice.push({ + key: 'seat', + tone: 'warning', + text: `The basement wall’s top stands ${length(Math.abs(over))} ${over < 0 ? 'under' : 'over'} the high side of the ground; the foundation is set for ${length(asked)} over it.`, + fix: { label: `Seat it at ${length(asked)}`, patch: {} }, + }) + if (foundation.basementTopAboveGradeIn < 8) + advice.push({ + key: 'basement-top', + tone: 'warning', + text: `The sill and the floor framing sit ${length(asked)} over the ground: keep them ${length(8 * IN)} off it at least (IRC R317.1).`, + fix: { label: `Set it ${length(8 * IN)}`, patch: { basementTopAboveGradeIn: 8 } }, + }) + advice.push({ + key: 'excavation', + tone: 'note', + text: `Cut ${length(report.highestGradeY - basement.excavationY)} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.`, + }) + const fall = report.highestGradeY - report.lowestGradeY + const exposed = basement.wallTopY - report.lowestGradeY + if (fall > 0.5 * IN && exposed > 0.5 * IN) + advice.push({ + key: 'daylight', + tone: 'note', + text: `The ground falls ${length(fall)} across the house: the downhill walls stand up to ${length(exposed)} out of it.`, + }) + return advice +} diff --git a/packages/editor/src/lib/continuation.ts b/packages/editor/src/lib/continuation.ts index fdb5c8c2f0..151668ecbb 100644 --- a/packages/editor/src/lib/continuation.ts +++ b/packages/editor/src/lib/continuation.ts @@ -17,8 +17,9 @@ export const CONTINUATION_PROFILES: Record< } > = { // The wall tool's drawing mode: a chain that closes into a room ('room', the - // Build panel's Polygon), one wall per two clicks ('single', Walls), or a - // two-corner box of four walls ('rectangle', Rectangle). + // Build panel's Polygon), a run of walls committed one by one, each starting + // where the last ended ('single', Walls), or a two-corner box of four walls + // ('rectangle', Rectangle). wall: { options: ['room', 'single', 'rectangle'], default: 'room', diff --git a/packages/editor/src/lib/floor-footprints.test.ts b/packages/editor/src/lib/floor-footprints.test.ts index b454c24a43..ea750134e7 100644 --- a/packages/editor/src/lib/floor-footprints.test.ts +++ b/packages/editor/src/lib/floor-footprints.test.ts @@ -3,19 +3,26 @@ import { type AnyNode, applySceneOperationPatch, BuildingNode, + type CeilingNode, clearSceneHistory, createI18n, createZone, floorFootprintName, generateId, + getLevelElevations, + getOpeningFloorDatum, + getWallPlaneTop, ItemNode, initSpaceDetectionSync, LevelNode, + resolveCeilingHeight, type SceneCommit, type SlabNode, SlabNode as SlabSchema, subscribeSceneCommits, useScene, + type WallNode, + WindowNode, ZoneNode, } from '@pascal-app/core' import { moveTogetherLabel } from '../components/ui/panels/floor-foundation-panel' @@ -329,6 +336,82 @@ describe('floor footprints', () => { expect(zone?.type === 'zone' ? zone.floor?.elevation : null).toBeCloseTo(floor + 0.15) }) + test("the room's Floor thickness lifts what stands on that floor, upstairs and down, like Floor & foundation", () => { + // A room on the raised Shared floor, a storey over it, and a room up there. + const building = nodes()['building_footprints' as never] as BuildingNode + const upper = LevelNode.parse({ id: 'level_footprints_up', parentId: building.id, level: 1 }) + useScene.getState().applyNodeChanges({ + create: [{ node: upper, parentId: building.id as never }], + update: [], + delete: [], + }) + const upstairs = createZone(nodes(), { + levelId: upper.id, + polygon: [ + [0, 0], + [9, 0], + [9, 5], + [0, 5], + ], + enclose: true, + mintId: generateId, + name: 'Upstairs', + }) + applyRoomPlan(upstairs) + const upperWall = Object.values(nodes()).find( + (node): node is WallNode => node.type === 'wall' && node.parentId === upper.id, + )! + const opening = WindowNode.parse({ parentId: upperWall.id, position: [2, 1, 0] }) + useScene.getState().createNode(opening, upperWall.id as never) + expect(applyFloorFoundation(house.id, { foundationHeight: 0.254 })).toBeNull() + const ceilingOn = (levelId: string) => + Object.values(nodes()).find( + (node): node is CeilingNode => node.type === 'ceiling' && node.parentId === levelId, + )! + const groundWall = Object.values(nodes()).find( + (node): node is WallNode => node.type === 'wall' && node.parentId === LEVEL, + )! + const measure = () => { + const graph = nodes() + const wall = graph[upperWall.id] as WallNode + return { + upperBase: getLevelElevations(graph).get(upper.id)!.baseY, + upperWallTop: getWallPlaneTop(wall, upper.id, graph), + window: getOpeningFloorDatum(wall, graph[opening.id] as WindowNode, graph), + upperCeiling: resolveCeilingHeight(ceilingOn(upper.id), graph), + groundWallTop: getWallPlaneTop(graph[groundWall.id] as WallNode, LEVEL, graph), + groundCeiling: resolveCeilingHeight(ceilingOn(LEVEL), graph), + } + } + + // Thicker ground floor: its own walls and ceiling and the whole storey above go up. + let before = measure() + let delta = 0.3 - (nodes()[house.id] as SlabNode).thickness + clearSceneHistory() + expect(applyRoomFloorConstruction(livingId, { thickness: 0.3 })).toBeNull() + expect(history()).toBe(1) + let after = measure() + expect(after.groundWallTop).toBeCloseTo(before.groundWallTop + delta, 6) + expect(after.groundCeiling).toBeCloseTo(before.groundCeiling + delta, 6) + expect(after.upperBase).toBeCloseTo(before.upperBase + delta, 6) + expect(after.upperWallTop).toBeCloseTo(before.upperWallTop, 6) + + // Thicker upstairs floor: that storey's walls, window and ceiling rise; the ceiling below holds. + const upperRoom = upstairs.zoneId + const upperPlate = roomFloorOwner(nodes(), upperRoom)! + before = measure() + delta = 0.45 - upperPlate.thickness + clearSceneHistory() + expect(applyRoomFloorConstruction(upperRoom, { thickness: 0.45 })).toBeNull() + expect(history()).toBe(1) + after = measure() + expect(after.upperWallTop).toBeCloseTo(before.upperWallTop + delta, 6) + expect(after.window).toBeCloseTo(before.window + delta, 6) + expect(after.upperCeiling).toBeCloseTo(before.upperCeiling + delta, 6) + expect(after.groundCeiling).toBeCloseTo(before.groundCeiling, 6) + expect(after.groundWallTop).toBeCloseTo(before.groundWallTop, 6) + }) + test('a refused room height says why and changes nothing', () => { const before = nodes() const result = setRoomRelativeFloorHeight(livingId, 50) diff --git a/packages/editor/src/lib/floor-footprints.ts b/packages/editor/src/lib/floor-footprints.ts index a953ccc3ed..a218660e17 100644 --- a/packages/editor/src/lib/floor-footprints.ts +++ b/packages/editor/src/lib/floor-footprints.ts @@ -4,13 +4,21 @@ import { acquireSceneHistoryPause, applyStructureReconciliation, area, + DEFAULT_FLOOR_BUILD_UP, DEFAULT_FOUNDATION_HEIGHT, + type FloorBuildUp, type FloorFoundationPatch, + type FloorStructure, + floorBuildUpPatch, + floorStructureDepth, generateId, getRoomBaseElevation, groundFloorConstruction, + I_JOIST_DEPTH, intersection, + LUMBER_DEPTH, roomDrawnFloor, + runAsSingleSceneHistoryStep, type SlabNode, setFloorFoundation, setRoomFloorConstruction, @@ -299,6 +307,110 @@ export function beginFootprintThicknessPreview(slabId: string) { return beginFootprintPreview(slabId, setFootprintThickness, (_, plate) => plate.thickness) } +// A framed floor's layers (a storey's floor plate, a raised floor's framing) are edited the way a +// roof plane's are: each change is a whole new build-up, and no longer the preset it started as. + +/** The layers a framed floor can do without; its joists are always there. */ +export type FloorLayerKey = 'flooring' | 'sheathing' | 'ceiling' + +/** `base` with `change` applied and no longer a preset; a layer set to `undefined` goes (off). */ +export function editedFloorBuildUp( + base: FloorBuildUp, + change: Partial, +): FloorBuildUp { + const next: Record = { ...base, ...change } + delete next.presetId + for (const key of Object.keys(next)) if (next[key] === undefined) delete next[key] + return next as FloorBuildUp +} + +/** A layer switched off (it goes) or back on, as `defaults` has it (else as the default floor has it). */ +export function switchFloorLayer( + buildUp: FloorBuildUp, + layer: FloorLayerKey, + on: boolean, + defaults: FloorBuildUp = DEFAULT_FLOOR_BUILD_UP, +): FloorBuildUp { + const restored = defaults[layer] ?? DEFAULT_FLOOR_BUILD_UP[layer] + return editedFloorBuildUp(buildUp, { + [layer]: on ? restored : undefined, + } as Partial) +} + +/** + * A layer's material named ("Brazilian cherry"; its kind still says what it is); a blank name goes + * back to the kind's. The preset stays: a name builds nothing different. + */ +export function renameFloorLayer( + buildUp: FloorBuildUp, + layer: FloorLayerKey, + name: string, +): FloorBuildUp { + const current = buildUp[layer] + if (!current) return buildUp + const named = name.trim().slice(0, 60) + const { name: _, ...unnamed } = current + return { ...buildUp, [layer]: named ? { ...unnamed, name: named } : unnamed } as FloorBuildUp +} + +/** The joists as the other system, at the depth nearest theirs (2x12 lumber becomes an 11-7/8 in I-joist). */ +export function floorStructureAs( + structure: FloorStructure, + system: FloorStructure['system'], +): FloorStructure { + if (structure.system === system) return structure + const depth = floorStructureDepth(structure) + const sizes: [string, number][] = Object.entries( + system === 'joists' ? LUMBER_DEPTH : I_JOIST_DEPTH, + ) + const [size] = sizes.reduce((best, entry) => + Math.abs(entry[1] - depth) < Math.abs(best[1] - depth) ? entry : best, + ) + return { ...structure, system, size } as FloorStructure +} + +type FootprintPreview = ReturnType + +/** + * Sets a framed floor plate's layers. Its thickness becomes theirs through the floor height (the + * underside stays on the walls below, the storeys above ride with the top, and the shared rules + * refuse what they refuse); the layers are written with it in the same undo step, and only once + * the height is accepted. A scrub's preview `session` lands the height (its live writes taken back + * first); `together` moves the footprints sharing the storey above with this one. Returns why it + * was refused, or null. + */ +export function applyFloorBuildUp( + slabId: string, + buildUp: FloorBuildUp, + options: { session?: FootprintPreview | null; together?: readonly string[] } = {}, +): string | null { + const nodes = useScene.getState().nodes + const plate = nodes[slabId as AnyNodeId] + const control = isBase(plate) ? upperFloorHeightControl(nodes, plate) : null + if (!(isBase(plate) && control)) { + options.session?.cancel() + return isBase(plate) + ? 'Only a floor resting on the walls below is framed.' + : 'The floor is gone.' + } + const patch = floorBuildUpPatch(buildUp) + // The underside is held while a scrub previews, so it reads the same mid-scrub as before it. + const top = control.currentTop - plate.thickness + patch.thickness + return runAsSingleSceneHistoryStep(useScene, () => { + const refused = options.together + ? applyFloorFoundation(options.together, { thickness: patch.thickness }) + : options.session + ? options.session.commit(top) + : Math.abs(patch.thickness - plate.thickness) < 1e-6 + ? null + : setFootprintHeight(slabId, top) + if (refused) return refused + if (JSON.stringify(plate.buildUp) !== JSON.stringify(patch.buildUp)) + useScene.getState().updateNode(slabId as AnyNodeId, { buildUp: patch.buildUp }) + return null + }) +} + function beginFootprintPreview( slabId: string, write: (slabId: string, value: number) => string | null, diff --git a/packages/editor/src/lib/floorplan/dimension-drive.test.ts b/packages/editor/src/lib/floorplan/dimension-drive.test.ts index ea2818d808..c1b8a74699 100644 --- a/packages/editor/src/lib/floorplan/dimension-drive.test.ts +++ b/packages/editor/src/lib/floorplan/dimension-drive.test.ts @@ -157,7 +157,7 @@ describe('driving a wall length', () => { expect(plan?.updates[0]?.data).toEqual({ start: [0, 0], end: [3.5, 0] }) }) - test('a dimension across the wall is not drivable', () => { + test('a dimension across the wall that ends on neither face is not drivable', () => { const nodes = scene() const resolution = resolveDimensionDrive({ nodes, @@ -165,7 +165,10 @@ describe('driving a wall length', () => { start: [1.5, -1], end: [1.5, 1], }) - expect(resolution).toEqual({ drivable: false, reason: 'dimension is not along the wall' }) + expect(resolution).toEqual({ + drivable: false, + reason: 'dimension does not start or end on this wall', + }) }) test('a locked wall refuses to drive', () => { @@ -363,3 +366,29 @@ describe('an opening width tag resizes the opening', () => { expect(plan?.updates).toEqual([{ id: 'win_1' as AnyNodeId, data: { width: 1.2192 } }]) }) }) + +describe('a dimension across a wall slides the wall', () => { + test('a room width from the wall face drives the wall along its normal, away from the far end', () => { + // wall_a runs east along z = 0; the dimension leaves its south face for a wall 3 m south. + const resolution = resolveDimensionDrive({ + nodes: scene(), + ownerNodeId: 'wall_a' as AnyNodeId, + start: [1.5, 0.075], + end: [1.5, 3], + }) + expect(resolution).toEqual({ + drivable: true, + target: { kind: 'wall-offset', wallId: 'wall_a' as AnyNodeId, direction: [0, -1] }, + }) + }) + + test('the wall end of the dimension may be either witness point', () => { + const resolution = resolveDimensionDrive({ + nodes: scene(), + ownerNodeId: 'wall_a' as AnyNodeId, + start: [1.5, -4], + end: [1.5, -0.075], + }) + expect(resolution).toMatchObject({ drivable: true, target: { direction: [0, 1] } }) + }) +}) diff --git a/packages/editor/src/lib/floorplan/dimension-drive.ts b/packages/editor/src/lib/floorplan/dimension-drive.ts index bff942c6e6..29e6b6682d 100644 --- a/packages/editor/src/lib/floorplan/dimension-drive.ts +++ b/packages/editor/src/lib/floorplan/dimension-drive.ts @@ -120,6 +120,17 @@ export type DimensionDriveTarget = openingId: AnyNodeId wallId: AnyNodeId } + | { + /** + * The dimension runs across the wall, from one of its faces to another + * wall (a room width, the outside size): the whole wall slides along + * its normal, its neighbours following, as a push of the wall does. + */ + kind: 'wall-offset' + wallId: AnyNodeId + /** Unit vector the wall travels along when the value grows. */ + direction: DimensionPlanPoint + } export type DimensionDriveResolution = | { drivable: true; target: DimensionDriveTarget } @@ -174,7 +185,7 @@ function resolveWallDrive( const wallDirection = unit(subtract(wall.end, wall.start)) if (!wallDirection) return { drivable: false, reason: 'degenerate wall' } if (Math.abs(cross(wallDirection, dimDirection)) > PARALLEL_TOLERANCE) { - return { drivable: false, reason: 'dimension is not along the wall' } + return resolveWallOffsetDrive(query, wall, wallDirection, dimDirection) } if ((wall as { curvature?: number }).curvature) { return { drivable: false, reason: 'curved walls are not drivable yet' } @@ -210,6 +221,56 @@ function resolveWallDrive( } } +/** How far off a wall's face a witness point may sit and still be on that wall. */ +const FACE_MATCH_TOLERANCE_M = 0.05 + +/** + * A dimension square to the wall, with one end on it: typing a value slides + * the wall along its normal, away from the other end when the value grows. + */ +function resolveWallOffsetDrive( + query: DimensionDriveQuery, + wall: WallNode, + wallDirection: DimensionPlanPoint, + dimDirection: DimensionPlanPoint, +): DimensionDriveResolution { + if (Math.abs(dot(wallDirection, dimDirection)) > PARALLEL_TOLERANCE) { + return { drivable: false, reason: 'dimension is neither along nor across the wall' } + } + if (wall.curveOffset) return { drivable: false, reason: 'curved walls are not drivable yet' } + const reach = (wall.thickness ?? 0.1) + FACE_MATCH_TOLERANCE_M + const onWall = (point: DimensionPlanPoint) => + distanceToSegment(point, wall.start as DimensionPlanPoint, wall.end as DimensionPlanPoint) <= + reach + const startOn = onWall(query.start) + const endOn = onWall(query.end) + if (startOn === endOn) { + return { drivable: false, reason: 'dimension does not start or end on this wall' } + } + const direction = startOn + ? unit(subtract(query.start, query.end)) + : unit(subtract(query.end, query.start)) + if (!direction) return { drivable: false, reason: 'degenerate dimension' } + return { + drivable: true, + target: { kind: 'wall-offset', wallId: wall.id as AnyNodeId, direction }, + } +} + +function distanceToSegment( + point: DimensionPlanPoint, + start: DimensionPlanPoint, + end: DimensionPlanPoint, +): number { + const segment = subtract(end, start) + const lengthSq = dot(segment, segment) + const t = + lengthSq <= 1e-12 + ? 0 + : Math.max(0, Math.min(1, dot(subtract(point, start), segment) / lengthSq)) + return length(subtract(point, [start[0] + segment[0] * t, start[1] + segment[1] * t])) +} + function resolveOpeningDrive( query: DimensionDriveQuery, opening: DoorNode | WindowNode, @@ -360,6 +421,9 @@ export function planDimensionDrive(args: { }): DimensionDrivePlan | null { const delta = args.nextLength - args.currentLength if (!Number.isFinite(delta) || Math.abs(delta) < 1e-9) return { updates: [], delta: 0 } + // A wall offset moves the wall with its junctions re-planned, bridges and + // all: that is the wall-push planner's job (`commitDimensionValue`). + if (args.target.kind === 'wall-offset') return null if (args.target.kind === 'opening-width') { const opening = args.nodes[args.target.openingId] diff --git a/packages/editor/src/lib/floorplan/floorplan-extension.ts b/packages/editor/src/lib/floorplan/floorplan-extension.ts index 590305a313..8fe7a90783 100644 --- a/packages/editor/src/lib/floorplan/floorplan-extension.ts +++ b/packages/editor/src/lib/floorplan/floorplan-extension.ts @@ -8,6 +8,7 @@ import type { SceneApi, } from '@pascal-app/core' import type { ComponentType } from 'react' +import type { WallSelectionDimension } from '../room-push-dimensions' export const FLOORPLAN_NODE_EXTENSION_KEY = 'pascal:editor/floorplan' export const FLOORPLAN_GEOMETRY_METADATA_KEY = 'pascal:editor/floorplan' @@ -18,6 +19,18 @@ export type FloorplanMetricNotation = 'meters' | 'millimeters' export type FloorplanToolMode = 'default' | 'expert' export type FloorplanWallDimensionReference = 'finished-faces' | 'centerline' | 'stud-faces' export const DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE = 'finished-faces' +/** + * Which faces a selected wall's temporary dimensions measure to: the interior + * faces (ID, the room size), the exterior faces (OD, the outside size) or both. + */ +export type FloorplanWallDimensionFaces = 'interior' | 'exterior' | 'both' +export const DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES: FloorplanWallDimensionFaces = 'both' + +export function normalizeFloorplanWallDimensionFaces(value: unknown): FloorplanWallDimensionFaces { + return value === 'interior' || value === 'exterior' + ? value + : DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES +} export type FloorplanAnnotationRole = | 'automatic-dimension' | 'contextual-dimension' @@ -67,6 +80,23 @@ export type FloorplanNodeExtension = { preferredView?: '2d' | '3d' referencedSelectionAnnotationRole?: FloorplanAnnotationRole contextualDimensions?: (node: N, ctx: GeometryContext) => FloorplanGeometry | null + /** + * A plan hit on this kind selects the node itself, not the room it bounds + * (3D stays room-first). For thin boundary bands whose rooms are clickable + * on either side of them. + */ + planHitSelectsNode?: boolean + /** + * Temporary dimensions shown while this node is the sole selection in + * Select mode, in 2D and 3D (level-local plan points). Each is typeable: + * `commitDimensionValue` drives the node so it reads the typed value. + * Recomputed when the node or its level's rooms change. + */ + selectionDimensions?: (args: { + node: N + nodes: Readonly> + faces: FloorplanWallDimensionFaces + }) => readonly WallSelectionDimension[] actionMenu?: { canCurve?: (args: { node: N; nodes: Readonly> }) => boolean /** Buttons the kind adds to the selection's action menu (2D and 3D); each decides its own visibility. */ @@ -131,6 +161,7 @@ type FloorplanContextExtension = { roomLabelOverlay: boolean metricNotation: FloorplanMetricNotation wallDimensionReference: FloorplanWallDimensionReference + wallDimensionFaces: FloorplanWallDimensionFaces } export function normalizeFloorplanWallDimensionReference( @@ -188,6 +219,7 @@ export function createFloorplanContextExtensions( wallDimensionReference: normalizeFloorplanWallDimensionReference( values.wallDimensionReference, ), + wallDimensionFaces: normalizeFloorplanWallDimensionFaces(values.wallDimensionFaces), } satisfies FloorplanContextExtension, } } @@ -205,6 +237,7 @@ export function readFloorplanContext(ctx: GeometryContext): FloorplanContextExte wallDimensionReference: normalizeFloorplanWallDimensionReference( extension.wallDimensionReference, ), + wallDimensionFaces: normalizeFloorplanWallDimensionFaces(extension.wallDimensionFaces), } } return { @@ -214,6 +247,7 @@ export function readFloorplanContext(ctx: GeometryContext): FloorplanContextExte roomLabelOverlay: false, metricNotation: 'meters', wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE, + wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES, } } diff --git a/packages/editor/src/lib/floorplan/floorplan-readonly.ts b/packages/editor/src/lib/floorplan/floorplan-readonly.ts index 6ca787cd90..f98bbcca08 100644 --- a/packages/editor/src/lib/floorplan/floorplan-readonly.ts +++ b/packages/editor/src/lib/floorplan/floorplan-readonly.ts @@ -1,6 +1,7 @@ import type { AnyNode, AnyNodeId, FloorplanPalette, GeometryContext } from '@pascal-app/core' import { createFloorplanContextExtensions, + type FloorplanWallDimensionFaces, type FloorplanWallDimensionReference, } from './floorplan-extension' @@ -15,6 +16,7 @@ export type FloorplanViewState = { /** Room labels drawn as screen pills (see `FloorplanContextExtension.roomLabelOverlay`). */ roomLabelOverlay?: boolean wallDimensionReference?: FloorplanWallDimensionReference + wallDimensionFaces?: FloorplanWallDimensionFaces highlighted: boolean hovered: boolean moving: boolean @@ -66,6 +68,7 @@ export function buildFloorplanContext( drafting: viewState.drafting === true, roomLabelOverlay: viewState.roomLabelOverlay === true, wallDimensionReference: viewState.wallDimensionReference, + wallDimensionFaces: viewState.wallDimensionFaces, }), viewState: viewState.palette ? { @@ -88,6 +91,8 @@ export function floorplanLayerRank(type: string): number { return 0 case 'slab': case 'ceiling': + // Under the footings drawn inside it, which stay pickable over it. + case 'foundation': return 1 default: return 2 diff --git a/packages/editor/src/lib/floorplan/foundation-dimensions.test.tsx b/packages/editor/src/lib/floorplan/foundation-dimensions.test.tsx new file mode 100644 index 0000000000..dcdeb23502 --- /dev/null +++ b/packages/editor/src/lib/floorplan/foundation-dimensions.test.tsx @@ -0,0 +1,96 @@ +import { expect, test } from 'bun:test' +import { renderToStaticMarkup } from 'react-dom/server' +import { FoundationDimensionsGroup } from '../../components/editor-2d/floorplan-foundation-layer' +import { foundationDimensionStrings } from './foundation-dimensions' + +const meters = (value: number) => value.toFixed(2) +const texts = (strings: ReturnType) => + strings.map((string) => string.segments.map((segment) => segment.text)) + +test('a box: one length a side, its dimension line 0.6 m out, from corner to corner', () => { + const strings = foundationDimensionStrings( + [ + [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + ], + meters, + ) + // Round the box: the -z side, +x, +z, -x; a single length needs no overall row. + expect(texts(strings)).toEqual([['10.00'], ['8.00'], ['10.00'], ['8.00']]) + const [south] = strings + expect(south?.offsetNormal).toEqual([0, -1]) + expect(south?.segments[0]).toMatchObject({ + start: [0, 0], + end: [10, 0], + dimensionStart: [0, -0.6], + dimensionEnd: [10, -0.6], + }) +}) + +test('an L: each side chained through its corners, the overall length a row further out', () => { + // Wound clockwise: the strings come out the same. + const strings = foundationDimensionStrings( + [ + [ + [0, 8], + [6, 8], + [6, 4], + [10, 4], + [10, 0], + [0, 0], + ], + ], + meters, + ) + expect(texts(strings)).toEqual([ + ['10.00'], + ['4.00', '4.00'], + ['8.00'], + ['6.00', '4.00'], + ['10.00'], + ['8.00'], + ]) + // The +z side's break at x 6 stands on the corner nearest that side, its overall row 1.2 m out. + const north = strings.filter((string) => string.offsetNormal[1] === 1) + expect(north[0]?.segments[0]?.end).toEqual([6, 8]) + expect(north[1]?.segments[0]?.dimensionStart).toEqual([0, 9.2]) +}) + +test('a side split in two reads its two pieces, and the whole', () => { + const strings = foundationDimensionStrings( + [ + [ + [0, 0], + [4, 0], + [10, 0], + [10, 8], + [0, 8], + ], + ], + meters, + ) + expect(texts(strings).slice(0, 2)).toEqual([['4.00', '6.00'], ['10.00']]) +}) + +test('the plan draws them as dimension strings, out of the way of the pointer', () => { + const strings = foundationDimensionStrings( + [ + [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + ], + meters, + ) + const markup = renderToStaticMarkup() + expect(markup).toContain('data-foundation-dimensions=""') + expect(markup.match(/data-floorplan-dimension-string=""/g)).toHaveLength(4) + expect(markup).toContain('10.00') + expect(renderToStaticMarkup()).toBe('') +}) diff --git a/packages/editor/src/lib/floorplan/foundation-dimensions.ts b/packages/editor/src/lib/floorplan/foundation-dimensions.ts new file mode 100644 index 0000000000..fe238fee34 --- /dev/null +++ b/packages/editor/src/lib/floorplan/foundation-dimensions.ts @@ -0,0 +1,143 @@ +import { + type AnyNode, + type AnyNodeId, + buildingFoundationNodes, + type FloorplanGeometry, + type FloorplanPoint, + traceFoundationOutlines, + union, +} from '@pascal-app/core' +import { houseFoundationOutlines } from '../foundation-view' + +type P = [number, number] +type DimensionString = Extract + +/** The rows' distances out of the foundation, metres: each corner and break, then the overall length. */ +const ROWS: readonly [number, number] = [0.6, 1.2] +/** How far an extension line runs past its dimension line, metres. */ +const OVERSHOOT = 0.12 +/** Corners closer than this along a side are one station of its dimension string, metres. */ +const SAME_STATION = 0.01 +/** A side facing a row at least this much (the cosine) is dimensioned on it. */ +const FACING = 0.1 +const DIMENSION_STROKE = '#475569' + +/** A ring as the clipper returns it, closed by repeating its first point: open it again. */ +function openRing(ring: ReadonlyArray): P[] { + const points = ring.map(([x, z]) => [x, z] as P) + const first = points[0] + const last = points[points.length - 1] + if (first && last && points.length > 1 && first[0] === last[0] && first[1] === last[1]) + points.pop() + return points +} + +/** + * The foundation's outline in the plan, the outlines it pours merged (the house's, traced from the + * walls while it follows them, and every outline drawn: a porch slab joins it): one ring per piece, + * level-local. + */ +export function foundationOutlineRings(nodes: Record, buildingId: string): P[][] { + const graph = nodes as Record + const drawn = buildingFoundationNodes(graph, buildingId).map((node) => node.polygon) + const traced = + houseFoundationOutlines(graph, buildingId).length > 0 + ? [] + : traceFoundationOutlines(graph, buildingId) + const rings = [...traced, ...drawn] + .map((ring) => ring.map(([x, z]) => [x, z] as P)) + .filter((ring) => ring.length >= 3) + if (rings.length === 0) return [] + return union(rings) + .map((polygon) => openRing(polygon.outer)) + .filter((ring) => ring.length >= 3) +} + +/** The four rows round a foundation: their outward normal, and the coordinate they run along. */ +const SIDES: { normal: FloorplanPoint; run: 0 | 1 }[] = [ + { normal: [0, -1], run: 0 }, + { normal: [1, 0], run: 1 }, + { normal: [0, 1], run: 0 }, + { normal: [-1, 0], run: 1 }, +] + +/** + * A foundation plan's dimensions, as a drafter strings them round it: on every side two rows out + * of it, the first chained through each corner and break the side shows (the sides facing that + * way, however the outline steps or turns), the second the overall length, where the first has + * more than one piece. Each station's extension line starts at its corner; `format` writes the + * lengths. `rows` and `overshoot` (metres) set the rows' distances out and how far the extension + * lines run past them: a plan on screen keeps them a constant size in pixels. + */ +export function foundationDimensionStrings( + rings: ReadonlyArray>, + format: (meters: number) => string, + { + rows = ROWS, + overshoot = OVERSHOOT, + }: { rows?: readonly [number, number]; overshoot?: number } = {}, +): DimensionString[] { + const strings: DimensionString[] = [] + for (const input of rings) { + if (input.length < 3) continue + let area = 0 + for (let i = 0; i < input.length; i++) { + const a = input[i]! + const b = input[(i + 1) % input.length]! + area += a[0] * b[1] - b[0] * a[1] + } + // Counter-clockwise, so each side's outward normal is to its right. + const ring = area > 0 ? [...input] : [...input].reverse() + for (const { normal, run } of SIDES) { + const depth = run === 0 ? 1 : 0 + const sign = normal[depth] + // The side's stations: the corners of the sides facing it, the one nearest it at each. + const stations: { at: number; witness: P }[] = [] + for (let i = 0; i < ring.length; i++) { + const a = ring[i]! + const b = ring[(i + 1) % ring.length]! + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (length < 1e-9) continue + const out: P = [(b[1] - a[1]) / length, -(b[0] - a[0]) / length] + if (out[0] * normal[0] + out[1] * normal[1] <= FACING) continue + for (const corner of [a, b]) { + const station = stations.find((s) => Math.abs(s.at - corner[run]) < SAME_STATION) + if (!station) stations.push({ at: corner[run], witness: [corner[0], corner[1]] }) + else if (corner[depth] * sign > station.witness[depth] * sign) + station.witness = [corner[0], corner[1]] + } + } + if (stations.length < 2) continue + stations.sort((p, q) => p.at - q.at) + const edge = Math.max(...ring.map((point) => point[depth] * sign)) * sign + const onRow = (row: number, at: number): FloorplanPoint => + run === 0 ? [at, edge + sign * row] : [edge + sign * row, at] + const row = (distance: number, from: number, to: number) => ({ + start: stations[from]!.witness, + end: stations[to]!.witness, + dimensionStart: onRow(distance, stations[from]!.at), + dimensionEnd: onRow(distance, stations[to]!.at), + text: format(stations[to]!.at - stations[from]!.at), + }) + const pieces = stations.slice(1).map((_, i) => row(rows[0], i, i + 1)) + strings.push({ + kind: 'dimension-string', + segments: pieces, + offsetNormal: normal, + offsetDistance: rows[0], + extensionOvershoot: overshoot, + stroke: DIMENSION_STROKE, + }) + if (stations.length > 2) + strings.push({ + kind: 'dimension-string', + segments: [row(rows[1], 0, stations.length - 1)], + offsetNormal: normal, + offsetDistance: rows[1], + extensionOvershoot: overshoot, + stroke: DIMENSION_STROKE, + }) + } + } + return strings +} diff --git a/packages/editor/src/lib/foundation-view.test.tsx b/packages/editor/src/lib/foundation-view.test.tsx new file mode 100644 index 0000000000..d743322f4e --- /dev/null +++ b/packages/editor/src/lib/foundation-view.test.tsx @@ -0,0 +1,229 @@ +import { afterEach, beforeEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FoundationNode, + LevelNode, + SiteNode, + SlabNode, + useScene, + WallNode, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { renderToStaticMarkup } from 'react-dom/server' +import { + FloorplanFoundationLayer, + FoundationPlanGroup, + foundationPlanDrawing, +} from '../components/editor-2d/floorplan-foundation-layer' +import { FoundationRow } from '../components/ui/floating-level-selector' +import { roomPickingEnabled } from '../hooks/use-selected-room' +import useEditor from '../store/use-editor' +import { installImmediateAnimationFrames } from '../test-utils/immediate-animation-frames' +import { + armFootingTool, + armFoundationTool, + editFoundationOutline, + enterFoundationView, + followWallsForFoundation, + houseFoundationOutlines, + traceFoundationFromWalls, +} from './foundation-view' + +const BOX: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] +const walls = BOX.map((start, index) => + WallNode.parse({ + id: `wall_g_${index}`, + parentId: 'level_g', + start, + end: BOX[(index + 1) % BOX.length], + thickness: 0.1, + }), +) + +/** A house on a slab, with an empty level under its ground floor (footings go there). */ +const HOUSE: AnyNode[] = [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_b', 'level_g'], + metadata: { foundation: { enabled: true, type: 'slab' } }, + }), + LevelNode.parse({ id: 'level_b', parentId: 'building_a', level: -1, children: [] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((wall) => wall.id), 'slab_g'], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: BOX, + elevation: 0.05, + thickness: 0.05, + }), + ...walls, +] + +let restoreFrames: () => void = () => {} +let previous: { + viewer: ReturnType + scene: ReturnType + editor: ReturnType +} +beforeEach(() => { + restoreFrames = installImmediateAnimationFrames() + previous = { + viewer: useViewer.getState(), + scene: useScene.getState(), + editor: useEditor.getState(), + } + useScene.setState({ + nodes: Object.fromEntries(HOUSE.map((node) => [node.id, node])) as Record, + rootNodeIds: ['site_a' as AnyNodeId], + }) + useViewer.getState().setSelection({ + buildingId: 'building_a' as never, + levelId: 'level_g' as never, + selectedIds: ['wall_g_0' as never], + }) +}) +afterEach(() => { + restoreFrames() + useViewer.setState(previous.viewer, true) + useScene.setState(previous.scene, true) + useEditor.setState(previous.editor, true) +}) + +test('the Foundation row selects the level footings go on, with nothing picked, and turns the view on', () => { + useEditor.setState({ room: { levelId: 'level_g', zoneId: 'zone_a' } as never }) + expect(enterFoundationView('building_a')).toBe(true) + const viewer = useViewer.getState() + expect(viewer.foundationView).toBe(true) + expect(viewer.selection).toMatchObject({ + buildingId: 'building_a', + levelId: 'level_b', + selectedIds: [], + }) + expect(useEditor.getState().room).toBeNull() + // Rooms are not what the view shows: a click picks a footing or nothing. + expect(roomPickingEnabled()).toBe(false) + + // A level row picked ends it. + useViewer.getState().setSelection({ levelId: 'level_g' as never }) + expect(useViewer.getState().foundationView).toBe(false) + expect(enterFoundationView('nothing')).toBe(false) +}) + +test('Draw a footing arms the Footing tool and stays in the foundation view', () => { + enterFoundationView('building_a') + armFootingTool() + expect(useEditor.getState()).toMatchObject({ mode: 'build', tool: 'footing' }) + expect(useViewer.getState().foundationView).toBe(true) + expect(useViewer.getState().selection.levelId).toBe('level_b') +}) + +test('the row reads Foundation with its badge, and shows as selected while the view is on', () => { + const row = (selected: boolean) => + renderToStaticMarkup( {}} />) + expect(row(true)).toContain('>Foundation<') + expect(row(true)).toContain('data-foundation-badge="Slab"') + expect(row(true)).toContain('aria-pressed="true"') + expect(row(false)).toContain('aria-pressed="false"') + expect( + renderToStaticMarkup( {}} />), + ).toContain('title="No foundation drawn: click to set one up"') +}) + +test('the plan: the slab outlined, its edge dashed where it ends inside, under the walls merged into one faint ghost', () => { + const nodes = useScene.getState().nodes as Record + const drawing = foundationPlanDrawing(nodes, 'building_a') + expect(drawing.shapes[0]?.kind).toBe('slab') + // The four walls are one shape: its outside and its inside, no seams at the corners. + expect(drawing.walls).toHaveLength(2) + + // Off, the layer draws nothing (a server render reads the store as it starts: off). + expect(renderToStaticMarkup()).toBe('') + const plan = renderToStaticMarkup() + expect(plan).toContain('data-foundation-member="slab"') + expect(plan).toMatch( + /data-foundation-hidden=""[^>]*data-foundation-member="turndown"[^>]*stroke-dasharray="6 4"/, + ) + expect(plan).toContain('data-foundation-walls=""') + // Dashes are the foundation's hidden lines alone: the walls are not dashed. + expect(plan).not.toContain('stroke-dasharray="7 5"') + expect(plan).toContain('pointer-events="none"') +}) + +test('Edit the outline traces the walls into an outline of its own, picked; Re-trace sets it back', () => { + enterFoundationView('building_a') + expect(editFoundationOutline('building_a')).toBe(true) + const outlines = Object.values(useScene.getState().nodes).filter( + (node) => node.type === 'foundation', + ) + expect(outlines).toHaveLength(1) + const outline = outlines[0]! + // On the level footings go on, picked, the view still on. + expect(outline.parentId).toBe('level_b') + expect(useViewer.getState().selection.selectedIds).toEqual([outline.id]) + expect(useViewer.getState().foundationView).toBe(true) + // Stretched, then traced again: the same outline, back at the walls' faces. + const traced = outline.type === 'foundation' ? outline.polygon : [] + useScene.getState().updateNode(outline.id, { + polygon: traced.map(([x, z]) => [x > 5 ? x + 2 : x, z] as [number, number]), + }) + expect(traceFoundationFromWalls('building_a')).toEqual([outline.id]) + const back = useScene.getState().nodes[outline.id] + expect(back?.type === 'foundation' && back.polygon).toEqual(traced) + expect(editFoundationOutline('nothing')).toBe(false) +}) + +test('Draw a foundation arms the Foundation tool in the view', () => { + enterFoundationView('building_a') + armFoundationTool() + expect(useEditor.getState()).toMatchObject({ mode: 'build', tool: 'foundation' }) + expect(useViewer.getState().foundationView).toBe(true) +}) + +test('a porch slab drawn on its own stays through Edit, Re-trace and Follow the walls', () => { + enterFoundationView('building_a') + const porch = FoundationNode.parse({ + name: 'Slab 1', + construction: 'slab', + drop: 0.04, + polygon: [ + [2, 8], + [6, 8], + [6, 10], + [2, 10], + ], + }) + useScene.getState().createNode(porch, 'level_b' as AnyNodeId) + const house = () => houseFoundationOutlines(useScene.getState().nodes, 'building_a') + // The porch is not the house's outline: the house still follows the walls, and Edit traces one. + expect(house()).toHaveLength(0) + expect(editFoundationOutline('building_a')).toBe(true) + const [own] = house() + expect(own).toBeDefined() + expect(traceFoundationFromWalls('building_a')).toEqual([own!.id]) + // Edit again picks the outline it has, tracing none anew. + useViewer.getState().setSelection({ selectedIds: [] }) + expect(editFoundationOutline('building_a')).toBe(true) + expect(house().map((node) => node.id)).toEqual([own!.id]) + expect(useViewer.getState().selection.selectedIds).toEqual([own!.id]) + // Follow the walls lets the house's outline go, and its pick; the porch stays as drawn. + followWallsForFoundation('building_a') + expect(house()).toHaveLength(0) + expect(useViewer.getState().selection.selectedIds).toEqual([]) + const kept = useScene.getState().nodes[porch.id] + expect(kept?.type === 'foundation' && kept.polygon).toEqual(porch.polygon) +}) diff --git a/packages/editor/src/lib/foundation-view.ts b/packages/editor/src/lib/foundation-view.ts new file mode 100644 index 0000000000..e7629ce7d9 --- /dev/null +++ b/packages/editor/src/lib/foundation-view.ts @@ -0,0 +1,114 @@ +import { + type AnyNode, + type AnyNodeId, + buildingFoundationNodes, + FoundationNode, + foundationViewOf, + isHouseOutline, + type LevelNode, + traceFoundationOutlines, + useScene, +} from '@pascal-app/core' +import { markPerfAction, useViewer } from '@pascal-app/viewer' +import useEditor from '../store/use-editor' + +/** + * The level list's Foundation row: select the level footings go on with nothing picked on it (the + * panel then shows the foundation's settings), and turn the foundation view on, last, since + * selecting another level ends it. `false` for a building with no level. + */ +export function enterFoundationView(buildingId: string): boolean { + const view = foundationViewOf(useScene.getState().nodes, buildingId) + if (!view.levelId) return false + const viewer = useViewer.getState() + if (view.levelId !== viewer.selection.levelId) markPerfAction('level-switch', view.levelId) + viewer.setSelection({ + buildingId: buildingId as NonNullable, + levelId: view.levelId as LevelNode['id'], + }) + useEditor.getState().clearRoom() + useViewer.getState().setFoundationView(true) + return true +} + +/** Arm the Footing tool, as Build › Advanced › Footing does, keeping the last Strip / Pad choice. */ +export function armFootingTool(): void { + const editor = useEditor.getState() + editor.setPhase('building') + editor.armToolMode({ mode: 'build', tool: 'footing' }) +} + +/** Arm the Foundation tool: draw an outline from scratch, corner by corner. */ +export function armFoundationTool(): void { + const editor = useEditor.getState() + editor.setPhase('building') + editor.armToolMode({ mode: 'build', tool: 'foundation' }) +} + +/** + * The building's outlines of the house's own foundation (`isHouseOutline`): while it has none, the + * foundation follows the outside walls. A porch's or a patio's slab drawn on its own is not one. + */ +export function houseFoundationOutlines( + nodes: Parameters[0], + buildingId: string, +): FoundationNode[] { + return buildingFoundationNodes(nodes, buildingId).filter(isHouseOutline) +} + +/** + * The house's foundation outlines set to the walls' outside faces (`traceFoundationOutlines`), as + * one undo step: the outlines it has take the traced shapes, in order; the traced loops left over + * become new outlines on the level footings go on; outlines left over go. Outlines poured on their + * own (a porch slab) stay as they are. Returns the outlines' ids, or an empty list where the walls + * close no loop (nothing changes then). + */ +export function traceFoundationFromWalls(buildingId: string): string[] { + const nodes = useScene.getState().nodes + const traced = traceFoundationOutlines(nodes, buildingId) + const levelId = foundationViewOf(nodes, buildingId).levelId + if (traced.length === 0 || !levelId) return [] + const existing = houseFoundationOutlines(nodes, buildingId) + const count = Object.values(nodes).filter((node) => node.type === 'foundation').length + const update = existing.slice(0, traced.length).map((node, index) => ({ + id: node.id as AnyNodeId, + data: { polygon: traced[index]! } as Partial, + })) + const create = traced.slice(existing.length).map((polygon, index) => ({ + node: FoundationNode.parse({ + name: `Foundation ${count + index + 1}`, + polygon, + }) as AnyNode, + parentId: levelId as AnyNodeId, + })) + const remove = existing.slice(traced.length).map((node) => node.id as AnyNodeId) + useScene.getState().applyNodeChanges({ create, update, delete: remove }) + return [...update.map((change) => change.id), ...create.map((change) => change.node.id)] +} + +/** + * "Edit the outline": the foundation, traced from the walls into outlines of its own, picked so + * their corners and sides can be dragged. Its outlines stop following the walls from here on. + */ +export function editFoundationOutline(buildingId: string): boolean { + const existing = houseFoundationOutlines(useScene.getState().nodes, buildingId) + const ids = + existing.length > 0 ? existing.map((node) => node.id) : traceFoundationFromWalls(buildingId) + if (ids.length === 0) return false + useViewer.getState().setSelection({ selectedIds: [ids[0] as AnyNodeId] }) + return true +} + +/** + * "Follow the walls": the house's own foundation outlines go, in one undo step, so the foundation + * is traced from the outside walls again and moves with them. Outlines poured on their own stay. + */ +export function followWallsForFoundation(buildingId: string): void { + const outlines = houseFoundationOutlines(useScene.getState().nodes, buildingId) + if (outlines.length === 0) return + const ids = new Set(outlines.map((node) => node.id)) + const viewer = useViewer.getState() + if (viewer.selection.selectedIds.some((id) => ids.has(id))) + viewer.setSelection({ selectedIds: [] }) + useScene.getState().applyNodeChanges({ delete: [...ids] as AnyNodeId[] }) +} diff --git a/packages/editor/src/lib/grid-frame.test.ts b/packages/editor/src/lib/grid-frame.test.ts index 3ff9920231..f93eca6742 100644 --- a/packages/editor/src/lib/grid-frame.test.ts +++ b/packages/editor/src/lib/grid-frame.test.ts @@ -1,6 +1,11 @@ -import { expect, test } from 'bun:test' -import { Group, Vector3 } from 'three' -import { gridLocalNormal, gridLocalPoint } from './grid-frame' +import { describe, expect, test } from 'bun:test' +import { Group, Quaternion, Vector3 } from 'three' +import { + gridLocalNormal, + gridLocalPoint, + HORIZONTAL_GRID_QUATERNION, + layHorizontalGrid, +} from './grid-frame' test('grid floor and cursor stay in model coordinates after God-view pan, scale and yaw', () => { const root = new Group() @@ -17,3 +22,51 @@ test('grid floor and cursor stay in model coordinates after God-view pan, scale ).toBeLessThan(1e-8) expect(world.distanceTo(local)).toBeGreaterThan(1) }) + +// The grid plane's lattice lines run at multiples of the cell size in its own +// XY, from its origin: where does plane-local (u, v) land? +function planePoint(position: Vector3, quaternion: Quaternion, u: number, v: number) { + return new Vector3(u, v, 0).applyQuaternion(quaternion).add(position) +} + +describe('layHorizontalGrid', () => { + test("lays the lattice on a turned, offset building's own frame", () => { + const yaw = (17 * Math.PI) / 180 + const position = new Vector3() + const quaternion = new Quaternion() + layHorizontalGrid({ position: [12.37, 0, -8.91], rotationY: yaw }, 0.301, position, quaternion) + + expect(position.toArray()).toEqual([12.37, 0.301, -8.91]) + // Flat… + expect(new Vector3(0, 0, 1).applyQuaternion(quaternion).y).toBeCloseTo(1, 12) + // …and each lattice point is a building-local lattice point: plane (1, -1.5) + // is building-local (1, 1.5), wherever the building's yaw puts it. + const point = planePoint(position, quaternion, 1, -1.5) + const cos = Math.cos(yaw) + const sin = Math.sin(yaw) + expect(point.x).toBeCloseTo(12.37 + cos + 1.5 * sin, 12) + expect(point.y).toBeCloseTo(0.301, 12) + expect(point.z).toBeCloseTo(-8.91 - sin + 1.5 * cos, 12) + }) + + test('with no building the world grid stays anchored at the origin, as before', () => { + const position = new Vector3(5, 5, 5) + const quaternion = new Quaternion() + layHorizontalGrid(null, 0.001, position, quaternion) + + expect(position.toArray()).toEqual([0, 0.001, 0]) + expect(Math.abs(quaternion.dot(HORIZONTAL_GRID_QUATERNION))).toBeCloseTo(1, 12) + const point = planePoint(position, quaternion, 2, -3) + expect(point.x).toBeCloseTo(2, 12) + expect(point.z).toBeCloseTo(3, 12) + }) + + test('an unturned building at the origin draws the same grid as no building', () => { + const position = new Vector3() + const quaternion = new Quaternion() + layHorizontalGrid({ position: [0, 0, 0], rotationY: 0 }, 0.001, position, quaternion) + + expect(position.toArray()).toEqual([0, 0.001, 0]) + expect(Math.abs(quaternion.dot(HORIZONTAL_GRID_QUATERNION))).toBeCloseTo(1, 12) + }) +}) diff --git a/packages/editor/src/lib/grid-frame.ts b/packages/editor/src/lib/grid-frame.ts index 189ef9636b..dd7265f960 100644 --- a/packages/editor/src/lib/grid-frame.ts +++ b/packages/editor/src/lib/grid-frame.ts @@ -1,4 +1,5 @@ -import type { Object3D, Vector3 } from 'three' +import type { BuildingPose } from '@pascal-app/core' +import { type Object3D, Quaternion, Vector3 } from 'three' // Placement surfaces are world-space; the grid lives under the scalable XR // scene root. Keep its lattice and reveal in that root's model coordinates. @@ -16,3 +17,29 @@ export function gridLocalNormal(parent: Object3D | null, normal: Vector3, target if (parent) target.transformDirection(parent.matrixWorld.clone().invert()) return target } + +const UP = new Vector3(0, 1, 0) + +/** Lays the grid's PlaneGeometry (which faces +Z) flat, its normal → +Y. */ +export const HORIZONTAL_GRID_QUATERNION = new Quaternion().setFromUnitVectors( + new Vector3(0, 0, 1), + UP, +) + +/** + * Lay the horizontal grid plane on the lattice the active tools snap on: a + * building's own frame (`pose`, its yaw and offset — the lattice + * `snapWorldXZForActiveBuilding` snaps to), or with `pose` null the world XZ + * grid anchored at (0, 0). The lattice lines run at multiples of the cell size + * from the plane's origin, so a turned house's walls sit square on them. `y` + * is the plane's height in the grid parent's frame. + */ +export function layHorizontalGrid( + pose: BuildingPose | null, + y: number, + position: Vector3, + quaternion: Quaternion, +) { + position.set(pose?.position[0] ?? 0, y, pose?.position[2] ?? 0) + quaternion.setFromAxisAngle(UP, pose?.rotationY ?? 0).multiply(HORIZONTAL_GRID_QUATERNION) +} diff --git a/packages/editor/src/lib/ground-surface.ts b/packages/editor/src/lib/ground-surface.ts index 6137d77f35..a8e957fda9 100644 --- a/packages/editor/src/lib/ground-surface.ts +++ b/packages/editor/src/lib/ground-surface.ts @@ -14,23 +14,26 @@ // it delegates to (`raycastTerrain`, `heightAt`) is pure core. import { + gradedTerrainFieldOf, heightAt, isSiteDatum, raycastTerrain, type SiteNode, type TerrainField, type TerrainHit, - terrainFieldOf, useScene, } from '@pascal-app/core' -/** The active site's terrain field, or null when the scene has no sculpted ground. */ +/** + * The active site's terrain field as built (graded to the slabs' building pads, as it draws), or + * null when the scene has no sculpted ground. + */ export function siteTerrainField(): TerrainField | null { const { nodes, rootNodeIds } = useScene.getState() const siteId = rootNodeIds[0] const site = siteId ? nodes[siteId] : null if (site?.type !== 'site') return null - return terrainFieldOf(site as SiteNode) + return gradedTerrainFieldOf(nodes, site as SiteNode) } /** diff --git a/packages/editor/src/lib/keyboard-pan.test.ts b/packages/editor/src/lib/keyboard-pan.test.ts index f6f96180e6..88ce880288 100644 --- a/packages/editor/src/lib/keyboard-pan.test.ts +++ b/packages/editor/src/lib/keyboard-pan.test.ts @@ -111,6 +111,13 @@ test('modifier chords stay shortcuts, and typing in a field never pans', () => { expect(acceptsKeyboardPan(key({ target: new Input() }))).toBe(false) }) +test('Shift with W, S or D arms a tool instead of panning; Shift with A still pans', () => { + for (const code of ['KeyW', 'KeyS', 'KeyD']) + expect(acceptsKeyboardPan(key({ code, shiftKey: true }))).toBe(false) + expect(acceptsKeyboardPan(key({ code: 'KeyA', shiftKey: true }))).toBe(true) + expect(acceptsKeyboardPan(key({ code: 'KeyD' }))).toBe(true) +}) + test('cruise speed scales with the visible width within fixed bounds', () => { expect(keyboardPanSpeed(0)).toBe(0.5) expect(keyboardPanSpeed(20)).toBeCloseTo(6) diff --git a/packages/editor/src/lib/keyboard-pan.ts b/packages/editor/src/lib/keyboard-pan.ts index c5f55d1f04..3d0e3a695e 100644 --- a/packages/editor/src/lib/keyboard-pan.ts +++ b/packages/editor/src/lib/keyboard-pan.ts @@ -2,6 +2,8 @@ // move the same way. Keys match by physical position (`event.code`): the // cluster stays under the left hand on any layout (Z/Q/S/D on AZERTY). +import { claimsKeyboardPan } from './tool-shortcuts' + export type KeyboardPanState = { forward: boolean backward: boolean @@ -79,10 +81,15 @@ export function clearKeyboardPanKeys(state: KeyboardPanState) { state.right = false } -/** Pan keys are ignored with a modifier held (shortcuts) or while typing. */ +/** + * Pan keys are ignored with a modifier held (shortcuts), for a Shift chord that + * arms a tool (Shift+W window, Shift+S stairs, Shift+D door), and while typing. + */ export function acceptsKeyboardPan(event: KeyboardEvent) { return ( - !(event.metaKey || event.ctrlKey || event.altKey) && !isEditableKeyboardTarget(event.target) + !(event.metaKey || event.ctrlKey || event.altKey) && + !claimsKeyboardPan(event) && + !isEditableKeyboardTarget(event.target) ) } diff --git a/packages/editor/src/lib/layer-thickness.test.ts b/packages/editor/src/lib/layer-thickness.test.ts new file mode 100644 index 0000000000..8c66aa58d4 --- /dev/null +++ b/packages/editor/src/lib/layer-thickness.test.ts @@ -0,0 +1,22 @@ +import { expect, test } from 'bun:test' +import { ROOF_BUILD_UP_PRESETS, roofBuildUpDepths } from '@pascal-app/core' +import { formatLayerDepth, formatLayerThickness } from './layer-thickness' + +const IN = 0.0254 + +// The roof layers read DECK 10-3/16" beside COVERING 0.54": a covering of shingle and underlayment +// is no exact 32nd, so it fell to decimals. Summed depths read as fractions, to the nearest 1/16". +test('summed depths read as fractions to the 1/16", like the deck beside them', () => { + expect(formatLayerDepth(0.54 * IN, 'imperial')).toBe('9/16"') + for (const preset of ROOF_BUILD_UP_PRESETS) { + const { deck, covering } = roofBuildUpDepths(preset.buildUp) + for (const depth of [deck, covering]) + expect(formatLayerDepth(depth, 'imperial')).toMatch(/^(\d+|\d+\/\d+|\d+-\d+\/\d+)"$/) + } + expect(formatLayerDepth(0.0137, 'metric')).toBe('13.7 mm') +}) + +test('a typed thickness still reads back as typed', () => { + expect(formatLayerThickness((7 / 16) * IN, 'imperial')).toBe('7/16"') + expect(formatLayerThickness(0.0111, 'imperial')).toBe('0.437"') +}) diff --git a/packages/editor/src/lib/layer-thickness.ts b/packages/editor/src/lib/layer-thickness.ts new file mode 100644 index 0000000000..c9f62aa1bb --- /dev/null +++ b/packages/editor/src/lib/layer-thickness.ts @@ -0,0 +1,79 @@ +import type { LinearUnit } from './measurements' + +const METRES_PER_INCH = 0.0254 + +/** + * Imperial-friendly thickness input. Accepts `5/8`, `5/8"`, `7/16 in`, + * `1-1/4"`, `1 1/2"`, `12mm`, `0.012m`. A bare number means INCHES in imperial + * display and MILLIMETRES in metric — layers are millimetre-scale and + * typing `0.0127` for half-inch board is nobody's idea of usable. + */ +export function parseLayerThickness(raw: string, unit: LinearUnit): number | null { + const text = raw.trim().toLowerCase().replace(/["”]/g, ' in ') + if (!text) return null + const explicit = /(mm|cm|m|in|ft|')\s*$/.exec(text) + const suffix = explicit?.[1] + const body = (suffix ? text.slice(0, explicit?.index) : text).trim() + + // `1-1/4` / `1 1/2` / `5/8` / `0.4375` + const mixed = /^(\d+(?:\.\d+)?)[\s-]+(\d+)\s*\/\s*(\d+)$/.exec(body) + const fraction = /^(\d+)\s*\/\s*(\d+)$/.exec(body) + let value: number + if (mixed) { + value = Number(mixed[1]) + Number(mixed[2]) / Number(mixed[3]) + } else if (fraction) { + value = Number(fraction[1]) / Number(fraction[2]) + } else { + value = Number(body) + } + if (!Number.isFinite(value) || value < 0) return null + + switch (suffix) { + case 'mm': + return value / 1000 + case 'cm': + return value / 100 + case 'm': + return value + case 'in': + return value * METRES_PER_INCH + case 'ft': + case "'": + return value * 12 * METRES_PER_INCH + default: + return unit === 'imperial' ? value * METRES_PER_INCH : value / 1000 + } +} + +/** + * An exact 32nd of an inch as its fraction, so 7/16 in (0.0111125 m) reads back as `7/16"`; any + * other value as decimal inches (0.0111 m is `0.437"`). + */ +export function formatLayerThickness(metres: number, unit: LinearUnit): string { + if (unit !== 'imperial') return `${Math.round(metres * 1000 * 10) / 10} mm` + const inchValue = metres / METRES_PER_INCH + const parts = Math.round(inchValue * 32) + if (Math.abs(inchValue * 32 - parts) > 1e-6) return `${Math.round(inchValue * 1000) / 1000}"` + const whole = Math.floor(parts / 32) + let numerator = parts % 32 + let denominator = 32 + while (numerator % 2 === 0 && numerator > 0) { + numerator /= 2 + denominator /= 2 + } + if (numerator === 0) return `${whole}"` + return whole > 0 ? `${whole}-${numerator}/${denominator}"` : `${numerator}/${denominator}"` +} + +/** + * A summed depth to read, not type back (a plane's deck or covering, a ceiling's thickness): in + * imperial to the nearest 1/16" as a fraction, as the lumber and panels it adds up read, so a + * covering of 0.54" reads 9/16" beside a 10-3/16" deck. + */ +export function formatLayerDepth(metres: number, unit: LinearUnit): string { + if (unit !== 'imperial') return formatLayerThickness(metres, unit) + return formatLayerThickness( + (Math.round((metres / METRES_PER_INCH) * 16) / 16) * METRES_PER_INCH, + unit, + ) +} diff --git a/packages/editor/src/lib/level-selection.ts b/packages/editor/src/lib/level-selection.ts index 1a3beb780c..0d66f26d81 100644 --- a/packages/editor/src/lib/level-selection.ts +++ b/packages/editor/src/lib/level-selection.ts @@ -1,5 +1,6 @@ import type { AnyNodeId, BuildingNode, LevelNode } from '@pascal-app/core' -import { findLevelAncestorId, useScene } from '@pascal-app/core' +import { findLevelAncestorId, runAsSingleSceneHistoryStep, useScene } from '@pascal-app/core' +import { buildLevelRemovalChanges } from '@pascal-app/core/building' import { useViewer } from '@pascal-app/viewer' function getAdjacentLevelIdForDeletion(levelId: AnyNodeId): LevelNode['id'] | null { @@ -19,11 +20,20 @@ function getAdjacentLevelIdForDeletion(levelId: AnyNodeId): LevelNode['id'] | nu return siblingLevelIds[currentIndex - 1] ?? siblingLevelIds[currentIndex + 1] ?? null } +/** + * Deletes a level, the building standing as it did (`buildLevelRemovalChanges`: the floors over a + * deleted basement stay put, a ground floor seated on its foundation stays seated), in one undo + * step, and moves the selection off it. + */ export function deleteLevelWithFallbackSelection(levelId: AnyNodeId) { const isSelectedLevel = useViewer.getState().selection.levelId === levelId const nextLevelId = getAdjacentLevelIdForDeletion(levelId) - useScene.getState().deleteNode(levelId) + const update = buildLevelRemovalChanges({ nodes: useScene.getState().nodes, levelId }) + runAsSingleSceneHistoryStep(useScene, () => { + useScene.getState().deleteNode(levelId) + if (update.length > 0) useScene.getState().applyNodeChanges({ update }) + }) if (isSelectedLevel) { useViewer.getState().setSelection({ levelId: nextLevelId }) diff --git a/packages/editor/src/lib/material-swatch-style.ts b/packages/editor/src/lib/material-swatch-style.ts new file mode 100644 index 0000000000..84275f067f --- /dev/null +++ b/packages/editor/src/lib/material-swatch-style.ts @@ -0,0 +1,24 @@ +import type { MaterialSwatch } from '@pascal-app/core' +import type { CSSProperties } from 'react' + +/** + * The CSS that paints a material swatch: the image over its colour, and a tinted + * albedo multiplied by the colour, as the 3D material tints its map. + */ +export function materialSwatchStyle({ + color, + imageUrl, + tinted, +}: Pick & { tinted?: boolean }): CSSProperties { + return { + backgroundColor: color, + ...(imageUrl + ? { + backgroundImage: `url(${JSON.stringify(imageUrl)})`, + backgroundPosition: 'center', + backgroundSize: 'cover', + ...(tinted ? { backgroundBlendMode: 'multiply' } : {}), + } + : {}), + } +} diff --git a/packages/editor/src/lib/orbit-near-plane.test.ts b/packages/editor/src/lib/orbit-near-plane.test.ts new file mode 100644 index 0000000000..0562722dee --- /dev/null +++ b/packages/editor/src/lib/orbit-near-plane.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, test } from 'bun:test' +import { CLOSE_UP_NEAR, ORBIT_NEAR, orbitNearPlane } from './orbit-near-plane' + +describe('the orbit near plane follows the eye in', () => { + test("standing back it is the viewer's own; close in, a tenth of the distance; never under an inch-off look", () => { + expect(orbitNearPlane(40)).toBe(ORBIT_NEAR) + expect(orbitNearPlane(3)).toBe(ORBIT_NEAR) + expect(orbitNearPlane(1)).toBeCloseTo(0.1, 9) + expect(orbitNearPlane(0.5)).toBeCloseTo(0.05, 9) + expect(orbitNearPlane(0.01)).toBe(CLOSE_UP_NEAR) + }) +}) diff --git a/packages/editor/src/lib/orbit-near-plane.ts b/packages/editor/src/lib/orbit-near-plane.ts new file mode 100644 index 0000000000..b46856b10b --- /dev/null +++ b/packages/editor/src/lib/orbit-near-plane.ts @@ -0,0 +1,13 @@ +/** The viewer's near plane (`viewerCameraClipping`), kept while the eye stands back. */ +export const ORBIT_NEAR = 0.3 +/** How close the near plane comes in when the eye does: a casing seen from an inch off. */ +export const CLOSE_UP_NEAR = 0.01 + +/** + * The near plane for an orbit `distance` from its target: a tenth of it, between `CLOSE_UP_NEAR` + * and the viewer's own, so zooming in on a window's trim never clips it while the eye standing + * back keeps the depth precision it had. + */ +export function orbitNearPlane(distance: number): number { + return Math.min(ORBIT_NEAR, Math.max(CLOSE_UP_NEAR, distance * 0.1)) +} diff --git a/packages/editor/src/lib/paint-scope.test.ts b/packages/editor/src/lib/paint-scope.test.ts index 5ff5a82ef5..8ebb32bc95 100644 --- a/packages/editor/src/lib/paint-scope.test.ts +++ b/packages/editor/src/lib/paint-scope.test.ts @@ -790,3 +790,55 @@ describe('generated floor plates: scopes per surface', () => { expect(paintScopeRole(manual, 'surface', 'room')).toBe('surface') }) }) + +describe('a surface that declares its own scopes', () => { + it('offers its scopes and labels and paints the role it names for the scope', () => { + const kind = 'paint-surface-scope-test' + if (!nodeRegistry.has(kind)) + registerNode({ + kind, + schemaVersion: 1, + schema: z.object({ type: z.literal(kind) }) as never, + category: 'structure', + defaults: () => ({}), + capabilities: { + paint: { + resolveRole: () => null, + buildPatch: () => ({}), + applyPreview: () => null, + roleLabel: (_node: AnyNode, role: string) => (role === 'end:1' ? 'This end' : null), + scopes: (_node: AnyNode, role: string) => + role === 'end:1' + ? { + scopes: ['single', 'matching'], + labels: { matching: 'Every end' }, + roleFor: (scope: PaintScope) => (scope === 'matching' ? 'ends:all' : role), + } + : null, + }, + }, + floorplanScope: 'level', + renderer: { kind: 'parametric', module: async () => ({ default: () => null }) }, + } as unknown as AnyNodeDefinition) + const node = { id: 'scoped_1', type: kind } as unknown as AnyNode + const hover = paintHoverInfo(node, 'end:1', []) + expect(hover.scopes).toEqual(['single', 'matching']) + expect(paintScopeLabel('single', hover)).toBe('This end') + expect(paintScopeLabel('matching', hover)).toBe('Every end') + const targets = (scope: PaintScope) => + resolvePaintScopeTargets({ + node, + role: 'end:1', + scope, + nodes: { scoped_1: node }, + spaces: {}, + slotRolesOf: () => [], + }).map((target) => `${target.nodeId}:${target.role}`) + expect(targets('single')).toEqual(['scoped_1:end:1']) + expect(targets('matching')).toEqual(['scoped_1:ends:all']) + // a scope the surface does not offer paints the surface alone + expect(targets('room')).toEqual(['scoped_1:end:1']) + // other roles of the kind keep the derived set + expect(availablePaintScopes({ node, slotRoles: [], role: 'side' })).toEqual(['single']) + }) +}) diff --git a/packages/editor/src/lib/paint-scope.ts b/packages/editor/src/lib/paint-scope.ts index 84130be31a..7b3df32a63 100644 --- a/packages/editor/src/lib/paint-scope.ts +++ b/packages/editor/src/lib/paint-scope.ts @@ -55,6 +55,11 @@ function nodeOffersRoomScope(node: AnyNode): boolean { return nodeRegistry.get(node.type)?.capabilities?.paint?.roomScope === true } +/** The scopes the hovered surface declares for itself (`capabilities.paint.scopes`), if any. */ +function surfaceScopes(node: AnyNode, role: string) { + return nodeRegistry.get(node.type)?.capabilities?.paint?.scopes?.(node, role) ?? null +} + const isFloorPlate = (node: AnyNode): boolean => node.type === 'slab' && node.boundary === 'auto' const FOOTPRINT_FACE_LABELS: Record = { @@ -104,6 +109,8 @@ export function availablePaintScopes(args: { if (args.node.type === 'wall') return ['single', 'room'] const plate = args.role === undefined ? null : platePaintScopes(args.node, args.role) if (plate) return plate.scopes + const own = args.role === undefined ? null : surfaceScopes(args.node, args.role) + if (own) return own.scopes const scopes: PaintScope[] = ['single'] if (args.slotRoles.length > 1) scopes.push('object') if (nodeHasAsset(args.node)) scopes.push('matching') @@ -123,7 +130,7 @@ export function paintHoverInfo(node: AnyNode, role: string, slotRoles: string[]) scopes: availablePaintScopes({ node, slotRoles, role }), slotLabel: paintSurfaceLabel(node, role), nodeNoun: node.type, - labels: platePaintScopes(node, role)?.labels, + labels: platePaintScopes(node, role)?.labels ?? surfaceScopes(node, role)?.labels, } } @@ -506,6 +513,12 @@ export function resolvePaintScopeTargets(args: { .map((other) => ({ nodeId: other.id, role: stepRole })) return targets.length ? targets : [{ nodeId: node.id as AnyNodeId, role: stepRole }] } + // A surface with its own scope set paints the role it names for the scope. + const own = surfaceScopes(node, role) + if (own) { + const roleFor = own.roleFor?.(effectivePaintScope(scope, own.scopes)) ?? role + return [{ nodeId: node.id as AnyNodeId, role: roleFor }] + } if (scope === 'single') return single // Whole object: paint every slot of the clicked node. Generic across any diff --git a/packages/editor/src/lib/panel-rows.ts b/packages/editor/src/lib/panel-rows.ts index ef0faaf97e..ebbe0f867c 100644 --- a/packages/editor/src/lib/panel-rows.ts +++ b/packages/editor/src/lib/panel-rows.ts @@ -65,19 +65,28 @@ export const PANEL_MODEL_EXTENSION = 'pascal:editor/panel-model' /** * A control the multi-selection inspector renders for every selected node of * a kind at once (derived settings the raw parametrics can't express). `value` - * null means the selection is mixed: no option shows as active. + * null means the selection is mixed: no option shows as active. `segmented` + * suits a few short options, `select` a longer list; `readout` only shows a + * value the selection cannot edit here. */ export type MultiPanelControl = { id: string - kind: 'segmented' - /** The parametrics group title the control sits in. */ + /** + * The section the control sits in: a parametrics group title, or a section + * of its own, shown after the groups in the order controls name them. + */ section: string label: string - options: ReadonlyArray<{ value: string; label: string }> - value: string | null - /** Applies to every selected node in one undo step. */ - onChange: (value: string) => void -} +} & ( + | { + kind: 'segmented' | 'select' + options: ReadonlyArray<{ value: string; label: string }> + value: string | null + /** Applies to every selected node in one undo step. */ + onChange: (value: string) => void + } + | { kind: 'readout'; value: string; detail?: string } +) export type NodePanelModel = { rows: (context: { diff --git a/packages/editor/src/lib/room-controls-phase6.test.ts b/packages/editor/src/lib/room-controls-phase6.test.ts index e01b2821a0..c3fef87727 100644 --- a/packages/editor/src/lib/room-controls-phase6.test.ts +++ b/packages/editor/src/lib/room-controls-phase6.test.ts @@ -17,6 +17,7 @@ import { import { useViewer } from '@pascal-app/viewer' import { createElement } from 'react' import { roomTransformSource } from '../components/editor/room-controls' +import { commitDimensionValue } from '../components/editor-2d/renderers/floorplan-dimension-edit-overlay' import { getRoomSelectionIndex } from '../hooks/use-selected-room' import useEditor from '../store/use-editor' import useInteractionScope from '../store/use-interaction-scope' @@ -34,6 +35,7 @@ import { roomFloorElevation, roomPushHandles, runRoomHandleDrag, + selectedWallDimensions, } from './room-handle-drag' import { applyRoomPlan, renameRoom } from './room-structure-commands' import { @@ -376,6 +378,45 @@ describe('room handles (elevation, push/pull)', () => { expect(useScene.temporal.getState().pastStates).toHaveLength(1) }) + test('a selected wall shows its room width and outside size, and typing either moves the wall', () => { + const east = () => + Object.values(useScene.getState().nodes).find( + (n): n is WallNode => n.type === 'wall' && n.start[0] === n.end[0] && n.start[0] > 3, + )! + const wall = east() + const thickness = wall.thickness ?? 0.1 + const dimensions = selectedWallDimensions(useScene.getState().nodes, wall, 'both') + const interior = dimensions.find((d) => d.faces === 'interior')! + const exterior = dimensions.find((d) => d.faces === 'exterior')! + // The 6 m room between centrelines: face to face inside, outer face to outer face outside. + expect(interior.distance).toBeCloseTo(6 - thickness) + expect(exterior.distance).toBeCloseTo(6 + thickness) + expect(selectedWallDimensions(useScene.getState().nodes, wall, 'interior')).toEqual([interior]) + expect(selectedWallDimensions(useScene.getState().nodes, wall, 'exterior')).toEqual([exterior]) + + // Type 10' ID: the wall moves out until the room is exactly that wide, one undo step. + const target = (d: typeof interior) => ({ + ownerNodeId: wall.id as AnyNodeId, + witnessStart: d.from, + witnessEnd: d.to, + value: d.distance, + }) + expect(commitDimensionValue({ target: target(interior), nextLength: 3.048 })).toEqual({ + ok: true, + }) + // The west face sits half a thickness inside x = 0; the east centreline half outside its face. + expect(east().start[0]).toBeCloseTo(3.048 + thickness) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + const after = selectedWallDimensions(useScene.getState().nodes, east(), 'both') + expect(after.find((d) => d.faces === 'interior')!.distance).toBeCloseTo(3.048) + + // Type the outside size: the same wall moves so the building reads it. + const outside = after.find((d) => d.faces === 'exterior')! + expect(commitDimensionValue({ target: target(outside), nextLength: 5 })).toEqual({ ok: true }) + expect(east().start[0]).toBeCloseTo(5 - thickness) + expect(useScene.temporal.getState().pastStates).toHaveLength(2) + }) + test('a partial span splits off and moves only the room-facing piece, openings stay put', () => { // A 10 m north wall, of which this 6 m room faces only 0..0.6; a door // on the far stretch must stay where it is. diff --git a/packages/editor/src/lib/room-handle-drag.ts b/packages/editor/src/lib/room-handle-drag.ts index fc6ad96dd0..33b7c5e2cd 100644 --- a/packages/editor/src/lib/room-handle-drag.ts +++ b/packages/editor/src/lib/room-handle-drag.ts @@ -17,12 +17,16 @@ import { planWallDivision, resolveMovedWallSupportSlabPatch, runAsSingleSceneHistoryStep, + SEPARATOR_JOIN_REACH, + type SeparatorNode, + type SeparatorSpan, type StructureNodes, type StructurePlan, setRoomFloorConstruction, setWallGeometry, setZoneEdges, setZoneIntent, + translateSeparators, upperFloorHeightControl, useLiveNodeOverrides, useScene, @@ -46,13 +50,25 @@ import { roomOwnPlate } from './room-built-on' import { mezzanineEdgeDimensions, type RoomDimension, + separatorEdgeDimensionRooms, + type WallDimensionFaces, + type WallSelectionDimension, wallPushDimensionRooms, wallPushDimensions, + wallSelectionDimensions, } from './room-push-dimensions' import { applyRoomPlan } from './room-structure-commands' +import { levelSeparatorLines, separatorWallLine } from './separator-lines' import { sfxEmitter } from './sfx-bus' import { type SpatialPointerId, spatialPointerInput } from './spatial-pointer-input' import { isTypingTarget } from './typing-target' +import { + nearestWallParallelTarget, + publishWallParallelGuide, + type WallParallelTarget, + wallParallelTargets, +} from './wall-parallel-snap' +import { isWallRunFree } from './wall-run-snap' export const ROOM_ELEVATION_DRAG_LABEL = 'room-elevation' /** Scope label of every wall push: a selected wall's side arrow and a room's boundary arrow. */ @@ -129,9 +145,16 @@ export function roomFloorElevation(nodes: Nodes, zoneId: string): number { /** * The stretch of a wall a push moves: `[0, 1]` is the whole wall (its * neighbours follow through the junction planner), anything shorter is split - * off first and only that piece moves. + * off first and only that piece moves. An edge of invisible walls carries its + * stretches in `separators` (`wallId` is the first one's): they move as one + * line, the invisible walls joined to them following (`translateSeparators`). */ -export type WallPushSpan = { wallId: string; t0: number; t1: number } +export type WallPushSpan = { + wallId: string + t0: number + t1: number + separators?: readonly SeparatorSpan[] +} /** One push/pull arrow: a selected wall's side arrow or a room's boundary arrow. */ export type WallPushHandle = WallPushSpan & { @@ -191,22 +214,27 @@ export function wallPushHandles(wall: WallNode, nodes: Nodes): WallPushHandle[] }) } +type BoundarySpanRef = { + boundaryId: string + kind: string + face: 'a' | 'b' + t0: number + t1: number +} + /** * One push/pull arrow per boundary wall span of the room, outside the wall's * far face, pointing away from the room — where the wall's own side arrow for - * that face sits. `face` is the wall face the room lies on (`a` = front). + * that face sits. `face` is the wall face the room lies on (`a` = front). Its + * invisible walls get one arrow per straight edge (`separatorEdges`), lying + * at `floor` (the room's floor height), where their dashed line is drawn. */ export function roomPushHandles( nodes: Nodes, - spans: ReadonlyArray<{ - boundaryId: string - kind: string - face: 'a' | 'b' - t0: number - t1: number - }>, + spans: ReadonlyArray, + floor = 0, ): WallPushHandle[] { - return spans.flatMap((span) => { + const walls = spans.flatMap((span) => { const wall = nodes[span.boundaryId] if (span.kind !== 'wall' || wall?.type !== 'wall') return [] const handle = wallPushHandle( @@ -218,6 +246,145 @@ export function roomPushHandles( ) return handle ? [handle] : [] }) + const edges = separatorEdges(nodes, spans).map((edge) => { + const first = edge.members[0]! + return separatorPushHandle( + edge, + edge.outward, + floor, + `${first.separatorId}:${edge.face}:${first.t0.toFixed(4)}`, + ) + }) + return [...walls, ...edges] +} + +/** How far outside an invisible wall (m) its push arrow sits, as a mezzanine edge's does. */ +const SEPARATOR_ARROW_OFFSET = 0.3 +/** Its 3D arrow's lift over the floor: where the invisible wall's dashed line is drawn. */ +const SEPARATOR_ARROW_LIFT = 0.02 +/** An invisible-wall edge shorter than this (m) gets no arrow, as a mezzanine edge. */ +const SEPARATOR_EDGE_MIN_LENGTH = 0.2 +/** Stretches this far off each other's line (m) still run along one edge. */ +const EDGE_IN_LINE = 0.01 + +type SeparatorEdge = { + /** The stretches along the edge, in order. */ + members: SeparatorSpan[] + start: Point + end: Point + /** Away from the room. */ + outward: Point + face: 'a' | 'b' +} + +/** + * A room's invisible-wall stretches gathered into its straight edges: stretches + * end to end on one line, the room on the same side, are one edge and push as + * one (an edge drawn in two pieces, or broken where another one meets it). Ends + * meet as far as the room graph joins them (`SEPARATOR_JOIN_REACH`). + */ +function separatorEdges(nodes: Nodes, spans: ReadonlyArray): SeparatorEdge[] { + const pieces = spans.flatMap((span): SeparatorEdge[] => { + const separator = nodes[span.boundaryId] + if (span.kind !== 'separator' || separator?.type !== 'separator') return [] + const dx = separator.end[0] - separator.start[0] + const dz = separator.end[1] - separator.start[1] + const length = Math.hypot(dx, dz) + if (length < 1e-6 || span.t1 - span.t0 < WHOLE) return [] + // Face `a` is the +normal side: the arrow points the other way, off the room. + const sign = span.face === 'a' ? -1 : 1 + const at = (t: number): Point => [separator.start[0] + dx * t, separator.start[1] + dz * t] + return [ + { + members: [{ separatorId: separator.id, t0: span.t0, t1: span.t1 }], + start: at(span.t0), + end: at(span.t1), + outward: [(-dz / length) * sign, (dx / length) * sign], + face: span.face, + }, + ] + }) + const across = (edge: SeparatorEdge, [x, z]: Point) => + Math.abs((x - edge.start[0]) * edge.outward[0] + (z - edge.start[1]) * edge.outward[1]) + const joins = (a: SeparatorEdge, b: SeparatorEdge) => + a.outward[0] * b.outward[0] + a.outward[1] * b.outward[1] > 1 - 1e-4 && + across(a, b.start) < EDGE_IN_LINE && + across(a, b.end) < EDGE_IN_LINE && + [a.start, a.end].some((p) => + [b.start, b.end].some((q) => Math.hypot(p[0] - q[0], p[1] - q[1]) <= SEPARATOR_JOIN_REACH), + ) + let groups: SeparatorEdge[][] = [] + for (const piece of pieces) { + const touching = groups.filter((group) => group.some((other) => joins(piece, other))) + groups = [...groups.filter((group) => !touching.includes(group)), [piece, ...touching.flat()]] + } + return groups.flatMap((group): SeparatorEdge[] => { + const { outward, face } = group[0]! + const dir: Point = [outward[1], -outward[0]] + const along = ([x, z]: Point) => x * dir[0] + z * dir[1] + const ends = group + .flatMap((piece) => [piece.start, piece.end]) + .sort((p, q) => along(p) - along(q)) + const start = ends[0]! + const end = ends.at(-1)! + if (Math.hypot(end[0] - start[0], end[1] - start[1]) < SEPARATOR_EDGE_MIN_LENGTH) return [] + const middle = (piece: SeparatorEdge) => along(piece.start) + along(piece.end) + const members = [...group].sort((p, q) => middle(p) - middle(q)).flatMap((p) => p.members) + return [{ members, start, end, outward, face }] + }) +} + +function separatorPushHandle( + edge: Pick, + outward: Point, + floor: number, + key: string, +): WallPushHandle { + const first = edge.members[0]! + const middle: Point = [(edge.start[0] + edge.end[0]) / 2, (edge.start[1] + edge.end[1]) / 2] + return { + key, + wallId: first.separatorId, + t0: first.t0, + t1: first.t1, + separators: edge.members, + position: [ + middle[0] + outward[0] * SEPARATOR_ARROW_OFFSET, + middle[1] + outward[1] * SEPARATOR_ARROW_OFFSET, + ], + height: floor + SEPARATOR_ARROW_LIFT, + outward, + line: middle[0] * outward[0] + middle[1] * outward[1], + } +} + +/** + * A selected invisible wall's two side arrows, one off each side, at `floor`: + * each pushes the whole separator, the ones joined to it following. + */ +export function separatorPushHandles(separator: SeparatorNode, floor = 0): WallPushHandle[] { + const dx = separator.end[0] - separator.start[0] + const dz = separator.end[1] - separator.start[1] + const length = Math.hypot(dx, dz) + if (length < 1e-6) return [] + const edge = { + members: [{ separatorId: separator.id, t0: 0, t1: 1 }], + start: separator.start, + end: separator.end, + } + return (['a', 'b'] as const).map((side) => { + const sign = side === 'a' ? 1 : -1 + const outward: Point = [(-dz / length) * sign, (dx / length) * sign] + return separatorPushHandle(edge, outward, floor, `${separator.id}:${side}`) + }) +} + +/** The floor an invisible wall lies on: the walking surface of a room it closes, else 0. */ +export function separatorFloorElevation(nodes: Nodes, separatorId: string): number { + const zone = Object.values(nodes).find( + (node) => node.type === 'zone' && node.boundarySeparatorIds.includes(separatorId), + ) + return zone ? roomFloorElevation(nodes, zone.id) : 0 } /** @@ -294,7 +461,8 @@ function diffNodes(before: Scratch, after: Scratch): NodeChange[] { * Pushes the room's stretch of a wall `distance` along `outward`. A span that * covers the whole wall moves the wall; a partial span is split off at its * ends first (hosted openings stay on their pieces) and only that piece moves, - * bridged back to the rest by the junction planner. One plan, one undo step. + * bridged back to the rest by the junction planner. An edge of invisible walls + * moves as one line (`translateSeparators`). One plan, one undo step. */ export function planRoomPush( nodes: StructureNodes, @@ -303,6 +471,12 @@ export function planRoomPush( distance: number, mintId: (kind: 'zone' | 'wall' | 'separator') => string = generateId, ): StructurePlan { + if (span.separators?.length) + return translateSeparators(nodes, { + spans: span.separators, + offset: [outward[0] * distance, outward[1] * distance], + mintId, + }) const wall = nodes[span.wallId] if (wall?.type !== 'wall') throw Error('Select a wall.') const shift = ([x, z]: Point): Point => [x + outward[0] * distance, z + outward[1] * distance] @@ -388,14 +562,18 @@ function clearPushPreview() { function previewSurfaces(nodes: Nodes, levelId: string, after: Scratch) { if (surfacePreview?.levelId !== levelId || surfacePreview.nodes !== nodes) surfacePreview = { levelId, nodes, run: createLevelStructurePreview(levelId, nodes) } - const walls = Object.values(after).filter( - (node): node is WallNode => node.type === 'wall' && node.parentId === levelId, - ) + const level = Object.values(after).filter((node) => node.parentId === levelId) return surfacePreview - .run(walls) + .run( + level.filter((node): node is WallNode => node.type === 'wall'), + level.filter((node): node is SeparatorNode => node.type === 'separator'), + ) .map((patch) => [patch.id as AnyNodeId, patch.data as Partial] as const) } +/** The ghost of an invisible wall a push would create (a jog): a thin strip on the floor. */ +const SEPARATOR_GHOST = { thickness: 0.03, height: 0.04 } + /** * Shows the push live: the plan's updates to existing walls — and the room * surfaces reshaped around them — ride `useLiveNodeOverrides` (renderers merge @@ -427,8 +605,11 @@ export function previewRoomPush( .setMany(updates.map(([id, data]) => [id, data] as [AnyNodeId, Partial])) previewIds = updates.map(([id]) => id) for (const id of previewIds) useScene.getState().markDirty(id as AnyNodeId) - const ghosts = plan.changes.flatMap((change): RoomPushGhost[] => - change.op === 'create' && change.node.type === 'wall' + const ghosts = plan.changes.flatMap((change): RoomPushGhost[] => { + if (change.op !== 'create') return [] + if (change.node.type === 'separator') + return [{ start: change.node.start, end: change.node.end, ...SEPARATOR_GHOST }] + return change.node.type === 'wall' ? [ { start: change.node.start, @@ -437,8 +618,8 @@ export function previewRoomPush( height: getWallEffectiveHeightForNodes(change.node, nodes), }, ] - : [], - ) + : [] + }) // The floor plan draws the same pieces through its bridge-ghost layer. if (ghosts.length) { useWallMoveGhosts.getState().setBridges( @@ -716,12 +897,13 @@ export function cancelRoomHandleDrag() { /** * Runs one handle drag on window listeners, independent of the handle's own * mount (handles hide while any scope is active). `sample` maps a pointer to - * the drag's value (`spatial.sample` a controller ray, for a spatial pointer); - * release commits, Escape / ⌘Z / the gesture owner cancel. Cleanup runs + * the drag's value (`spatial.sample` a controller ray, for a spatial pointer): + * a distance or a height, or a plan point for an end dragged across the floor. + * Release commits, Escape / ⌘Z / the gesture owner cancel. Cleanup runs * exactly once whatever happens, and releases only this drag's own scope; * nothing republishes a preview after it. */ -export function runRoomHandleDrag({ +export function runRoomHandleDrag({ label, nodeId, zoneId, @@ -738,9 +920,9 @@ export function runRoomHandleDrag({ zoneId?: string levelId: string requires: string[] - sample: (clientX: number, clientY: number) => number | null - spatial?: { pointerId: SpatialPointerId; sample: (ray: Ray) => number | null } - onValue: (value: number) => void + sample: (clientX: number, clientY: number) => T | null + spatial?: { pointerId: SpatialPointerId; sample: (ray: Ray) => T | null } + onValue: (value: T) => void onCommit: () => void onCancel: () => void }) { @@ -891,10 +1073,20 @@ export function runWallPushDrag({ spatial?: { pointerId: SpatialPointerId; along: (ray: Ray) => number | null } }) { const nodes = useScene.getState().nodes - const wall = nodes[handle.wallId as AnyNodeId] - if (wall?.type !== 'wall') return null - const base = getWallBaseElevationForNodes(wall, nodes) + const boundary = nodes[handle.wallId as AnyNodeId] + // An edge of invisible walls is measured, and lines up, as the one line it moves as. + const edge = handle.separators?.length ? separatorEdgeLine(nodes, handle.separators) : null + const wall = edge ?? (boundary?.type === 'wall' ? boundary : null) + if (!wall) return null + const base = + wall.type === 'wall' + ? getWallBaseElevationForNodes(wall, nodes) + : handle.height - SEPARATOR_ARROW_LIFT const rooms = pushDimensionRooms(nodes, levelId, handle, base) + const levelWalls = Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === levelId, + ) + const moving = new Set([handle.wallId, ...(handle.separators ?? []).map((s) => s.separatorId)]) let distance = 0 let blocked = false let ghosts: RoomPushGhost[] = [] @@ -921,15 +1113,49 @@ export function runWallPushDrag({ message, }, }) - const toDistance = (at: number | null) => - at === null ? null : snapPushDistance(handle.line, at - from) + // The pushed wall lines up with the parallel walls on its level + // (`wall-parallel-snap`) before the grid; Alt pushes it free. Invisible walls + // line up with the walls and with the level's other invisible walls. + const lineUpTargets = + wall.type === 'wall' + ? wallParallelTargets(wall, handle.outward, levelWalls) + : wallParallelTargets(separatorWallLine(wall), handle.outward, [ + ...levelWalls, + ...levelSeparatorLines(nodes, levelId, moving), + ]) + let lineUp: WallParallelTarget | null = null + let altHeld = false + const trackAlt = (event: KeyboardEvent | PointerEvent) => { + altHeld = event.altKey + } + window.addEventListener('pointermove', trackAlt, true) + window.addEventListener('keydown', trackAlt, true) + window.addEventListener('keyup', trackAlt, true) + const stopLineUp = () => { + window.removeEventListener('pointermove', trackAlt, true) + window.removeEventListener('keydown', trackAlt, true) + window.removeEventListener('keyup', trackAlt, true) + if (lineUp) publishWallParallelGuide(null) + lineUp = null + } + const toDistance = (at: number | null) => { + if (at === null) return null + const raw = at - from + const next = isWallRunFree({ altKey: altHeld }) + ? null + : nearestWallParallelTarget(lineUpTargets, handle.line + raw) + if (next !== lineUp) publishWallParallelGuide(next) + lineUp = next + if (next) return next.position - handle.line + return isWallRunFree({ altKey: altHeld }) ? raw : snapPushDistance(handle.line, raw) + } publish() return runRoomHandleDrag({ label: WALL_PUSH_DRAG_LABEL, nodeId: handle.wallId, zoneId, levelId, - requires: zoneId ? [zoneId, handle.wallId] : [handle.wallId], + requires: zoneId ? [zoneId, ...moving] : [...moving], sample: (x, y) => toDistance(along(x, y)), spatial: spatial && { pointerId: spatial.pointerId, @@ -945,10 +1171,14 @@ export function runWallPushDrag({ publish(preview.code ? "Can't move this wall there" : undefined) }, onCommit: () => { + stopLineUp() if (blocked) endRoomPushPreview() else commitRoomPush(handle, handle.outward, distance) }, - onCancel: () => endRoomPushPreview(), + onCancel: () => { + stopLineUp() + endRoomPushPreview() + }, }) } @@ -960,9 +1190,74 @@ function pushDimensionRooms(nodes: Nodes, levelId: string, span: WallPushSpan, b const index = getRoomSelectionIndex(levelId) const records = index.update(nodes) const topology = index.topology.getLevelTopology(levelId) - return wallPushDimensionRooms(topology?.rooms ?? [], span, (roomId) => { + const elevationOf = (roomId: string) => { const zoneId = records.find((record) => record.id === roomId)?.zoneId return zoneId ? roomFloorElevation(nodes, zoneId) : base + } + const edge = span.separators?.length ? separatorEdgeLine(nodes, span.separators) : null + return edge && span.separators + ? separatorEdgeDimensionRooms( + topology?.rooms ?? [], + { line: edge, separators: span.separators }, + (id) => nodes[id], + elevationOf, + ) + : wallPushDimensionRooms(topology?.rooms ?? [], span, elevationOf) +} + +/** + * The one line an edge of invisible walls pushes as: its first separator, + * running from the edge's first end to its last. + */ +function separatorEdgeLine(nodes: Nodes, spans: readonly SeparatorSpan[]): SeparatorNode | null { + const first = nodes[spans[0]?.separatorId as AnyNodeId] + if (first?.type !== 'separator') return null + const dx = first.end[0] - first.start[0] + const dz = first.end[1] - first.start[1] + const length = Math.hypot(dx, dz) + if (length < 1e-6) return null + const along = ([x, z]: Point) => + ((x - first.start[0]) * dx + (z - first.start[1]) * dz) / (length * length) + let lo = Number.POSITIVE_INFINITY + let hi = Number.NEGATIVE_INFINITY + for (const span of spans) { + const separator = nodes[span.separatorId as AnyNodeId] + if (separator?.type !== 'separator') return null + for (const t of [span.t0, span.t1]) { + const u = along([ + separator.start[0] + (separator.end[0] - separator.start[0]) * t, + separator.start[1] + (separator.end[1] - separator.start[1]) * t, + ]) + lo = Math.min(lo, u) + hi = Math.max(hi, u) + } + } + const at = (u: number): Point => [first.start[0] + dx * u, first.start[1] + dz * u] + return { ...first, start: at(lo), end: at(hi) } +} + +/** + * A selected wall's temporary dimensions as the scene stands: the room widths + * its push shows, before any push, plus the outside size for an exterior wall. + */ +export function selectedWallDimensions( + nodes: Nodes, + wall: WallNode, + faces: WallDimensionFaces, +): WallSelectionDimension[] { + const levelId = wall.parentId + if (!levelId) return [] + const base = getWallBaseElevationForNodes(wall, nodes) + const walls = Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === levelId, + ) + return wallSelectionDimensions({ + rooms: pushDimensionRooms(nodes, levelId, { wallId: wall.id, t0: 0, t1: 1 }, base), + wall, + walls, + faces, + resolve: (id) => nodes[id], + baseElevation: base, }) } @@ -986,6 +1281,7 @@ export function wallPointAt(wall: WallNode, t: number): Point { */ export const WALL_PUSH_AFFORDANCE = 'wall-push' +/** A wall's or an invisible wall's side arrow: `wallId` is either. */ export type WallPushArrowPayload = { wallId: string; side: 'a' | 'b' } type PlanPointer = (clientX: number, clientY: number) => readonly [number, number] | null @@ -1001,8 +1297,9 @@ function planAlong(position: Point, outward: Point, toPlan: PlanPointer) { } /** - * A floor-plan wall arrow: the 3D arrow's handle (`wallPushHandles`), drag - * (`runWallPushDrag`) and commit, with the pointer read in plan coordinates. + * A floor-plan wall arrow: the 3D arrow's handle (`wallPushHandles`, or a + * selected invisible wall's `separatorPushHandles`), drag (`runWallPushDrag`) + * and commit, with the pointer read in plan coordinates. */ export function runFloorplanWallPush( payload: unknown, @@ -1013,8 +1310,12 @@ export function runFloorplanWallPush( const { wallId, side } = (payload ?? {}) as Partial const nodes = useScene.getState().nodes const wall = wallId ? nodes[wallId as AnyNodeId] : undefined - if (wall?.type !== 'wall' || !wall.parentId) return null - const handle = wallPushHandles(wall, nodes).find((h) => h.key === `${wall.id}:${side}`) + if ((wall?.type !== 'wall' && wall?.type !== 'separator') || !wall.parentId) return null + const handles = + wall.type === 'wall' + ? wallPushHandles(wall, nodes) + : separatorPushHandles(wall, separatorFloorElevation(nodes, wall.id)) + const handle = handles.find((h) => h.key === `${wall.id}:${side}`) if (!handle) return null return runFloorplanRoomPush({ handle, levelId: wall.parentId, clientX, clientY, toPlan }) } diff --git a/packages/editor/src/lib/room-push-dimensions.test.ts b/packages/editor/src/lib/room-push-dimensions.test.ts index e93223f7a7..9869e04539 100644 --- a/packages/editor/src/lib/room-push-dimensions.test.ts +++ b/packages/editor/src/lib/room-push-dimensions.test.ts @@ -11,8 +11,10 @@ import { DIMENSION_FLOOR_LIFT, mezzanineEdgeDimensions, type RoomDimension, + type WallDimensionFaces, wallPushDimensionRooms, wallPushDimensions, + wallSelectionDimensions, } from './room-push-dimensions' // Rooms are real: the walls go through core's room topology, so the faces the @@ -66,6 +68,20 @@ function measure( }) } +function selection({ nodes, walls }: Scene, wall: WallNode, faces: WallDimensionFaces) { + const index = createRoomTopologyIndex() + index.rebuildLevel(LEVEL, nodes) + const rooms = index.getLevelTopology(LEVEL)?.rooms ?? [] + return wallSelectionDimensions({ + rooms: wallPushDimensionRooms(rooms, { wallId: wall.id, t0: 0, t1: 1 }, () => 0), + wall, + walls, + faces, + resolve: (id) => nodes[id], + baseElevation: 0, + }) +} + const distances = (dimensions: RoomDimension[]) => dimensions.map((dimension) => Number(dimension.distance.toFixed(4))) @@ -206,6 +222,27 @@ describe('two rooms sharing the moving wall', () => { expect(west.key).not.toBe(east.key) }) + test('selected, a shared wall shows both room widths and no outside size, whatever the faces', () => { + for (const faces of ['both', 'interior', 'exterior'] as const) { + const dimensions = selection(rooms, middle, faces) + expect(dimensions.map((dimension) => dimension.faces)).toEqual(['interior', 'interior']) + expect(distances(dimensions).sort()).toEqual([3.8, 3.8]) + } + }) + + test('selected, an exterior wall adds the outside size across the building', () => { + const east = wallAt(rooms, [8, 0], [8, 4]) + const dimensions = selection(rooms, east, 'both') + const outside = dimensions.find((dimension) => dimension.faces === 'exterior')! + // Outer face to outer face, past the middle wall, clear of the room width's line. + expect(outside.distance).toBeCloseTo(8 + THICKNESS) + expect(outside.from[0]).toBeCloseTo(8 + THICKNESS / 2) + expect(outside.to[0]).toBeCloseTo(-THICKNESS / 2) + expect(distances(dimensions.filter((dimension) => dimension.faces === 'interior'))).toEqual([ + 3.8, + ]) + }) + test('a push over part of the wall measures only the rooms along that part', () => { const dimensions = measure(rooms, middle, [1, 0], 0, { t0: 0, t1: 0.5 }) expect(dimensions).toHaveLength(2) diff --git a/packages/editor/src/lib/room-push-dimensions.ts b/packages/editor/src/lib/room-push-dimensions.ts index 38b86bcc8e..61ad4ea7fe 100644 --- a/packages/editor/src/lib/room-push-dimensions.ts +++ b/packages/editor/src/lib/room-push-dimensions.ts @@ -4,6 +4,7 @@ import { getWallFaceOffsets, isCurvedWall, type Point, + type SeparatorNode, type WallNode, } from '@pascal-app/core' @@ -153,12 +154,62 @@ export function wallPushDimensionRooms( ) } +/** + * The rooms along a pushed edge of invisible walls, as `wallPushDimensionRooms` + * reads them along a wall: each room's stretch of the edge's separators, put + * on the edge's one `line` (`[0, 1]` its length), faced as that line runs. + */ +export function separatorEdgeDimensionRooms( + rooms: ReadonlyArray<{ id: string; spans: readonly BoundarySpan[] }>, + edge: { + line: Pick + separators: ReadonlyArray<{ separatorId: string; t0: number; t1: number }> + }, + resolve: (id: string) => AnyNode | undefined, + elevationOf: (roomId: string) => number, +): PushDimensionRoom[] { + const { start, end } = edge.line + const dx = end[0] - start[0] + const dz = end[1] - start[1] + const lengthSq = dx * dx + dz * dz + if (lengthSq < 1e-12) return [] + const members = new Set(edge.separators.map((member) => member.separatorId)) + const along = ([x, z]: Point) => ((x - start[0]) * dx + (z - start[1]) * dz) / lengthSq + return rooms.flatMap((room) => + room.spans.flatMap((own): PushDimensionRoom[] => { + const member = edge.separators.find((entry) => entry.separatorId === own.boundaryId) + const separator = resolve(own.boundaryId) + if (!member || separator?.type !== 'separator') return [] + const t0 = Math.max(member.t0, own.t0) + const t1 = Math.min(member.t1, own.t1) + if (t1 - t0 < 1e-4) return [] + const sx = separator.end[0] - separator.start[0] + const sz = separator.end[1] - separator.start[1] + const point = (t: number): Point => [separator.start[0] + sx * t, separator.start[1] + sz * t] + const [u0, u1] = [along(point(t0)), along(point(t1))] + // A separator drawn against the edge's line has its faces the other way round. + const same = sx * dx + sz * dz > 0 + return [ + { + id: room.id, + face: same ? own.face : own.face === 'a' ? 'b' : 'a', + t0: Math.min(u0, u1), + t1: Math.max(u0, u1), + others: room.spans.filter((other) => !members.has(other.boundaryId)), + elevation: elevationOf(room.id), + }, + ] + }), + ) +} + /** * The live dimensions of a wall push: for every room along the pushed stretch, * the moving face (the resting face moved `distance` along `outward` — a push * is a pure translation) to each facing parallel boundary of that room, read * through `resolve` so the preview's geometry counts. Per side of the wall: - * nearest first, one per distinct distance, at most three. + * nearest first, one per distinct distance, at most three. An edge of + * invisible walls is measured as its one line. */ export function wallPushDimensions({ rooms, @@ -168,7 +219,7 @@ export function wallPushDimensions({ resolve, }: { rooms: readonly PushDimensionRoom[] - wall: WallNode + wall: WallNode | SeparatorNode outward: Point distance: number resolve: (id: string) => AnyNode | undefined @@ -195,6 +246,149 @@ export function wallPushDimensions({ ) } +/** A selected wall's temporary dimension: a room width (ID) or the outside size (OD). */ +export type WallSelectionDimension = RoomDimension & { faces: 'interior' | 'exterior' } + +/** Which faces a selected wall's temporary dimensions measure to. */ +export type WallDimensionFaces = 'interior' | 'exterior' | 'both' + +const CONNECTION_TOLERANCE = 0.03 +/** Where along the shared stretch the outside size is drawn, clear of the room widths at its middle. */ +const EXTERIOR_DIMENSION_AT = 0.25 + +function pointSegmentDistance(p: Point, a: Point, b: Point): number { + const dx = b[0] - a[0] + const dz = b[1] - a[1] + const lengthSq = dx * dx + dz * dz + const t = + lengthSq < 1e-12 + ? 0 + : Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dz) / lengthSq)) + return Math.hypot(p[0] - (a[0] + dx * t), p[1] - (a[1] + dz * t)) +} + +function wallsTouch(left: WallNode, right: WallNode): boolean { + return ( + [left.start, left.end].some( + (p) => pointSegmentDistance(p, right.start, right.end) <= CONNECTION_TOLERANCE, + ) || + [right.start, right.end].some( + (p) => pointSegmentDistance(p, left.start, left.end) <= CONNECTION_TOLERANCE, + ) + ) +} + +/** The walls joined to `wall` through shared corners and T junctions, itself included. */ +function connectedWalls(wall: WallNode, walls: readonly WallNode[]): WallNode[] { + const found = new Map([[wall.id, wall]]) + const queue = [wall] + for (let current = queue.pop(); current; current = queue.pop()) { + for (const candidate of walls) { + if (found.has(candidate.id) || !wallsTouch(current, candidate)) continue + found.set(candidate.id, candidate) + queue.push(candidate) + } + } + return [...found.values()] +} + +/** + * The outside size across the building from a wall's exterior face: to the + * far face of the farthest parallel wall of the same connected building that + * shares some of its length — the opposite exterior wall. Null when nothing + * is across (a lone wall, a curved one). + */ +function overallExteriorDimension( + wall: WallNode, + exteriorFace: 'a' | 'b', + walls: readonly WallNode[], +): Measured | null { + const own = boundaryFace(wall, 0, 1, exteriorFace) + if (!own) return null + const dx = own.end[0] - own.start[0] + const dz = own.end[1] - own.start[1] + const length = Math.hypot(dx, dz) + const dir: Point = [dx / length, dz / length] + // The face's room-ward normal points out of the building here: across is its opposite. + const across: Point = [-own.inward[0], -own.inward[1]] + const along = (p: Point) => (p[0] - own.start[0]) * dir[0] + (p[1] - own.start[1]) * dir[1] + let best: { distance: number; lo: number; hi: number } | null = null + for (const other of connectedWalls(wall, walls)) { + if (other.id === wall.id) continue + for (const face of ['a', 'b'] as const) { + const candidate = boundaryFace(other, 0, 1, face) + if (!candidate) continue + const fx = candidate.end[0] - candidate.start[0] + const fz = candidate.end[1] - candidate.start[1] + const faceLength = Math.hypot(fx, fz) + if (Math.abs(dir[0] * fz - dir[1] * fx) / faceLength > PARALLEL_SIN) continue + const distance = + (candidate.start[0] - own.start[0]) * across[0] + + (candidate.start[1] - own.start[1]) * across[1] + if (distance <= MIN_GAP) continue + const a = along(candidate.start) + const b = along(candidate.end) + const lo = Math.max(0, Math.min(a, b)) + const hi = Math.min(length, Math.max(a, b)) + if (hi - lo < MIN_OVERLAP) continue + if (!best || distance > best.distance + SAME_DISTANCE) best = { distance, lo, hi } + } + } + if (!best) return null + const u = best.lo + (best.hi - best.lo) * EXTERIOR_DIMENSION_AT + const from: Point = [own.start[0] + dir[0] * u, own.start[1] + dir[1] * u] + return { + from, + to: [from[0] + across[0] * best.distance, from[1] + across[1] * best.distance], + distance: best.distance, + } +} + +/** + * The temporary dimensions of a selected wall at rest, in 2D and 3D: the room + * widths a push of it shows (its face to each facing wall, both sides — ID), + * and for an exterior wall the outside size across the building (OD). `faces` + * picks which; a wall with no outside (rooms on both faces) shows its room + * widths whatever the choice. Each dimension runs `from` the selected wall, + * so typing a value moves that wall (`commitDimensionValue`). + */ +export function wallSelectionDimensions({ + rooms, + wall, + walls, + faces, + resolve, + baseElevation, +}: { + rooms: readonly PushDimensionRoom[] + wall: WallNode + /** The level's walls, for the opposite exterior wall. */ + walls: readonly WallNode[] + faces: WallDimensionFaces + resolve: (id: string) => AnyNode | undefined + /** The wall's base, for a dimension with no room floor to stand on. */ + baseElevation: number +}): WallSelectionDimension[] { + const interior = wallPushDimensions({ rooms, wall, outward: [0, 0], distance: 0, resolve }).map( + (dimension): WallSelectionDimension => ({ ...dimension, faces: 'interior' }), + ) + const roomFaces = new Set(rooms.map((room) => room.face)) + const exteriorFace = roomFaces.size === 1 ? (roomFaces.has('a') ? 'b' : 'a') : null + if (!exteriorFace || faces === 'interior') return interior + const overall = overallExteriorDimension(wall, exteriorFace, walls) + const exterior: WallSelectionDimension[] = overall + ? [ + { + ...overall, + key: 'exterior', + faces: 'exterior', + elevation: (rooms[0]?.elevation ?? baseElevation) + DIMENSION_FLOOR_LIFT, + }, + ] + : [] + return faces === 'exterior' ? exterior : [...interior, ...exterior] +} + /** * The live dimensions of a mezzanine edge push: the resting edge moved * `distance` along `outward`, to each facing parallel edge of the outline the diff --git a/packages/editor/src/lib/room-selection.test.ts b/packages/editor/src/lib/room-selection.test.ts index 0d3e23c4f4..8776a12b56 100644 --- a/packages/editor/src/lib/room-selection.test.ts +++ b/packages/editor/src/lib/room-selection.test.ts @@ -650,7 +650,7 @@ describe('room drill-down state through the mounted selection manager', () => { expect(useEditor.getState().room).toBeNull() }) }) - test('a second click on the selected crate picks it up, and that click cannot drop it', async () => { + test('repeated clicks on the selected crate keep it selected and never start a move', async () => { jest.useFakeTimers() if (!nodeRegistry.has('test-crate')) { registerNode({ @@ -674,19 +674,12 @@ describe('room drill-down state through the mounted selection manager', () => { await act(async () => { useScene.setState({ nodes: { ...useScene.getState().nodes, [crate.id]: crate } }) }) - await click(crate.id, [2, 2]) - jest.advanceTimersByTime(50) - await click(crate.id, [2, 2]) - expect(getMovingNode()?.id).toBe(crate.id) - - // The browser's click that ends this press would otherwise commit the move in place. - const endingClick = new Event('click', { cancelable: true }) - window.dispatchEvent(endingClick) - expect(endingClick.defaultPrevented).toBe(true) - const dropClick = new Event('click', { cancelable: true }) - window.dispatchEvent(dropClick) - expect(dropClick.defaultPrevented).toBe(false) - await act(async () => useEditor.getState().setMovingNode(null)) + for (let index = 0; index < 3; index++) { + await click(crate.id, [2, 2]) + jest.advanceTimersByTime(50) + expect(getMovingNode()).toBeNull() + expect(useViewer.getState().selection.selectedIds).toEqual([crate.id]) + } }) }) @@ -868,17 +861,34 @@ describe('room drill-down state through the mounted selection manager', () => { expect(useViewer.getState().selection.selectedIds).toEqual(['wall_free']) }) }) - test('the 2D plan resolves walls room-first and drills on the second click', async () => { + test('the 2D plan gives a wall hit to the wall and a room interior to the room', async () => { await withRooms(async ({ left, right }) => { - expect(resolvePlanRoomHit('wall_west', [-0.05, 2])).toEqual(left) + // Without the kind's `planHitSelectsNode`, a band hit stays room-first. expect(resolvePlanRoomHit('wall_shared', [3.95, 2])).toEqual(left) - expect(resolvePlanRoomHit('wall_shared', [4.05, 2])).toEqual(right) - expect(resolvePlanRoomHit('wall_free', [9.95, 1])).toBeNull() - const hit = resolvePlanRoomHit('wall_west', [-0.05, 2]) + // The wall definition declares it (its floor-plan extension). + const get = nodeRegistry.get.bind(nodeRegistry) + const spy = spyOn(nodeRegistry, 'get').mockImplementation(((kind: string) => + kind === 'wall' + ? ({ + ...get(kind), + extensions: { 'pascal:editor/floorplan': { planHitSelectsNode: true } }, + } as AnyNodeDefinition) + : get(kind)) as typeof nodeRegistry.get) + try { + // Either half of a shared wall's band is the wall, never the room beside it. + expect(resolvePlanRoomHit('wall_west', [-0.05, 2])).toBeNull() + expect(resolvePlanRoomHit('wall_shared', [3.95, 2])).toBeNull() + expect(resolvePlanRoomHit('wall_shared', [4.05, 2])).toBeNull() + expect(resolvePlanRoomHit('wall_free', [9.95, 1])).toBeNull() + } finally { + spy.mockRestore() + } + expect(resolvePlanRoomHit(null, [2, 2])).toEqual(left) + expect(resolvePlanRoomHit(null, [6, 2])).toEqual(right) const select = async (modifiers = plain) => { let selected = false await act(async () => { - selected = selectRoomFromHit(hit, modifiers, 'wall_west') + selected = selectRoomFromHit(resolvePlanRoomHit(null, [2, 2]), modifiers, null) }) return selected } diff --git a/packages/editor/src/lib/selection-cycle.ts b/packages/editor/src/lib/selection-cycle.ts new file mode 100644 index 0000000000..40affb1e1c --- /dev/null +++ b/packages/editor/src/lib/selection-cycle.ts @@ -0,0 +1,63 @@ +/** + * Tab steps the selection through everything stacked under the pointer in the + * floor plan, top first: where walls + * overlap, or a short wall lies inside a longer one or under a marker, a click + * takes the top one and Tab the ones below it in turn. Shift+Tab steps back. + */ + +import type { AnyNodeId } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' + +let pointer: { x: number; y: number } | null = null +let tracking = false + +/** Start following the pointer, so a Tab knows where it is. Idempotent. */ +export function trackSelectionCyclePointer(): void { + if (tracking || typeof window === 'undefined') return + tracking = true + const follow = (event: PointerEvent) => { + pointer = { x: event.clientX, y: event.clientY } + } + window.addEventListener('pointermove', follow, { capture: true, passive: true }) + window.addEventListener('pointerdown', follow, { capture: true, passive: true }) +} + +/** The plan nodes under a screen point, topmost first, each once. */ +export function floorplanNodesAt(x: number, y: number): string[] { + if (typeof document === 'undefined') return [] + const ids: string[] = [] + for (const element of document.elementsFromPoint(x, y)) { + if (!element.closest('[data-floorplan-scene]')) continue + const entry = element.closest('.floorplan-registry-entry[data-node-id]') + const id = entry?.dataset.nodeId + if (id && !ids.includes(id)) ids.push(id) + } + return ids +} + +/** + * The id a Tab selects among `stack` (topmost first): the one after the + * current selection, or before it for Shift+Tab, wrapping round; the top one + * when the selection is not in the stack. + */ +export function nextInStack( + stack: readonly string[], + selected: readonly string[], + back: boolean, +): string | null { + if (stack.length === 0) return null + const index = selected.length === 1 ? stack.indexOf(selected[0]!) : -1 + if (index < 0) return stack[0]! + return stack[(index + (back ? stack.length - 1 : 1)) % stack.length]! +} + +/** Select the next object under the pointer in the floor plan. True when Tab was used. */ +export function cycleSelectionAtPointer(back: boolean): boolean { + if (!pointer) return false + const stack = floorplanNodesAt(pointer.x, pointer.y) + const viewer = useViewer.getState() + const next = nextInStack(stack, viewer.selection.selectedIds, back) + if (!next) return false + viewer.setSelection({ selectedIds: [next as AnyNodeId] }) + return true +} diff --git a/packages/editor/src/lib/separator-edit.test.ts b/packages/editor/src/lib/separator-edit.test.ts new file mode 100644 index 0000000000..9c5d6773cc --- /dev/null +++ b/packages/editor/src/lib/separator-edit.test.ts @@ -0,0 +1,431 @@ +import { afterAll, afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + area, + BuildingNode, + clearSceneHistory, + createZone, + generateId, + initSpaceDetectionSync, + LevelNode, + nodeRegistry, + planSeparatorInsertion, + railingWallDefaults, + type SeparatorNode, + structureChangeBatch, + useLiveNodeOverrides, + useScene, + WallNode, + type ZoneNode, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { getRoomSelectionIndex } from '../hooks/use-selected-room' +import useEditor from '../store/use-editor' +import useInteractionScope from '../store/use-interaction-scope' +import { installImmediateAnimationFrames } from '../test-utils/immediate-animation-frames' +import { cancelGestures } from './gesture-lifecycle' +import { endpointReshapeScope } from './interaction/scope' +import { + roomPushHandles, + runFloorplanRoomPush, + runFloorplanWallPush, + separatorPushHandles, + useRoomHandleDrag, + type WallPushHandle, +} from './room-handle-drag' +import { applyRoomPlan } from './room-structure-commands' +import { + beginSeparatorEndDrag, + runSeparatorEndDrag, + SEPARATOR_END_DRAG_LABEL, +} from './separator-edit' + +// A porch off an 8 × 4 house, closed by invisible walls, pushed and dragged +// the way the plan and the 3D view do it. The pointer is faked: plan points +// come straight from the client point (x → plan x, y → plan z). + +type Point = [number, number] +const LEVEL = 'level_separator_edit' +const stubbed: string[] = [] +let stop = () => {} +let restoreFrames = () => {} +let registry: { mockRestore: () => void } | null = null +const toPlan = (clientX: number, clientY: number): [number, number] => [clientX, clientY] + +function pointer(type: 'pointermove' | 'pointerup', x: number, y: number, altKey = false) { + window.dispatchEvent(Object.assign(new Event(type), { clientX: x, clientY: y, altKey })) +} + +const near = (a: Point, b: Point) => Math.hypot(a[0] - b[0], a[1] - b[1]) < 1e-6 +const separators = () => + Object.values(useScene.getState().nodes).filter( + (node): node is SeparatorNode => node.type === 'separator', + ) +/** The invisible wall from `a` to `b`, either way round. */ +const between = (a: Point, b: Point) => + separators().find( + (node) => + (near(node.start, a) && near(node.end, b)) || (near(node.start, b) && near(node.end, a)), + ) +const porchZone = () => + Object.values(useScene.getState().nodes).find( + (node): node is ZoneNode => + node.type === 'zone' && (node.roomType === 'porch' || node.roomType === 'deck'), + ) +const porchArea = () => { + const zone = porchZone() + return zone ? area([{ outer: zone.polygon, holes: zone.holes }]) : 0 +} + +/** Draws the porch's sides — invisible walls, or railings where `railed` — through the store. */ +function drawPorch( + sides: Array<[Point, Point]> = [ + [ + [2, 4], + [2, 6], + ], + [ + [2, 6], + [6, 6], + ], + [ + [6, 6], + [6, 4], + ], + ], + railed = false, +) { + for (const [start, end] of sides) { + const nodes = useScene.getState().nodes + if (railed) { + const railing = WallNode.parse({ + ...railingWallDefaults(), + id: generateId('wall'), + parentId: LEVEL, + start, + end, + }) + useScene.getState().applyNodeChanges(structureChangeBatch([{ op: 'create', node: railing }])) + continue + } + const plan = planSeparatorInsertion(nodes as Record, { + levelId: LEVEL as AnyNodeId, + start, + end, + joinRadius: 0.05, + mintId: () => generateId('separator'), + }) + useScene.getState().applyNodeChanges(plan!.changes) + } + clearSceneHistory() +} + +function porchHandles(): WallPushHandle[] { + const zone = porchZone()! + const record = getRoomSelectionIndex(LEVEL) + .update(useScene.getState().nodes) + .find((room) => room.zoneId === zone.id)! + return roomPushHandles(useScene.getState().nodes, record.spans) +} + +/** The porch arrow pointing along `direction`. */ +const arrow = (handles: WallPushHandle[], direction: Point) => + handles.find( + (handle) => + handle.outward[0] * direction[0] + handle.outward[1] * direction[1] > 0.99 && + !!handle.separators, + ) + +beforeEach(() => { + restoreFrames = installImmediateAnimationFrames() + if (!globalThis.document) { + globalThis.document = { body: { style: { cursor: '' } } } as unknown as Document + stubbed.push('document') + } + if (!globalThis.window) { + globalThis.window = new EventTarget() as Window & typeof globalThis + stubbed.push('window') + } + // The nodes package that registers walls and invisible walls is not loaded + // here: answer their snap context lookup as their definitions do. + const get = nodeRegistry.get.bind(nodeRegistry) + registry = spyOn(nodeRegistry, 'get').mockImplementation(((kind: string) => + kind === 'wall' || kind === 'separator' + ? { snapProfile: 'structural' } + : get(kind)) as typeof nodeRegistry.get) + cancelGestures('cancel') + useInteractionScope.getState().end() + const building = BuildingNode.parse({ id: 'building_separator_edit', children: [LEVEL] }) + const level = LevelNode.parse({ id: LEVEL, parentId: building.id }) + useScene.setState({ + nodes: { [building.id]: building, [level.id]: level }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + materials: {}, + collections: {}, + readOnly: false, + }) + useScene.temporal.getState().resume() + stop = initSpaceDetectionSync(useScene, { getState: () => ({ spaces: {}, setSpaces: () => {} }) }) + applyRoomPlan( + createZone(useScene.getState().nodes, { + levelId: LEVEL, + polygon: [ + [0, 0], + [8, 0], + [8, 4], + [0, 4], + ], + enclose: true, + mintId: generateId, + }), + ) + useViewer.getState().setSelection({ buildingId: building.id, levelId: LEVEL, selectedIds: [] }) + useEditor.setState({ phase: 'building', mode: 'select', gridSnapStep: 0.5 }) + useEditor.getState().setSnappingMode('polygon', 'grid') + useEditor.getState().setSnappingMode('wall', 'grid') + clearSceneHistory() +}) + +afterEach(() => { + cancelGestures('cancel') + stop() + registry?.mockRestore() + registry = null + restoreFrames() +}) + +afterAll(async () => { + // Let the click-swallow timers fire before the stubs go. + await new Promise((resolve) => setTimeout(resolve, 350)) + for (const key of stubbed.splice(0)) delete (globalThis as Record)[key] +}) + +describe('a porch closed by invisible walls, selected', () => { + test('has a push arrow on each invisible-wall edge, pointing off the porch, beside the house wall’s', () => { + drawPorch() + expect(porchZone()?.roomType).toBe('porch') + const handles = porchHandles() + for (const direction of [ + [-1, 0], + [0, 1], + [1, 0], + ] as Point[]) + expect(arrow(handles, direction)?.separators).toHaveLength(1) + // The house wall's arrow is the wall push it always was. + expect(handles.filter((handle) => !handle.separators)).toHaveLength(1) + const bottom = arrow(handles, [0, 1])! + expect(bottom.position[0]).toBeCloseTo(4, 6) + expect(bottom.position[1]).toBeGreaterThan(6) + }) + + test('an edge drawn in two pieces has one arrow, and it pushes both', () => { + drawPorch([ + [ + [2, 4], + [2, 6], + ], + [ + [2, 6], + [4, 6], + ], + [ + [4, 6], + [6, 6], + ], + [ + [6, 6], + [6, 4], + ], + ]) + const bottom = arrow(porchHandles(), [0, 1])! + expect(bottom.separators).toHaveLength(2) + expect(bottom.position[0]).toBeCloseTo(4, 6) + runFloorplanRoomPush({ handle: bottom, levelId: LEVEL, clientX: 4, clientY: 6.3, toPlan }) + pointer('pointermove', 4, 7.3) + pointer('pointerup', 4, 7.3) + expect(between([2, 7], [4, 7])).toBeDefined() + expect(between([4, 7], [6, 7])).toBeDefined() + expect(porchArea()).toBeCloseTo(12, 6) + }) + + test('its edge pushed out moves the invisible wall, the sides follow, the porch grows; one undo step', () => { + drawPorch() + const before = useScene.getState().nodes + const bottom = arrow(porchHandles(), [0, 1])! + const zoneId = porchZone()!.id + expect( + runFloorplanRoomPush({ + handle: bottom, + levelId: LEVEL, + zoneId, + clientX: 4, + clientY: 6.3, + toPlan, + }), + ).not.toBeNull() + const depth = () => { + const drag = useRoomHandleDrag.getState().drag + return drag?.kind === 'push' ? drag.dimensions.map((dimension) => dimension.distance) : [] + } + // The porch's depth, edge to the house wall's outside face. + const [rest] = depth() + expect(rest).toBeGreaterThan(1.8) + expect(rest).toBeLessThan(2) + // 0.8 m of travel lands the edge on the 0.5 m lattice, a metre out. + pointer('pointermove', 4, 7.1) + expect(useRoomHandleDrag.getState().drag).toMatchObject({ kind: 'push', distance: 1 }) + expect(depth()[0]).toBeCloseTo(rest! + 1, 6) + const live = useLiveNodeOverrides.getState().overrides + const preview = live.get(bottom.wallId) as Partial | undefined + expect(preview?.start?.[1]).toBeCloseTo(7, 6) + // The porch floor follows live; nothing is written until release. + expect(live.get(zoneId as AnyNodeId)).toHaveProperty('polygon') + expect(useScene.getState().nodes).toBe(before) + + pointer('pointerup', 4, 7.1) + expect(useLiveNodeOverrides.getState().overrides.has(bottom.wallId)).toBe(false) + expect(between([2, 7], [6, 7])?.id).toBe(bottom.wallId as SeparatorNode['id']) + expect(between([2, 4], [2, 7])).toBeDefined() + expect(between([6, 7], [6, 4])).toBeDefined() + expect(porchArea()).toBeCloseTo(12, 6) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + + useScene.temporal.getState().undo() + expect(between([2, 6], [6, 6])).toBeDefined() + expect(between([2, 4], [2, 6])).toBeDefined() + expect(porchArea()).toBeCloseTo(8, 6) + }) + + test('pulled in, it shrinks the porch and stays closed', () => { + drawPorch() + const bottom = arrow(porchHandles(), [0, 1])! + runFloorplanRoomPush({ handle: bottom, levelId: LEVEL, clientX: 4, clientY: 6.3, toPlan }) + pointer('pointermove', 4, 5.3) + pointer('pointerup', 4, 5.3) + expect(between([2, 5], [6, 5])).toBeDefined() + expect(porchArea()).toBeCloseTo(4, 6) + }) + + test('its house-wall arrow still pushes the house wall, the porch following it', () => { + drawPorch() + const house = porchHandles().find((handle) => !handle.separators)! + expect(house.outward[1]).toBeCloseTo(-1, 6) + runFloorplanRoomPush({ + handle: house, + levelId: LEVEL, + clientX: house.position[0], + clientY: house.position[1], + toPlan, + }) + pointer('pointermove', house.position[0], house.position[1] - 1) + pointer('pointerup', house.position[0], house.position[1] - 1) + const moved = Object.values(useScene.getState().nodes).find( + (node): node is WallNode => + node.type === 'wall' && + Math.abs(node.start[1] - 3) < 1e-6 && + Math.abs(node.end[1] - 3) < 1e-6, + ) + expect(moved).toBeDefined() + expect(porchArea()).toBeCloseTo(12, 6) + }) +}) + +test('a deck fenced by railings has the wall arrows on its railings', () => { + drawPorch(undefined, true) + const deck = porchZone() + expect(deck?.roomType).toBe('deck') + const record = getRoomSelectionIndex(LEVEL) + .update(useScene.getState().nodes) + .find((room) => room.zoneId === deck!.id)! + const handles = roomPushHandles(useScene.getState().nodes, record.spans) + const railings = handles.filter( + (handle) => + (useScene.getState().nodes[handle.wallId as AnyNodeId] as WallNode).wallType === 'railing', + ) + expect(railings).toHaveLength(3) + expect(railings.every((handle) => !handle.separators)).toBe(true) +}) + +describe('a selected invisible wall', () => { + test('its side arrow pushes it, the invisible wall joined to it stretching and its end sliding on the house', () => { + drawPorch() + const left = between([2, 4], [2, 6])! + useViewer.getState().setSelection({ selectedIds: [left.id] }) + const side = separatorPushHandles(left).find((handle) => handle.outward[0] < -0.99)! + expect( + runFloorplanWallPush( + { wallId: left.id, side: side.key.endsWith(':a') ? 'a' : 'b' }, + 1.9, + 5, + toPlan, + ), + ).not.toBeNull() + pointer('pointermove', 0.9, 5) + pointer('pointerup', 0.9, 5) + expect(between([1, 4], [1, 6])?.id).toBe(left.id) + expect(between([1, 6], [6, 6])).toBeDefined() + expect(porchArea()).toBeCloseTo(10, 6) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + }) + + /** Drags the end of `separator` at `corner` (its 3D grip) along `path`, Alt held when `alt`. */ + function dragEnd(separator: SeparatorNode, corner: Point, path: Point[], alt = false) { + const endpoint = near(separator.start, corner) ? 'start' : 'end' + runSeparatorEndDrag({ separatorId: separator.id, endpoint, levelId: LEVEL, toPlan }) + for (const [x, z] of path) pointer('pointermove', x, z, alt) + const [x, z] = path.at(-1)! + pointer('pointerup', x, z, alt) + } + + test('an end dragged takes the invisible wall joined there along, square and on the grid', () => { + drawPorch() + const bottom = between([2, 6], [6, 6])! + const endpoint = near(bottom.start, [2, 6]) ? 'start' : 'end' + runSeparatorEndDrag({ separatorId: bottom.id, endpoint, levelId: LEVEL, toPlan }) + expect(useInteractionScope.getState().scope).toMatchObject({ + kind: 'handle-drag', + nodeId: bottom.id, + handle: SEPARATOR_END_DRAG_LABEL, + }) + pointer('pointermove', 1.07, 5.96) + const live = useLiveNodeOverrides.getState().overrides.get(bottom.id) as Partial + expect(live[endpoint]).toEqual([1, 6]) + expect(between([2, 6], [6, 6])).toBeDefined() + pointer('pointerup', 1.07, 5.96) + expect(between([1, 6], [6, 6])?.id).toBe(bottom.id) + expect(between([2, 4], [1, 6])).toBeDefined() + expect(useLiveNodeOverrides.getState().overrides.has(bottom.id)).toBe(false) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + expect(useInteractionScope.getState().scope.kind).toBe('idle') + }) + + test('let go with Alt, its end comes back together on its old corner', () => { + drawPorch() + const bottom = between([2, 6], [6, 6])! + dragEnd(bottom, [2, 6], [[1, 7]], true) + expect(between([2, 4], [2, 6])).toBeDefined() + expect(between([2, 6], [6, 6])).toBeUndefined() + + const moved = useScene.getState().nodes[bottom.id as AnyNodeId] as SeparatorNode + dragEnd(moved, near(moved.start, [6, 6]) ? moved.end : moved.start, [[2.12, 6.09]], true) + expect(between([2, 6], [6, 6])?.id).toBe(bottom.id) + expect(porchArea()).toBeCloseTo(8, 6) + }) + + test('the plan’s end handle runs the same drag', () => { + drawPorch() + const bottom = between([2, 6], [6, 6])! + const endpoint = near(bottom.start, [2, 6]) ? 'start' : 'end' + // The plan begins its end-drag scope (the wall snapping set) around the session. + useInteractionScope.getState().begin(endpointReshapeScope(bottom.id, endpoint, 'floorplan')) + const session = beginSeparatorEndDrag(bottom.id, endpoint)! + session.move([1.07, 6.04], false) + expect(session.canCommit()).toBe(true) + useInteractionScope.getState().end() + expect(session.commit()).toBe(true) + expect(between([1, 6], [6, 6])?.id).toBe(bottom.id) + expect(between([2, 4], [1, 6])).toBeDefined() + }) +}) diff --git a/packages/editor/src/lib/separator-edit.ts b/packages/editor/src/lib/separator-edit.ts new file mode 100644 index 0000000000..b3d5022d7c --- /dev/null +++ b/packages/editor/src/lib/separator-edit.ts @@ -0,0 +1,179 @@ +import { + type AnyNode, + type AnyNodeId, + joinedSeparatorIds, + runAsSingleSceneHistoryStep, + type SeparatorNode, + setSeparatorEnd, + useLiveNodeOverrides, + useScene, + type WallNode, + type WallPlanPoint, +} from '@pascal-app/core' +import { runRoomHandleDrag } from './room-handle-drag' +import { applyRoomPlan } from './room-structure-commands' +import { levelSeparatorLines } from './separator-lines' +import { isWallRunFree, publishWallRunFeedback, snapWallRunDraftPoint } from './wall-run-snap' + +/** Scope label of an invisible wall's end drag (its 3D grip). */ +export const SEPARATOR_END_DRAG_LABEL = 'separator-end' + +const SAME_POINT = 1e-6 + +export type SeparatorEndDrag = { + /** The invisible walls the drag may move: the dragged one and those it can carry. */ + affectedIds: AnyNodeId[] + /** Snaps the end to `point` (level plan) and previews it; `detach` (Alt) leaves the joined ones. */ + move: (point: WallPlanPoint, detach: boolean) => void + /** Whether the end has moved somewhere it may stay. */ + canCommit: () => boolean + /** Writes the end as one undo step; false when it did not move or core refused it. */ + commit: () => boolean + /** Drops the preview; writes nothing. */ + cancel: () => void +} + +/** + * An invisible wall's end dragged: its handle on the plan, its grip in 3D. The + * end snaps as a wall run does — square to the house or to its own line, its + * length on the grid, onto wall and invisible-wall ends and bodies, so it + * comes back together on its old corner — the invisible walls joined at that + * end follow it live, and the release writes it once (`setSeparatorEnd`). + * The scene is untouched until then. + */ +export function beginSeparatorEndDrag( + separatorId: string, + endpoint: 'start' | 'end', +): SeparatorEndDrag | null { + const nodes = useScene.getState().nodes + const separator = nodes[separatorId as AnyNodeId] + if (separator?.type !== 'separator') return null + const from = separator[endpoint] + const fixed = endpoint === 'start' ? separator.end : separator.start + const levelId = separator.parentId + const atFrom = (point: WallPlanPoint) => + Math.abs(point[0] - from[0]) <= SAME_POINT && Math.abs(point[1] - from[1]) <= SAME_POINT + const others = Object.values(nodes).filter( + (node): node is SeparatorNode => + node.type === 'separator' && node.parentId === levelId && node.id !== separator.id, + ) + // The ends following this one are no snap targets while they do: they sit where it left. + const joined = joinedSeparatorIds(nodes, separator, endpoint) + const walls = Object.values(nodes).filter( + (node): node is WallNode => node.type === 'wall' && node.parentId === levelId, + ) + const targets = { + attached: [ + ...walls, + ...levelSeparatorLines(nodes, levelId, new Set([separator.id, ...joined])), + ], + detached: [...walls, ...levelSeparatorLines(nodes, levelId, new Set([separator.id]))], + } + const length = Math.hypot(from[0] - fixed[0], from[1] - fixed[1]) + const reference: WallPlanPoint | null = + length > 1e-9 ? [(from[0] - fixed[0]) / length, (from[1] - fixed[1]) / length] : null + let target: { point: WallPlanPoint; detach: boolean } | null = null + const previewIds = new Set() + const preview = (updates: Array<[AnyNodeId, Partial]>) => { + const overrides = useLiveNodeOverrides.getState() + const next = new Set(updates.map(([id]) => id)) + for (const id of previewIds) { + if (next.has(id)) continue + overrides.clear(id) + useScene.getState().markDirty(id) + previewIds.delete(id) + } + if (updates.length) overrides.setMany(updates) + for (const id of next) { + previewIds.add(id) + useScene.getState().markDirty(id) + } + } + return { + affectedIds: [separator.id, ...others.map((node) => node.id)] as AnyNodeId[], + move(point, detach) { + const snap = snapWallRunDraftPoint({ + point, + start: fixed, + runStart: fixed, + reference, + walls: detach ? targets.detached : targets.attached, + free: isWallRunFree(null), + }) + publishWallRunFeedback(snap) + const plan = setSeparatorEnd(nodes, { separatorId, endpoint, point: snap.point, detach }) + if (plan.conflicts?.length) { + target = null + preview([]) + return + } + target = { point: snap.point, detach } + preview( + plan.changes.flatMap( + (change): Array<[AnyNodeId, Partial]> => + change.op === 'update' ? [[change.id as AnyNodeId, change.data]] : [], + ), + ) + }, + canCommit: () => !!target && !atFrom(target.point), + commit() { + preview([]) + publishWallRunFeedback(null) + if (!target || atFrom(target.point)) return false + const current = useScene.getState().nodes + if (current[separatorId as AnyNodeId]?.type !== 'separator') return false + const plan = setSeparatorEnd(current, { separatorId, endpoint, ...target }) + return runAsSingleSceneHistoryStep(useScene, () => applyRoomPlan(plan)) + }, + cancel() { + preview([]) + publishWallRunFeedback(null) + }, + } +} + +/** + * An invisible wall's 3D end grip: `toPlan` reads the pointer on the floor the + * separator lies on, in level plan metres. One gesture under the lifecycle + * owner (Escape or ⌘Z cancels), one undo step; Alt leaves the joined + * invisible walls where they are. + */ +export function runSeparatorEndDrag({ + separatorId, + endpoint, + levelId, + toPlan, +}: { + separatorId: string + endpoint: 'start' | 'end' + levelId: string + toPlan: (clientX: number, clientY: number) => WallPlanPoint | null +}) { + const drag = beginSeparatorEndDrag(separatorId, endpoint) + if (!drag) return null + let altHeld = false + const trackAlt = (event: KeyboardEvent | PointerEvent) => { + altHeld = event.altKey + } + const events = ['pointermove', 'keydown', 'keyup'] as const + for (const type of events) window.addEventListener(type, trackAlt, true) + const stop = () => { + for (const type of events) window.removeEventListener(type, trackAlt, true) + } + return runRoomHandleDrag({ + label: SEPARATOR_END_DRAG_LABEL, + nodeId: separatorId, + levelId, + requires: [separatorId], + sample: toPlan, + onValue: (point) => drag.move(point, altHeld), + onCommit: () => { + stop() + drag.commit() + }, + onCancel: () => { + stop() + drag.cancel() + }, + }) +} diff --git a/packages/editor/src/lib/separator-lines.ts b/packages/editor/src/lib/separator-lines.ts new file mode 100644 index 0000000000..c05db6f6aa --- /dev/null +++ b/packages/editor/src/lib/separator-lines.ts @@ -0,0 +1,38 @@ +import type { AnyNode, SeparatorNode, WallNode } from '@pascal-app/core' + +/** + * Invisible walls (room separators) as the wall snapping reads walls: a line + * with no thickness. A drafted or dragged invisible-wall end catches another's + * ends and body this way, and a pushed one lines up with them, as walls do + * with walls. Never written to the scene. + */ +export function separatorWallLine(separator: SeparatorNode): WallNode { + return { + object: 'node', + id: separator.id as string as WallNode['id'], + type: 'wall', + name: separator.name, + parentId: separator.parentId, + visible: true, + metadata: {}, + children: [], + start: separator.start, + end: separator.end, + thickness: 0, + frontSide: 'unknown', + backSide: 'unknown', + } +} + +/** The invisible walls on `levelId` as wall lines, leaving out `exclude`. */ +export function levelSeparatorLines( + nodes: Readonly>, + levelId: string | null, + exclude: ReadonlySet = new Set(), +): WallNode[] { + return Object.values(nodes).flatMap((node) => + node.type === 'separator' && node.parentId === levelId && !exclude.has(node.id) + ? [separatorWallLine(node)] + : [], + ) +} diff --git a/packages/editor/src/lib/tool-shortcuts.ts b/packages/editor/src/lib/tool-shortcuts.ts new file mode 100644 index 0000000000..ab67e2b185 --- /dev/null +++ b/packages/editor/src/lib/tool-shortcuts.ts @@ -0,0 +1,240 @@ +import type { PascalI18n } from '@pascal-app/core' +import useEditor from '../store/use-editor' +import useInteractionScope from '../store/use-interaction-scope' +import type { ContinuationMode } from './continuation' +import { openSidebarPanel } from './sidebar-panel' +import { + getWallDrawVariantInfo, + getWallLineKind, + getWallTypeOverride, + selectWallDrawVariant, + setWallLineKind, + setWallTypeOverride, + type WallDrawVariant, + type WallLineKind, +} from './wall-draw-variant' +import type { WallTypePlacement } from './wall-type' + +/** + * The single-key tools of the editor, and the keys tooltips print for them. + * + * The drafting tools (Door, Window, Stairs, Roof, Exterior and Interior wall) + * sit on their own letter where it was free (I) and on Shift with + * their letter where the bare key was taken: W/A/S/D pan the view in 2D and 3D, + * R flips or rotates the selection and the armed placement, and E operates the + * selected door, window or cabinet. Pan stands down for those Shift chords + * (`claimsKeyboardPan`), and R/E still work bare as before. + */ +export type ToolShortcutId = + | 'select' + | 'delete' + | 'wall' + | 'item' + | 'zone' + | 'measurement' + | 'paint' + | 'terrain' + | 'door' + | 'window' + | 'stair' + | 'roof' + | 'exterior-wall' + | 'interior-wall' + +type ArmedByChord = { + /** `KeyboardEvent.code`: the physical key, as pan reads it. */ + code: string + shift: boolean +} + +/** Keys as `ShortcutToken` names them. */ +export const TOOL_SHORTCUT_KEYS: Record = { + select: ['V'], + delete: ['X'], + wall: ['B'], + item: ['F'], + zone: ['Z'], + measurement: ['M'], + paint: ['P'], + terrain: ['G'], + door: ['Shift', 'D'], + window: ['Shift', 'W'], + stair: ['Shift', 'S'], + roof: ['Shift', 'R'], + 'exterior-wall': ['Shift', 'E'], + 'interior-wall': ['I'], +} + +/** The tools this module arms itself; the older keys stay in `use-keyboard`. */ +const CHORDS: Partial> = { + door: { code: 'KeyD', shift: true }, + window: { code: 'KeyW', shift: true }, + stair: { code: 'KeyS', shift: true }, + roof: { code: 'KeyR', shift: true }, + 'exterior-wall': { code: 'KeyE', shift: true }, + 'interior-wall': { code: 'KeyI', shift: false }, +} + +/** A tool's shortcut as a tooltip prints it: `Shift+D`, `B`. Null for a tool without one. */ +export function toolShortcutLabel(tool: string): string | null { + const keys = TOOL_SHORTCUT_KEYS[tool as ToolShortcutId] + return keys ? keys.join('+') : null +} + +/** A tool's tooltip with its shortcut: `Door (Shift+D)`; the bare label when it has none. */ +export function withToolShortcut(label: string, tool: string | undefined): string { + const shortcut = tool ? toolShortcutLabel(tool) : null + return shortcut ? `${label} (${shortcut})` : label +} + +/** + * A Rooms tile's tooltip with the keys that arm it: Rectangle is B; Walls is what + * Shift+E (exterior walls) and I (interior walls) draw. + */ +export function wallVariantTooltip(variant: WallDrawVariant, i18n: PascalI18n): string { + const title = i18n._(getWallDrawVariantInfo(variant).title) + if (variant === 'rectangle') return withToolShortcut(title, 'wall') + if (variant === 'walls') + return i18n._({ + id: 'editor.lib.toolShortcuts.wallsTooltip', + message: '{title} ({exterior} exterior, {interior} interior)', + values: { + title, + exterior: toolShortcutLabel('exterior-wall'), + interior: toolShortcutLabel('interior-wall'), + }, + }) + return title +} + +// Gestures a held Shift modifies (free move, free rotate, force place): a Shift +// chord there is part of the gesture, never a tool switch. +const SHIFT_GESTURE_SCOPES = new Set([ + 'room-divide', + 'placing', + 'moving', + 'handle-drag', + 'mesh-editing', + 'reshaping', + 'box-select', + 'painting', + 'sculpting', + 'pointing', +]) + +/** + * The tool a key press arms, or null: the physical key and Shift, with no other + * modifier and no repeat. A Shift chord is off while a gesture holds Shift, so + * Shift+WASD keeps panning there. + */ +export function toolShortcutForKey( + event: Pick, +): ToolShortcutId | null { + if (event.metaKey || event.ctrlKey || event.altKey || event.repeat) return null + const entry = Object.entries(CHORDS).find( + ([, chord]) => chord.code === event.code && chord.shift === event.shiftKey, + ) + if (!entry) return null + if (event.shiftKey && SHIFT_GESTURE_SCOPES.has(useInteractionScope.getState().scope.kind)) + return null + return entry[0] as ToolShortcutId +} + +/** Whether WASD pan must leave this key to a tool shortcut (Shift+W/S/D). */ +export function claimsKeyboardPan( + event: Pick, +): boolean { + // A held key repeats: once claimed, the repeats stay claimed too. (Fields one by + // one: a DOM event's are getters a spread would drop.) + return ( + event.shiftKey && + toolShortcutForKey({ + code: event.code, + shiftKey: event.shiftKey, + metaKey: event.metaKey, + ctrlKey: event.ctrlKey, + altKey: event.altKey, + repeat: false, + }) !== null + ) +} + +/** Arm a build tool from the keyboard, as its Build panel tile does, with the panel showing. */ +function armBuildTool(tool: string) { + const editor = useEditor.getState() + editor.setPhase('building') + openSidebarPanel(['build']) + // A raw tool, as a tile click arms it: no preset carried over from earlier. + editor.setToolDefaults(tool, null) + editor.armToolMode({ mode: 'build', tool }) +} + +// The saved Rooms shape an Exterior / Interior wall shortcut drew over, put back when its walls +// end: the shortcut's Walls shape is for that run, not a new Rooms choice. +let shapeBeforeWallShortcut: ContinuationMode | null = null + +/** + * Arm the tool `id` names. Exterior and Interior wall draw single walls (the Walls + * shape) with that Build panel wall type wherever they land, until the wall tool + * is left or its shape changed. + */ +export function armToolShortcut(id: ToolShortcutId): void { + if (id === 'exterior-wall' || id === 'interior-wall') { + armWallTypeTool(id === 'exterior-wall' ? 'exterior' : 'interior') + return + } + armBuildTool(id) +} + +/** + * The Exterior / Interior wall tool, from its shortcut or its Rooms tile: single walls + * (the Walls shape) of that placement's Build panel type and finish wherever they land, + * until the wall tool is left or its shape changed; leaving puts the Rooms shape back. + * `arm` arms the wall tool as the host's tiles do (by default as the shortcut does). + */ +export function armWallTypeTool( + placement: WallTypePlacement, + arm: () => void = () => armBuildTool('wall'), +): void { + const shape = useEditor.getState().getContinuation('wall') + selectWallDrawVariant('walls') + arm() + shapeBeforeWallShortcut ??= shape + setWallTypeOverride(placement) +} + +/** + * The Invisible wall or Railing tool, from its Rooms tile: runs of invisible walls (room + * separators no wall stands on, room dividers) or of railings, drawn with the + * wall tool's Walls shape, until the wall tool is left or its shape changed; leaving puts the + * Rooms shape back. `arm` arms the wall tool as the host's tiles do. + */ +export function armWallLineTool( + kind: WallLineKind, + arm: () => void = () => armBuildTool('wall'), +): void { + const shape = useEditor.getState().getContinuation('wall') + selectWallDrawVariant('walls') + arm() + shapeBeforeWallShortcut ??= shape + setWallLineKind(kind) +} + +/** + * Drops the Exterior / Interior wall override (or the Invisible wall / Railing line kind) once the + * wall tool is left or reshaped. Leaving puts the Rooms shape the shortcut replaced back; a shape + * picked in the tool stays. + */ +export function subscribeWallTypeOverrideReset(): () => void { + return useEditor.subscribe((state) => { + const inWallTool = state.mode === 'build' && state.tool === 'wall' + if (inWallTool && state.getContinuation('wall') === 'single') return + const restore = + !inWallTool && (getWallTypeOverride() !== null || getWallLineKind() !== null) + ? shapeBeforeWallShortcut + : null + shapeBeforeWallShortcut = null + setWallTypeOverride(null) + if (restore) state.setContinuation('wall', restore) + }) +} diff --git a/packages/editor/src/lib/wall-draw-variant.test.ts b/packages/editor/src/lib/wall-draw-variant.test.ts index 73caf8129a..d25cb608cf 100644 --- a/packages/editor/src/lib/wall-draw-variant.test.ts +++ b/packages/editor/src/lib/wall-draw-variant.test.ts @@ -1,13 +1,31 @@ import { afterEach, describe, expect, test } from 'bun:test' -import { emitter } from '@pascal-app/core' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + emitter, + LevelNode, + useScene, + WallNode, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' import useEditor from '../store/use-editor' +import { useFloorplanDraftPreview } from '../store/use-floorplan-draft-preview' import { toolHudTitle } from './hud-title' import { + getDrawnWallTypes, getWallDrawVariant, + getWallTypeOverride, selectWallDrawVariant, + setWallTypeOverride, + useWallDraftThickness, + useWallSegmentDraftThickness, WALL_DRAW_VARIANTS, wallDrawVariantOf, } from './wall-draw-variant' +import { DEFAULT_WALL_TYPES, normalizeWallTypes, wallTypeThickness } from './wall-type' afterEach(() => { useEditor.getState().setContinuation('wall', 'room') @@ -69,3 +87,153 @@ describe('Rooms variants', () => { expect(toolHudTitle('wall', 'single')?.label.message).toBe('Walls') }) }) + +describe('Wall types', () => { + afterEach(() => { + setWallTypeOverride(null) + useEditor.setState({ wallTypes: DEFAULT_WALL_TYPES }) + }) + + test('default to an Exterior 2x6 lap siding outline and 2x4 drywall partitions', () => { + expect(useEditor.getState().wallTypes).toEqual({ + exterior: 'exterior-2x6-siding', + interior: 'interior-2x4-drywall', + }) + const types = getDrawnWallTypes() + expect(types.exterior?.assembly?.presetId).toBe('exterior-2x6-siding') + expect(types.interior?.assembly?.presetId).toBe('interior-2x4-drywall') + }) + + test('stored picks come back, unknown ones fall back to the defaults', () => { + expect(normalizeWallTypes({ exterior: 'exterior-2x6-stucco', interior: 'plain' })).toEqual({ + exterior: 'exterior-2x6-stucco', + interior: 'plain', + }) + expect(normalizeWallTypes({ exterior: 'gone' })).toEqual(DEFAULT_WALL_TYPES) + expect(normalizeWallTypes(undefined)).toEqual(DEFAULT_WALL_TYPES) + }) + + test('a session override draws every wall with one placement type', () => { + setWallTypeOverride('interior') + const types = getDrawnWallTypes() + expect(types.exterior?.assembly?.presetId).toBe('interior-2x4-drywall') + expect(types.interior?.assembly?.presetId).toBe('interior-2x4-drywall') + }) + + test('picking a Rooms shape, even the one in hand, drops the override', () => { + selectWallDrawVariant('walls') + setWallTypeOverride('interior') + selectWallDrawVariant('walls') + expect(getWallTypeOverride()).toBeNull() + expect(getDrawnWallTypes().exterior?.assembly?.presetId).toBe('exterior-2x6-siding') + }) +}) + +// The draft drew a partition at the exterior type's thickness (and the plan at 0.1 m), so a wall +// drawn inside a room jumped to its 2x4 thickness on commit. +test('the wall draft draws at the thickness the wall commits with, partition or outline', async () => { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + ;(globalThis as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= () => {} + const previousViewer = useViewer.getState() + const building = BuildingNode.parse({}) + const level = LevelNode.parse({ parentId: building.id, level: 0 }) + building.children = [level.id] + const corners: Array<[number, number]> = [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], + ] + const walls = corners.map((start, index) => + WallNode.parse({ parentId: level.id, start, end: corners[(index + 1) % 4] }), + ) + level.children = walls.map((wall) => wall.id) + useScene + .getState() + .setScene( + Object.fromEntries([building, level, ...walls].map((node) => [node.id, node])) as Record< + AnyNodeId, + AnyNode + >, + [building.id as AnyNodeId], + { installedPlugins: [], hasExplicitPluginInstallState: false }, + ) + useViewer.getState().setSelection({ buildingId: building.id, levelId: level.id }) + const seen: number[] = [] + const Probe = () => { + seen.push(useWallDraftThickness(0.1)) + return null + } + const draft = (start: [number, number], end: [number, number]) => + act(async () => { + useFloorplanDraftPreview.getState().setWallDraftStart(start) + useFloorplanDraftPreview.getState().setWallDraftEnd(end) + }) + const renderer = await create(createElement(Probe)) + try { + await draft([2, 0], [2, 3]) + expect(seen.at(-1)).toBe(wallTypeThickness(DEFAULT_WALL_TYPES.interior)!) + await draft([4, 0], [8, 0]) + expect(seen.at(-1)).toBe(wallTypeThickness(DEFAULT_WALL_TYPES.exterior)!) + await act(async () => setWallTypeOverride('interior')) + expect(seen.at(-1)).toBe(wallTypeThickness(DEFAULT_WALL_TYPES.interior)!) + } finally { + await act(async () => renderer.unmount()) + setWallTypeOverride(null) + useFloorplanDraftPreview.getState().setWallDraftStart(null) + useFloorplanDraftPreview.getState().setWallDraftEnd(null) + useViewer.setState(previousViewer, true) + } +}) + +// A Rectangle or Polygon room has no line draft, so its sides drew at the exterior thickness: a +// closet drawn inside a room showed 2x6 + siding and committed as 2x4 partitions. +test('each side of a room drafted inside a room draws at the partition thickness it commits with', async () => { + ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + ;(globalThis as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= () => {} + const previousViewer = useViewer.getState() + const building = BuildingNode.parse({}) + const level = LevelNode.parse({ parentId: building.id, level: 0 }) + building.children = [level.id] + const house: Array<[number, number]> = [ + [0, 0], + [6, 0], + [6, 5], + [0, 5], + ] + const walls = house.map((start, index) => + WallNode.parse({ parentId: level.id, start, end: house[(index + 1) % house.length] }), + ) + level.children = walls.map((wall) => wall.id) + useScene + .getState() + .setScene( + Object.fromEntries([building, level, ...walls].map((node) => [node.id, node])) as Record< + AnyNodeId, + AnyNode + >, + [building.id as AnyNodeId], + { installedPlugins: [], hasExplicitPluginInstallState: false }, + ) + useViewer.getState().setSelection({ buildingId: building.id, levelId: level.id }) + let thicknessOf: ((start: [number, number], end: [number, number]) => number) | null = null + const Probe = () => { + thicknessOf = useWallSegmentDraftThickness(0.1) + return null + } + const renderer = await create(createElement(Probe)) + try { + const closet: Array<[number, number]> = [ + [2, 2], + [3, 2], + [3, 3], + [2, 3], + ] + const sides = closet.map((start, index) => thicknessOf!(start, closet[(index + 1) % 4]!)) + expect(sides).toEqual(Array(4).fill(wallTypeThickness(DEFAULT_WALL_TYPES.interior)!)) + expect(thicknessOf!([8, 0], [10, 0])).toBe(wallTypeThickness(DEFAULT_WALL_TYPES.exterior)!) + } finally { + await act(async () => renderer.unmount()) + useViewer.setState(previousViewer, true) + } +}) diff --git a/packages/editor/src/lib/wall-draw-variant.ts b/packages/editor/src/lib/wall-draw-variant.ts index 8f04a9e41d..9fd10271f4 100644 --- a/packages/editor/src/lib/wall-draw-variant.ts +++ b/packages/editor/src/lib/wall-draw-variant.ts @@ -1,7 +1,30 @@ -import type { MessageDescriptor } from '@pascal-app/core' -import { emitter } from '@pascal-app/core' +import { + type AnyNode, + type AnyNodeId, + type DrawnWallTypes, + detectSpacesForLevel, + emitter, + type MessageDescriptor, + pointInPolygon2D, + railingWallDefaults, + useScene, + type WallNode, + type WallPlanPoint, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { useCallback } from 'react' +import { create } from 'zustand' import useEditor from '../store/use-editor' +import { useFloorplanDraftPreview } from '../store/use-floorplan-draft-preview' import type { ContinuationMode } from './continuation' +import { levelSeparatorLines } from './separator-lines' +import { + availableWallType, + drawnWallTypesOf, + type WallTypePlacement, + type WallTypes, + wallTypeThickness, +} from './wall-type' /** * How the wall tool draws, picked from the Build panel's Rooms group before @@ -53,8 +76,8 @@ export const WALL_DRAW_VARIANTS: readonly WallDrawVariantInfo[] = [ title: /* i18n */ { id: 'editor.lib.wallDrawVariant.walls', message: 'Walls' }, iconSrc: '/icons/wall.webp', caption: { - lead: 'Draw one wall at a time.', - rest: 'Close a loop and it becomes a room; leave it open for partitions.', + lead: 'Click each corner;', + rest: 'every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room.', }, }, ] @@ -84,9 +107,189 @@ export function useWallDrawVariant(): WallDrawVariant { * finishes as a rectangle. */ export function selectWallDrawVariant(variant: WallDrawVariant): void { + // Picking a shape, even the one in hand, goes back to choosing the wall type + // by placement. + setWallTypeOverride(null) const editor = useEditor.getState() const { mode } = getWallDrawVariantInfo(variant) if (editor.getContinuation('wall') === mode) return if (editor.mode === 'build' && editor.tool === 'wall') emitter.emit('tool:cancel') editor.setContinuation('wall', mode) } + +/** + * The construction Rectangle, Polygon and Walls give the walls they draw: the + * Build panel's exterior and interior wall types, with the finishes picked + * beside them. A preset the host placed (`toolDefaults.wall` with its own + * thickness or assembly) keeps its own. + */ +export function getDrawnWallTypes(): DrawnWallTypes { + const { wallTypes, wallFinishes } = useEditor.getState() + const override = useWallTypeOverride.getState().placement + if (!override) return drawnWallTypesOf(wallTypes, wallFinishes) + const forced = availableWallType(wallTypes[override], override) + return drawnWallTypesOf({ exterior: forced, interior: forced }, wallFinishes) +} + +/** + * What the wall tool draws in place of walls of a type: invisible walls (room dividers, + * room separators no wall stands on) or railings. + */ +export type WallLineKind = 'invisible' | 'railing' + +/** The line an invisible wall's draft is drawn at, metres. */ +export const INVISIBLE_WALL_DRAFT_THICKNESS = 0.01 + +// Session-only, so it lives beside the persisted editor store, not in it. +const useWallTypeOverride = create<{ + placement: WallTypePlacement | null + lineKind: WallLineKind | null +}>(() => ({ + placement: null, + lineKind: null, +})) + +/** + * For this session only: draw every wall with the exterior or the interior + * type, wherever it lands (null goes back to choosing by placement). Not + * persisted; the Build panel's picks are unchanged. It ends any invisible wall + * or railing run. + */ +export function setWallTypeOverride(placement: WallTypePlacement | null): void { + useWallTypeOverride.setState({ placement, lineKind: null }) +} + +/** For this session only: the wall tool draws invisible walls or railings (null: walls again). */ +export function setWallLineKind(lineKind: WallLineKind | null): void { + useWallTypeOverride.setState({ lineKind, placement: null }) +} + +export function getWallLineKind(): WallLineKind | null { + return useWallTypeOverride.getState().lineKind +} + +/** The line kind the wall tool draws, for the Build panel to light its tile; null when walls. */ +export function useWallLineKind(): WallLineKind | null { + return useWallTypeOverride((state) => state.lineKind) +} + +/** + * What an invisible-wall draft snaps to besides walls: the current level's + * invisible walls, as lines (`separatorWallLine`), so a new one closes onto + * their ends and bodies — its old corner. Nothing while walls or railings draw. + */ +export function invisibleWallDraftSnapLines(): WallNode[] { + if (getWallLineKind() !== 'invisible') return [] + return levelSeparatorLines(useScene.getState().nodes, useViewer.getState().selection.levelId) +} + +export function getWallTypeOverride(): WallTypePlacement | null { + return useWallTypeOverride.getState().placement +} + +/** The forced wall type placement, for the Build panel to show; null when none. */ +export function useWallTypeOverridePlacement(): WallTypePlacement | null { + return useWallTypeOverride((state) => state.placement) +} + +// A level's rooms, once per scene state: the draft asks on every pointer move. +const levelRoomsCache = new WeakMap>>>() + +function levelRooms( + nodes: Readonly>, + levelId: string, +): Array> { + let byLevel = levelRoomsCache.get(nodes) + if (!byLevel) { + byLevel = new Map() + levelRoomsCache.set(nodes, byLevel) + } + const cached = byLevel.get(levelId) + if (cached) return cached + const level = nodes[levelId as AnyNodeId] + const walls = + level?.type === 'level' + ? level.children + .map((id) => nodes[id as AnyNodeId]) + .filter((node): node is WallNode => node?.type === 'wall' && node.visible !== false) + : [] + const rooms = detectSpacesForLevel(levelId, walls) + .spaces.filter((space) => !space.isExterior) + .map((space) => space.polygon) + byLevel.set(levelId, rooms) + return rooms +} + +/** + * The wall type a wall drawn `start` → `end` on `levelId` gets, as the commit decides it + * (`DrawnWallTypes`): interior inside a room the level's walls close (a partition), exterior + * elsewhere (the outline, an open run, the first walls of a plan). + */ +export function drawnWallPlacement( + nodes: Readonly>, + levelId: string, + start: WallPlanPoint, + end: WallPlanPoint, +): WallTypePlacement { + const middle: [number, number] = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2] + return levelRooms(nodes, levelId).some((room) => + pointInPolygon2D(middle, room, { includeBoundary: false }), + ) + ? 'interior' + : 'exterior' +} + +function draftThickness( + seeded: unknown, + wallTypes: WallTypes, + placement: WallTypePlacement, + fallback: number, +): number { + if (typeof seeded === 'number') return seeded + return wallTypeThickness(wallTypes[placement], placement) ?? fallback +} + +function draftPlacement(start: WallPlanPoint, end: WallPlanPoint): WallTypePlacement { + const levelId = useViewer.getState().selection.levelId + return levelId ? drawnWallPlacement(useScene.getState().nodes, levelId, start, end) : 'exterior' +} + +/** + * The thickness the wall tool's line draft draws at: a placed preset's, else + * the wall type it draws with (the override's, or the one the drafted segment + * gets where it lies), else `fallback` (a plain wall). + */ +export function useWallDraftThickness(fallback: number): number { + const override = useWallTypeOverride((state) => state.placement) + // Read once per draft move: the scene and level stay put while a segment is drafted. + const drawn = useFloorplanDraftPreview((state) => + state.wallDraftStart && state.wallDraftEnd + ? draftPlacement(state.wallDraftStart, state.wallDraftEnd) + : 'exterior', + ) + const placement = override ?? drawn + const lineKind = useWallTypeOverride((state) => state.lineKind) + const thickness = useEditor((state) => + draftThickness(state.toolDefaults.wall?.thickness, state.wallTypes, placement, fallback), + ) + if (lineKind === 'invisible') return INVISIBLE_WALL_DRAFT_THICKNESS + return lineKind === 'railing' ? railingWallDefaults().thickness : thickness +} + +/** + * The same thickness for each side of a Rectangle or Polygon room draft, which + * has no line draft to judge by: the commit types every side by where it lies, + * so a closet drawn inside a room is all partition. + */ +export function useWallSegmentDraftThickness( + fallback: number, +): (start: WallPlanPoint, end: WallPlanPoint) => number { + const override = useWallTypeOverride((state) => state.placement) + const seeded = useEditor((state) => state.toolDefaults.wall?.thickness) + const wallTypes = useEditor((state) => state.wallTypes) + return useCallback( + (start, end) => + draftThickness(seeded, wallTypes, override ?? draftPlacement(start, end), fallback), + [fallback, override, seeded, wallTypes], + ) +} diff --git a/packages/editor/src/lib/wall-nudge.test.ts b/packages/editor/src/lib/wall-nudge.test.ts new file mode 100644 index 0000000000..bf2882cf6b --- /dev/null +++ b/packages/editor/src/lib/wall-nudge.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from 'bun:test' +import { WallNode } from '@pascal-app/core' +import { isWallNudgeKey, WALL_NUDGE_IMPERIAL, wallNudgeDistance } from './wall-nudge' + +const outside = { frontSide: 'exterior', backSide: 'interior' } as const +const wall = (id: string, start: [number, number], end: [number, number]) => + WallNode.parse({ id: `wall_${id}`, start, end, thickness: 0.15, ...outside }) + +describe('wallNudgeDistance', () => { + const moving = wall('moving', [5, 0.3], [8, 0.3]) + + test('an arrow across the wall moves it one inch that way', () => { + const down = wallNudgeDistance(moving, [0, 1], [moving], WALL_NUDGE_IMPERIAL) + const up = wallNudgeDistance(moving, [0, -1], [moving], WALL_NUDGE_IMPERIAL) + expect(down).not.toBeNull() + expect(up).not.toBeNull() + // Signed along the move axis: both land 1 in from where the wall was, opposite ways. + const move = (nudge: NonNullable) => nudge.axis[1] * nudge.distance + expect(move(down!)).toBeCloseTo(0.0254, 10) + expect(move(up!)).toBeCloseTo(-0.0254, 10) + }) + + test('an arrow along the wall does nothing', () => { + expect(wallNudgeDistance(moving, [1, 0], [moving], WALL_NUDGE_IMPERIAL)).toBeNull() + expect(wallNudgeDistance(moving, [-1, 0], [moving], WALL_NUDGE_IMPERIAL)).toBeNull() + }) + + test('stops flush with a parallel outside wall it would pass', () => { + const top = wall('top', [0, 0.29], [4, 0.29]) + const nudge = wallNudgeDistance(moving, [0, -1], [moving, top], WALL_NUDGE_IMPERIAL)! + expect(nudge.axis[1] * nudge.distance).toBeCloseTo(-0.01, 10) + }) + + test('a turned plan still picks the arrow nearest across the wall', () => { + const turned = [Math.SQRT1_2, Math.SQRT1_2] as [number, number] + const nudge = wallNudgeDistance(moving, turned, [moving], WALL_NUDGE_IMPERIAL)! + expect(nudge.axis[1] * nudge.distance).toBeCloseTo(0.0254, 10) + }) +}) + +test('only the four bare arrows nudge', () => { + expect(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].every(isWallNudgeKey)).toBe(true) + expect(isWallNudgeKey('w')).toBe(false) +}) diff --git a/packages/editor/src/lib/wall-nudge.ts b/packages/editor/src/lib/wall-nudge.ts new file mode 100644 index 0000000000..f73afdc27c --- /dev/null +++ b/packages/editor/src/lib/wall-nudge.ts @@ -0,0 +1,131 @@ +/** + * Arrow keys nudge the selected wall square to itself in the floor plan: one + * inch a press (a centimetre in metric), or less when a parallel wall it lines + * up with comes first (`wall-parallel-snap`), so it stops flush. The arrow's + * direction on screen picks the way; an arrow along the wall does nothing. + * The nudge is the side arrows' push (`commitRoomPush`), so joined walls + * follow, jogs and overlaps come out as when the wall is pushed, and each + * press is one undo step. + */ + +import { + type AnyNodeId, + getPerpendicularWallMoveAxis, + useScene, + type WallNode, + type WallPlanPoint, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { commitRoomPush } from './room-handle-drag' +import { levelSeparatorLines, separatorWallLine } from './separator-lines' +import { nudgeWallPosition, wallParallelTargets } from './wall-parallel-snap' + +/** One nudge in feet and inches, in metres. */ +export const WALL_NUDGE_IMPERIAL = 0.0254 +/** One nudge in metric, in metres. */ +export const WALL_NUDGE_METRIC = 0.01 + +const ARROW_SCREEN_DIRECTIONS: Record = { + ArrowUp: [0, -1], + ArrowDown: [0, 1], + ArrowLeft: [-1, 0], + ArrowRight: [1, 0], +} + +// An arrow within about 67° of the wall's own direction runs along it. +const MIN_ACROSS = 0.4 + +/** True for the four bare arrow keys. */ +export function isWallNudgeKey(key: string): boolean { + return key in ARROW_SCREEN_DIRECTIONS +} + +/** + * The plan direction an arrow points on screen: the shown floor plan's scene + * transform, turned or not, maps it back. Null when no floor plan is shown. + */ +export function floorplanArrowDirection(key: string): WallPlanPoint | null { + const screen = ARROW_SCREEN_DIRECTIONS[key] + if (!screen || typeof document === 'undefined') return null + const scenes = document.querySelectorAll('[data-floorplan-scene]') + for (const scene of scenes) { + const rect = scene.ownerSVGElement?.getBoundingClientRect() + const ctm = scene.getScreenCTM() + if (!ctm || !rect || rect.width === 0 || rect.height === 0) continue + const inverse = ctm.inverse() + const origin = new DOMPoint(0, 0).matrixTransform(inverse) + const tip = new DOMPoint(screen[0], screen[1]).matrixTransform(inverse) + const dx = tip.x - origin.x + const dz = tip.y - origin.y + const length = Math.hypot(dx, dz) + if (length > 1e-12) return [dx / length, dz / length] + } + return null +} + +/** + * How far (signed, along the wall's move axis) one nudge of `wall` in plan + * direction `direction` goes; null when the direction runs along the wall. + */ +export function wallNudgeDistance( + wall: WallNode, + direction: WallPlanPoint, + walls: readonly WallNode[], + step: number, +): { axis: WallPlanPoint; distance: number } | null { + const axis = getPerpendicularWallMoveAxis(wall.start, wall.end) + if (!axis) return null + const across = direction[0] * axis[0] + direction[1] * axis[1] + if (Math.abs(across) < MIN_ACROSS) return null + const position = wall.start[0] * axis[0] + wall.start[1] * axis[1] + const end = nudgeWallPosition( + wallParallelTargets(wall, axis, walls), + position, + Math.sign(across) * step, + ) + return { axis, distance: end - position } +} + +/** + * Nudge the one selected wall for an arrow key while the floor plan is shown. + * An invisible wall nudges the same way, as a line: it stops flush with the + * walls and the other invisible walls, and those joined to it follow. + * Returns true when the key was used. + */ +export function nudgeSelectedWall(key: string): boolean { + const { selectedIds } = useViewer.getState().selection + if (selectedIds.length !== 1) return false + const nodes = useScene.getState().nodes + const selected = nodes[selectedIds[0] as AnyNodeId] + if (selected?.type !== 'wall' && selected?.type !== 'separator') return false + const direction = floorplanArrowDirection(key) + if (!direction) return false + + const walls = Object.values(nodes).filter( + (entry): entry is WallNode => entry?.type === 'wall' && entry.parentId === selected.parentId, + ) + const step = useViewer.getState().unit === 'imperial' ? WALL_NUDGE_IMPERIAL : WALL_NUDGE_METRIC + const nudge = + selected.type === 'wall' + ? wallNudgeDistance(selected, direction, walls, step) + : wallNudgeDistance( + separatorWallLine(selected), + direction, + [...walls, ...levelSeparatorLines(nodes, selected.parentId, new Set([selected.id]))], + step, + ) + if (!nudge) return false + commitRoomPush( + selected.type === 'wall' + ? { wallId: selected.id, t0: 0, t1: 1 } + : { + wallId: selected.id, + t0: 0, + t1: 1, + separators: [{ separatorId: selected.id, t0: 0, t1: 1 }], + }, + nudge.axis, + nudge.distance, + ) + return true +} diff --git a/packages/editor/src/lib/wall-parallel-snap.test.ts b/packages/editor/src/lib/wall-parallel-snap.test.ts new file mode 100644 index 0000000000..464c6e7c37 --- /dev/null +++ b/packages/editor/src/lib/wall-parallel-snap.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, test } from 'bun:test' +import { WallNode } from '@pascal-app/core' +import { + nearestWallParallelTarget, + nudgeWallPosition, + WALL_PARALLEL_SNAP_RADIUS, + wallParallelTargets, +} from './wall-parallel-snap' + +const wall = (id: string, start: [number, number], end: [number, number], extra = {}) => + WallNode.parse({ id: `wall_${id}`, start, end, thickness: 0.15, ...extra }) +const outside = { frontSide: 'exterior', backSide: 'interior' } as const +const DOWN: [number, number] = [0, 1] + +describe('wallParallelTargets', () => { + test('an outside wall lines up with a parallel outside wall, not a partition', () => { + const moving = wall('moving', [5, 0.3], [8, 0.3], outside) + const top = wall('top', [0, 0.07], [4, 0.07], outside) + const partition = wall('partition', [1, 0.2], [3, 0.2]) + const side = wall('side', [4, 0.07], [4, 4], outside) + + const targets = wallParallelTargets(moving, DOWN, [moving, top, partition, side]) + + expect(targets.map((target) => target.wallId)).toEqual(['wall_top']) + expect(targets).toHaveLength(1) + expect(targets[0]!.position).toBeCloseTo(0.07) + // The guide runs along both walls, on the top wall's outdoor face: frontSide + // is the left-normal side, +z for a wall running +x. + const [from, to] = [targets[0]!.from, targets[0]!.to] + expect([from[0], to[0]]).toEqual([0, 8]) + expect(from[1]).toBeCloseTo(0.07 + 0.075) + expect(to[1]).toBeCloseTo(0.07 + 0.075) + }) + + test('a partition lines up with every parallel wall', () => { + const moving = wall('moving', [1, 2], [3, 2]) + const top = wall('top', [0, 0], [4, 0], outside) + const other = wall('other', [1, 3], [3, 3]) + const ids = wallParallelTargets(moving, DOWN, [moving, top, other]).map((t) => t.wallId) + expect(new Set(ids)).toEqual(new Set(['wall_top', 'wall_other'])) + }) + + test('walls of different thickness line up face to face and centre to centre', () => { + const moving = wall('moving', [5, 1], [8, 1], { ...outside, thickness: 0.2 }) + const top = wall('top', [0, 0], [4, 0], { ...outside, thickness: 0.1 }) + const positions = wallParallelTargets(moving, DOWN, [top]).map((t) => t.position) + // Faces at ±0.05 against ±0.1, and the centres. + expect(positions.map((p) => Number(p.toFixed(3))).sort()).toEqual([-0.05, 0, 0.05]) + }) + + test('a reversed or justified wall lines up by its faces', () => { + const moving = wall('moving', [8, 1], [5, 1], { ...outside, justification: 'a' }) + const top = wall('top', [0, 0], [4, 0], outside) + const positions = wallParallelTargets(moving, DOWN, [top]).map((t) => t.position) + // Justified 'a' on a wall running -x puts its body on the -z side of its + // line, so its faces and centre all line up with the top wall's when that + // line sits half a thickness below it. + expect(positions.map((p) => Number(p.toFixed(3)))).toEqual([0.075]) + }) +}) + +describe('nearestWallParallelTarget', () => { + test('snaps within the radius and lets go past it', () => { + const moving = wall('moving', [5, 0.3], [8, 0.3], outside) + const targets = wallParallelTargets(moving, DOWN, [wall('top', [0, 0], [4, 0], outside)]) + expect(nearestWallParallelTarget(targets, 0.1)?.position).toBeCloseTo(0) + expect(nearestWallParallelTarget(targets, WALL_PARALLEL_SNAP_RADIUS + 0.01)).toBeNull() + }) +}) + +describe('nudgeWallPosition', () => { + const moving = wall('moving', [5, 0.3], [8, 0.3], outside) + const targets = wallParallelTargets(moving, DOWN, [wall('top', [0, 0.29], [4, 0.29], outside)]) + + test('stops on a line-up it would pass', () => { + expect(nudgeWallPosition(targets, 0.3, -0.0254)).toBeCloseTo(0.29) + }) + + test('otherwise moves the whole step', () => { + expect(nudgeWallPosition(targets, 0.3, 0.0254)).toBeCloseTo(0.3254) + expect(nudgeWallPosition(targets, 0.29, -0.0254)).toBeCloseTo(0.2646) + }) +}) diff --git a/packages/editor/src/lib/wall-parallel-snap.ts b/packages/editor/src/lib/wall-parallel-snap.ts new file mode 100644 index 0000000000..3ee9434d13 --- /dev/null +++ b/packages/editor/src/lib/wall-parallel-snap.ts @@ -0,0 +1,176 @@ +/** + * Lines a wall that is moving sideways up with the parallel walls around it: + * a face onto the face on the same side of another wall, or its body centre + * onto theirs. An outside wall broken up by partitions moves one piece at a + * time, so each piece can be pulled back flush with the rest of the outside. + * + * A wall with an outdoor face lines up only with other outside walls; any + * other wall lines up with every wall on its level. The 2D and 3D wall moves + * snap through here, and the arrow-key nudge stops on these positions. + */ + +import { + getWallFaceOffsets, + isCurvedWall, + type WallNode, + type WallPlanPoint, +} from '@pascal-app/core' +import useAlignmentGuides from '../store/use-alignment-guides' + +/** How near (metres) a moving wall comes to lining up before it snaps there. */ +export const WALL_PARALLEL_SNAP_RADIUS = 0.15 + +// Walls within about 1° of each other count as parallel. +const PARALLEL_TOLERANCE = Math.sin(Math.PI / 180) +const SAME_POSITION = 1e-6 + +type MovingWall = Pick< + WallNode, + 'id' | 'start' | 'end' | 'thickness' | 'justification' | 'frontSide' | 'backSide' +> & + Partial> + +/** + * A position where the moving wall lines up with another: `position` is where + * its reference line sits along the move axis, `from` → `to` the shared face + * or centre line, drawn along both walls. + */ +export type WallParallelTarget = { + position: number + wallId: string + from: WallPlanPoint + to: WallPlanPoint +} + +function isOutsideWall(wall: Pick): boolean { + return wall.frontSide === 'exterior' || wall.backSide === 'exterior' +} + +function direction(wall: Pick): WallPlanPoint | null { + const dx = wall.end[0] - wall.start[0] + const dz = wall.end[1] - wall.start[1] + const length = Math.hypot(dx, dz) + return length > 1e-9 ? [dx / length, dz / length] : null +} + +const dot = (a: readonly [number, number], b: readonly [number, number]) => + a[0] * b[0] + a[1] * b[1] + +// Which way the wall's left normal (-dy, dx) runs along `axis`. +function normalSign(dir: WallPlanPoint, axis: WallPlanPoint): number { + return Math.sign(dot([-dir[1], dir[0]], axis)) || 1 +} + +/** + * The wall's faces and body centre along `axis`, relative to its reference + * line: the face on the axis's positive side, the other face, the centre. + */ +function sidePlanes(wall: MovingWall, dir: WallPlanPoint, axis: WallPlanPoint) { + const sign = normalSign(dir, axis) + const { a, b } = getWallFaceOffsets(wall) + const high = Math.max(a * sign, b * sign) + const low = Math.min(a * sign, b * sign) + return [high, low, (high + low) / 2] as const +} + +/** + * The order to try a wall's planes in, so the guide of planes that coincide + * is drawn on its outdoor face (frontSide is the left-normal side), else on + * its centre. + */ +function planeOrder(wall: MovingWall, dir: WallPlanPoint, axis: WallPlanPoint): number[] { + const outdoor = wall.frontSide === 'exterior' ? 1 : wall.backSide === 'exterior' ? -1 : 0 + const outward = outdoor * normalSign(dir, axis) + return outward > 0 ? [0, 1, 2] : outward < 0 ? [1, 0, 2] : [2, 0, 1] +} + +/** + * Every position along `axis` (a unit vector square to the wall) where `wall` + * lines up with a parallel wall in `walls`. + */ +export function wallParallelTargets( + wall: MovingWall, + axis: WallPlanPoint, + walls: readonly WallNode[], +): WallParallelTarget[] { + const dir = direction(wall) + if (!dir || isCurvedWall(wall)) return [] + const own = sidePlanes(wall, dir, axis) + const outsideOnly = isOutsideWall(wall) + const ownSpan = [dot(wall.start, dir), dot(wall.end, dir)] + const targets: WallParallelTarget[] = [] + + for (const other of walls) { + if (other.id === wall.id || isCurvedWall(other)) continue + if (outsideOnly && !isOutsideWall(other)) continue + const otherDir = direction(other) + if (!otherDir || Math.abs(otherDir[0] * dir[1] - otherDir[1] * dir[0]) > PARALLEL_TOLERANCE) { + continue + } + const line = dot(other.start, axis) + const planes = sidePlanes(other, otherDir, axis) + const along = [...ownSpan, dot(other.start, dir), dot(other.end, dir)] + const lo = Math.min(...along) + const hi = Math.max(...along) + const seen: number[] = [] + for (const index of planeOrder(other, otherDir, axis)) { + const shared = line + planes[index]! + const position = shared - own[index]! + if (seen.some((value) => Math.abs(value - position) < SAME_POSITION)) continue + seen.push(position) + targets.push({ + position, + wallId: other.id, + from: [dir[0] * lo + axis[0] * shared, dir[1] * lo + axis[1] * shared], + to: [dir[0] * hi + axis[0] * shared, dir[1] * hi + axis[1] * shared], + }) + } + } + return targets +} + +/** The target nearest `position`, if one is within `radius`. */ +export function nearestWallParallelTarget( + targets: readonly WallParallelTarget[], + position: number, + radius = WALL_PARALLEL_SNAP_RADIUS, +): WallParallelTarget | null { + let best: WallParallelTarget | null = null + for (const target of targets) { + const gap = Math.abs(target.position - position) + if (gap > radius) continue + if (!best || gap < Math.abs(best.position - position)) best = target + } + return best +} + +/** + * Where a nudge of `step` (signed, along the axis) from `position` ends: the + * full step, or the first line-up it passes on the way. + */ +export function nudgeWallPosition( + targets: readonly WallParallelTarget[], + position: number, + step: number, +): number { + let end = position + step + for (const target of targets) { + const travel = (target.position - position) * Math.sign(step) + if (travel > SAME_POSITION && travel < Math.abs(step) - SAME_POSITION) { + if (Math.abs(target.position - position) < Math.abs(end - position)) end = target.position + } + } + return end +} + +/** Show the line a moving wall has snapped onto (null clears it). */ +export function publishWallParallelGuide(target: WallParallelTarget | null): void { + if (!target) { + useAlignmentGuides.getState().clear() + return + } + useAlignmentGuides.getState().setDraft({ + lines: [{ kind: 'alignment', from: target.from, to: target.to }], + intersections: [], + }) +} diff --git a/packages/editor/src/lib/wall-placement-pointer.test.ts b/packages/editor/src/lib/wall-placement-pointer.test.ts index e8c1bc1083..a1554f60cc 100644 --- a/packages/editor/src/lib/wall-placement-pointer.test.ts +++ b/packages/editor/src/lib/wall-placement-pointer.test.ts @@ -4,6 +4,7 @@ import { createWallPointerTracker, wallPointerInParent, wallSideAtPointer, + wallSideOfPlanPoint, } from './wall-placement-pointer' test('keeps the grab offset across a pointer gap on the original face', () => { @@ -40,3 +41,25 @@ test('reads the pointer in the rotated wall parent and identifies its face', () expect(local[1]).toBeCloseTo(3) expect(wallSideAtPointer(local)).toBe('back') }) + +test('a plan point reads the wall face it is on, as a 3D hover does', () => { + // Along +X the frame's +Z (front) is the plan's +Z side. + const wall = { + start: [0, 0] as [number, number], + end: [4, 0] as [number, number], + thickness: 0.2, + } + expect(wallSideOfPlanPoint(wall, [2, 0.3])).toBe('front') + expect(wallSideOfPlanPoint(wall, [2, -0.3])).toBe('back') + // Drawn the other way, the same point is on its back. + expect( + wallSideOfPlanPoint( + { ...wall, start: [4, 0] as [number, number], end: [0, 0] as [number, number] }, + [2, 0.3], + ), + ).toBe('back') + // A justified wall splits at its body's middle, not its reference line. + const justified = { ...wall, justification: 'a' as const } + expect(wallSideOfPlanPoint(justified, [2, 0.05])).toBe('back') + expect(wallSideOfPlanPoint(justified, [2, 0.15])).toBe('front') +}) diff --git a/packages/editor/src/lib/wall-placement-pointer.ts b/packages/editor/src/lib/wall-placement-pointer.ts index 06d288bbb8..9fdffe4e66 100644 --- a/packages/editor/src/lib/wall-placement-pointer.ts +++ b/packages/editor/src/lib/wall-placement-pointer.ts @@ -1,3 +1,4 @@ +import { getWallBodyCenterOffset, type WallNode } from '@pascal-app/core' import { type Object3D, Vector3 } from 'three' type WallSide = 'front' | 'back' @@ -55,3 +56,20 @@ export function wallPointerInParent( export function wallSideAtPointer(point: readonly number[]): WallSide { return point[2]! >= 0 ? 'front' : 'back' } + +/** + * The face of a straight wall a plan point is on, as a 3D hover reports it: `front` is the wall + * frame's +Z (the plan side `(-dz, dx)` of start → end), measured from the wall body's centre so a justified + * wall splits at its middle. The plan's stand-in for the hovered face's normal. + */ +export function wallSideOfPlanPoint( + wall: Pick, + point: readonly [number, number], +): WallSide { + const dx = wall.end[0] - wall.start[0] + const dz = wall.end[1] - wall.start[1] + const length = Math.hypot(dx, dz) + if (length < 1e-9) return 'front' + const across = ((point[0] - wall.start[0]) * -dz + (point[1] - wall.start[1]) * dx) / length + return across >= getWallBodyCenterOffset(wall) ? 'front' : 'back' +} diff --git a/packages/editor/src/lib/wall-polygon-draft.ts b/packages/editor/src/lib/wall-polygon-draft.ts index 704a9443f8..b28a9ddc53 100644 --- a/packages/editor/src/lib/wall-polygon-draft.ts +++ b/packages/editor/src/lib/wall-polygon-draft.ts @@ -21,6 +21,7 @@ import useEditor, { isMagneticSnapActive } from '../store/use-editor' import { useFloorplanDraftPreview } from '../store/use-floorplan-draft-preview' import { visibleScene } from '../store/view-layout' import { sfxEmitter } from './sfx-bus' +import { getDrawnWallTypes } from './wall-draw-variant' /** * A Polygon room is draft state until it completes: its corners live in @@ -130,6 +131,7 @@ function planPolygon( let last: WallNode | undefined const joinRadius = isMagneticSnapActive() ? WALL_JOIN_SNAP_RADIUS : WALL_CONNECT_SNAP_RADIUS const wallDefaults = useEditor.getState().toolDefaults.wall ?? {} + const wallTypes = getDrawnWallTypes() for (let index = 0; index < corners.length - 1; index++) { const result = planWallInsertion(virtual, { levelId, @@ -137,6 +139,7 @@ function planPolygon( end: corners[index + 1]!, joinRadius, wallDefaults, + wallTypes, }) if (!result.ok) continue const draftBefore = [...draftIds].flatMap((id) => { diff --git a/packages/editor/src/lib/wall-pull.test.ts b/packages/editor/src/lib/wall-pull.test.ts new file mode 100644 index 0000000000..1e10dd1f5f --- /dev/null +++ b/packages/editor/src/lib/wall-pull.test.ts @@ -0,0 +1,228 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + detectSpacesForLevel, + getWallAssemblyPreset, + LevelNode as LevelSchema, + nodeRegistry, + registerNode, + useScene, + type WallNode, + WallNode as WallSchema, + wallAssemblyPatch, + wallClosesRoom, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { createWallOnCurrentLevel } from '../components/tools/wall/wall-drafting' +import type { WallPlanPoint } from '../components/tools/wall/wall-snap-geometry' +import useEditor from '../store/use-editor' +import { + endWallPullRun, + startWallPull, + useWallPullRun, + wallPullSeed, + wallPullTypePatch, +} from './wall-pull' +import { isWallRunFree, snapWallRunDraftPoint, wallRunDirection } from './wall-run-snap' + +// `updateNodes` batches its dirty-marking through requestAnimationFrame, +// which bun's test runtime doesn't provide. +if (typeof globalThis.requestAnimationFrame === 'undefined') { + globalThis.requestAnimationFrame = ((callback: FrameRequestCallback) => + setTimeout(() => callback(0), 0)) as unknown as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = ((id: number) => + clearTimeout(id)) as typeof cancelAnimationFrame +} + +const LEVEL_ID = 'level_pull' as AnyNodeId + +function levelWalls(): WallNode[] { + return Object.values(useScene.getState().nodes).filter( + (node): node is WallNode => node?.type === 'wall' && node.parentId === LEVEL_ID, + ) +} + +/** A lone stucco wall running east to the origin, 3.1 m tall on a 2.7 m storey, painted. */ +function seedSourceWall(): WallNode { + const preset = getWallAssemblyPreset('exterior-2x6-stucco') + if (!preset) throw new Error('Expected the stucco preset') + const source = WallSchema.parse({ + id: 'wall_source', + name: 'Source', + parentId: LEVEL_ID, + start: [-4, 0], + end: [0, 0], + height: 3.1, + ...wallAssemblyPatch(structuredClone(preset.assembly)), + slots: { a: 'library:paint-sage', b: 'library:stucco-white' }, + skirting: { enabled: true, sides: 'both', height: 0.12, proud: 0.02, profile: 'flat' }, + }) + const level = LevelSchema.parse({ + id: LEVEL_ID, + level: 0, + height: 2.7, + children: [source.id], + }) + useScene.setState({ + nodes: { [level.id]: level as AnyNode, [source.id]: source as AnyNode }, + rootNodeIds: [level.id], + dirtyNodes: new Set(), + collections: {}, + } as never) + return source +} + +describe('pulling the next wall from a wall end', () => { + let restoreRegistry: (() => void) | undefined + let previousEditor: Partial> + + beforeEach(() => { + previousEditor = { + toolDefaults: useEditor.getState().toolDefaults, + continuationByContext: useEditor.getState().continuationByContext, + } + // The wall's snap profile makes the armed wall tool the 'wall' snapping + // context (grid lengths), as in the app. + restoreRegistry = nodeRegistry._snapshot() + registerNode({ + kind: 'wall', + schemaVersion: 10, + schema: WallSchema, + category: 'structure', + snapProfile: 'structural', + defaults: () => WallSchema.parse({ start: [0, 0], end: [1, 0] }), + capabilities: {}, + } as never) + useEditor.getState().setContinuation('wall', 'room') + useEditor.getState().setSnappingMode('wall', 'grid') + useEditor.getState().armToolMode({ mode: 'select' }) + useViewer.setState({ + selection: { + buildingId: null, + levelId: LEVEL_ID, + zoneId: null, + selectedIds: ['wall_source'], + }, + } as never) + seedSourceWall() + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() + }) + + afterEach(() => { + endWallPullRun() + restoreRegistry?.() + restoreRegistry = undefined + useEditor.setState(previousEditor as never) + useEditor.getState().armToolMode({ mode: 'select' }) + }) + + test('the pull arms the Walls run at the end, with the pulled wall’s type', () => { + const source = useScene.getState().nodes['wall_source' as AnyNodeId] as WallNode + expect(startWallPull({ wallId: source.id, endpoint: 'end', origin: '3d' })).toBe(true) + + const run = useWallPullRun.getState().run + expect(run).toMatchObject({ + sourceWallId: source.id, + levelId: LEVEL_ID, + runStart: [0, 0], + start: [0, 0], + origin: '3d', + dragging: true, + }) + expect(run?.reference[0]).toBeCloseTo(1, 9) + expect(run?.reference[1]).toBeCloseTo(0, 9) + const editor = useEditor.getState() + expect(editor.mode).toBe('build') + expect(editor.tool).toBe('wall') + expect(editor.getContinuation('wall')).toBe('single') + expect(useViewer.getState().selection.selectedIds).toEqual([]) + + // The pulled wall's construction, finishes and own height, not the Build + // panel's types. + const pulled = createWallOnCurrentLevel([0, 0], [3, 0]) + expect(pulled?.assembly).toEqual(source.assembly) + expect(pulled?.thickness).toBe(source.thickness) + expect(pulled?.height).toBe(3.1) + expect(pulled?.slots).toEqual(source.slots) + expect(pulled?.skirting).toEqual(source.skirting) + expect(pulled?.start).toEqual([0, 0]) + + // Ending puts back the Rooms shape and the wall tool defaults, in Select. + endWallPullRun() + expect(useWallPullRun.getState().run).toBeNull() + expect(useEditor.getState().mode).toBe('select') + expect(useEditor.getState().getContinuation('wall')).toBe('room') + expect(useEditor.getState().toolDefaults.wall).toBeUndefined() + }) + + test('the type patch keeps a plain wall plain and a storey-high wall on the storey', () => { + const plain = WallSchema.parse({ start: [0, 0], end: [2, 0], height: 2.7 }) + expect(wallPullTypePatch(plain, 2.7)).toEqual({ thickness: plain.thickness ?? 0.1 }) + const pulledFromStart = wallPullSeed(plain, 'start') + expect(pulledFromStart?.start).toEqual([0, 0]) + expect(pulledFromStart?.reference[0]).toBeCloseTo(-1, 9) + }) + + test('closing the run back to its start forms a room, one undo step per wall', () => { + startWallPull({ wallId: 'wall_source', endpoint: 'end', origin: '3d' }) + expect(isWallRunFree()).toBe(false) + const run = useWallPullRun.getState().run! + // The run as the tool drafts it: each wall from the last one's end, + // square to it — the pulled wall first. + let start = run.start + let reference: WallPlanPoint | null = run.reference + const place = (cursor: WallPlanPoint) => { + const snapped = snapWallRunDraftPoint({ + point: cursor, + start, + runStart: run.runStart, + reference, + walls: levelWalls(), + free: isWallRunFree(), + }) + const before = useScene.temporal.getState().pastStates.length + const wall = createWallOnCurrentLevel(start, snapped.point)! + expect(useScene.temporal.getState().pastStates.length).toBe(before + 1) + start = wall.end + reference = wallRunDirection(wall.start, wall.end) + return { wall, snapped } + } + + // Straight on (6° off) to a grid length; square up; back west until the + // run's start lines up; then onto the start itself. + const first = place([5.1, 0.45]) + expect(first.snapped.lock).toBe('straight') + expect(first.wall.end).toEqual([5, 0]) + const second = place([5.25, 3.9]) + expect(second.snapped.lock).toBe('square') + expect(second.wall.end).toEqual([5, 4]) + expect(wallClosesRoom(levelWalls(), second.wall)).toBe(false) + const third = place([0.1, 4.05]) + expect(third.snapped.snap).toBe('alignment') + expect(third.wall.end).toEqual([0, 4]) + const last = place([0.12, 0.2]) + expect(last.snapped.snap).toBe('endpoint') + expect(last.wall.end).toEqual([0, 0]) + + const walls = levelWalls() + expect(wallClosesRoom(walls, last.wall)).toBe(true) + const rooms = detectSpacesForLevel(LEVEL_ID, walls).spaces.filter((space) => !space.isExterior) + expect(rooms).toHaveLength(1) + const xs = rooms[0]!.polygon.map((point) => point[0]) + const zs = rooms[0]!.polygon.map((point) => point[1]) + expect([Math.min(...xs), Math.max(...xs), Math.min(...zs), Math.max(...zs)]).toEqual([ + 0, 5, 0, 4, + ]) + }) + + test('leaving the wall tool ends the run without leaving Select behind', () => { + startWallPull({ wallId: 'wall_source', endpoint: 'start', origin: '2d' }) + expect(useWallPullRun.getState().run?.start).toEqual([-4, 0]) + useEditor.getState().armToolMode({ mode: 'build', tool: 'slab' }) + expect(useWallPullRun.getState().run).toBeNull() + expect(useEditor.getState().tool).toBe('slab') + expect(useEditor.getState().getContinuation('wall')).toBe('room') + }) +}) diff --git a/packages/editor/src/lib/wall-pull.ts b/packages/editor/src/lib/wall-pull.ts new file mode 100644 index 0000000000..9ac30b19d7 --- /dev/null +++ b/packages/editor/src/lib/wall-pull.ts @@ -0,0 +1,199 @@ +/** + * "Pull to continue": a selected wall's end carries a grip that, dragged, + * draws the next wall from that end. + * + * Pressing the grip arms the wall tool's Walls run (each wall starts where the + * last ended) already started at the pulled end, with the pulled wall's type: + * its assembly, thickness, height and finishes. The drag shows the next wall; + * letting go places it, and the run carries on from its far end, click by + * click, until Esc, a double-click, closing the loop or teeing into a wall — + * exactly as the Walls run ends. Then the editor is back in Select, with the + * Rooms shape and wall tool settings the user had. + * + * Its walls snap as every line run does once started (`wall-run-snap.ts`): + * square to the wall before — the pulled one, for the first — or the house's + * axes, 15° steps past that, Alt free. The 3D wall tool and the floor plan each + * seed their run from this store, the way they share the plain run: whichever + * view owns the input places the walls. + */ + +import { + type AnyNodeId, + getWallCurveFrameAt, + getWallThickness, + useScene, + WALL_CONSTRUCTION_FIELDS, + type WallNode, + type WallPlanPoint, + wallFollowsStorey, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { create } from 'zustand' +import useEditor from '../store/use-editor' +import type { ContinuationMode } from './continuation' +import { selectWallDrawVariant } from './wall-draw-variant' +import { publishWallRunFeedback } from './wall-run-snap' + +/** The floor plan's affordance id for a wall end's pull grip (a `move-arrow`, variant `pull`). */ +export const WALL_PULL_AFFORDANCE = 'wall-pull' + +/** What a pull grip carries: the wall, and which of its ends it pulls from. */ +export type WallPullGripPayload = { wallId: string; endpoint: 'start' | 'end' } + +/** The grip's tooltip, in both views. */ +export const WALL_PULL_GRIP_TITLE = 'Drag to draw the next wall' + +export type WallPullRun = { + /** Bumped per pull, so each view seeds its draft from it once. */ + seq: number + sourceWallId: AnyNodeId + levelId: AnyNodeId + /** The pulled end, where the run started: its walls can close square back to it. */ + runStart: WallPlanPoint + /** Where the first wall starts: the pulled end. */ + start: WallPlanPoint + /** Unit direction of the pulled wall, pointing out of the pulled end. */ + reference: WallPlanPoint + /** The view the grip was pulled in; the floor plan drafts a 3D pull only beside it. */ + origin: '2d' | '3d' + /** From the press on the grip until it is let go. */ + dragging: boolean +} + +type WallPullState = { run: WallPullRun | null } + +/** The open pull run, if any. */ +export const useWallPullRun = create(() => ({ run: null })) + +/** + * The wall tool defaults that draw walls of `wall`'s type (core + * `WALL_CONSTRUCTION_FIELDS`; position, support and paint regions stay with + * each wall): its assembly and the thickness it sums to (a plain wall's own + * thickness, so the Build panel's types don't take over), its finishes and + * trims, and its height when it has its own rather than following the storey + * (`storeyHeight`). + */ +export function wallPullTypePatch(wall: WallNode, storeyHeight: number | null): Partial { + const patch: Record = {} + for (const field of WALL_CONSTRUCTION_FIELDS) { + if (wall[field] !== undefined) patch[field] = structuredClone(wall[field]) + } + patch.thickness = getWallThickness(wall) + if (!wallFollowsStorey(wall, storeyHeight)) patch.height = wall.height + return patch as Partial +} + +/** + * The pulled end of `wall` and the direction out of it: along the wall for a + * straight one, along its tangent for a curved one. + */ +export function wallPullSeed( + wall: Pick, + endpoint: 'start' | 'end', +): { start: WallPlanPoint; reference: WallPlanPoint } | null { + const start: WallPlanPoint = + endpoint === 'start' ? [wall.start[0], wall.start[1]] : [wall.end[0], wall.end[1]] + const tangent = getWallCurveFrameAt(wall as WallNode, endpoint === 'start' ? 0 : 1).tangent + const sign = endpoint === 'start' ? -1 : 1 + const length = Math.hypot(tangent.x, tangent.y) + if (!(length > 1e-9)) return null + return { start, reference: [(tangent.x / length) * sign, (tangent.y / length) * sign] } +} + +// What the pull changed for its run, put back when it ends. +let restore: { shape: ContinuationMode; toolDefaults: Record | null } | null = null +let stopWatching: (() => void) | null = null +let pullSeq = 0 + +/** + * Start a pull from `endpoint` of wall `wallId`: arm the Walls run at that end + * with the wall's type. `origin` is the view the grip was pressed in. False + * when there is no such wall. + */ +export function startWallPull(args: { + wallId: string + endpoint: 'start' | 'end' + origin: '2d' | '3d' +}): boolean { + const nodes = useScene.getState().nodes + const wall = nodes[args.wallId as AnyNodeId] + if (wall?.type !== 'wall' || !wall.parentId) return false + const level = nodes[wall.parentId as AnyNodeId] + if (level?.type !== 'level') return false + const seed = wallPullSeed(wall, args.endpoint) + if (!seed) return false + + endWallPullRun() + const editor = useEditor.getState() + restore = { + shape: editor.getContinuation('wall'), + toolDefaults: (editor.toolDefaults.wall as Record | undefined) ?? null, + } + // The grips belong to a selected wall; drawing goes on without one. + useViewer.getState().setSelection({ selectedIds: [], levelId: level.id }) + selectWallDrawVariant('walls') + editor.setToolDefaults('wall', wallPullTypePatch(wall, level.height ?? null)) + editor.armToolMode({ mode: 'build', tool: 'wall' }) + pullSeq += 1 + useWallPullRun.setState({ + run: { + seq: pullSeq, + sourceWallId: wall.id as AnyNodeId, + levelId: wall.parentId as AnyNodeId, + runStart: seed.start, + start: seed.start, + reference: seed.reference, + origin: args.origin, + dragging: true, + }, + }) + watchWallPullRun() + return true +} + +function watchWallPullRun() { + stopWatching?.() + // Leaving the wall tool (another tool, Select) ends the run. + const stopEditor = useEditor.subscribe((state) => { + if (!(state.mode === 'build' && state.tool === 'wall')) endWallPullRun() + }) + // A release the view did not take as the placing click (off the plan, say) + // still ends the drag; the run waits for a click. + const onPointerUp = () => releaseWallPullDrag() + const target = typeof window === 'undefined' ? null : window + target?.addEventListener('pointerup', onPointerUp) + target?.addEventListener('pointercancel', onPointerUp) + stopWatching = () => { + stopEditor() + target?.removeEventListener('pointerup', onPointerUp) + target?.removeEventListener('pointercancel', onPointerUp) + } +} + +/** The grip was let go: the run goes on by clicks. */ +export function releaseWallPullDrag(): void { + const run = useWallPullRun.getState().run + if (run?.dragging) useWallPullRun.setState({ run: { ...run, dragging: false } }) +} + +/** + * End the pull run, however its walls ended: back to Select from the wall tool, + * with the Rooms shape and wall tool defaults the pull replaced. No-op when no + * run is open. + */ +export function endWallPullRun(): void { + const run = useWallPullRun.getState().run + if (!run) return + useWallPullRun.setState({ run: null }) + stopWatching?.() + stopWatching = null + publishWallRunFeedback(null) + const editor = useEditor.getState() + if (editor.mode === 'build' && editor.tool === 'wall') editor.armToolMode({ mode: 'select' }) + const saved = restore + restore = null + if (!saved) return + const after = useEditor.getState() + if (after.getContinuation('wall') !== saved.shape) after.setContinuation('wall', saved.shape) + after.setToolDefaults('wall', saved.toolDefaults) +} diff --git a/packages/editor/src/lib/wall-push-line-up.test.ts b/packages/editor/src/lib/wall-push-line-up.test.ts new file mode 100644 index 0000000000..b32ec80365 --- /dev/null +++ b/packages/editor/src/lib/wall-push-line-up.test.ts @@ -0,0 +1,132 @@ +import { afterAll, afterEach, beforeEach, describe, expect, spyOn, test } from 'bun:test' +import { clearSceneHistory, nodeRegistry, useScene, WallNode } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import useAlignmentGuides from '../store/use-alignment-guides' +import useEditor from '../store/use-editor' +import useInteractionScope from '../store/use-interaction-scope' +import { installImmediateAnimationFrames } from '../test-utils/immediate-animation-frames' +import { seedRoundTripScene } from '../test-utils/live-gesture-round-trip' +import { cancelGestures } from './gesture-lifecycle' +import { runWallPushDrag, wallPushHandles } from './room-handle-drag' +import { nextInStack } from './selection-cycle' + +// A side arrow push lines the wall up with a parallel wall before the grid, as +// the wall moves do; Alt pushes it free. + +const LEVEL = 'level_push_line_up' +const stubbed: string[] = [] +let stop = () => {} +let restoreFrames = () => {} +let registry: { mockRestore: () => void } | null = null + +const nodes = () => useScene.getState().nodes +const eastWall = () => + Object.values(nodes()).find( + (n): n is WallNode => n.type === 'wall' && n.start[0] === 6 && n.end[0] === 6, + )! + +function pointer(type: 'pointermove' | 'pointerup', clientX: number, altKey = false) { + window.dispatchEvent(Object.assign(new Event(type), { clientX, clientY: 0, altKey })) +} + +beforeEach(() => { + restoreFrames = installImmediateAnimationFrames() + if (!globalThis.document) { + globalThis.document = { body: { style: { cursor: '' } } } as unknown as Document + stubbed.push('document') + } + if (!globalThis.window) { + globalThis.window = new EventTarget() as Window & typeof globalThis + stubbed.push('window') + } + // The nodes package that registers walls is not loaded here. + const get = nodeRegistry.get.bind(nodeRegistry) + registry = spyOn(nodeRegistry, 'get').mockImplementation(((kind: string) => + kind === 'wall' ? { snapProfile: 'structural' } : get(kind)) as typeof nodeRegistry.get) + cancelGestures('cancel') + useInteractionScope.getState().end() + useScene.temporal.getState().resume() + ;({ stop } = seedRoundTripScene('building_push_line_up', LEVEL)) + // An outside wall standing apart, 0.3 m east of the room's east wall line. + const apart = WallNode.parse({ + id: 'wall_apart', + start: [6.3, 6], + end: [6.3, 9], + frontSide: 'exterior', + backSide: 'interior', + }) + useScene.getState().applyNodeChanges({ create: [{ node: apart, parentId: LEVEL as never }] }) + useViewer.getState().setSelection({ + buildingId: 'building_push_line_up', + levelId: LEVEL, + selectedIds: [], + }) + useEditor.setState({ phase: 'building', mode: 'select', gridSnapStep: 0.5 }) + useEditor.getState().setSnappingMode('polygon', 'grid') + clearSceneHistory() +}) + +afterEach(() => { + cancelGestures('cancel') + stop() + registry?.mockRestore() + registry = null + restoreFrames() +}) + +afterAll(async () => { + // Let the click-swallow timers fire before the stubs go. + await new Promise((resolve) => setTimeout(resolve, 350)) + for (const key of stubbed.splice(0)) delete (globalThis as Record)[key] +}) + +function startPush() { + const handle = wallPushHandles(eastWall(), nodes()).find((h) => h.outward[0] > 0.5)! + runWallPushDrag({ handle, levelId: LEVEL, from: 0, along: (clientX) => clientX }) + return eastWall().id +} + +describe('wall push line-up', () => { + test('a push near a parallel outside wall lands on its line, guide shown while it holds', () => { + expect(eastWall().frontSide === 'exterior' || eastWall().backSide === 'exterior').toBe(true) + const id = startPush() + pointer('pointermove', 0.24) + expect(useAlignmentGuides.getState().draft?.lines).toHaveLength(1) + pointer('pointerup', 0.24) + expect(useAlignmentGuides.getState().draft).toBeNull() + const wall = nodes()[id as never] as WallNode + expect(wall.start[0]).toBeCloseTo(6.3, 9) + expect(wall.end[0]).toBeCloseTo(6.3, 9) + }) + + test('away from any line the push still steps on the grid', () => { + const id = startPush() + pointer('pointermove', 0.9) + pointer('pointerup', 0.9) + expect((nodes()[id as never] as WallNode).start[0]).toBeCloseTo(7, 9) + }) + + test('Alt pushes it free', () => { + const id = startPush() + pointer('pointermove', 0.24, true) + expect(useAlignmentGuides.getState().draft).toBeNull() + pointer('pointerup', 0.24, true) + expect((nodes()[id as never] as WallNode).start[0]).toBeCloseTo(6.24, 9) + }) +}) + +describe('Tab through stacked objects', () => { + test('steps to the next one under the pointer and wraps round', () => { + const stack = ['wall_top', 'wall_inside', 'zone_room'] + expect(nextInStack(stack, [], false)).toBe('wall_top') + expect(nextInStack(stack, ['wall_top'], false)).toBe('wall_inside') + expect(nextInStack(stack, ['zone_room'], false)).toBe('wall_top') + }) + + test('Shift+Tab steps back; a selection elsewhere starts at the top', () => { + const stack = ['wall_top', 'wall_inside'] + expect(nextInStack(stack, ['wall_top'], true)).toBe('wall_inside') + expect(nextInStack(stack, ['wall_other'], true)).toBe('wall_top') + expect(nextInStack([], ['wall_top'], false)).toBeNull() + }) +}) diff --git a/packages/editor/src/lib/wall-region-list.ts b/packages/editor/src/lib/wall-region-list.ts index 01dc9b6bc8..be625dc9c1 100644 --- a/packages/editor/src/lib/wall-region-list.ts +++ b/packages/editor/src/lib/wall-region-list.ts @@ -1,6 +1,7 @@ import { type AnyNode, getCatalogMaterialById, + getMaterialSwatch, parseMaterialRef, roomSideFaces, type SceneMaterial, @@ -18,7 +19,13 @@ import { getLinearUnitLabel, type LinearUnit, metersToLinearUnit } from './measu const FALLBACK_SWATCH_COLOR = '#d4d4d8' -export type WallRegionFinish = { name: string; color: string; imageUrl?: string } +export type WallRegionFinish = { + name: string + color: string + imageUrl?: string + /** The image is a white albedo `color` tints: multiply it (a siding's profile). */ + tinted?: boolean +} export type WallRegionRow = { id: string @@ -63,11 +70,13 @@ export function describeRegionFinish( } if (parsed?.kind === 'library') { const item = getCatalogMaterialById(parsed.id) - const imageUrl = item?.previewThumbnailUrl ?? item?.preset.maps.albedoMap + if (!item) return { name: 'Library finish', color: FALLBACK_SWATCH_COLOR } + const swatch = getMaterialSwatch(item) return { - name: item?.label ?? 'Library finish', - color: item?.previewColor ?? item?.preset.mapProperties.color ?? FALLBACK_SWATCH_COLOR, - ...(imageUrl ? { imageUrl } : {}), + name: item.label, + color: swatch.color, + ...(swatch.imageUrl ? { imageUrl: swatch.imageUrl } : {}), + ...(swatch.tinted ? { tinted: true } : {}), } } return { name: 'Custom finish', color: FALLBACK_SWATCH_COLOR } diff --git a/packages/editor/src/lib/wall-run-snap.test.ts b/packages/editor/src/lib/wall-run-snap.test.ts new file mode 100644 index 0000000000..0bf2312aa8 --- /dev/null +++ b/packages/editor/src/lib/wall-run-snap.test.ts @@ -0,0 +1,188 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + detectSpacesForLevel, + LevelNode as LevelSchema, + nodeRegistry, + registerNode, + useScene, + type WallNode, + WallNode as WallSchema, + wallClosesRoom, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { createWallOnCurrentLevel } from '../components/tools/wall/wall-drafting' +import type { WallPlanPoint } from '../components/tools/wall/wall-snap-geometry' +import useEditor from '../store/use-editor' +import { + addWallPolygonDraftCorner, + discardWallPolygonDraft, + startWallPolygonDraft, + wallPolygonDraftWalls, +} from './wall-polygon-draft' +import { isWallRunFree, snapWallRunDraftPoint, wallRunDirection } from './wall-run-snap' + +// `updateNodes` batches its dirty-marking through requestAnimationFrame, +// which bun's test runtime doesn't provide. +if (typeof globalThis.requestAnimationFrame === 'undefined') { + globalThis.requestAnimationFrame = ((callback: FrameRequestCallback) => + setTimeout(() => callback(0), 0)) as unknown as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = ((id: number) => + clearTimeout(id)) as typeof cancelAnimationFrame +} + +const LEVEL_ID = 'level_run' as AnyNodeId +const deg = (degrees: number) => (degrees * Math.PI) / 180 + +function levelWalls(): WallNode[] { + return Object.values(useScene.getState().nodes).filter( + (node): node is WallNode => node?.type === 'wall' && node.parentId === LEVEL_ID, + ) +} + +/** + * A plain run as the wall tool drafts it once its first point is placed: each + * wall from the last one's end, square to it, snapped against the level's walls. + */ +function plainRun(runStart: WallPlanPoint) { + let start = runStart + let reference: WallPlanPoint | null = null + return (cursor: WallPlanPoint, free = false) => { + const snapped = snapWallRunDraftPoint({ + point: cursor, + start, + runStart, + reference, + walls: levelWalls(), + free, + }) + const wall = createWallOnCurrentLevel(start, snapped.point)! + start = wall.end + reference = wallRunDirection(wall.start, wall.end) + return { wall, snapped } + } +} + +describe('a plain wall run snaps like a pulled one once started', () => { + let restoreRegistry: (() => void) | undefined + let previousEditor: Partial> + + beforeEach(() => { + previousEditor = { + toolDefaults: useEditor.getState().toolDefaults, + continuationByContext: useEditor.getState().continuationByContext, + } + // The wall's snap profile makes the armed wall tool the 'wall' snapping + // context (grid lengths), as in the app. + restoreRegistry = nodeRegistry._snapshot() + registerNode({ + kind: 'wall', + schemaVersion: 10, + schema: WallSchema, + category: 'structure', + snapProfile: 'structural', + defaults: () => WallSchema.parse({ start: [0, 0], end: [1, 0] }), + capabilities: {}, + } as never) + useEditor.getState().setSnappingMode('wall', 'grid') + useEditor.getState().armToolMode({ mode: 'build', tool: 'wall' }) + useViewer.setState({ + selection: { buildingId: null, levelId: LEVEL_ID, zoneId: null, selectedIds: [] }, + } as never) + const level = LevelSchema.parse({ id: LEVEL_ID, level: 0, height: 2.7, children: [] }) + useScene.setState({ + nodes: { [level.id]: level as AnyNode }, + rootNodeIds: [level.id], + dirtyNodes: new Set(), + collections: {}, + } as never) + }) + + afterEach(() => { + discardWallPolygonDraft() + restoreRegistry?.() + restoreRegistry = undefined + useEditor.getState().setSnappingMode('wall', 'grid') + useEditor.setState(previousEditor as never) + useEditor.getState().armToolMode({ mode: 'select' }) + }) + + test("the first wall holds to the house's axes, steps 15° past them, and Alt frees it", () => { + const snap = (cursor: WallPlanPoint, free = false) => + snapWallRunDraftPoint({ + point: cursor, + start: [0, 0], + runStart: [0, 0], + reference: null, + walls: [], + free, + }) + const axis = snap([4.1, 0.35]) + expect(axis.lock).toBe('axis') + expect(axis.point).toEqual([4, 0]) + const stepped = snap([Math.cos(deg(40)) * 3, Math.sin(deg(40)) * 3]) + expect(stepped.lock).toBe('angle') + expect(stepped.point[0]).toBeCloseTo(Math.cos(deg(45)) * 3, 6) + expect(stepped.point[1]).toBeCloseTo(Math.sin(deg(45)) * 3, 6) + expect(isWallRunFree({ altKey: true })).toBe(true) + expect(isWallRunFree()).toBe(false) + expect(snap([3.13, 1.07], isWallRunFree({ altKey: true })).point).toEqual([3.13, 1.07]) + // Snapping off frees it too. + useEditor.getState().setSnappingMode('wall', 'off') + expect(isWallRunFree()).toBe(true) + }) + + test('a Walls run squares up, lines up with its start and closes into a room', () => { + // The first point keeps the tool's own snapping; the run starts at (0, 0). + const place = plainRun([0, 0]) + const first = place([5.1, 0.4]) + expect(first.snapped.lock).toBe('axis') + expect(first.wall.end).toEqual([5, 0]) + const second = place([5.3, 3.9]) + expect(second.snapped.lock).toBe('square') + expect(second.wall.end).toEqual([5, 4]) + const third = place([0.1, 4.05]) + expect(third.snapped.snap).toBe('alignment') + expect(third.snapped.label).toBe('In line with start') + expect(third.snapped.guides.some((guide) => guide.kind === 'run-start')).toBe(true) + expect(third.snapped.guides.some((guide) => guide.kind === 'extension')).toBe(true) + expect(third.wall.end).toEqual([0, 4]) + const last = place([0.12, 0.18]) + expect(last.snapped.snap).toBe('endpoint') + expect(last.wall.end).toEqual([0, 0]) + expect(wallClosesRoom(levelWalls(), last.wall)).toBe(true) + const rooms = detectSpacesForLevel(LEVEL_ID, levelWalls()).spaces.filter( + (space) => !space.isExterior, + ) + expect(rooms).toHaveLength(1) + }) + + test('a Polygon room snaps its corners the same way and closes on its first', () => { + startWallPolygonDraft(LEVEL_ID, [0, 0]) + let start: WallPlanPoint = [0, 0] + let reference: WallPlanPoint | null = null + const corner = (cursor: WallPlanPoint) => { + const snapped = snapWallRunDraftPoint({ + point: cursor, + start, + runStart: [0, 0], + reference, + walls: [...levelWalls(), ...wallPolygonDraftWalls()], + free: false, + }) + const outcome = addWallPolygonDraftCorner(snapped.point) + reference = wallRunDirection(start, snapped.point) + start = snapped.point + return { snapped, outcome } + } + expect(corner([3.9, -0.3]).snapped.point).toEqual([4, 0]) + expect(corner([4.2, 2.95]).snapped.point).toEqual([4, 3]) + const across = corner([-0.08, 3.1]) + expect(across.snapped.label).toBe('In line with start') + expect(across.snapped.point).toEqual([0, 3]) + const close = corner([0.1, 0.2]) + expect(close.snapped.snap).toBe('endpoint') + expect(close.outcome).toBe('closed') + }) +}) diff --git a/packages/editor/src/lib/wall-run-snap.ts b/packages/editor/src/lib/wall-run-snap.ts new file mode 100644 index 0000000000..f24b5dbc93 --- /dev/null +++ b/packages/editor/src/lib/wall-run-snap.ts @@ -0,0 +1,83 @@ +/** + * Snapping for the wall tool's line runs once their first point is placed — + * the Walls run (its tile, the Exterior and Interior wall tools), a Polygon + * room's sides, and a run pulled from a wall end (`wall-pull.ts`). + * + * Each wall holds square: straight on from the wall before it, square to it, + * or along the house's axes within about 7°, and on 15° steps past that, with + * its length on the grid; existing walls, the run's start and the projection + * guides win over that (`snapWallPullPoint`). Alt, or snapping off, leaves the + * end at the cursor. The run's first point keeps the tool's own snapping. + * + * The 3D wall tool and the floor plan each draft a run from their own start, + * run start and previous wall, and snap through here, so both views place the + * same point; the view that owns the input publishes the guides and marker. + */ + +import type { WallNode, WallPlanPoint } from '@pascal-app/core' +import { getSegmentGridStep } from '../components/tools/wall/wall-drafting' +import { snapWallPullPoint, type WallPullSnapResult } from '../components/tools/wall/wall-pull-snap' +import useAlignmentGuides from '../store/use-alignment-guides' +import { getActiveSnappingMode } from '../store/use-editor' +import useWallSnapIndicator from '../store/use-wall-snap-indicator' + +/** True while a run's walls follow the cursor freely: Alt, or snapping off. */ +export function isWallRunFree(event?: { altKey?: boolean } | null): boolean { + return Boolean(event?.altKey) || getActiveSnappingMode() === 'off' +} + +/** Unit direction of a placed wall `start` → `end`: the next wall holds square to it. */ +export function wallRunDirection(start: WallPlanPoint, end: WallPlanPoint): WallPlanPoint | null { + const dx = end[0] - start[0] + const dz = end[1] - start[1] + const length = Math.hypot(dx, dz) + return length > 1e-9 ? [dx / length, dz / length] : null +} + +/** + * Snap the drafted end of a run's current wall, which starts at `start`. + * `runStart` is where the run began, `reference` the direction of the wall + * before (null for the run's first wall), and `walls` the level's walls with + * any open Polygon room's sides. + */ +export function snapWallRunDraftPoint(args: { + point: WallPlanPoint + start: WallPlanPoint + runStart: WallPlanPoint + reference: WallPlanPoint | null + walls: readonly WallNode[] + free: boolean +}): WallPullSnapResult { + return snapWallPullPoint({ ...args, gridStep: getSegmentGridStep() }) +} + +/** + * Show a run's snap: its projection guides and crossings (the alignment guide + * layers draw them) and the snap marker with what it caught. Null clears both. + */ +export function publishWallRunFeedback(result: WallPullSnapResult | null): void { + if (!result) { + useAlignmentGuides.getState().clear() + useWallSnapIndicator.getState().clear() + return + } + useAlignmentGuides.getState().setDraft({ + lines: result.guides.map((guide) => ({ + kind: guide.kind, + from: [guide.from[0], guide.from[1]], + to: [guide.to[0], guide.to[1]], + })), + intersections: result.intersections.map((point) => [point[0], point[1]]), + }) + useWallSnapIndicator.getState().set( + result.snap + ? { + x: result.point[0], + z: result.point[1], + kind: result.snap, + wallIds: result.targetWallIds.length > 0 ? result.targetWallIds : undefined, + label: result.label ?? undefined, + } + : null, + ) +} diff --git a/packages/editor/src/lib/wall-type.test.ts b/packages/editor/src/lib/wall-type.test.ts new file mode 100644 index 0000000000..a90b3e2c09 --- /dev/null +++ b/packages/editor/src/lib/wall-type.test.ts @@ -0,0 +1,232 @@ +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + assemblyThickness, + DEFAULT_INTERIOR_WALL_PAINT, + GYPSUM_HALF, + getSceneDocument, + SiteNode, + STUD_2X8, + useScene, + WallTypeDefinition, + wallTypeAssembly, +} from '@pascal-app/core' +import useEditor from '../store/use-editor' +import { getDrawnWallTypes, setWallTypeOverride } from './wall-draw-variant' +import { + currentProjectWallTypes, + DEFAULT_WALL_FINISHES, + DEFAULT_WALL_TYPES, + deleteProjectWallType, + newWallTypeBlockedReason, + normalizeWallFinishes, + normalizeWallTypes, + PLAIN_WALL_TYPE, + PROJECT_WALL_TYPE_LIMIT, + saveProjectWallType, + WALL_TYPE_LAYER_LIMIT, + WALL_TYPE_SAVE_FAILURE, + wallTypeOptions, + wallTypePlacementOf, + wallTypeThickness, +} from './wall-type' + +// Scene writes schedule their dirty pass on a frame; bun has none. +globalThis.requestAnimationFrame ??= () => 0 +globalThis.cancelAnimationFrame ??= () => {} + +/** A 2x8 at 24 in partition, drywall both faces. */ +const SOUND_WALL: WallTypeDefinition = { + id: 'custom-soundwall', + label: '2x8 sound wall', + assembly: wallTypeAssembly('custom-soundwall', [ + { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF }, + { role: 'structure', core: true, material: 'wood', thickness: STUD_2X8, spacing: 0.6096 }, + { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF }, + ]), +} + +beforeEach(() => { + useScene.getState().unloadScene() + useScene.getState().loadScene() +}) + +afterEach(() => { + setWallTypeOverride(null) + useEditor.setState({ wallTypes: DEFAULT_WALL_TYPES, wallFinishes: DEFAULT_WALL_FINISHES }) + useScene.getState().unloadScene() +}) + +describe('the Exterior and Interior lists', () => { + test('each lists the presets that build its kind of wall, and the plain wall', () => { + const exterior = wallTypeOptions('exterior', []).map((option) => option.id) + const interior = wallTypeOptions('interior', []).map((option) => option.id) + expect(exterior).toContain('exterior-2x6-siding') + expect(exterior).toContain('exterior-cmu-stucco') + expect(exterior).not.toContain('interior-2x4-drywall') + expect(interior).toEqual([ + 'interior-2x4-drywall', + 'interior-2x6-plumbing', + 'cmu-8-furred-drywall', + PLAIN_WALL_TYPE, + ]) + expect(exterior.at(-1)).toBe(PLAIN_WALL_TYPE) + }) + + test('a pick made before the split still shows in its old list', () => { + const exterior = wallTypeOptions('exterior', [], 'interior-2x4-drywall') + expect(exterior.map((option) => option.id)).toContain('interior-2x4-drywall') + }) + + test("a project type lists under the placement its layers give, in a group of the project's own", () => { + expect(wallTypePlacementOf(SOUND_WALL.assembly)).toBe('interior') + const interior = wallTypeOptions('interior', [SOUND_WALL]) + expect(interior.find((option) => option.id === SOUND_WALL.id)?.group).toBe('This project') + expect(wallTypeOptions('exterior', [SOUND_WALL]).map((option) => option.id)).not.toContain( + SOUND_WALL.id, + ) + }) +}) + +describe('project wall types', () => { + test('save with the project and come back from a saved scene', () => { + expect(saveProjectWallType(SOUND_WALL)).toBe('saved') + expect(currentProjectWallTypes()).toEqual([SOUND_WALL]) + const document = JSON.parse(JSON.stringify(getSceneDocument())) + useScene.getState().unloadScene() + expect(currentProjectWallTypes()).toEqual([]) + useScene.getState().setScene(document.nodes, document.rootNodeIds) + expect(currentProjectWallTypes()).toEqual([SOUND_WALL]) + // Saving again under the same id edits it in place. + saveProjectWallType({ ...SOUND_WALL, label: 'Sound wall' }) + expect(currentProjectWallTypes().map((type) => type.label)).toEqual(['Sound wall']) + deleteProjectWallType(SOUND_WALL.id) + expect(currentProjectWallTypes()).toEqual([]) + }) + + test('walls drawn with one take its stack, at the thickness its layers sum to', () => { + saveProjectWallType(SOUND_WALL) + useEditor.getState().setWallType('interior', SOUND_WALL.id) + expect(useEditor.getState().wallTypes.interior).toBe(SOUND_WALL.id) + const drawn = getDrawnWallTypes().interior + expect(drawn?.assembly?.presetId).toBe(SOUND_WALL.id) + expect(drawn?.thickness).toBeCloseTo(assemblyThickness(SOUND_WALL.assembly), 12) + expect(drawn?.thickness).toBeCloseTo(STUD_2X8 + 2 * GYPSUM_HALF, 12) + expect(wallTypeThickness(SOUND_WALL.id, 'interior')).toBeCloseTo(drawn!.thickness!, 12) + }) + + test('a 13th layer and a 65th type are refused, each saying why, and nothing is written', () => { + const stack = (linings: number) => + wallTypeAssembly('custom-thick', [ + { role: 'structure', core: true, material: 'wood', thickness: STUD_2X8 }, + ...Array.from({ length: linings }, () => ({ + role: 'lining' as const, + material: 'drywall', + thickness: GYPSUM_HALF, + })), + ]) + const thick = { id: 'custom-thick', label: 'Thick', assembly: stack(WALL_TYPE_LAYER_LIMIT) } + expect(saveProjectWallType(thick)).toBe('too-many-layers') + expect(currentProjectWallTypes()).toEqual([]) + // The limits are the schema's own. + expect(WallTypeDefinition.safeParse(thick).success).toBe(false) + expect(saveProjectWallType({ ...thick, assembly: stack(WALL_TYPE_LAYER_LIMIT - 1) })).toBe( + 'saved', + ) + deleteProjectWallType(thick.id) + + const numbered = (n: number) => ({ + ...SOUND_WALL, + id: `custom-sound-${n}`, + label: `Sound ${n}`, + }) + for (let n = 1; n <= PROJECT_WALL_TYPE_LIMIT; n += 1) + expect(saveProjectWallType(numbered(n))).toBe('saved') + expect(newWallTypeBlockedReason(currentProjectWallTypes())).toContain( + `${PROJECT_WALL_TYPE_LIMIT} wall types`, + ) + expect(saveProjectWallType(numbered(PROJECT_WALL_TYPE_LIMIT + 1))).toBe('too-many-types') + expect(currentProjectWallTypes()).toHaveLength(PROJECT_WALL_TYPE_LIMIT) + const site = Object.values(useScene.getState().nodes).find((node) => node.type === 'site') + expect(SiteNode.safeParse(site).success).toBe(true) + const over = [...currentProjectWallTypes(), numbered(PROJECT_WALL_TYPE_LIMIT + 1)] + expect(SiteNode.safeParse({ ...site, wallTypes: over }).success).toBe(false) + // A type the project keeps can still be edited in place. + expect(saveProjectWallType({ ...numbered(1), label: 'Quiet wall' })).toBe('saved') + expect(currentProjectWallTypes()[0]?.label).toBe('Quiet wall') + deleteProjectWallType(numbered(1).id) + expect(newWallTypeBlockedReason(currentProjectWallTypes())).toBeNull() + }) + + test('a type that does not parse says so, apart from a scene with no site', () => { + const coreless = { + ...SOUND_WALL, + assembly: { + ...SOUND_WALL.assembly, + layers: SOUND_WALL.assembly.layers.filter((l) => !l.core), + }, + } + expect(saveProjectWallType(coreless)).toBe('invalid') + expect(saveProjectWallType({ ...SOUND_WALL, label: ' ' })).toBe('invalid') + expect(currentProjectWallTypes()).toEqual([]) + useScene.getState().unloadScene() + expect(saveProjectWallType(SOUND_WALL)).toBe('no-site') + expect(WALL_TYPE_SAVE_FAILURE.invalid).not.toBe(WALL_TYPE_SAVE_FAILURE['no-site']) + expect(WALL_TYPE_SAVE_FAILURE.invalid).not.toContain('site') + }) + + test("a pick this project does not have draws the placement's default", () => { + useEditor.getState().setWallType('interior', 'custom-elsewhere') + expect(useEditor.getState().wallTypes.interior).toBe('custom-elsewhere') + expect(getDrawnWallTypes().interior?.assembly?.presetId).toBe(DEFAULT_WALL_TYPES.interior) + expect(normalizeWallTypes({ exterior: 'custom-x', interior: 'custom bad' })).toEqual({ + exterior: 'custom-x', + interior: DEFAULT_WALL_TYPES.interior, + }) + }) +}) + +describe('finishes picked while drafting', () => { + test('start at the defaults, which write nothing on the wall', () => { + expect(useEditor.getState().wallFinishes.paint).toBe(DEFAULT_INTERIOR_WALL_PAINT) + const types = getDrawnWallTypes() + expect(types.exterior?.slots).toBeUndefined() + expect(types.interior?.slots).toBeUndefined() + }) + + test('go on the cladding of the exterior type and on every drywall face', () => { + useEditor.getState().setWallCladding('siding', 'library:siding-lap-navy') + useEditor.getState().setWallPaint('library:preset-sage') + const types = getDrawnWallTypes() + expect(types.exterior?.slots).toEqual({ + 'layer:exterior': 'library:siding-lap-navy', + 'layer:interior': 'library:preset-sage', + }) + expect(types.interior?.slots).toEqual({ + 'layer:interior-back': 'library:preset-sage', + 'layer:interior': 'library:preset-sage', + }) + // Stucco keeps its own pick: the navy siding is not a stucco. + useEditor.getState().setWallType('exterior', 'exterior-2x6-stucco') + expect(getDrawnWallTypes().exterior?.slots).toEqual({ 'layer:interior': 'library:preset-sage' }) + useEditor.getState().setWallCladding('stucco', 'library:preset-sand') + expect(getDrawnWallTypes().exterior?.slots?.['layer:exterior']).toBe('library:preset-sand') + }) + + test('the Interior wall tool draws its type and paint everywhere', () => { + useEditor.getState().setWallPaint('library:preset-sage') + setWallTypeOverride('interior') + const types = getDrawnWallTypes() + expect(types.exterior?.assembly?.presetId).toBe(DEFAULT_WALL_TYPES.interior) + expect(types.exterior?.slots?.['layer:interior']).toBe('library:preset-sage') + }) + + test('stored picks come back; anything else is dropped', () => { + expect( + normalizeWallFinishes({ + cladding: { siding: 'library:siding-lap-sage', stucco: 42 }, + paint: 'library:preset-mint', + }), + ).toEqual({ cladding: { siding: 'library:siding-lap-sage' }, paint: 'library:preset-mint' }) + expect(normalizeWallFinishes(undefined)).toEqual(DEFAULT_WALL_FINISHES) + }) +}) diff --git a/packages/editor/src/lib/wall-type.ts b/packages/editor/src/lib/wall-type.ts new file mode 100644 index 0000000000..5095d66441 --- /dev/null +++ b/packages/editor/src/lib/wall-type.ts @@ -0,0 +1,380 @@ +import { + type AnyNodeId, + type Assembly, + assemblyThickness, + CUSTOM_WALL_TYPE_PREFIX, + DEFAULT_INTERIOR_WALL_PAINT, + type DrawnWallTypes, + getWallAssemblyPreset, + isExteriorWallAssembly, + parseWallTypes, + projectWallTypes, + type SiteNode, + useScene, + WALL_ASSEMBLY_PRESETS, + type WallNode, + WallTypeDefinition, + WallTypeDefinitionId, + wallAssemblyCladding, + wallAssemblyFinishSlots, + wallAssemblyPatch, +} from '@pascal-app/core' +import { useMemo } from 'react' + +/** + * The wall types new walls are drawn with, picked in the Build panel before + * drawing and kept with the editor preferences. A type is a wall assembly + * preset id, a project wall type id (`custom-…`, saved on the site), or `plain` + * for a single-layer wall with no assembly. + * + * `exterior` builds the walls that hold the outside back (the outline, an open + * run, the first walls of a plan); `interior` builds the walls a closed circuit + * of walls encloses (`DrawnWallTypes` in core). + */ +export type WallTypePlacement = 'exterior' | 'interior' +export type WallTypeId = string +export type WallTypes = Record + +export const PLAIN_WALL_TYPE = 'plain' + +export const DEFAULT_WALL_TYPES: WallTypes = { + exterior: 'exterior-2x6-siding', + interior: 'interior-2x4-drywall', +} + +/** + * The finishes new walls are drawn with, picked beside their types and kept with + * them: the cladding product for each cladding kind (so going from siding to + * stucco and back keeps the siding colour), and the paint every lining gets — + * both faces of a partition and the inside face of an exterior wall. Absent = + * the kind's own cladding (`WALL_FINISH_LIBRARY_REF`). + */ +export type WallFinishes = { + cladding: Partial> + paint: string +} + +/** + * The paint starts at the default interior paint every unpainted face already + * shows, so a wall drawn with it carries none of its own (`wallAssemblyFinishSlots`). + */ +export const DEFAULT_WALL_FINISHES: WallFinishes = { + cladding: {}, + paint: DEFAULT_INTERIOR_WALL_PAINT, +} + +/** + * The Rooms group's Exterior wall and Interior wall tiles, beside Walls: single + * walls of that placement's type and finish wherever they land (`armWallTypeTool`, + * the tiles of the Shift+E and I shortcuts). + */ +export const WALL_TYPE_TOOLS: ReadonlyArray<{ + placement: WallTypePlacement + label: string + iconSrc: string + /** `ToolShortcutId` of the key that arms it. */ + shortcut: 'exterior-wall' | 'interior-wall' + title: string +}> = [ + { + placement: 'exterior', + label: 'Exterior wall', + iconSrc: '/icons/walls.webp', + shortcut: 'exterior-wall', + title: 'Exterior walls: every wall drawn takes the exterior type and cladding', + }, + { + placement: 'interior', + label: 'Interior wall', + iconSrc: '/icons/divide-room.webp', + shortcut: 'interior-wall', + title: 'Interior walls: every wall drawn takes the interior type and paint', + }, +] + +/** + * The Rooms group's Invisible wall tile (and Railing, beside it): runs of lines the + * wall tool draws that are no walls of a type (`armWallLineTool`). + */ +export const WALL_LINE_TOOLS: ReadonlyArray<{ + kind: 'invisible' | 'railing' + label: string + iconSrc: string + title: string + /** The Rooms caption's rest while the tile is armed. */ + caption: string +}> = [ + { + kind: 'invisible', + label: 'Invisible wall', + iconSrc: '/icons/zone.webp', + title: + 'Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall.', + caption: + 'every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch.', + }, + { + kind: 'railing', + label: 'Railing', + iconSrc: '/icons/fence.webp', + title: + 'Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel.', + caption: + 'every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening.', + }, +] + +export type WallTypeOption = { id: WallTypeId; label: string; group: string } + +const GROUP_LABEL: Record = { + exterior: 'Exterior', + interior: 'Interior', + masonry: 'Masonry', +} + +export const WALL_TYPE_OPTIONS: readonly WallTypeOption[] = [ + ...WALL_ASSEMBLY_PRESETS.map((preset) => ({ + id: preset.id, + label: preset.label, + group: GROUP_LABEL[preset.category] ?? preset.category, + })), + { id: PLAIN_WALL_TYPE, label: 'Plain wall (single layer)', group: 'Other' }, +] + +const PLAIN_OPTION = WALL_TYPE_OPTIONS[WALL_TYPE_OPTIONS.length - 1]! + +/** Whether `id` is a project wall type id (it may still be missing from this project). */ +export function isProjectWallTypeId(id: string): boolean { + return id.startsWith(CUSTOM_WALL_TYPE_PREFIX) && WallTypeDefinitionId.safeParse(id).success +} + +function isWallTypeId(value: unknown): value is WallTypeId { + return ( + typeof value === 'string' && + (value === PLAIN_WALL_TYPE || !!getWallAssemblyPreset(value) || isProjectWallTypeId(value)) + ) +} + +export function normalizeWallTypes(value: unknown): WallTypes { + const record = (value && typeof value === 'object' ? value : {}) as Record + return { + exterior: isWallTypeId(record.exterior) ? record.exterior : DEFAULT_WALL_TYPES.exterior, + interior: isWallTypeId(record.interior) ? record.interior : DEFAULT_WALL_TYPES.interior, + } +} + +const isFinishRef = (value: unknown): value is string => + typeof value === 'string' && (value.startsWith('library:') || /^#[\da-f]{6}$/i.test(value)) + +export function normalizeWallFinishes(value: unknown): WallFinishes { + const record = (value && typeof value === 'object' ? value : {}) as Record + const cladding = ( + record.cladding && typeof record.cladding === 'object' ? record.cladding : {} + ) as Record + return { + cladding: Object.fromEntries( + Object.entries(cladding).filter((entry): entry is [string, string] => isFinishRef(entry[1])), + ), + paint: isFinishRef(record.paint) ? record.paint : DEFAULT_WALL_FINISHES.paint, + } +} + +/** The project's own wall types, as the scene holds them now (`SiteNode.wallTypes`). */ +export function currentProjectWallTypes(): WallTypeDefinition[] { + const { nodes, rootNodeIds } = useScene.getState() + return projectWallTypes(nodes, rootNodeIds) +} + +function siteOf(state: ReturnType): SiteNode | undefined { + for (const id of state.rootNodeIds) { + const node = state.nodes[id] + if (node?.type === 'site') return node + } + return undefined +} + +/** The project's own wall types, kept current as they are saved, edited or undone. */ +export function useProjectWallTypes(): WallTypeDefinition[] { + const saved = useScene((state) => siteOf(state)?.wallTypes) + return useMemo(() => parseWallTypes(saved), [saved]) +} + +/** + * A type's name and stack: a preset's, or a project type's. Null for a plain wall, + * and for a project type this project does not have (picked in another project). + */ +export function resolveWallType( + id: WallTypeId, + projectTypes: readonly WallTypeDefinition[] = currentProjectWallTypes(), +): { label: string; assembly: Assembly; project: boolean } | null { + const preset = getWallAssemblyPreset(id) + if (preset) return { label: preset.label, assembly: preset.assembly, project: false } + const own = projectTypes.find((type) => type.id === id) + return own ? { label: own.label, assembly: own.assembly, project: true } : null +} + +/** + * The type a placement draws with: its pick, or the placement's default when the + * pick is a project type this project does not have. + */ +export function availableWallType( + id: WallTypeId, + placement: WallTypePlacement, + projectTypes: readonly WallTypeDefinition[] = currentProjectWallTypes(), +): WallTypeId { + return isProjectWallTypeId(id) && !resolveWallType(id, projectTypes) + ? DEFAULT_WALL_TYPES[placement] + : id +} + +/** The placement whose list a stack belongs in, read from its layers (`isExteriorWallAssembly`). */ +export function wallTypePlacementOf(assembly: Assembly): WallTypePlacement { + return isExteriorWallAssembly(assembly) ? 'exterior' : 'interior' +} + +/** + * The types a placement's list offers: the presets that build that kind of wall, + * then the project's own, then the plain wall (in both). `current` is listed + * even off its placement, so a pick made before the lists split still shows. + */ +export function wallTypeOptions( + placement: WallTypePlacement, + projectTypes: readonly WallTypeDefinition[], + current?: WallTypeId, +): WallTypeOption[] { + const options: WallTypeOption[] = [ + ...WALL_ASSEMBLY_PRESETS.filter( + (preset) => wallTypePlacementOf(preset.assembly) === placement || preset.id === current, + ).map((preset) => ({ id: preset.id, label: preset.label, group: 'Presets' })), + ...projectTypes + .filter((type) => wallTypePlacementOf(type.assembly) === placement || type.id === current) + .map((type) => ({ id: type.id, label: type.label, group: 'This project' })), + ] + return [...options, PLAIN_OPTION] +} + +/** + * The wall patch a type gives a new wall: its assembly, the thickness it sums to, + * and the `slots` of the finishes picked for it (`wallAssemblyFinishSlots`). + */ +export function wallTypePatch( + id: WallTypeId, + finishes?: WallFinishes, + projectTypes: readonly WallTypeDefinition[] = currentProjectWallTypes(), +): Partial { + const type = resolveWallType(id, projectTypes) + if (!type) return {} + const assembly = structuredClone(type.assembly) + const slots = finishes + ? wallAssemblyFinishSlots(assembly, { + cladding: finishes.cladding[wallAssemblyCladding(assembly) ?? ''], + paint: finishes.paint, + }) + : {} + return { ...wallAssemblyPatch(assembly), ...(Object.keys(slots).length > 0 ? { slots } : {}) } +} + +export function drawnWallTypesOf(types: WallTypes, finishes?: WallFinishes): DrawnWallTypes { + const projectTypes = currentProjectWallTypes() + const patch = (placement: WallTypePlacement) => + wallTypePatch( + availableWallType(types[placement], placement, projectTypes), + finishes, + projectTypes, + ) + return { exterior: patch('exterior'), interior: patch('interior') } +} + +/** + * The thickness a type draws at, or undefined for a plain wall (the wall default). + * With its placement, a project type this project lacks reads as that placement's default. + */ +export function wallTypeThickness( + id: WallTypeId, + placement?: WallTypePlacement, +): number | undefined { + const projectTypes = currentProjectWallTypes() + const type = resolveWallType( + placement ? availableWallType(id, placement, projectTypes) : id, + projectTypes, + ) + return type ? assemblyThickness(type.assembly) : undefined +} + +/** The most layers a wall type's stack holds (`Assembly.layers`). */ +export const WALL_TYPE_LAYER_LIMIT = 12 + +/** The most wall types a project keeps on its site (`SiteNode.wallTypes`). */ +export const PROJECT_WALL_TYPE_LIMIT = 64 + +/** + * Why no new wall type can be made in this project — it keeps `PROJECT_WALL_TYPE_LIMIT` + * already — or null while it has room: what "New wall type…" and "Duplicate as a new + * type…" say when they are off. + */ +export function newWallTypeBlockedReason( + projectTypes: readonly WallTypeDefinition[], +): string | null { + return projectTypes.length >= PROJECT_WALL_TYPE_LIMIT + ? `This project keeps ${PROJECT_WALL_TYPE_LIMIT} wall types, the most it can. Delete one to make another.` + : null +} + +/** + * What saving a project wall type came to: `saved`, or why not — the scene has no + * site to keep it on, its stack has more layers than a wall type holds, the project + * keeps as many types as it can and this one is new, or it does not parse. + */ +export type WallTypeSaveResult = + | 'saved' + | 'no-site' + | 'too-many-layers' + | 'too-many-types' + | 'invalid' + +/** What the wall type editor says when a type was not saved. */ +export const WALL_TYPE_SAVE_FAILURE: Record, string> = { + 'no-site': 'This project has no site to keep wall types on, so the type was not saved.', + 'too-many-layers': `A wall type holds at most ${WALL_TYPE_LAYER_LIMIT} layers. Remove one to save it.`, + 'too-many-types': `This project keeps ${PROJECT_WALL_TYPE_LIMIT} wall types, the most it can. Delete one to save another.`, + invalid: + 'These layers do not make a wall type (one framing core, every layer a thickness), so it was not saved.', +} + +/** + * Saves a project wall type on the site, in place of the one with its id or after + * the others: one undo step, saved and shared with the project. Nothing is written + * unless it comes to `saved` (`WallTypeSaveResult`): a stack of more than + * `WALL_TYPE_LAYER_LIMIT` layers, or a new type past `PROJECT_WALL_TYPE_LIMIT`, would + * leave a site that no longer parses. + */ +export function saveProjectWallType(type: WallTypeDefinition): WallTypeSaveResult { + const state = useScene.getState() + const site = siteOf(state) + if (!site) return 'no-site' + if ((type.assembly?.layers?.length ?? 0) > WALL_TYPE_LAYER_LIMIT) return 'too-many-layers' + const parsed = WallTypeDefinition.safeParse(type) + if (!parsed.success) return 'invalid' + const current = projectWallTypes(state.nodes, state.rootNodeIds) + const editing = current.some((entry) => entry.id === type.id) + if (!editing && current.length >= PROJECT_WALL_TYPE_LIMIT) return 'too-many-types' + const next = editing + ? current.map((entry) => (entry.id === type.id ? parsed.data : entry)) + : [...current, parsed.data] + state.updateNode(site.id as AnyNodeId, { wallTypes: next } as Partial) + return 'saved' +} + +/** Removes a project wall type from the site. Walls drawn with it keep their stack. */ +export function deleteProjectWallType(id: WallTypeId): void { + const state = useScene.getState() + const site = siteOf(state) + if (!site) return + const next = projectWallTypes(state.nodes, state.rootNodeIds).filter((type) => type.id !== id) + state.updateNode( + site.id as AnyNodeId, + { + wallTypes: next.length > 0 ? next : undefined, + } as Partial, + ) +} diff --git a/packages/editor/src/lib/world-grid-snap.test.ts b/packages/editor/src/lib/world-grid-snap.test.ts index 70436b2692..5a29340d69 100644 --- a/packages/editor/src/lib/world-grid-snap.test.ts +++ b/packages/editor/src/lib/world-grid-snap.test.ts @@ -1,14 +1,26 @@ import { afterEach, describe, expect, test } from 'bun:test' -import { BuildingNode, LevelNode, useScene } from '@pascal-app/core' +import { BuildingNode, LevelNode, snapWorldXZToBuildingLocal, useScene } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' -import { getActiveBuildingId } from './world-grid-snap' +import useEditor from '../store/use-editor' +import useInteractionScope from '../store/use-interaction-scope' +import { + getActiveBuildingId, + getActiveGridPose, + resolveAlignmentForActiveBuilding, + resolveAlignmentForFloorplanView, + snapBuildingLocalToWorldGrid, + snapWorldXZForActiveBuilding, +} from './world-grid-snap' const originalNodes = useScene.getState().nodes const originalSelection = useViewer.getState().selection +const originalPhase = useEditor.getState().phase afterEach(() => { useScene.setState({ nodes: originalNodes } as never) useViewer.setState({ selection: originalSelection } as never) + useEditor.setState({ phase: originalPhase }) + useInteractionScope.getState().end() }) describe('getActiveBuildingId', () => { @@ -46,3 +58,128 @@ describe('getActiveBuildingId', () => { expect(getActiveBuildingId()).toBe(building.id) }) }) + +// A building the site planner set on its lot: turned and offset. The plan only +// carries the level, as it often does (`selection.buildingId` null). +const YAW = (17 * Math.PI) / 180 +const POSITION: [number, number, number] = [12.37, 0, -8.91] + +function selectTurnedBuilding() { + const level = LevelNode.parse({}) + const building = BuildingNode.parse({ + position: POSITION, + rotation: [0, YAW, 0], + children: [level.id], + }) + useScene.setState({ + nodes: { [building.id]: building, [level.id]: { ...level, parentId: building.id } }, + } as never) + useViewer.setState({ + selection: { buildingId: null, levelId: level.id, zoneId: null, selectedIds: [] }, + } as never) + return building +} + +describe("grid snap on the building's own lattice", () => { + test('a turned, offset building snaps square to itself, not to the world grid', () => { + selectTurnedBuilding() + expect(snapBuildingLocalToWorldGrid([1.26, 3.74], 0.5)).toEqual([1.5, 3.5]) + expect(snapBuildingLocalToWorldGrid([1.26, 3.74], 0)).toEqual([1.26, 3.74]) + }) + + test('an unturned building at the origin keeps the world grid, which is its lattice', () => { + const building = BuildingNode.parse({}) + useScene.setState({ nodes: { [building.id]: building } } as never) + useViewer.setState({ + selection: { buildingId: building.id, levelId: null, zoneId: null, selectedIds: [] }, + } as never) + expect(snapBuildingLocalToWorldGrid([1.26, 3.74], 0.5)).toEqual([1.5, 3.5]) + expect(snapWorldXZForActiveBuilding(1.26, 3.74, 0.5)).toEqual({ + world: [1.5, 3.5], + local: [1.5, 3.5], + }) + }) + + test('a world cursor lands on the building lattice, in both frames', () => { + selectTurnedBuilding() + const { world, local } = snapWorldXZForActiveBuilding(15.2, -4.4, 0.5) + const raw = snapWorldXZToBuildingLocal(15.2, -4.4, POSITION, YAW, 0).local + expect(local).toEqual(snapBuildingLocalToWorldGrid(raw, 0.5)) + const back = snapWorldXZToBuildingLocal(world[0], world[1], POSITION, YAW, 0).local + expect(back[0]).toBeCloseTo(local[0], 9) + expect(back[1]).toBeCloseTo(local[1], 9) + }) +}) + +describe('resolveAlignmentForFloorplanView', () => { + test("aligns on a turned building's own axes and returns building-local guides", () => { + selectTurnedBuilding() + const result = resolveAlignmentForFloorplanView({ + moving: [{ nodeId: 'moving', kind: 'corner', x: 2.05, z: 1 }], + candidates: [{ nodeId: 'wall', kind: 'corner', x: 2, z: 5 }], + threshold: 0.08, + }) + expect(result.snap?.dx).toBeCloseTo(-0.05, 9) + expect(result.snap?.dz).toBeCloseTo(0, 9) + expect(result.guides).toHaveLength(1) + expect(result.guides[0]?.axis).toBe('x') + expect(result.guides[0]?.anchor).toEqual({ x: 2, z: 5 }) + }) +}) + +describe('resolveAlignmentForActiveBuilding', () => { + test("aligns on a turned building's own axes; guide ends come back in world coords", () => { + selectTurnedBuilding() + const result = resolveAlignmentForActiveBuilding({ + moving: [{ nodeId: 'moving', kind: 'corner', x: 2.05, z: 1 }], + candidates: [{ nodeId: 'wall', kind: 'corner', x: 2, z: 5 }], + threshold: 0.08, + }) + expect(result.snap?.dx).toBeCloseTo(-0.05, 9) + expect(result.snap?.dz).toBeCloseTo(0, 9) + const anchor = result.guides[0]?.anchor + const ends = [result.guides[0]?.from, result.guides[0]?.to] + // The candidate end sits at the anchor's world position. + const world = ends.find( + (end) => + end && + Math.abs(end.x - (POSITION[0] + 2 * Math.cos(YAW) + 5 * Math.sin(YAW))) < 1e-9 && + Math.abs(end.z - (POSITION[2] - 2 * Math.sin(YAW) + 5 * Math.cos(YAW))) < 1e-9, + ) + expect(anchor).toEqual({ x: 2, z: 5 }) + expect(world).toBeDefined() + }) +}) + +describe('getActiveGridPose (the frame the 3D grid is drawn in)', () => { + test("is the active building's pose while working in it", () => { + useEditor.setState({ phase: 'building' }) + selectTurnedBuilding() + expect(getActiveGridPose()).toEqual({ position: POSITION, rotationY: YAW }) + }) + + test('is the world grid for site work and while the building itself moves', () => { + const building = selectTurnedBuilding() + useEditor.setState({ phase: 'site' }) + expect(getActiveGridPose()).toBeNull() + + useEditor.setState({ phase: 'building' }) + useInteractionScope.getState().begin({ + kind: 'moving', + node: building, + nodeId: building.id, + nodeType: 'building', + view: '3d', + }) + expect(getActiveGridPose()).toBeNull() + }) + + test('is the world grid with no building', () => { + useEditor.setState({ phase: 'building' }) + useScene.setState({ nodes: {} } as never) + useViewer.setState({ + selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] }, + } as never) + expect(getActiveGridPose()).toBeNull() + }) +}) diff --git a/packages/editor/src/lib/world-grid-snap.ts b/packages/editor/src/lib/world-grid-snap.ts index 2889cad3e0..ef79bee3bb 100644 --- a/packages/editor/src/lib/world-grid-snap.ts +++ b/packages/editor/src/lib/world-grid-snap.ts @@ -1,12 +1,18 @@ /** - * World-grid snap for tools that consume `grid:move` / `grid:click`. + * Grid snap and alignment frames for tools that consume `grid:move` / + * `grid:click` inside a building. * - * Tools historically snapped on `event.localPosition` (the cursor in the - * active building's local frame). After the floor-plan grid was pulled - * out of the rotated scene group, snapping has to follow the WORLD XZ - * grid — otherwise a rotated building drags every placement off the - * visible grid lines. This helper resolves the active building's pose - * and projects the world snap back into local coords for storage. + * A building placed on a lot by the site planner is usually turned (yaw) and + * offset. Everything drawn or placed in it — walls, slabs, rooms, furniture, + * doors and windows — lives in the building's own frame, and so does the grid + * both views draw for it: the floor plan's grid sits inside the rotated scene + * group, and the 3D grid is laid on the building (`getActiveGridPose`). The + * snaps here follow that lattice, so a wall drawn square to the house stays + * square to it and lands on the lattice its neighbours were drawn on. A + * world-XZ lattice sits at the building's yaw to the house and off it by the + * building's offset, leaving drafted ends a fraction short of the walls they + * meet. For an unturned building at the origin the two coincide; outside any + * building the world grid is the lattice. */ import { type AlignmentAnchor, @@ -21,6 +27,8 @@ import { useScene, } from '@pascal-app/core' import { useViewer } from '@pascal-app/viewer' +import useEditor from '../store/use-editor' +import { getMovingNode } from '../store/use-interaction-scope' /** * Look up the active building's pose, or null when we're at the site root. @@ -68,13 +76,26 @@ export function getActiveBuildingPose(): BuildingPose | null { } /** - * Resolve Figma-style alignment for tools whose anchors are in the active - * building's local frame, but where alignment must run on the WORLD axes - * (the frame the user sees the grid in). Wraps `resolveAlignmentInBuildingWorld` - * with the active building lookup so callers don't repeat the boilerplate. + * The frame of the lattice the 3D grid is drawn on: the active building's pose + * for everything drawn or placed in it, or null — the world XZ grid — for + * site work (the site phase, which terrain sculpting also runs in), while a + * building itself is being moved (it moves on the site), and with no building. + */ +export function getActiveGridPose(): BuildingPose | null { + if (useEditor.getState().phase === 'site') return null + if (getMovingNode()?.type === 'building') return null + return getActiveBuildingPose() +} + +/** + * Resolve Figma-style alignment for 3D tools whose anchors are in the active + * building's local frame, on the building's own axes (the axes its grid is + * drawn on). Wraps `resolveAlignmentInBuildingWorld` with the active building + * lookup so callers don't repeat the boilerplate. * * Returns: - * - `guides` in WORLD coords (renderer must live in a world-space group), + * - `guides` with `from` / `to` in WORLD coords (callers bring them back + * with `projectAlignmentGuidesWorldToActiveBuildingLocal`), * - `snap` in BUILDING-LOCAL coords (ready to add to a local position). */ export function resolveAlignmentForActiveBuilding(args: { @@ -82,7 +103,24 @@ export function resolveAlignmentForActiveBuilding(args: { candidates: readonly AlignmentAnchor[] threshold: number }): ResolveAlignmentInBuildingResult { - return resolveAlignmentInBuildingWorld({ ...args, pose: getActiveBuildingPose() }) + // Pose `null` resolves in the anchors' own (building-local) frame. + const result = resolveAlignmentInBuildingWorld({ ...args, pose: null }) + const pose = getActiveBuildingPose() + if (!pose) return result + return { + snap: result.snap, + guides: result.guides.map((guide) => { + const [fromX, fromZ] = poseLocalXZToWorld(guide.from.x, guide.from.z, pose) + const [toX, toZ] = poseLocalXZToWorld(guide.to.x, guide.to.z, pose) + return { ...guide, from: { x: fromX, z: fromZ }, to: { x: toX, z: toZ } } + }), + } +} + +function poseLocalXZToWorld(x: number, z: number, pose: BuildingPose): [number, number] { + const cos = Math.cos(pose.rotationY) + const sin = Math.sin(pose.rotationY) + return [pose.position[0] + x * cos + z * sin, pose.position[2] - x * sin + z * cos] } function worldXZToPoseLocal(x: number, z: number, pose: BuildingPose | null): [number, number] { @@ -97,9 +135,10 @@ function worldXZToPoseLocal(x: number, z: number, pose: BuildingPose | null): [n /** * Project WORLD-frame alignment guides into the active building's LOCAL frame. * - * The 3D alignment layer is still mounted inside the building-local tool group, - * so tools that resolve alignment on the world axes (item placement, slab move) - * need their guides converted before publishing to `useAlignmentGuides`. + * The 3D alignment layer is mounted inside the building-local tool group, so + * tools that resolve through `resolveAlignmentForActiveBuilding` (item + * placement, slab move) convert its guides before publishing to + * `useAlignmentGuides`. */ export function projectAlignmentGuidesWorldToActiveBuildingLocal( guides: readonly AlignmentGuide[], @@ -117,119 +156,77 @@ export function projectAlignmentGuidesWorldToActiveBuildingLocal( } /** - * Baseline rotation the floor-plan view applies on top of the building - * rotation. Mirrors `FLOORPLAN_VIEW_ROTATION_DEG = 0` in lib/floorplan/geometry.ts — - * the scene group reads it via `floorplanSceneRotationDeg = FVR - buildingRot`. - */ -const FLOORPLAN_VIEW_ROTATION_RAD = 0 - -function rotateAnchorsBy( - anchors: readonly AlignmentAnchor[], - cos: number, - sin: number, -): AlignmentAnchor[] { - return anchors.map((a) => ({ - nodeId: a.nodeId, - kind: a.kind, - x: a.x * cos - a.z * sin, - z: a.x * sin + a.z * cos, - })) -} - -/** - * Resolve alignment in the 2D floor-plan view frame — the frame the user - * sees the (always axis-aligned) grid lines in, regardless of how the - * building has been rotated. Use this from EVERY 2D floor-plan path so - * alignment guides stay parallel to the visible grid. + * Resolve alignment for the 2D floor plan, on the active building's own axes. + * Use this from EVERY 2D floor-plan path so alignment guides stay parallel to + * the plan's grid. * - * Why it differs from `resolveAlignmentForActiveBuilding`: the 3D viewport - * shows the world XZ grid, so world-frame alignment matches the visible - * grid there. The 2D floor plan, however, rotates the scene `` by - * `floorplanSceneRotationDeg = FVR − buildingRot` and renders the grid - * OUTSIDE that rotated group — so the visible axes are - * `R(FVR − buildingRot) · local`. World-frame alignment would land on - * world axes, which appear diagonal in this view; view-frame alignment - * lands on the SVG axes the user actually reads. + * The floor plan rotates its scene `` by the building's yaw (and the user's + * view rotation) and draws both its grid and the alignment-guide layer inside + * that group, in building-local metres. So the axes the user reads are the + * building's local axes, and guides must come back in local coords: resolving + * on world axes would align a turned building's corners on a diagonal to its + * walls and draw each guide away from the anchors it matched. * - * Returns guides in view-frame coords (correct input for the floor-plan - * alignment-guide layer, which is mounted outside the rotated scene - * group) and a snap delta projected back into building-local (so callers - * can add it to a local position as-is). + * Returns guides and the snap delta in building-local coords (add the delta + * to a local position as-is). */ export function resolveAlignmentForFloorplanView(args: { moving: readonly AlignmentAnchor[] candidates: readonly AlignmentAnchor[] threshold: number }): ResolveAlignmentInBuildingResult { - const pose = getActiveBuildingPose() - const buildingRotY = pose?.rotationY ?? 0 - const rot = FLOORPLAN_VIEW_ROTATION_RAD - buildingRotY - const cos = Math.cos(rot) - const sin = Math.sin(rot) - const result = resolveAlignmentInBuildingWorld({ - moving: rotateAnchorsBy(args.moving, cos, sin), - candidates: rotateAnchorsBy(args.candidates, cos, sin), - threshold: args.threshold, - // Pose `null` keeps `resolveAlignmentInBuildingWorld` in its no-op - // path: it just runs `resolveAlignment` on the already-rotated - // anchors and returns the snap delta in the same view frame. - pose: null, - }) - if (!result.snap) return result - // View-frame delta → local-frame delta (transpose of R(rot)). - const { dx, dz } = result.snap - return { - guides: result.guides, - snap: { dx: dx * cos + dz * sin, dz: -dx * sin + dz * cos }, - } + // Pose `null` resolves in the anchors' own (building-local) frame. + return resolveAlignmentInBuildingWorld({ ...args, pose: null }) } /** - * Snap a world XZ position to the grid, then express it in the active - * building's local frame. When no building is active, world == local. + * Snap a world XZ position onto the active building's grid lattice (see + * `snapBuildingLocalToWorldGrid`) and return the snapped point in both + * frames. With no active building, world == local and the world grid is the + * lattice. */ export function snapWorldXZForActiveBuilding( worldX: number, worldZ: number, step: number, ): { world: [number, number]; local: [number, number] } { - const buildingId = useViewer.getState().selection.buildingId - const building = buildingId ? useScene.getState().nodes[buildingId] : null - if (building?.type !== 'building') { - if (step <= 0) return { world: [worldX, worldZ], local: [worldX, worldZ] } - const sx = Math.round(worldX / step) * step - const sz = Math.round(worldZ / step) * step - return { world: [sx, sz], local: [sx, sz] } + const pose = getActiveBuildingPose() + if (!pose) { + const [x, z] = snapBuildingLocalToWorldGrid([worldX, worldZ], step) + return { world: [x, z], local: [x, z] } } - return snapWorldXZToBuildingLocal( - worldX, - worldZ, - building.position, - building.rotation[1] ?? 0, - step, - ) + const { local } = snapWorldXZToBuildingLocal(worldX, worldZ, pose.position, pose.rotationY, 0) + const [x, z] = snapBuildingLocalToWorldGrid(local, step) + return { world: poseLocalXZToWorld(x, z, pose), local: [x, z] } } /** - * Snap a building-local plan point so the resulting position sits on the - * world XZ grid. The returned point is still in building-local coords — - * useful as a `gridSnap` callback for snapWallDraftPoint / snapFenceDraftPoint - * etc., which operate entirely in the local frame. + * Snap a plan point in the 3D tools' frame (a grid event's `localPosition`) + * onto the lattice the grid is drawn on, one axis at a time with the caller's + * per-axis snap (an item snaps its edges by its own size). Inside a building + * the point is building-local and the lattice runs along the building's axes; + * outside any building it is the world point and the world grid is the + * lattice — so no pose is needed either way. + */ +export function snapPlanPointToActiveGrid( + point: readonly [number, number], + snap: (value: number, axis: 0 | 1) => number, +): [number, number] { + return [snap(point[0], 0), snap(point[1], 1)] +} + +/** + * Snap a building-local plan point onto the building's own grid lattice: + * multiples of `step` along its local axes from its origin, the lattice both + * views draw and the wall tool drafts on. Coordinates in and out are + * building-local, so it suits a `gridSnap` callback for snapWallDraftPoint / + * snapFenceDraftPoint etc. (The name predates the building frame: it used to + * snap onto the world XZ grid, at a turned building's yaw to the house.) */ export function snapBuildingLocalToWorldGrid( local: readonly [number, number], step: number, ): [number, number] { - const buildingId = useViewer.getState().selection.buildingId - const building = buildingId ? useScene.getState().nodes[buildingId] : null - if (building?.type !== 'building') { - if (step <= 0) return [local[0], local[1]] - return [Math.round(local[0] / step) * step, Math.round(local[1] / step) * step] - } - const rotY = building.rotation[1] ?? 0 - const cos = Math.cos(rotY) - const sin = Math.sin(rotY) - const worldX = building.position[0] + local[0] * cos + local[1] * sin - const worldZ = building.position[2] - local[0] * sin + local[1] * cos - return snapWorldXZToBuildingLocal(worldX, worldZ, building.position, rotY, step).local + if (step <= 0) return [local[0], local[1]] + return snapPlanPointToActiveGrid(local, (value) => Math.round(value / step) * step) } diff --git a/packages/editor/src/store/use-alignment-guides.ts b/packages/editor/src/store/use-alignment-guides.ts index 4f6f3eff67..09dc4c684e 100644 --- a/packages/editor/src/store/use-alignment-guides.ts +++ b/packages/editor/src/store/use-alignment-guides.ts @@ -6,16 +6,42 @@ import type { AlignmentGuide } from '@pascal-app/core' import { create } from 'zustand' +/** + * A wall draft's dashed projection guide, in building-local plan coordinates: + * the extension of the wall the draft comes from, a line through another wall's + * end on one of the house's axes, or the same through the run's start. + */ +export type DraftGuideLine = { + kind: 'extension' | 'alignment' | 'run-start' + from: [number, number] + to: [number, number] +} + +/** + * The projection guides of a wall pulled from a selected wall's end (and of + * the run it continues): dashed lines, and markers where two of them cross. + */ +export type DraftGuides = { + lines: DraftGuideLine[] + intersections: Array<[number, number]> +} + type AlignmentGuidesState = { guides: AlignmentGuide[] + /** A wall draft's projection guides, drawn by the same layers; null when none. */ + draft: DraftGuides | null set(guides: AlignmentGuide[]): void + /** Publish a wall draft's projection guides, which take the place of alignment guides. */ + setDraft(draft: DraftGuides | null): void clear(): void } const useAlignmentGuides = create((set) => ({ guides: [], + draft: null, set: (guides) => set({ guides }), - clear: () => set({ guides: [] }), + setDraft: (draft) => set({ guides: [], draft }), + clear: () => set({ guides: [], draft: null }), })) export default useAlignmentGuides diff --git a/packages/editor/src/store/use-editor.tsx b/packages/editor/src/store/use-editor.tsx index f64c2c5d56..cf1ee4cd6b 100644 --- a/packages/editor/src/store/use-editor.tsx +++ b/packages/editor/src/store/use-editor.tsx @@ -65,6 +65,16 @@ import { snapContextOf, snappingModesFor, } from '../lib/snapping-mode' +import { + DEFAULT_WALL_FINISHES, + DEFAULT_WALL_TYPES, + normalizeWallFinishes, + normalizeWallTypes, + type WallFinishes, + type WallTypeId, + type WallTypePlacement, + type WallTypes, +} from '../lib/wall-type' import { cameraPoseStore } from './camera-pose-store' import { publishNavigationSyncPoseToStore } from './navigation-sync-pose-store' import useInteractionScope from './use-interaction-scope' @@ -521,6 +531,15 @@ type EditorState = { setContinuation: (context: ContinuationContext, mode: ContinuationMode) => void cycleContinuation: (context: ContinuationContext) => ContinuationMode getContinuation: (context: ContinuationContext) => ContinuationMode + // The wall types new walls are drawn with (`lib/wall-type.ts`), picked in the + // Build panel's Rooms group. Persisted. + wallTypes: WallTypes + setWallType: (placement: WallTypePlacement, id: WallTypeId) => void + // The finishes picked beside them: a cladding product per cladding kind and + // the drywall paint (`WallFinishes`). Persisted. + wallFinishes: WallFinishes + setWallCladding: (kind: string, ref: string) => void + setWallPaint: (ref: string) => void showReferenceFloor: boolean toggleReferenceFloor: () => void setShowReferenceFloor: (show: boolean) => void @@ -528,6 +547,24 @@ type EditorState = { setReferenceFloorOffset: (offset: number) => void referenceFloorOpacity: number setReferenceFloorOpacity: (opacity: number) => void + // The 2D plan's dashed outlines of the walls on the level below / above + // the active one. "Below" is set apart for the roof plan, where it traces + // the top storey's walls the roof is checked against and starts on, and the + // floor plans, where it starts off (`useWallsBelowSetting`). Persisted. + showWallsBelow: boolean + setShowWallsBelow: (show: boolean) => void + showRoofWallsBelow: boolean + setShowRoofWallsBelow: (show: boolean) => void + showWallsAbove: boolean + setShowWallsAbove: (show: boolean) => void + // Ceilings drawn solid from above as well as from below. Set apart like + // "Walls below": on for the roof level, where the roof is checked over closed + // rooms, off for the floor plans, where the camera looks into the rooms + // (`useCeilingsSetting`). Persisted. + showCeilings: boolean + setShowCeilings: (show: boolean) => void + showRoofCeilings: boolean + setShowRoofCeilings: (show: boolean) => void // Development-only camera debug flag for inspecting underside geometry allowUndergroundCamera: boolean setAllowUndergroundCamera: (enabled: boolean) => void @@ -602,9 +639,16 @@ type PersistedEditorLayoutState = Pick< | 'lastMeasurementKind' | 'snappingModeByContext' | 'continuationByContext' + | 'wallTypes' + | 'wallFinishes' | 'showReferenceFloor' | 'referenceFloorOffset' | 'referenceFloorOpacity' + | 'showWallsBelow' + | 'showRoofWallsBelow' + | 'showWallsAbove' + | 'showCeilings' + | 'showRoofCeilings' > type PersistedEditorState = PersistedEditorUiState & PersistedEditorLayoutState @@ -637,9 +681,16 @@ export const DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE: PersistedEditorLayoutState = cabinet: CONTINUATION_PROFILES.cabinet.default, canopy: CONTINUATION_PROFILES.canopy.default, }, + wallTypes: DEFAULT_WALL_TYPES, + wallFinishes: DEFAULT_WALL_FINISHES, showReferenceFloor: false, referenceFloorOffset: 1, referenceFloorOpacity: 0.35, + showWallsBelow: false, + showRoofWallsBelow: true, + showWallsAbove: false, + showCeilings: false, + showRoofCeilings: true, } const GRID_SNAP_STEPS: GridSnapStep[] = [0.5, 0.25, 0.1, 0.05] @@ -889,6 +940,8 @@ export function normalizePersistedEditorLayoutState( rotation: migrateSnappingMode(state?.snappingModeByContext?.rotation, 'rotation'), }, continuationByContext: normalizeContinuationByContext(state), + wallTypes: normalizeWallTypes(state?.wallTypes), + wallFinishes: normalizeWallFinishes(state?.wallFinishes), showReferenceFloor: state?.showReferenceFloor === true, referenceFloorOffset: typeof state?.referenceFloorOffset === 'number' && state.referenceFloorOffset >= 1 @@ -899,6 +952,11 @@ export function normalizePersistedEditorLayoutState( Number.isFinite(state.referenceFloorOpacity) ? Math.min(0.8, Math.max(0.1, state.referenceFloorOpacity)) : DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOpacity, + showWallsBelow: state?.showWallsBelow === true, + showRoofWallsBelow: state?.showRoofWallsBelow !== false, + showWallsAbove: state?.showWallsAbove === true, + showCeilings: state?.showCeilings === true, + showRoofCeilings: state?.showRoofCeilings !== false, } } @@ -1558,6 +1616,23 @@ const useEditor = create()( const current = get().continuationByContext[context] return migrateContinuationMode(current, context) ?? CONTINUATION_PROFILES[context].default }, + wallTypes: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.wallTypes, + setWallType: (placement, id) => + set((state) => ({ + wallTypes: normalizeWallTypes({ ...state.wallTypes, [placement]: id }), + })), + wallFinishes: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.wallFinishes, + setWallCladding: (kind, ref) => + set((state) => ({ + wallFinishes: normalizeWallFinishes({ + ...state.wallFinishes, + cladding: { ...state.wallFinishes.cladding, [kind]: ref }, + }), + })), + setWallPaint: (ref) => + set((state) => ({ + wallFinishes: normalizeWallFinishes({ ...state.wallFinishes, paint: ref }), + })), showReferenceFloor: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showReferenceFloor, toggleReferenceFloor: () => set((state) => ({ showReferenceFloor: !state.showReferenceFloor })), @@ -1568,6 +1643,16 @@ const useEditor = create()( referenceFloorOpacity: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.referenceFloorOpacity, setReferenceFloorOpacity: (opacity) => set({ referenceFloorOpacity: Math.min(0.8, Math.max(0.1, opacity)) }), + showWallsBelow: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showWallsBelow, + setShowWallsBelow: (show) => set({ showWallsBelow: show }), + showRoofWallsBelow: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showRoofWallsBelow, + setShowRoofWallsBelow: (show) => set({ showRoofWallsBelow: show }), + showWallsAbove: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showWallsAbove, + setShowWallsAbove: (show) => set({ showWallsAbove: show }), + showCeilings: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showCeilings, + setShowCeilings: (show) => set({ showCeilings: show }), + showRoofCeilings: DEFAULT_PERSISTED_EDITOR_LAYOUT_STATE.showRoofCeilings, + setShowRoofCeilings: (show) => set({ showRoofCeilings: show }), allowUndergroundCamera: false, setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }), show2dVoronoi: false, @@ -1669,9 +1754,16 @@ const useEditor = create()( lastMeasurementKind: state.lastMeasurementKind, snappingModeByContext: state.snappingModeByContext, continuationByContext: state.continuationByContext, + wallTypes: state.wallTypes, + wallFinishes: state.wallFinishes, showReferenceFloor: state.showReferenceFloor, referenceFloorOffset: state.referenceFloorOffset, referenceFloorOpacity: state.referenceFloorOpacity, + showWallsBelow: state.showWallsBelow, + showRoofWallsBelow: state.showRoofWallsBelow, + showWallsAbove: state.showWallsAbove, + showCeilings: state.showCeilings, + showRoofCeilings: state.showRoofCeilings, }), skipHydration: true, }, diff --git a/packages/editor/src/store/use-floorplan-annotation-visibility.ts b/packages/editor/src/store/use-floorplan-annotation-visibility.ts index 2c9f21864e..38918c7fdb 100644 --- a/packages/editor/src/store/use-floorplan-annotation-visibility.ts +++ b/packages/editor/src/store/use-floorplan-annotation-visibility.ts @@ -9,16 +9,22 @@ import { normalizeFloorplanAnnotationVisibility, } from '../lib/floorplan/annotation-visibility' import { + DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES, DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE, + type FloorplanWallDimensionFaces, type FloorplanWallDimensionReference, + normalizeFloorplanWallDimensionFaces, normalizeFloorplanWallDimensionReference, } from '../lib/floorplan/floorplan-extension' type FloorplanAnnotationVisibilityState = { visibility: FloorplanAnnotationVisibility wallDimensionReference: FloorplanWallDimensionReference + /** What a selected wall's temporary dimensions measure to, in 2D and 3D. */ + wallDimensionFaces: FloorplanWallDimensionFaces setCategory: (category: FloorplanAnnotationCategory, visible: boolean) => void setWallDimensionReference: (reference: FloorplanWallDimensionReference) => void + setWallDimensionFaces: (faces: FloorplanWallDimensionFaces) => void reset: () => void } @@ -27,13 +33,16 @@ const useFloorplanAnnotationVisibility = create ({ visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY }, wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE, + wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES, setCategory: (category, visible) => set((state) => ({ visibility: { ...state.visibility, [category]: visible } })), setWallDimensionReference: (wallDimensionReference) => set({ wallDimensionReference }), + setWallDimensionFaces: (wallDimensionFaces) => set({ wallDimensionFaces }), reset: () => set({ visibility: { ...DEFAULT_FLOORPLAN_ANNOTATION_VISIBILITY }, wallDimensionReference: DEFAULT_FLOORPLAN_WALL_DIMENSION_REFERENCE, + wallDimensionFaces: DEFAULT_FLOORPLAN_WALL_DIMENSION_FACES, }), }), { @@ -47,11 +56,15 @@ const useFloorplanAnnotationVisibility = create ({ visibility: state.visibility, wallDimensionReference: state.wallDimensionReference, + wallDimensionFaces: state.wallDimensionFaces, }) as FloorplanAnnotationVisibilityState, }, ), diff --git a/packages/editor/src/store/use-wall-snap-indicator.ts b/packages/editor/src/store/use-wall-snap-indicator.ts index 9cdbd4a9c2..4a98e59adf 100644 --- a/packages/editor/src/store/use-wall-snap-indicator.ts +++ b/packages/editor/src/store/use-wall-snap-indicator.ts @@ -7,8 +7,19 @@ import { create } from 'zustand' -/** Which kind of wall geometry the draft point snapped to. */ -export type WallSnapKind = 'endpoint' | 'midpoint' | 'intersection' | 'wall' +/** + * Which kind of geometry the draft point snapped to. A pulled wall also lands + * square on a wall (`perpendicular`), on a projection guide (`alignment`), or + * at a grid length (`grid`). + */ +export type WallSnapKind = + | 'endpoint' + | 'midpoint' + | 'intersection' + | 'wall' + | 'perpendicular' + | 'alignment' + | 'grid' export type WallSnapPoint = { /** Building-local plan coordinates (XZ meters). */ @@ -17,6 +28,8 @@ export type WallSnapPoint = { kind: WallSnapKind /** Optional wall ids whose geometry produced this snap. */ wallIds?: string[] + /** Words the marker shows beside its glyph ("Endpoint", "On wall"); none when absent. */ + label?: string } type WallSnapIndicatorState = { diff --git a/packages/mcp/src/tools/measure-world.test.ts b/packages/mcp/src/tools/measure-world.test.ts index 42fab0d910..575e7b3df8 100644 --- a/packages/mcp/src/tools/measure-world.test.ts +++ b/packages/mcp/src/tools/measure-world.test.ts @@ -43,6 +43,7 @@ type MeasurePayload = { /** Fields a kind needs beyond its schema defaults (mirrors core's node fixtures). */ const REQUIRED_FIELDS: Record> = { 'procedural-item': { recipe: shelfRecipe }, + beam: { start: [0, 0], end: [4, 0] }, ceiling: { polygon: [ [0, 0], diff --git a/packages/nodes/src/beam/actions/move-endpoint.test.ts b/packages/nodes/src/beam/actions/move-endpoint.test.ts new file mode 100644 index 0000000000..abef5e11c9 --- /dev/null +++ b/packages/nodes/src/beam/actions/move-endpoint.test.ts @@ -0,0 +1,71 @@ +import { beforeEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BeamNode, + BuildingNode, + clearSceneHistory, + createDragSession, + createSceneApi, + LevelNode, + useScene, +} from '@pascal-app/core' +import { moveBeamEndpointDragAction } from './move-endpoint' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} + +const BEAM = BeamNode.parse({ id: 'beam_a', parentId: 'level_g', start: [0, 0], end: [3, 0] }) +// Alt: the end goes where the pointer is, whatever the snapping mode. +const free = { shift: false, alt: true, ctrl: false, meta: false } + +beforeEach(() => { + const building = BuildingNode.parse({ id: 'building_a', children: ['level_g'] }) + const level = LevelNode.parse({ + id: 'level_g', + parentId: building.id, + level: 0, + children: [BEAM.id], + }) + const nodes = Object.fromEntries([building, level, BEAM].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() +}) + +const beam = () => useScene.getState().nodes[BEAM.id as AnyNodeId] as BeamNode + +test('a dragged end follows the pointer, the other stays, and the drag is one undo step', () => { + const session = createDragSession(moveBeamEndpointDragAction, createSceneApi(useScene)) + session.start({ node: BEAM, handleId: 'end', point: [3, 0] }) + session.move([4, 1], free) + expect(beam().end).toEqual([4, 1]) + session.move([5, 2], free) + expect(session.commit()).toBe(true) + expect(beam()).toMatchObject({ start: [0, 0], end: [5, 2] }) + useScene.temporal.getState().undo() + expect(beam()).toMatchObject({ start: [0, 0], end: [3, 0] }) +}) + +test('the start drags too, and a beam too short to draw is put back', () => { + const session = createDragSession(moveBeamEndpointDragAction, createSceneApi(useScene)) + session.start({ node: BEAM, handleId: 'start', point: [0, 0] }) + session.move([2.998, 0], free) + expect(session.commit()).toBe(false) + expect(beam()).toMatchObject({ start: [0, 0], end: [3, 0] }) +}) + +test('Esc puts the beam back', () => { + const session = createDragSession(moveBeamEndpointDragAction, createSceneApi(useScene)) + session.start({ node: BEAM, handleId: 'start', point: [0, 0] }) + session.move([-1, 1], free) + session.cancel() + expect(beam()).toMatchObject({ start: [0, 0], end: [3, 0] }) +}) diff --git a/packages/nodes/src/beam/actions/move-endpoint.ts b/packages/nodes/src/beam/actions/move-endpoint.ts new file mode 100644 index 0000000000..1254b4fb93 --- /dev/null +++ b/packages/nodes/src/beam/actions/move-endpoint.ts @@ -0,0 +1,73 @@ +import { + type AnyNode, + type AnyNodeId, + type BeamNode, + type DragAction, + useScene, +} from '@pascal-app/core' +import { isSegmentLongEnough, type WallPlanPoint } from '@pascal-app/editor' +import { snapBeamPoint } from '../drafting' + +export type MoveBeamEndpointCtx = { + beamId: AnyNodeId + endpoint: 'start' | 'end' + levelId: string | null + /** The end that stays put. */ + fixed: WallPlanPoint +} + +export type MoveBeamEndpointDraft = { start: WallPlanPoint; end: WallPlanPoint } + +/** + * A beam's end dragged in 3D, as a pure drag action: + * - **preview**: the pointer snapped as the beam tool snaps (wall faces and ends, other beams, + * post centres, the grid), angle-stepped off the end that stays; Alt places it freely. + * - **apply**: writes the ends into the scene while history is paused. + * - **commit**: refuses a beam too short to draw; otherwise reverts, resumes history and writes + * the final ends once, so the drag is one undo step. + * - **cancel**: `createDragSession` restores the beam it touched. + */ +export const moveBeamEndpointDragAction: DragAction = { + begin: (input) => { + const beam = input.node as BeamNode | undefined + if (beam?.type !== 'beam') throw new Error('[moveBeamEndpointDragAction] begin requires a beam') + const endpoint = input.handleId === 'start' ? 'start' : 'end' + return { + beamId: beam.id as AnyNodeId, + endpoint, + levelId: beam.parentId ?? null, + fixed: endpoint === 'start' ? [beam.end[0], beam.end[1]] : [beam.start[0], beam.start[1]], + } + }, + + preview: (ctx, point, modifiers) => { + const moved: WallPlanPoint = ctx.levelId + ? snapBeamPoint({ + point: [point[0], point[1]], + nodes: useScene.getState().nodes, + levelId: ctx.levelId, + start: ctx.fixed, + ignoreIds: [ctx.beamId], + free: modifiers.alt, + }).point + : [point[0], point[1]] + return ctx.endpoint === 'start' + ? { start: moved, end: ctx.fixed } + : { start: ctx.fixed, end: moved } + }, + + apply: (draft, ctx, scene) => { + scene.update(ctx.beamId, { start: draft.start, end: draft.end } as Partial) + return [ctx.beamId] + }, + + commit: (draft, ctx, scene) => { + if (!(draft && isSegmentLongEnough(draft.start, draft.end))) return false + scene.restoreAll() + scene.resumeHistory() + scene.update(ctx.beamId, { start: draft.start, end: draft.end } as Partial) + return true + }, + + cancel: () => {}, +} diff --git a/packages/nodes/src/beam/definition.test.ts b/packages/nodes/src/beam/definition.test.ts new file mode 100644 index 0000000000..4f3521a627 --- /dev/null +++ b/packages/nodes/src/beam/definition.test.ts @@ -0,0 +1,117 @@ +import { expect, test } from 'bun:test' +import { type BeamNode, createBeam, createSceneApi, useScene } from '@pascal-app/core' +import { beamDefinition } from './definition' +import { beamToolOptions, useBeamDraft } from './draft-settings' +import { beamBox, buildBeamGeometry } from './geometry' + +const IN = 0.0254 + +test('a beam is a selectable, duplicable, deletable structural kind drawn with its own tool', () => { + expect(beamDefinition).toMatchObject({ + kind: 'beam', + category: 'structure', + snapProfile: 'structural', + presentation: { label: 'Beam', paletteSection: 'structure' }, + }) + expect(beamDefinition.capabilities).toMatchObject({ + selectable: { hitVolume: 'bbox' }, + duplicable: true, + deletable: true, + drawTool: true, + }) + expect(typeof beamDefinition.tool).toBe('function') + expect(Object.keys(beamDefinition.affordanceTools ?? {}).sort()).toEqual([ + 'move', + 'move-endpoint', + ]) + expect(Object.keys(beamDefinition.floorplanAffordances ?? {})).toEqual(['move-endpoint']) + const defaults = beamDefinition.defaults() + expect(defaults).toMatchObject({ plies: 1, material: 'sawn', placement: 'dropped' }) +}) + +test('selected, it has a move arrow either side, a picker at each end and a top tracker', () => { + const beam = createBeam({ start: [0, 0], end: [4, 0], preset: '6x8', topElevation: 2.4 }) + const handles = + typeof beamDefinition.handles === 'function' + ? beamDefinition.handles(beam, createSceneApi(useScene)) + : (beamDefinition.handles ?? []) + expect(handles.map((handle) => handle.kind)).toEqual([ + 'tap-action', + 'tap-action', + 'tap-action', + 'tap-action', + 'linear-resize', + ]) + const sceneApi = createSceneApi(useScene) + const [side, , startPicker, , top] = handles + // the side arrow sits beside the beam at its mid-depth + const at = side!.placement.position(beam, sceneApi) + expect(at[1]).toBeCloseTo(2.4 - 3.75 * IN) + expect(Math.abs(at[2])).toBeGreaterThan(beam.width / 2) + // the end picker stands at the beam's underside at its start + expect(startPicker!.placement.position(beam, sceneApi)).toEqual([0, 2.4 - 7.5 * IN, 0]) + // the top tracker writes the top it is dragged to + if (top?.kind !== 'linear-resize') throw new Error('the top handle resizes') + expect(top.currentValue(beam)).toBeCloseTo(2.4) + expect(top.apply(beam, 2.1, sceneApi)).toEqual({ topElevation: 2.1 } as Partial) +}) + +test('the box sits along the centreline with its top where the beam resolves it', () => { + const beam = createBeam({ start: [1, 1], end: [1, 5], preset: '6x8' }) + const box = beamBox(beam, 2.7)! + expect(box.position[0]).toBeCloseTo(1) + expect(box.position[1]).toBeCloseTo(2.7 - 3.75 * IN) + expect(box.position[2]).toBeCloseTo(3) + expect(box.size[0]).toBeCloseTo(4) + // local +X runs along the beam: a run toward +Z turns it -90° + expect(box.rotationY).toBeCloseTo(-Math.PI / 2) + expect(beamBox({ ...beam, end: beam.start }, 2.7)).toBeNull() +}) + +test('a built-up beam shows its plies, a steel one its flanges and web', () => { + const plies = buildBeamGeometry( + { width: 4.5 * IN, depth: 9.25 * IN, plies: 3, material: 'sawn' }, + 2, + ) + const solid = buildBeamGeometry( + { width: 5.5 * IN, depth: 7.5 * IN, plies: 1, material: 'sawn' }, + 2, + ) + const steel = buildBeamGeometry( + { width: 3.94 * IN, depth: 7.89 * IN, plies: 1, material: 'steel' }, + 2, + ) + const triangles = (geometry: ReturnType) => + geometry.getAttribute('position').count / 3 + expect(triangles(solid)).toBe(12) + expect(triangles(plies)).toBe(36) + expect(triangles(steel)).toBe(36) + // the plies keep the beam's full width between their outer faces + plies.computeBoundingBox() + expect(plies.boundingBox!.max.z - plies.boundingBox!.min.z).toBeCloseTo(4.5 * IN, 4) + expect(solid.getAttribute('uv')).toBeTruthy() +}) + +test("the tool's rows pick a material, its sizes and the top", () => { + expect(beamToolOptions.map((option) => option.id)).toEqual([ + 'beam-material', + 'beam-size-sawn', + 'beam-size-lvl', + 'beam-size-glulam', + 'beam-size-steel', + 'beam-top', + ]) + const [material, sawn, lvl] = beamToolOptions + expect(sawn!.visible!.value()).toBe(true) + expect(lvl!.visible!.value()).toBe(false) + // picking LVL brings its usual size along; going treated keeps a sawn size + material!.set('lvl') + expect(useBeamDraft.getState()).toMatchObject({ material: 'lvl', preset: '(2)lvl-1-3/4x11-7/8' }) + expect(lvl!.visible!.value()).toBe(true) + material!.set('pressure-treated') + expect(useBeamDraft.getState().preset).toBe('6x8') + sawn!.set('(3)2x12') + material!.set('sawn') + expect(useBeamDraft.getState().preset).toBe('(3)2x12') + useBeamDraft.setState({ preset: '6x8', material: 'sawn', top: 'ceiling-dropped' }) +}) diff --git a/packages/nodes/src/beam/definition.ts b/packages/nodes/src/beam/definition.ts new file mode 100644 index 0000000000..bdc0a43a6e --- /dev/null +++ b/packages/nodes/src/beam/definition.ts @@ -0,0 +1,204 @@ +import { + type BeamNode as BeamNodeType, + beamPlan, + beamTopElevation, + type HandleDescriptor, + type NodeDefinition, + type SceneApi, +} from '@pascal-app/core' +import { + clearStructuralElevationGuide, + FLOORPLAN_NODE_EXTENSION_KEY, + type FloorplanNodeExtension, + PANEL_MODEL_EXTENSION, + publishStructuralElevationGuide, + resolveStructuralElevationSnap, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { beamToolOptions } from './draft-settings' +import { buildBeamFloorplan } from './floorplan' +import { beamEndpointAffordance } from './floorplan-affordances' +import { beamFloorplanMoveTarget } from './floorplan-move' +import { beamPanelModel } from './panel-model' +import { beamParametrics } from './parametrics' +import { BeamNode } from './schema' + +const SIDE_HANDLE_OFFSET = 0.22 +const INCH = 0.0254 + +/** Where a beam's top is in the live scene (a descriptor may run before the scene has a level). */ +function topOf(beam: BeamNodeType, sceneApi?: SceneApi): number { + return beamTopElevation(sceneApi?.nodes() ?? {}, beam) +} + +function midFrame(beam: BeamNodeType) { + const plan = beamPlan(beam) + return { + mid: [(plan.start[0] + plan.end[0]) / 2, (plan.start[1] + plan.end[1]) / 2] as const, + normal: plan.normal, + } +} + +/** The beam's top, as the elevation guides read a structural datum. */ +function guideSource(beam: BeamNodeType) { + const { mid } = midFrame(beam) + return { nodeId: beam.id, levelId: beam.parentId, anchor: mid } +} + +// Side arrows: a tap hands the beam to its move tool, either side of its middle at mid-depth. +function beamSideMoveHandle(side: 1 | -1): HandleDescriptor { + return { + kind: 'tap-action', + onActivate: (node, _scene, editor) => editor.engageMove(node), + placement: { + position: (n, sceneApi) => { + const { mid, normal } = midFrame(n) + const offset = (n.width / 2 + SIDE_HANDLE_OFFSET) * side + return [ + mid[0] + normal[0] * offset, + topOf(n, sceneApi) - n.depth / 2, + mid[1] + normal[1] * offset, + ] + }, + rotationY: (n) => { + const { normal } = midFrame(n) + return Math.atan2(-normal[1] * side, normal[0] * side) + }, + }, + cursor: 'ew-resize', + } +} + +// End pickers: a dashed leader down the beam's depth and a disc at its end; a tap drags the end. +function beamEndPicker(endpoint: 'start' | 'end'): HandleDescriptor { + return { + kind: 'tap-action', + shape: 'corner-picker', + cursor: 'move', + nodeHeight: (n) => n.depth, + onActivate: (node, _scene, editor) => editor.engageEndpointMove(node, endpoint), + placement: { + position: (n, sceneApi) => { + const point = n[endpoint] + return [point[0], topOf(n, sceneApi) - n.depth, point[1]] + }, + }, + } +} + +/** + * The beam's top, dragged up and down: it stores the top it lands on, caught by the levels' + * structural heights (a ceiling plane, a slab top) and otherwise to the half inch (the centimetre + * on a metric plan). + */ +function beamTopHandle(current: number): HandleDescriptor { + return { + kind: 'linear-resize', + axis: 'y', + anchor: 'min', + shape: 'tracker', + currentValue: () => current, + magneticSnap: (n, value, sceneApi) => { + const snapped = resolveStructuralElevationSnap(guideSource(n), value, sceneApi.nodes()) + if (snapped !== value) return snapped + const step = useViewer.getState().unit === 'imperial' ? INCH / 2 : 0.01 + return Math.round(value / step) * step + }, + onDrag: (n, sceneApi) => + publishStructuralElevationGuide(guideSource(n), topOf(n, sceneApi), sceneApi.nodes()), + onDragEnd: (n) => clearStructuralElevationGuide(n.id), + apply: (_n, top) => ({ topElevation: top }), + placement: { + position: (n, sceneApi) => { + const { mid } = midFrame(n) + return [mid[0], topOf(n, sceneApi), mid[1]] + }, + }, + } +} + +const beamHandles = (node: BeamNodeType, sceneApi?: SceneApi): HandleDescriptor[] => [ + beamSideMoveHandle(1), + beamSideMoveHandle(-1), + beamEndPicker('start'), + beamEndPicker('end'), + beamTopHandle(topOf(node, sceneApi)), +] + +/** + * Beam — a horizontal framing member drawn by hand, end to end like a wall: a porch's header on + * its posts, a deck's beam, a girder, a beam across an open span. A box of its actual section + * along the centreline, its top stored or following the ceiling plane over it (`beamTopElevation` + * in core); the plan draws it dashed with its size, as a framing plan does. `createBeam` in core + * makes one from code. + * + * No `movable`: a beam has no `position`; it moves by both ends, through `affordanceTools.move` in + * 3D and `floorplanMoveTarget` in the plan. + */ +export const beamDefinition: NodeDefinition = { + kind: 'beam', + snapProfile: 'structural', + schemaVersion: 1, + schema: BeamNode, + category: 'structure', + extensions: { + [FLOORPLAN_NODE_EXTENSION_KEY]: { + // In 2D-only view the 3D tool never mounts: the plan drafts beams itself. + tool: () => import('./tool').then((module) => ({ default: module.BeamFloorplanTool })), + // A beam crosses rooms: a plan click on it picks the beam, not the room. + planHitSelectsNode: true, + } satisfies FloorplanNodeExtension, + [PANEL_MODEL_EXTENSION]: beamPanelModel, + }, + + defaults: () => { + const { id: _id, type: _type, ...rest } = BeamNode.parse({ start: [0, 0], end: [3, 0] }) + return rest + }, + + capabilities: { + selectable: { hitVolume: 'bbox' }, + surfaces: { hosting: false }, + duplicable: true, + deletable: true, + drawTool: true, + }, + + // The renderer reads the scene and live overrides itself; nothing consumes a dirty mark. + dirtyTracking: false, + rendersChildren: false, + renderer: { kind: 'parametric', module: () => import('./renderer') }, + parametrics: beamParametrics, + handles: beamHandles, + + tool: () => import('./tool'), + toolOptions: beamToolOptions, + toolHints: [ + { key: 'Left click', label: 'Set beam start / end; the next beam starts there' }, + { key: 'Alt', label: 'Free place' }, + { key: 'Esc', label: 'End the run' }, + ], + + floorplan: buildBeamFloorplan, + floorplanMoveTarget: beamFloorplanMoveTarget, + floorplanAffordances: { + 'move-endpoint': beamEndpointAffordance, + }, + affordanceTools: { + move: () => import('./move-tool'), + 'move-endpoint': () => import('./move-endpoint-tool'), + }, + + presentation: { + label: 'Beam', + description: 'A framing beam drawn end to end: a porch header, a deck beam, a girder.', + icon: { kind: 'url', src: '/icons/beam.svg' }, + paletteSection: 'structure', + paletteOrder: 72, + }, + + mcp: { + description: + 'A horizontal framing beam: start/end centreline (level-local), actual width/depth, nominal size and plies, material, dropped or flush placement, and a top elevation (absent = under the ceiling plane).', + }, +} diff --git a/packages/nodes/src/beam/draft-settings.ts b/packages/nodes/src/beam/draft-settings.ts new file mode 100644 index 0000000000..fd8ec74d22 --- /dev/null +++ b/packages/nodes/src/beam/draft-settings.ts @@ -0,0 +1,119 @@ +import { + BEAM_MATERIAL_LABELS, + BEAM_SIZE_PRESETS, + BEAM_TOP_REFERENCE_LABELS, + BEAM_TOP_REFERENCES, + type BeamMaterial, + type BeamTopReference, + beamSizePreset, + DEFAULT_BEAM_SIZE_PRESET_ID, + type ToolOption, +} from '@pascal-app/core' +import { create } from 'zustand' + +/** What the beam tool draws with: its size, material and where its top goes. */ +export type BeamDraftChoices = { + preset: string + material: BeamMaterial + top: BeamTopReference +} + +/** + * The beam tool's choices, kept apart from the editor's tool defaults: a host clears those when it + * arms a tool from its tile, which would forget the size just picked. + */ +export const useBeamDraft = create< + BeamDraftChoices & { set: (patch: Partial) => void } +>((set) => ({ + preset: DEFAULT_BEAM_SIZE_PRESET_ID, + material: 'sawn', + top: 'ceiling-dropped', + set: (patch) => set(patch), +})) + +/** The stock sizes come in four families; a treated beam is a sawn size. */ +type BeamFamily = 'sawn' | 'lvl' | 'glulam' | 'steel' + +const familyOf = (material: BeamMaterial): BeamFamily => + material === 'pressure-treated' ? 'sawn' : material + +/** The size a family starts at when its material is picked. */ +const FAMILY_SIZE: Record = { + sawn: DEFAULT_BEAM_SIZE_PRESET_ID, + lvl: '(2)lvl-1-3/4x11-7/8', + glulam: 'glulam-5-1/8x12', + steel: 'W8x10', +} + +const MATERIAL_DESCRIPTIONS: Record = { + sawn: 'A solid timber or 2x plies nailed up, inside where it stays dry', + 'pressure-treated': 'Treated sawn lumber, for a porch, a deck or anything in the weather', + lvl: 'Laminated veneer lumber: a stiff beam for a long span inside', + glulam: 'Glued-laminated timber: long spans, often left exposed', + steel: 'A steel W shape, for the longest spans', +} + +const TOP_DESCRIPTIONS: Record = { + 'ceiling-dropped': + 'Hangs under the ceiling plane, the top of the walls: the joists or rafters bear on it', + 'ceiling-flush': + 'In the ceiling framing, its underside on the ceiling plane: the joists hang from its sides', + 'floor-flush': 'Its top flush with the floor over it, in the floor framing', + 'floor-under': + "Under the floor's framing: a girder in a crawl space, or a deck's beam under its joists", +} + +const subscribe = (onChange: () => void) => useBeamDraft.subscribe(() => onChange()) + +/** The material row: picking another family brings its usual size along. */ +const materialOption: ToolOption = { + id: 'beam-material', + label: 'Material', + choices: (Object.keys(BEAM_MATERIAL_LABELS) as BeamMaterial[]).map((material) => ({ + value: material, + label: BEAM_MATERIAL_LABELS[material], + description: MATERIAL_DESCRIPTIONS[material], + })), + subscribe, + value: () => useBeamDraft.getState().material, + set: (value) => { + const material = value as BeamMaterial + const preset = beamSizePreset(useBeamDraft.getState().preset) + const keeps = preset && familyOf(preset.material) === familyOf(material) + useBeamDraft + .getState() + .set({ material, ...(keeps ? {} : { preset: FAMILY_SIZE[familyOf(material)] }) }) + }, +} + +/** One Size row per family, shown while its material is the one picked. */ +const sizeOptions: ToolOption[] = (['sawn', 'lvl', 'glulam', 'steel'] as const).map((family) => ({ + id: `beam-size-${family}`, + label: 'Size', + choices: BEAM_SIZE_PRESETS.filter((preset) => familyOf(preset.material) === family).map( + (preset) => ({ value: preset.id, label: preset.label.replace(/ (LVL|GLB)$/, '') }), + ), + subscribe, + value: () => useBeamDraft.getState().preset, + set: (value) => useBeamDraft.getState().set({ preset: value }), + visible: { + subscribe, + value: () => familyOf(useBeamDraft.getState().material) === family, + }, +})) + +const topOption: ToolOption = { + id: 'beam-top', + label: 'Top', + choices: BEAM_TOP_REFERENCES.map((top) => ({ + value: top, + label: BEAM_TOP_REFERENCE_LABELS[top], + description: TOP_DESCRIPTIONS[top], + })), + subscribe, + value: () => useBeamDraft.getState().top, + set: (value) => useBeamDraft.getState().set({ top: value as BeamTopReference }), +} + +/** The Beam tool's rows in the Build panel: material, size, top. */ +export const beamToolOptions: readonly ToolOption[] = [materialOption, ...sizeOptions, topOption] diff --git a/packages/nodes/src/beam/drafting.test.ts b/packages/nodes/src/beam/drafting.test.ts new file mode 100644 index 0000000000..747e2d99ee --- /dev/null +++ b/packages/nodes/src/beam/drafting.test.ts @@ -0,0 +1,173 @@ +import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BeamNode, + BuildingNode, + ColumnNode, + LevelNode, + nodeRegistry, + registerNode, + SlabNode, + WallNode, +} from '@pascal-app/core' +import { type SnappingMode, useEditor } from '@pascal-app/editor' +import { beamDefinition } from './definition' +import { useBeamDraft } from './draft-settings' +import { beamSnapTargets, draftBeam, snapBeamPoint } from './drafting' + +const IN = 0.0254 +type Nodes = Record +const previous = useEditor.getState() +let restore: () => void = () => {} + +beforeAll(() => { + restore = nodeRegistry._snapshot() + if (!nodeRegistry.has('beam')) registerNode(beamDefinition) +}) +afterAll(() => restore()) +afterEach(() => { + useEditor.setState(previous, true) + useBeamDraft.setState({ preset: '6x8', material: 'sawn', top: 'ceiling-dropped' }) +}) + +/** The beam tool armed, drafting under `mode`. */ +function drafting(mode: SnappingMode) { + useEditor.setState((state) => ({ + mode: 'build', + tool: 'beam', + gridSnapStep: 0.5, + snappingModeByContext: { ...state.snappingModeByContext, wall: mode }, + })) +} + +const WALL = WallNode.parse({ + id: 'wall_a', + parentId: 'level_g', + start: [0, 0], + end: [4, 0], + thickness: 0.2, +}) +const OTHER = BeamNode.parse({ id: 'beam_b', parentId: 'level_g', start: [0, 3], end: [4, 3] }) +const POST = ColumnNode.parse({ id: 'column_p', parentId: 'level_g', position: [6, 0, 6] }) +const ELSEWHERE = BeamNode.parse({ id: 'beam_u', parentId: 'level_u', start: [0, 9], end: [4, 9] }) + +function storey(extra: AnyNode[] = []): Nodes { + return Object.fromEntries( + [ + BuildingNode.parse({ id: 'building_a', children: ['level_g', 'level_u'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + height: 2.7, + children: ['slab_g', WALL.id, OTHER.id, POST.id, ...extra.map((n) => n.id)], + }), + LevelNode.parse({ id: 'level_u', parentId: 'building_a', level: 1, height: 2.7 }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + polygon: [ + [-1, -1], + [9, -1], + [9, 9], + [-1, 9], + ], + elevation: 0.05, + thickness: 0.25, + }), + WALL, + OTHER, + POST, + ELSEWHERE, + ...extra, + ].map((node) => [node.id, node]), + ) as Nodes +} + +describe('what a beam snaps to', () => { + test("the level's walls and both faces, other beams' centrelines and post centres", () => { + const targets = beamSnapTargets(storey(), 'level_g') + expect(targets.map((target) => target.id).sort()).toEqual( + ['beam_b', 'column_p', 'wall_a', 'wall_a:a', 'wall_a:b'].sort(), + ) + const faceA = targets.find((target) => target.id === 'wall_a:a')! + expect(faceA.start).toEqual([0, 0.1]) + expect(faceA.end).toEqual([4, 0.1]) + expect(targets.find((target) => target.id === 'column_p')).toMatchObject({ + start: [6, 6], + end: [6, 6], + }) + // the beam being edited is no target of its own + expect(beamSnapTargets(storey(), 'level_g', ['beam_b']).map((t) => t.id)).not.toContain( + 'beam_b', + ) + }) + + test('a point near a beam end, a post or a wall face corner lands on it', () => { + drafting('lines') + const nodes = storey() + const at = (point: [number, number]) => + snapBeamPoint({ point, nodes, levelId: 'level_g' }).point + expect(at([4.05, 3.04])).toEqual([4, 3]) + expect(at([6.03, 5.98])).toEqual([6, 6]) + const corner = at([0.03, 0.14]) + expect(corner[0]).toBeCloseTo(0) + expect(corner[1]).toBeCloseTo(0.1) + // Alt places it where the pointer is + expect(snapBeamPoint({ point: [4.05, 3.04], nodes, levelId: 'level_g', free: true })).toEqual({ + point: [4.05, 3.04], + snap: null, + }) + }) + + test('on the grid, a point lands on the grid away from anything to catch', () => { + drafting('grid') + expect(snapBeamPoint({ point: [2.2, 6.7], nodes: storey(), levelId: 'level_g' }).point).toEqual( + [2, 6.5], + ) + }) +}) + +describe('the beam a draft makes', () => { + test('takes the Build panel size, material and top', () => { + const nodes = storey() + const beam = draftBeam([1, 1], [5, 1], 'level_g', nodes, { + preset: '(3)2x12', + material: 'pressure-treated', + top: 'floor-under', + }) + expect(beam).toMatchObject({ + type: 'beam', + parentId: 'level_g', + start: [1, 1], + end: [5, 1], + size: '2x12', + plies: 3, + material: 'pressure-treated', + placement: 'dropped', + }) + expect(beam.width).toBeCloseTo(4.5 * IN) + // under the floor slab's body: 0.05 top, 0.25 thick + expect(beam.topElevation).toBeCloseTo(-0.2) + const under = draftBeam([1, 1], [5, 1], 'level_g', nodes, { + preset: '6x8', + material: 'sawn', + top: 'ceiling-dropped', + }) + expect(under.topElevation).toBeUndefined() + }) + + test('a preset the host armed the tool with wins over the panel', () => { + const beam = draftBeam( + [0, 0], + [3, 0], + 'level_g', + storey(), + { preset: '6x8', material: 'sawn', top: 'ceiling-dropped' }, + { material: 'glulam', width: 0.13, depth: 0.3, size: '5-1/8x12', topElevation: 2.4 }, + ) + expect(beam).toMatchObject({ material: 'glulam', width: 0.13, topElevation: 2.4 }) + expect(beam.start).toEqual([0, 0]) + }) +}) diff --git a/packages/nodes/src/beam/drafting.ts b/packages/nodes/src/beam/drafting.ts new file mode 100644 index 0000000000..72bffcd904 --- /dev/null +++ b/packages/nodes/src/beam/drafting.ts @@ -0,0 +1,154 @@ +import { + type AnyNode, + type AnyNodeId, + BeamNode, + beamTopReferencePatch, + createBeam, + getWallFaceLine, + type WallNode, +} from '@pascal-app/core' +import { + getSegmentGridStep, + isAngleSnapActive, + isMagneticSnapActive, + snapBuildingLocalToWorldGrid, + snapWallDraftPointDetailed, + type WallDraftSnapKind, + type WallPlanPoint, +} from '@pascal-app/editor' +import { type BeamDraftChoices, useBeamDraft } from './draft-settings' + +type Nodes = Readonly> + +/** A segment as the wall snap reads a wall: its ends, its middle and its body catch a point. */ +function reference( + id: string, + parentId: string | null, + start: WallPlanPoint, + end: WallPlanPoint, +): WallNode { + return { + object: 'node', + id: id as WallNode['id'], + type: 'wall', + parentId, + visible: true, + metadata: {}, + children: [], + start, + end, + frontSide: 'unknown', + backSide: 'unknown', + } +} + +const targetsMemo = new WeakMap>() + +/** + * What a beam drawn on `levelId` snaps to, through the wall snap (`snapWallDraftPoint`): the + * level's wall centrelines and both faces of each straight wall (so a beam end lands on a wall's + * face or its corner), other beams' centrelines (their ends, middles and runs), and every post + * and column centre (a segment of no length). Kept per scene, so a pointer move asks once. + */ +export function beamSnapTargets( + nodes: Nodes, + levelId: string, + ignoreIds: readonly string[] = [], +): WallNode[] { + const key = `${levelId}|${ignoreIds.join(',')}` + let byKey = targetsMemo.get(nodes) + if (!byKey) { + byKey = new Map() + targetsMemo.set(nodes, byKey) + } + const cached = byKey.get(key) + if (cached) return cached + const targets: WallNode[] = [] + for (const node of Object.values(nodes)) { + if (node.parentId !== levelId || node.visible === false || ignoreIds.includes(node.id)) continue + if (node.type === 'wall') { + targets.push(node) + if (Math.abs(node.curveOffset ?? 0) > 1e-6) continue + for (const face of ['a', 'b'] as const) { + const line = getWallFaceLine(node, face) + targets.push( + reference( + `${node.id}:${face}`, + node.parentId, + [line.start.x, line.start.y], + [line.end.x, line.end.y], + ), + ) + } + } else if (node.type === 'beam') { + targets.push(reference(node.id, node.parentId, node.start, node.end)) + } else if (node.type === 'column') { + const centre: WallPlanPoint = [node.position[0], node.position[2]] + targets.push(reference(node.id, node.parentId, centre, centre)) + } + } + byKey.set(key, targets) + return targets +} + +/** A snapped plan point and what caught it (null: the grid, an angle step, or nothing). */ +export type BeamPlanSnap = { point: WallPlanPoint; snap: WallDraftSnapKind | null } + +/** + * A beam point snapped as the beam tool snaps: wall and beam ends, faces, middles and crossings, + * post centres, the grid, and from `start` the angle steps. `free` (Alt) places it as it is. + */ +export function snapBeamPoint(args: { + point: WallPlanPoint + nodes: Nodes + levelId: string + start?: WallPlanPoint | null + ignoreIds?: readonly string[] + free?: boolean +}): BeamPlanSnap { + if (args.free) return { point: args.point, snap: null } + const step = getSegmentGridStep() + const angleSnap = !!args.start && isAngleSnapActive() + const result = snapWallDraftPointDetailed({ + point: args.point, + walls: beamSnapTargets(args.nodes, args.levelId, args.ignoreIds), + start: args.start ?? undefined, + angleSnap, + magnetic: isMagneticSnapActive(), + gridSnap: (point) => snapBuildingLocalToWorldGrid(point, step), + }) + return { point: result.point, snap: result.snap } +} + +/** + * The beam a draft makes from `start` to `end` on `levelId` with the tool's choices, its top set + * from the chosen reference; `seed` (a preset the host armed the tool with) overrides them. + */ +export function draftBeam( + start: WallPlanPoint, + end: WallPlanPoint, + levelId: string, + nodes: Nodes, + choices: BeamDraftChoices = useBeamDraft.getState(), + seed?: Readonly> | null, + fields: Partial = {}, +): BeamNode { + const base = createBeam({ + start, + end, + preset: choices.preset, + material: choices.material, + levelId, + }) + const placed = { ...base, ...beamTopReferencePatch(nodes, base, choices.top) } + return BeamNode.parse({ + ...placed, + ...(seed ?? {}), + ...fields, + id: base.id, + type: 'beam', + start, + end, + parentId: levelId, + }) +} diff --git a/packages/nodes/src/beam/floorplan-affordances.ts b/packages/nodes/src/beam/floorplan-affordances.ts new file mode 100644 index 0000000000..8ad2b26698 --- /dev/null +++ b/packages/nodes/src/beam/floorplan-affordances.ts @@ -0,0 +1,47 @@ +import { + type AnyNodeId, + type BeamNode, + type FloorplanAffordance, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { isSegmentLongEnough, type WallPlanPoint } from '@pascal-app/editor' +import { snapBeamPoint } from './drafting' +import type { BeamEndpointPayload } from './floorplan' + +/** + * A beam's end, dragged in the plan: snapped as the beam tool snaps, angle-stepped off the other + * end. Previews through live overrides, writes the last end on release, and refuses an end that + * would leave the beam too short to draw. + */ +export const beamEndpointAffordance: FloorplanAffordance = { + start({ node, payload, nodes }) { + const { endpoint } = payload as BeamEndpointPayload + const id = node.id as AnyNodeId + const fixed: WallPlanPoint = endpoint === 'start' ? node.end : node.start + let last: WallPlanPoint | null = null + return { + affectedIds: [id], + apply({ planPoint, modifiers }) { + last = node.parentId + ? snapBeamPoint({ + point: [planPoint[0], planPoint[1]], + nodes, + levelId: node.parentId, + start: fixed, + ignoreIds: [node.id], + free: modifiers.altKey, + }).point + : [planPoint[0], planPoint[1]] + useLiveNodeOverrides.getState().set(id, { [endpoint]: last }) + }, + canCommit() { + return last !== null && isSegmentLongEnough(fixed, last) + }, + commit() { + if (last) useScene.getState().updateNodes([{ id, data: { [endpoint]: last } }]) + useLiveNodeOverrides.getState().clear(id) + }, + } + }, +} diff --git a/packages/nodes/src/beam/floorplan-move.ts b/packages/nodes/src/beam/floorplan-move.ts new file mode 100644 index 0000000000..59691ea7da --- /dev/null +++ b/packages/nodes/src/beam/floorplan-move.ts @@ -0,0 +1,65 @@ +import { + type AnyNodeId, + type BeamNode, + type FloorplanMoveTarget, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { triggerSFX, type WallPlanPoint } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { snapBeamPoint } from './drafting' + +/** A beam translated by `delta`, both ends together. */ +export function movedBeamEnds( + beam: Pick, + delta: WallPlanPoint, +): Pick { + return { + start: [beam.start[0] + delta[0], beam.start[1] + delta[1]], + end: [beam.end[0] + delta[0], beam.end[1] + delta[1]], + } +} + +/** + * A beam moved in the plan: its start follows the pointer and snaps as the beam tool snaps (wall + * faces and ends, other beams, post centres, the grid); its end travels with it. Previews through + * live overrides, writes once on release. + */ +export const beamFloorplanMoveTarget: FloorplanMoveTarget = ({ node, nodes }) => { + const id = node.id as AnyNodeId + const origin: WallPlanPoint = [node.start[0], node.start[1]] + let anchor: WallPlanPoint | null = null + let delta: WallPlanPoint = [0, 0] + + return { + affectedIds: [id], + apply({ planPoint, modifiers }) { + if (!anchor) { + anchor = [planPoint[0], planPoint[1]] + return + } + const at = node.parentId + ? snapBeamPoint({ + point: [origin[0] + planPoint[0] - anchor[0], origin[1] + planPoint[1] - anchor[1]], + nodes, + levelId: node.parentId, + ignoreIds: [node.id], + free: modifiers.altKey, + }).point + : origin + const next: WallPlanPoint = [at[0] - origin[0], at[1] - origin[1]] + if (next[0] === delta[0] && next[1] === delta[1]) return + delta = next + triggerSFX('sfx:grid-snap') + useLiveNodeOverrides.getState().set(id, movedBeamEnds(node, delta)) + }, + canCommit() { + return delta[0] !== 0 || delta[1] !== 0 + }, + commit() { + useScene.getState().updateNodes([{ id, data: movedBeamEnds(node, delta) }]) + useLiveNodeOverrides.getState().clear(id) + useViewer.getState().setSelection({ selectedIds: [id] }) + }, + } +} diff --git a/packages/nodes/src/beam/floorplan.test.ts b/packages/nodes/src/beam/floorplan.test.ts new file mode 100644 index 0000000000..068d92c8f3 --- /dev/null +++ b/packages/nodes/src/beam/floorplan.test.ts @@ -0,0 +1,135 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BeamNode, + BuildingNode, + clearSceneHistory, + createBeam, + type FloorplanGeometry, + type GeometryContext, + LevelNode, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { readFloorplanGeometryMetadata } from '@pascal-app/editor' +import { buildBeamFloorplan } from './floorplan' +import { beamEndpointAffordance } from './floorplan-affordances' +import { beamFloorplanMoveTarget, movedBeamEnds } from './floorplan-move' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} + +const FT = 0.3048 +// Alt: the drags place freely, so the expected points are the pointer's. +const modifiers = { shiftKey: false, altKey: true, ctrlKey: false, metaKey: false } +const BEAM = createBeam({ start: [0, 0], end: [12 * FT, 0], preset: '(2)2x10', levelId: 'level_g' }) + +afterEach(() => { + useLiveNodeOverrides.getState().clearAll() +}) + +/** A storey holding the beam, as the scene store loads one. */ +function load(beam: BeamNode) { + const building = BuildingNode.parse({ id: 'building_a', children: ['level_g'] }) + const level = LevelNode.parse({ + id: 'level_g', + parentId: building.id, + level: 0, + height: 2.7, + children: [beam.id], + }) + const nodes = Object.fromEntries([building, level, beam].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() +} + +function flat(geometry: FloorplanGeometry | null): FloorplanGeometry[] { + if (!geometry) return [] + return geometry.kind === 'group' ? geometry.children.flatMap(flat) : [geometry] +} + +const view = (selected: boolean, unit: 'metric' | 'imperial' = 'imperial') => + ({ + viewState: { selected, highlighted: false, hovered: false, moving: false, unit }, + }) as unknown as GeometryContext + +test('a beam is drawn as a dashed double outline of its width, its size beside it', () => { + const parts = flat(buildBeamFloorplan(BEAM, view(false))) + const outline = parts.find((part) => part.kind === 'polygon') + expect(outline?.kind === 'polygon' && outline.strokeDasharray).toBeTruthy() + expect(outline?.kind === 'polygon' && outline.points.length).toBe(4) + // the outline spans the beam's 3 in across + const zs = outline?.kind === 'polygon' ? outline.points.map((point) => point[1]) : [] + expect(Math.max(...zs) - Math.min(...zs)).toBeCloseTo(3 * 0.0254) + expect(readFloorplanGeometryMetadata(outline as never)?.annotationObstacle).toBe('outline') + const labels = parts.filter((part) => part.kind === 'dimension-label') + expect(labels.map((label) => label.kind === 'dimension-label' && label.text)).toEqual([ + '(2) 2x10', + ]) + // a click near the line picks it, at any zoom + expect(parts.some((part) => part.kind === 'hit-line')).toBe(true) + // unselected: no handles + expect(parts.some((part) => part.kind === 'endpoint-handle')).toBe(false) +}) + +test('selected, it shows its end handles, a move arrow either side and its length', () => { + const parts = flat(buildBeamFloorplan(BEAM, view(true))) + const ends = parts.filter((part) => part.kind === 'endpoint-handle') + expect(ends.map((end) => end.kind === 'endpoint-handle' && end.payload)).toEqual([ + { endpoint: 'start' }, + { endpoint: 'end' }, + ]) + expect(parts.filter((part) => part.kind === 'move-arrow')).toHaveLength(2) + const labels = parts.flatMap((part) => (part.kind === 'dimension-label' ? [part.text] : [])) + expect(labels).toEqual(['(2) 2x10', `12'0"`]) + expect( + flat(buildBeamFloorplan(BEAM, view(true, 'metric'))).flatMap((part) => + part.kind === 'dimension-label' ? [part.text] : [], + ), + ).toContain('3.66m') +}) + +test('a beam of no length draws nothing', () => { + expect(buildBeamFloorplan(BeamNode.parse({ start: [1, 1], end: [1, 1] }), view(false))).toBeNull() +}) + +test('an end dragged in the plan previews live and writes once; a too-short beam is refused', () => { + load(BEAM) + const session = beamEndpointAffordance.start({ + node: BEAM, + payload: { endpoint: 'end' }, + nodes: useScene.getState().nodes, + initialPlanPoint: [12 * FT, 0], + gridSnapStep: 0.5, + }) + session.apply({ planPoint: [5, 1], modifiers }) + expect(useLiveNodeOverrides.getState().overrides.get(BEAM.id)).toEqual({ end: [5, 1] }) + expect(session.canCommit()).toBe(true) + session.commit?.() + expect((useScene.getState().nodes[BEAM.id] as BeamNode).end).toEqual([5, 1]) + expect(useLiveNodeOverrides.getState().overrides.get(BEAM.id)).toBeUndefined() + session.apply({ planPoint: [0.002, 0], modifiers }) + expect(session.canCommit()).toBe(false) +}) + +test('a plan move carries both ends together', () => { + expect(movedBeamEnds(BEAM, [1, 2])).toEqual({ start: [1, 2], end: [12 * FT + 1, 2] }) + load(BEAM) + const session = beamFloorplanMoveTarget({ node: BEAM, nodes: useScene.getState().nodes }) + session.apply({ planPoint: [2, 2], modifiers }) + session.apply({ planPoint: [3, 2.5], modifiers }) + expect(session.canCommit()).toBe(true) + session.commit?.() + const moved = useScene.getState().nodes[BEAM.id] as BeamNode + expect(moved.start).toEqual([1, 0.5]) + expect(moved.end[0]).toBeCloseTo(12 * FT + 1) +}) diff --git a/packages/nodes/src/beam/floorplan.ts b/packages/nodes/src/beam/floorplan.ts new file mode 100644 index 0000000000..1a6bf9df38 --- /dev/null +++ b/packages/nodes/src/beam/floorplan.ts @@ -0,0 +1,112 @@ +import { + type BeamNode, + beamOutline, + beamPlan, + beamSizeLabel, + type FloorplanGeometry, + type GeometryContext, +} from '@pascal-app/core' +import { + floorplanGeometryMetadata, + formatLinearMeasurement, + readFloorplanMetricNotationOverride, +} from '@pascal-app/editor' + +/** Hidden work: a beam is over the plan's cut, so it is drawn dashed, as a framing plan draws it. */ +const HIDDEN_DASH = '8 4' +const MOVE_ARROW_OFFSET = 0.17 +/** Screen pixels between the beam's line and its size label (and, selected, its length). */ +const LABEL_OFFSET_PX = 11 + +export type BeamEndpointPayload = { endpoint: 'start' | 'end' } + +/** + * A beam in the plan, the way a framing plan draws one: its width as a dashed double outline and + * its size beside it ("6x8", "(2) 2x10"), over a clear hit area. Selected, it gets its end handles, + * a move arrow either side and its length. + */ +export function buildBeamFloorplan(node: BeamNode, ctx: GeometryContext): FloorplanGeometry | null { + const plan = beamPlan(node) + if (plan.length < 1e-6) return null + const view = ctx.viewState + const selected = view?.selected ?? false + const chrome = selected || (view?.highlighted ?? false) + const stroke = + chrome && view?.palette + ? view.palette.selectedStroke + : view?.hovered && view.palette + ? view.palette.wallHoverStroke + : '#374151' + const mid: [number, number] = [ + (plan.start[0] + plan.end[0]) / 2, + (plan.start[1] + plan.end[1]) / 2, + ] + const angle = Math.atan2(plan.direction[1], plan.direction[0]) + + const children: FloorplanGeometry[] = [ + { + kind: 'polygon', + points: beamOutline(node), + fill: chrome ? '#fed7aa' : 'transparent', + fillOpacity: chrome ? 0.35 : undefined, + stroke, + strokeWidth: chrome ? 1.75 : 1.25, + strokeDasharray: HIDDEN_DASH, + vectorEffect: 'non-scaling-stroke', + // A room's name and area move off a beam rather than hide it. + metadata: floorplanGeometryMetadata({ annotationObstacle: 'outline' }), + }, + // A beam is a few inches wide: a click near its line picks it at any zoom. + { + kind: 'hit-line', + x1: plan.start[0], + y1: plan.start[1], + x2: plan.end[0], + y2: plan.end[1], + strokeWidthPx: 12, + }, + { + kind: 'dimension-label', + cx: mid[0], + cy: mid[1], + text: beamSizeLabel(node), + angle, + offsetPx: LABEL_OFFSET_PX, + appearance: 'outlined', + }, + ] + + if (selected) { + for (const endpoint of ['start', 'end'] as const) + children.push({ + kind: 'endpoint-handle', + point: plan[endpoint], + state: 'idle', + affordance: 'move-endpoint', + payload: { endpoint } satisfies BeamEndpointPayload, + }) + for (const side of [1, -1] as const) { + // No affordance: the layer hands the beam to `floorplanMoveTarget`. + const offset = (plan.width / 2 + MOVE_ARROW_OFFSET) * side + children.push({ + kind: 'move-arrow', + point: [mid[0] + plan.normal[0] * offset, mid[1] + plan.normal[1] * offset], + angle: Math.atan2(plan.normal[1] * side, plan.normal[0] * side), + }) + } + if (plan.length >= 0.1) + children.push({ + kind: 'dimension-label', + cx: mid[0], + cy: mid[1], + text: formatLinearMeasurement( + plan.length, + view?.unit ?? 'metric', + readFloorplanMetricNotationOverride(ctx) ?? 'meters', + ), + angle, + offsetPx: -LABEL_OFFSET_PX - 4, + }) + } + return { kind: 'group', children } +} diff --git a/packages/nodes/src/beam/geometry.ts b/packages/nodes/src/beam/geometry.ts new file mode 100644 index 0000000000..e410ec5eb9 --- /dev/null +++ b/packages/nodes/src/beam/geometry.ts @@ -0,0 +1,80 @@ +import { type BeamNode, beamPlan } from '@pascal-app/core' +import { BoxGeometry, type BufferGeometry } from 'three' +import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js' +import { applyPlanarWorldUvs, copyUvToSecondaryChannel } from '../shared/primitive-uv' + +/** The seam left between the plies of a built-up beam, so a (2) 2x10 reads as two boards. */ +const PLY_SEAM = 0.0015 + +/** + * Where a beam's box sits in its level, level-local: its centre, its turn about Y (local X runs + * start → end) and its size along, up and across. Null for a beam of no length. + */ +export type BeamBox = { + position: [number, number, number] + rotationY: number + size: [number, number, number] +} + +export function beamBox( + beam: Pick, + top: number, +): BeamBox | null { + const plan = beamPlan(beam) + if (plan.length < 1e-6) return null + return { + position: [ + (plan.start[0] + plan.end[0]) / 2, + top - beam.depth / 2, + (plan.start[1] + plan.end[1]) / 2, + ], + // Three.js turns local +X to (cos θ, 0, −sin θ): θ = −atan2(dz, dx) runs it along the beam. + rotationY: -Math.atan2(plan.direction[1], plan.direction[0]), + size: [plan.length, beam.depth, beam.width], + } +} + +function box(length: number, height: number, across: number, y: number, z: number): BufferGeometry { + const geometry = new BoxGeometry(length, height, across).toNonIndexed() + geometry.translate(0, y, z) + return geometry +} + +/** + * The beam's solid about its own centre, local X along it: one block for a solid timber, glulam + * or a single LVL; its plies side by side with a hairline between for a built-up beam; a W shape's + * two flanges and web for steel. UVs run a metre to the texture repeat, the grain along the beam. + */ +export function buildBeamGeometry( + beam: Pick, + length: number, +): BufferGeometry { + const { width, depth } = beam + let parts: BufferGeometry[] + if (beam.material === 'steel') { + // A W8x10's flanges are 0.205 in, its web 0.17 in: the same proportions at any size. + const flange = Math.max(0.004, depth * 0.026) + const web = Math.max(0.003, width * 0.043) + parts = [ + box(length, flange, width, depth / 2 - flange / 2, 0), + box(length, flange, width, -depth / 2 + flange / 2, 0), + box(length, depth - flange * 2, web, 0, 0), + ] + } else if (beam.plies > 1) { + // The seams fall between the plies; the outer faces stay the beam's. + const seam = Math.min(PLY_SEAM, width / beam.plies / 2) + const ply = (width - seam * (beam.plies - 1)) / beam.plies + parts = Array.from({ length: beam.plies }, (_, index) => + box(length, depth, ply, 0, -width / 2 + ply / 2 + index * (ply + seam)), + ) + } else { + parts = [box(length, depth, width, 0, 0)] + } + const merged = parts.length === 1 ? parts[0]! : (mergeGeometries(parts) ?? parts[0]!) + if (merged !== parts[0]) for (const part of parts) part.dispose() + applyPlanarWorldUvs(merged) + copyUvToSecondaryChannel(merged) + merged.computeBoundingBox() + merged.computeBoundingSphere() + return merged +} diff --git a/packages/nodes/src/beam/index.ts b/packages/nodes/src/beam/index.ts new file mode 100644 index 0000000000..f9fb6a8403 --- /dev/null +++ b/packages/nodes/src/beam/index.ts @@ -0,0 +1,2 @@ +export { BeamNode } from '@pascal-app/core' +export { beamDefinition } from './definition' diff --git a/packages/nodes/src/beam/move-endpoint-tool.tsx b/packages/nodes/src/beam/move-endpoint-tool.tsx new file mode 100644 index 0000000000..06401ae632 --- /dev/null +++ b/packages/nodes/src/beam/move-endpoint-tool.tsx @@ -0,0 +1,93 @@ +'use client' + +import { + type AnyNode, + type BeamNode, + beamTopElevation, + sceneRegistry, + useScene, +} from '@pascal-app/core' +import { + CursorSphere, + MeasurementPill, + triggerSFX, + useDragAction, + useInteractionScope, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { Html } from '@react-three/drei' +import { createPortal } from '@react-three/fiber' +import { moveBeamEndpointDragAction } from './actions/move-endpoint' + +/** + * The reshaped node and which end: the tool manager builds it from the endpoint scope, naming a + * kind it has no slot for under `wall`. + */ +export type MovingBeamEndpoint = { + wall?: AnyNode + fence?: AnyNode + beam?: AnyNode + endpoint: 'start' | 'end' +} + +/** + * A beam's end dragged in 3D: the thin wrapper around `moveBeamEndpointDragAction`, which owns + * the snapping and the single undo step. Shows the cursor at the moving end and the beam's height, + * length and width while it runs. Mounted by the tool manager from `move-endpoint`. + */ +export const MoveBeamEndpointTool: React.FC<{ target: MovingBeamEndpoint }> = ({ target }) => { + const initial = (target.beam ?? target.wall ?? target.fence) as BeamNode | undefined + const unit = useViewer((state) => state.unit) + const beamId = initial?.id + const live = useScene((state) => (beamId ? state.nodes[beamId] : undefined)) + const beam = live?.type === 'beam' ? live : initial + const top = useScene((state) => (beam ? beamTopElevation(state.nodes, beam) : 0)) + + const exit = (committed: boolean) => { + if (committed) triggerSFX('sfx:item-place') + if (beamId) useViewer.getState().setSelection({ selectedIds: [beamId] }) + useInteractionScope + .getState() + .endIf((scope) => scope.kind === 'reshaping' && scope.reshape === 'endpoint') + } + + useDragAction({ + active: initial?.type === 'beam', + action: moveBeamEndpointDragAction, + initial: { + node: initial, + handleId: target.endpoint, + point: initial ? initial[target.endpoint] : [0, 0], + }, + onCommit: () => exit(true), + onCancel: () => exit(false), + }) + + const level = beam?.parentId ? sceneRegistry.nodes.get(beam.parentId) : undefined + if (!(beam?.type === 'beam' && level)) return null + const moving = beam[target.endpoint] + const length = Math.hypot(beam.end[0] - beam.start[0], beam.end[1] - beam.start[1]) + // In the beam's level, where its top is measured. + return createPortal( + + + + + + , + level, + ) +} + +export default MoveBeamEndpointTool diff --git a/packages/nodes/src/beam/move-tool.tsx b/packages/nodes/src/beam/move-tool.tsx new file mode 100644 index 0000000000..a573e82aa7 --- /dev/null +++ b/packages/nodes/src/beam/move-tool.tsx @@ -0,0 +1,140 @@ +'use client' + +import { + type AnyNodeId, + type BeamNode, + beamTopElevation, + emitter, + type GridEvent, + sceneRegistry, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + CursorSphere, + consumePlacementDragRelease, + isFreshPlacementMetadata, + markToolCancelConsumed, + stripPlacementMetadataFlags, + triggerSFX, + useEditor, + type WallPlanPoint, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { createPortal } from '@react-three/fiber' +import { useEffect, useRef, useState } from 'react' +import { snapBeamPoint } from './drafting' +import { movedBeamEnds } from './floorplan-move' + +/** + * A beam moved in 3D, both ends together: its start follows the pointer, snapped as the beam tool + * snaps (wall faces and ends, other beams, post centres, the grid; Alt free), the run keeping its + * length and angle. Previews through live overrides and writes once on the click, one undo step; + * Esc puts it back. Mounted by the move dispatcher from `affordanceTools.move`. + */ +export const MoveBeamTool: React.FC<{ node: BeamNode }> = ({ node }) => { + const activatedAt = useRef(Date.now()) + const [cursor, setCursor] = useState<[number, number, number] | null>(null) + const top = useScene((state) => beamTopElevation(state.nodes, node)) + + useEffect(() => { + const id = node.id as AnyNodeId + const origin: WallPlanPoint = [node.start[0], node.start[1]] + const fresh = isFreshPlacementMetadata(node.metadata) + let anchor: WallPlanPoint | null = null + let delta: WallPlanPoint = [0, 0] + let committed = false + + const exit = () => useEditor.getState().setMovingNode(null) + const preview = () => { + useLiveNodeOverrides.getState().set(id, movedBeamEnds(node, delta)) + useScene.getState().markDirty(id) + } + + const onGridMove = (event: GridEvent) => { + const raw: WallPlanPoint = [event.localPosition[0], event.localPosition[2]] + anchor ??= raw + const at = node.parentId + ? snapBeamPoint({ + point: [origin[0] + raw[0] - anchor[0], origin[1] + raw[1] - anchor[1]], + nodes: useScene.getState().nodes, + levelId: node.parentId, + ignoreIds: [node.id], + free: event.nativeEvent?.altKey === true, + }).point + : origin + const next: WallPlanPoint = [at[0] - origin[0], at[1] - origin[1]] + if (next[0] === delta[0] && next[1] === delta[1]) return + delta = next + triggerSFX('sfx:grid-snap') + const moved = movedBeamEnds(node, delta) + setCursor([(moved.start[0] + moved.end[0]) / 2, 0, (moved.start[1] + moved.end[1]) / 2]) + preview() + } + + const onGridClick = (event: GridEvent) => { + if (committed) return + if (Date.now() - activatedAt.current < 150) { + event.nativeEvent?.stopPropagation?.() + return + } + committed = true + const moved = movedBeamEnds(node, delta) + useScene.getState().updateNodes([ + { + id, + data: fresh + ? { + ...moved, + metadata: stripPlacementMetadataFlags(node.metadata) as BeamNode['metadata'], + } + : moved, + }, + ]) + useLiveNodeOverrides.getState().clear(id) + triggerSFX('sfx:item-place') + useViewer.getState().setSelection({ selectedIds: [id] }) + exit() + event.nativeEvent?.stopPropagation?.() + } + + const onPlacementDragPointerUp = (event: PointerEvent) => { + if (!consumePlacementDragRelease(event)) return + activatedAt.current = 0 + onGridClick({ nativeEvent: event } as unknown as GridEvent) + } + + const onCancel = () => { + useLiveNodeOverrides.getState().clear(id) + useScene.getState().markDirty(id) + useViewer.getState().setSelection({ selectedIds: [id] }) + markToolCancelConsumed() + // This tool owns the teardown: the 2D overlay leaves the beam to it. + useEditor.getState().setMovingNodeOrigin('3d') + exit() + } + + emitter.on('grid:move', onGridMove) + emitter.on('grid:click', onGridClick) + emitter.on('tool:cancel', onCancel) + window.addEventListener('pointerup', onPlacementDragPointerUp) + return () => { + // Only the live preview is ever undone here: a move the 2D overlay committed is in the + // scene already, and an override is never the scene. + if (!committed) useLiveNodeOverrides.getState().clear(id) + emitter.off('grid:move', onGridMove) + emitter.off('grid:click', onGridClick) + emitter.off('tool:cancel', onCancel) + window.removeEventListener('pointerup', onPlacementDragPointerUp) + } + }, [node]) + + const level = node.parentId ? sceneRegistry.nodes.get(node.parentId) : undefined + if (!(cursor && level)) return null + return createPortal( + , + level, + ) +} + +export default MoveBeamTool diff --git a/packages/nodes/src/beam/panel-model.test.ts b/packages/nodes/src/beam/panel-model.test.ts new file mode 100644 index 0000000000..981781a8c8 --- /dev/null +++ b/packages/nodes/src/beam/panel-model.test.ts @@ -0,0 +1,109 @@ +import { beforeEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + type BeamNode, + BuildingNode, + clearSceneHistory, + createBeam, + LevelNode, + SlabNode, + useScene, +} from '@pascal-app/core' +import { beamPliesPatch } from './panel' +import { beamPanelModel, beamSizeOptions, beamSizeValue } from './panel-model' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} + +const IN = 0.0254 +const A = createBeam({ start: [0, 0], end: [3, 0], preset: '6x8', levelId: 'level_g' }) +const B = createBeam({ start: [0, 2], end: [3, 2], preset: '6x8', levelId: 'level_g' }) + +beforeEach(() => { + const building = BuildingNode.parse({ id: 'building_a', children: ['level_g'] }) + const level = LevelNode.parse({ + id: 'level_g', + parentId: building.id, + level: 0, + height: 2.7, + children: ['slab_g', A.id, B.id], + }) + const slab = SlabNode.parse({ + id: 'slab_g', + parentId: level.id, + polygon: [ + [-1, -1], + [5, -1], + [5, 5], + [-1, 5], + ], + elevation: 0.05, + thickness: 0.2, + }) + const nodes = Object.fromEntries([building, level, slab, A, B].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() +}) + +const read = (id: string) => useScene.getState().nodes[id as AnyNodeId] as BeamNode +const controls = () => + beamPanelModel.multiControls!({ + selection: [read(A.id), read(B.id)], + nodes: useScene.getState().nodes, + }) + +test('selected beams take a size, a material and a top together, in one undo step', () => { + const [size, material, top] = controls() + expect(size).toMatchObject({ id: 'beam-size', value: '6x8' }) + expect(material).toMatchObject({ id: 'beam-material', value: 'sawn' }) + expect(top).toMatchObject({ id: 'beam-top', value: 'ceiling-dropped' }) + if (size?.kind !== 'select') throw new Error('size is a select') + size.onChange('(3)2x10') + for (const id of [A.id, B.id]) { + expect(read(id)).toMatchObject({ size: '2x10', plies: 3 }) + expect(read(id).width).toBeCloseTo(4.5 * IN) + } + const [, , nextTop] = controls() + if (nextTop?.kind !== 'select') throw new Error('top is a select') + nextTop.onChange('floor-flush') + expect(read(A.id)).toMatchObject({ topElevation: 0.05, placement: 'flush' }) + useScene.temporal.getState().undo() + expect(read(B.id).topElevation).toBeUndefined() +}) + +test('a mixed selection shows no value; a custom section reads Custom', () => { + useScene.getState().updateNode(B.id, { material: 'lvl' }) + const [, material] = controls() + expect(material?.kind === 'select' && material.value).toBeNull() + const custom = { ...read(A.id), width: 2 * IN, size: undefined } + expect(beamSizeValue(custom)).toBe('custom') + expect(beamSizeOptions('custom')[0]).toEqual({ label: 'Custom', value: 'custom' }) + expect(beamSizeOptions('6x8').some((option) => option.value === 'custom')).toBe(false) +}) + +test('the XR rows step through sizes and materials', () => { + const rows = beamPanelModel.rows({ + node: read(A.id), + nodes: useScene.getState().nodes, + update: (patch) => useScene.getState().updateNode(A.id, patch), + }) + const size = rows.find((row) => row.id === 'beam-size') + expect(size?.kind === 'cycle' && size.value).toBe('6x8') + if (size?.kind === 'cycle') size.next() + expect(read(A.id).size).toBe('6x10') +}) + +test('plies add boards of the same width', () => { + const doubled = createBeam({ start: [0, 0], end: [1, 0], preset: '(2)2x10' }) + expect(beamPliesPatch(doubled, 3).width).toBeCloseTo(4.5 * IN) + expect(beamPliesPatch(doubled, 1)).toMatchObject({ plies: 1 }) +}) diff --git a/packages/nodes/src/beam/panel-model.ts b/packages/nodes/src/beam/panel-model.ts new file mode 100644 index 0000000000..c773b04a01 --- /dev/null +++ b/packages/nodes/src/beam/panel-model.ts @@ -0,0 +1,157 @@ +import { + type AnyNode, + type AnyNodeId, + BEAM_MATERIAL_LABELS, + BEAM_SIZE_PRESETS, + BEAM_TOP_REFERENCE_LABELS, + BEAM_TOP_REFERENCES, + type BeamMaterial, + type BeamNode, + type BeamTopReference, + beamSizePresetPatch, + beamTopReferenceOf, + beamTopReferencePatch, + matchBeamSizePreset, + runAsSingleSceneHistoryStep, + useScene, +} from '@pascal-app/core' +import type { NodePanelModel } from '@pascal-app/editor' + +type Nodes = Record + +const CUSTOM = 'custom' + +/** The Size choices: every stock size, and Custom while the beam is no stock size. */ +export function beamSizeOptions(current: string | null) { + return [ + ...(current === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ...BEAM_SIZE_PRESETS.map((preset) => ({ label: preset.label, value: preset.id })), + ] +} + +/** Which Size choice a beam is: its stock size, or Custom. */ +export function beamSizeValue(beam: BeamNode): string { + return matchBeamSizePreset(beam)?.id ?? CUSTOM +} + +export const BEAM_MATERIAL_OPTIONS = (Object.keys(BEAM_MATERIAL_LABELS) as BeamMaterial[]).map( + (material) => ({ label: BEAM_MATERIAL_LABELS[material], value: material }), +) + +/** The Top choices: the references, and Custom while the top is typed in. */ +export function beamTopOptions(current: string | null) { + return [ + ...BEAM_TOP_REFERENCES.map((top) => ({ label: BEAM_TOP_REFERENCE_LABELS[top], value: top })), + ...(current === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ] +} + +export function beamTopValue(nodes: Nodes, beam: BeamNode): string { + return beamTopReferenceOf(nodes, beam) ?? CUSTOM +} + +/** The one value every beam shares, or null for a mixed selection. */ +function shared(values: string[]): string | null { + return values.every((value) => value === values[0]) ? (values[0] ?? null) : null +} + +/** Writes one patch per beam in a single undo step. */ +function updateAll(beams: BeamNode[], patch: (beam: BeamNode) => Partial) { + runAsSingleSceneHistoryStep(useScene, () => + useScene + .getState() + .updateNodes(beams.map((beam) => ({ id: beam.id as AnyNodeId, data: patch(beam) }))), + ) +} + +/** Steps through a list of choices, wrapping round. */ +function cycle(list: readonly T[], current: T | string, step: 1 | -1): T { + const index = list.indexOf(current as T) + return list[(index + step + list.length) % list.length]! +} + +/** + * The beam's settings as panel rows (the XR wand's panel) and as controls the multi-selection + * inspector applies to every selected beam at once: size, material, top. A porch's beams take a + * new size together. + */ +export const beamPanelModel: NodePanelModel = { + rows: ({ node, nodes, update }) => { + const sizes = BEAM_SIZE_PRESETS.map((preset) => preset.id as string) + const materials = BEAM_MATERIAL_OPTIONS.map((option) => option.value) + const size = beamSizeValue(node) + const top = beamTopValue(nodes, node) + return [ + { + id: 'beam-size', + kind: 'cycle', + section: 'Beam', + label: 'Size', + value: BEAM_SIZE_PRESETS.find((preset) => preset.id === size)?.label ?? 'Custom', + next: () => update(beamSizePresetPatch(node, cycle(sizes, size, 1))), + previous: () => update(beamSizePresetPatch(node, cycle(sizes, size, -1))), + }, + { + id: 'beam-material', + kind: 'cycle', + section: 'Beam', + label: 'Material', + value: BEAM_MATERIAL_LABELS[node.material], + next: () => update({ material: cycle(materials, node.material, 1) }), + previous: () => update({ material: cycle(materials, node.material, -1) }), + }, + { + id: 'beam-top', + kind: 'cycle', + section: 'Beam', + label: 'Top', + value: top === CUSTOM ? 'Custom' : BEAM_TOP_REFERENCE_LABELS[top as BeamTopReference], + next: () => update(beamTopReferencePatch(nodes, node, cycle(BEAM_TOP_REFERENCES, top, 1))), + previous: () => + update(beamTopReferencePatch(nodes, node, cycle(BEAM_TOP_REFERENCES, top, -1))), + }, + ] + }, + multiControls: ({ selection, nodes }) => { + if (selection.length === 0) return [] + const size = shared(selection.map(beamSizeValue)) + const top = shared(selection.map((beam) => beamTopValue(nodes, beam))) + return [ + { + id: 'beam-size', + kind: 'select', + section: 'Beam', + label: 'Size', + options: beamSizeOptions(size), + value: size, + onChange: (id) => { + if (id !== CUSTOM) updateAll(selection, (beam) => beamSizePresetPatch(beam, id)) + }, + }, + { + id: 'beam-material', + kind: 'select', + section: 'Beam', + label: 'Material', + options: BEAM_MATERIAL_OPTIONS, + value: shared(selection.map((beam) => beam.material)), + onChange: (material) => + updateAll(selection, () => ({ material: material as BeamMaterial })), + }, + { + id: 'beam-top', + kind: 'select', + section: 'Beam', + label: 'Top', + options: beamTopOptions(top), + value: top, + onChange: (reference) => { + if (reference === CUSTOM) return + updateAll(selection, (beam) => + beamTopReferencePatch(nodes, beam, reference as BeamTopReference), + ) + }, + }, + ] + }, +} diff --git a/packages/nodes/src/beam/panel.tsx b/packages/nodes/src/beam/panel.tsx new file mode 100644 index 0000000000..8907fc00b7 --- /dev/null +++ b/packages/nodes/src/beam/panel.tsx @@ -0,0 +1,231 @@ +'use client' + +import { + type AnyNodeId, + type BeamMaterial, + type BeamNode, + type BeamPlacement, + type BeamTopReference, + beamCeilingPlane, + beamPlan, + beamSizeLabel, + beamSizePresetPatch, + beamTopElevation, + beamTopReferencePatch, + DEFAULT_FRAMING_POST_SIZE, + useScene, +} from '@pascal-app/core' +import { + ActionButton, + ActionGroup, + formatLinearMeasurement, + MetricControl, + PanelSection, + PanelWrapper, + SegmentedControl, + SelectControl, + triggerSFX, + useEditor, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { Columns2, Move, Trash2 } from 'lucide-react' +import { useState } from 'react' +import { electedPostFoot, framingPostsUnderBeam } from '../column/post-placement' +import { + BEAM_MATERIAL_OPTIONS, + beamSizeOptions, + beamSizeValue, + beamTopOptions, + beamTopValue, +} from './panel-model' + +const IN = 0.0254 +const CUSTOM = 'custom' + +/** A section size in metres, shown in inches on an imperial plan (feet would show 7-1/2 in as 0.63). */ +function InchField({ + label, + value, + onCommit, +}: { + label: string + value: number + onCommit: (meters: number) => void +}) { + const [draft, setDraft] = useState(null) + const imperial = useViewer((state) => state.unit) === 'imperial' + const toShown = (meters: number) => (imperial ? meters / IN : meters) + return ( + setDraft(next)} + onCommit={(next) => { + setDraft(null) + onCommit(imperial ? next * IN : next) + }} + precision={imperial ? 3 : 4} + restoreOnCommit={false} + step={imperial ? 0.25 : 0.005} + unit={imperial ? 'in' : 'm'} + value={draft ?? toShown(value)} + /> + ) +} + +/** The fields a ply count writes: as many plies of the same board, the width growing with them. */ +export function beamPliesPatch(beam: Pick, plies: number) { + return { plies, width: (beam.width / beam.plies) * plies } +} + +/** + * A beam's panel: its size (a stock size, or a width, depth and plies of its own), what it is made + * of, how the joists meet it and where its top is, with its length and underside; Move, Delete, + * and posts under it at each end and every 8 ft. + */ +export default function BeamPanel() { + const selectedId = useViewer((state) => state.selection.selectedIds[0]) + const setSelection = useViewer((state) => state.setSelection) + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const nodes = useScene((state) => state.nodes) + const beam = selectedId ? nodes[selectedId as AnyNodeId] : undefined + if (beam?.type !== 'beam') return null + + const update = (patch: Partial) => useScene.getState().updateNode(beam.id, patch) + const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation) + const size = beamSizeValue(beam) + const top = beamTopValue(nodes, beam) + const topY = beamTopElevation(nodes, beam) + const plane = beamCeilingPlane(nodes, beam) + + return ( + setSelection({ selectedIds: [] })} + title={beam.name ?? 'Beam'} + width={300} + > + + { + if (id !== CUSTOM) update(beamSizePresetPatch(beam, id)) + }} + options={beamSizeOptions(size)} + value={size} + /> + update({ material: material as BeamMaterial })} + options={BEAM_MATERIAL_OPTIONS} + value={beam.material} + /> + update({ width, size: undefined })} + value={beam.width} + /> + update({ depth, size: undefined })} + value={beam.depth} + /> + update(beamPliesPatch(beam, Number(plies)))} + options={['1', '2', '3', '4'].map((plies) => ({ + label: plies === '1' ? '1 ply' : `${plies} plies`, + value: plies, + }))} + value={String(beam.plies)} + /> + + + update({ placement })} + options={[ + { label: 'Dropped', value: 'dropped' }, + { label: 'Flush', value: 'flush' }, + ]} + value={beam.placement} + /> + { + if (reference !== CUSTOM) + update(beamTopReferencePatch(nodes, beam, reference as BeamTopReference)) + }} + options={beamTopOptions(top)} + value={top} + /> + update({ topElevation: value })} + precision={3} + step={0.01} + unit="m" + value={topY} + /> +

+ {length(beamPlan(beam).length)} long; its underside {length(topY - beam.depth)} over the + floor, the ceiling plane {length(plane)}. +

+ {beam.topElevation !== undefined && ( + + )} +
+ + + } + label="Add posts" + onClick={() => { + const posts = beam.parentId + ? framingPostsUnderBeam({ + nodes, + beam, + choices: { size: DEFAULT_FRAMING_POST_SIZE, base: 'none' }, + foot: electedPostFoot(nodes, beam.parentId), + }) + : [] + if (posts.length === 0) return + const standing = Object.values(nodes).filter( + (node) => node.type === 'column' && node.role === 'post', + ).length + useScene.getState().createNodes( + posts.map((post, index) => ({ + node: { ...post, name: `Post ${standing + index + 1}` }, + parentId: beam.parentId as AnyNodeId, + })), + ) + triggerSFX('sfx:structure-build') + }} + /> + } + label="Move" + onClick={() => { + triggerSFX('sfx:item-pick') + useEditor.getState().setMovingNode(beam) + }} + /> + } + label="Delete" + onClick={() => { + useScene.getState().deleteNode(beam.id) + setSelection({ selectedIds: [] }) + }} + /> + + +
+ ) +} diff --git a/packages/nodes/src/beam/parametrics.ts b/packages/nodes/src/beam/parametrics.ts new file mode 100644 index 0000000000..f0bd58b84f --- /dev/null +++ b/packages/nodes/src/beam/parametrics.ts @@ -0,0 +1,6 @@ +import type { BeamNode, ParametricDescriptor } from '@pascal-app/core' + +export const beamParametrics: ParametricDescriptor = { + groups: [], + customPanel: () => import('./panel'), +} diff --git a/packages/nodes/src/beam/renderer.tsx b/packages/nodes/src/beam/renderer.tsx new file mode 100644 index 0000000000..ff78b4e75c --- /dev/null +++ b/packages/nodes/src/beam/renderer.tsx @@ -0,0 +1,77 @@ +'use client' + +import { + BEAM_MATERIAL_LIBRARY_REFS, + type BeamNode, + beamTopElevation, + useLiveNodeOverrides, + useRegistry, + useScene, +} from '@pascal-app/core' +import { + createSurfaceRoleMaterial, + resolveSlotDefaultMaterial, + useNodeEvents, + useViewer, +} from '@pascal-app/viewer' +import { useEffect, useMemo, useRef } from 'react' +import type { Group, Material } from 'three' +import { beamBox, buildBeamGeometry } from './geometry' + +/** How a beam of `material` looks: its library material, or the plain surface colour when textures are off. */ +export function useBeamMaterial(material: BeamNode['material']): Material { + const shading = useViewer((state) => state.shading) + const textures = useViewer((state) => state.textures) + const colorPreset = useViewer((state) => state.colorPreset) + return useMemo( + () => + textures + ? resolveSlotDefaultMaterial(BEAM_MATERIAL_LIBRARY_REFS[material], shading) + : createSurfaceRoleMaterial('wall', colorPreset), + [material, shading, textures, colorPreset], + ) +} + +/** + * A beam: its solid laid along the centreline in its level, the top where the beam's top resolves + * (`beamTopElevation`: its own, else the ceiling plane over it). Reads live overrides, so a drag + * in either view moves it at once. + */ +export default function BeamRenderer({ node: stored }: { node: BeamNode }) { + const ref = useRef(null!) + useRegistry(stored.id, 'beam', ref) + const live = useLiveNodeOverrides((state) => state.overrides.get(stored.id)) + const node = useMemo(() => (live ? ({ ...stored, ...live } as BeamNode) : stored), [stored, live]) + const handlers = useNodeEvents(node, 'beam') + const top = useScene((state) => beamTopElevation(state.nodes, node)) + const material = useBeamMaterial(node.material) + const placed = beamBox(node, top) + const length = placed?.size[0] ?? 0 + const geometry = useMemo( + () => + length > 0 + ? buildBeamGeometry( + { width: node.width, depth: node.depth, plies: node.plies, material: node.material }, + length, + ) + : null, + [length, node.width, node.depth, node.plies, node.material], + ) + useEffect(() => () => geometry?.dispose(), [geometry]) + + return ( + + {placed && geometry && ( + + )} + + ) +} diff --git a/packages/nodes/src/beam/schema.ts b/packages/nodes/src/beam/schema.ts new file mode 100644 index 0000000000..704388383a --- /dev/null +++ b/packages/nodes/src/beam/schema.ts @@ -0,0 +1 @@ +export { BeamNode } from '@pascal-app/core' diff --git a/packages/nodes/src/beam/tool.test.ts b/packages/nodes/src/beam/tool.test.ts new file mode 100644 index 0000000000..47249072e5 --- /dev/null +++ b/packages/nodes/src/beam/tool.test.ts @@ -0,0 +1,126 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + type BeamNode, + BuildingNode, + clearSceneHistory, + emitter, + type GridEvent, + LevelNode, + useScene, +} from '@pascal-app/core' +import { + getFloorplanNodeExtension, + sceneLayout, + useEditor, + usePlacementPreview, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import { beamDefinition } from './definition' +import { useBeamDraft } from './draft-settings' +import { beamDraftDimensions } from './tool' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } + +afterEach(() => { + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) + usePlacementPreview.getState().clear() + useBeamDraft.setState({ preset: '6x8', material: 'sawn', top: 'ceiling-dropped' }) +}) + +function setScene() { + const building = BuildingNode.parse({}) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 2.7 }) + building.children = [ground.id] + const nodes = Object.fromEntries([building, ground].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: ground.id }) + useEditor.setState({ + mode: 'build', + tool: 'beam', + workspaceMode: 'edit', + viewLayouts: { edit: sceneLayout('2d') }, + }) + return { ground } +} + +const beams = () => + Object.values(useScene.getState().nodes).filter((node): node is BeamNode => node.type === 'beam') + +// Alt: the points are the pointer's, whatever the snapping mode. +const gridEvent = (x: number, z: number) => + ({ + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: { altKey: true } as GridEvent['nativeEvent'], + }) satisfies GridEvent + +const move = (x: number, z: number) => act(async () => emitter.emit('grid:move', gridEvent(x, z))) +const click = (x: number, z: number) => + act(async () => { + emitter.emit('grid:move', gridEvent(x, z)) + emitter.emit('grid:click', gridEvent(x, z)) + }) + +test('in 2D-only view the plan draws a run of beams, each from where the last ended', async () => { + const { ground } = setScene() + useBeamDraft.setState({ preset: '(2)2x10', material: 'pressure-treated' }) + const extension = getFloorplanNodeExtension(beamDefinition) + // A plan click on a beam over a room picks the beam. + expect(extension?.planHitSelectsNode).toBe(true) + const { default: PlanTool } = await extension!.tool!() + const renderer = await create(createElement(PlanTool as () => null)) + try { + await click(0, 0) + await move(3, 0) + expect(usePlacementPreview.getState().node).toMatchObject({ + type: 'beam', + start: [0, 0], + end: [3, 0], + }) + // the plan shows the draft's length + expect(usePlacementPreview.getState().dimensions[0]).toMatchObject({ value: 3 }) + await click(3, 0) + await click(3, 2) + expect(beams().map(({ start, end, parentId }) => ({ start, end, parentId }))).toEqual([ + { start: [0, 0], end: [3, 0], parentId: ground.id }, + { start: [3, 0], end: [3, 2], parentId: ground.id }, + ]) + expect(beams()[0]).toMatchObject({ size: '2x10', plies: 2, material: 'pressure-treated' }) + + // Esc ends the run: the next click starts a new beam. + await act(async () => emitter.emit('tool:cancel')) + await click(5, 5) + expect(beams()).toHaveLength(2) + await click(6, 5) + expect(beams()[2]).toMatchObject({ start: [5, 5], end: [6, 5] }) + } finally { + await act(async () => renderer.unmount()) + } + expect(usePlacementPreview.getState().node).toBeNull() +}) + +test('the draft length reads along the beam, off its side', () => { + const [dimension] = beamDraftDimensions( + beamDefinition.schema.parse({ start: [0, 0], end: [0, 4], width: 0.14 }), + ) + expect(dimension).toMatchObject({ value: 4, renderIn3d: false }) + expect(dimension?.offsetNormal[0]).toBeCloseTo(-1) +}) diff --git a/packages/nodes/src/beam/tool.tsx b/packages/nodes/src/beam/tool.tsx new file mode 100644 index 0000000000..c527524336 --- /dev/null +++ b/packages/nodes/src/beam/tool.tsx @@ -0,0 +1,220 @@ +'use client' + +import { + type AnyNodeId, + type BeamNode, + beamPlan, + beamTopElevation, + emitter, + type GridEvent, + sceneRegistry, + useScene, +} from '@pascal-app/core' +import { + CursorSphere, + DRAFT_LABEL_Y_OFFSET, + DraftMeasurementLabel, + formatLinearMeasurement, + isSegmentLongEnough, + markToolCancelConsumed, + type PlacementPreviewDimension, + triggerSFX, + useEditor, + usePlacementPreview, + useWallSnapIndicator, + visibleScene, + type WallPlanPoint, +} from '@pascal-app/editor' +import { getSceneTheme, useViewer } from '@pascal-app/viewer' +import { createPortal } from '@react-three/fiber' +import { useEffect, useMemo, useState } from 'react' +import { DoubleSide } from 'three' +import { MeshBasicNodeMaterial } from 'three/webgpu' +import { + type FloorPlacementClickTriggerEvent, + stopPlacementCommitPropagation, + subscribeFloorPlacementClicks, +} from '../shared/floor-placement' +import { draftBeam, snapBeamPoint } from './drafting' +import { beamBox, buildBeamGeometry } from './geometry' + +const ghostMaterial = new MeshBasicNodeMaterial({ + color: '#f59e0b', + depthWrite: false, + opacity: 0.5, + side: DoubleSide, + transparent: true, +}) + +/** The draft's length, as a dimension along it in the plan (the 3D view prints its own label). */ +export function beamDraftDimensions(beam: BeamNode): PlacementPreviewDimension[] { + const plan = beamPlan(beam) + if (plan.length < 0.05) return [] + return [ + { + id: 'beam-length', + start: [plan.start[0], 0, plan.start[1]], + end: [plan.end[0], 0, plan.end[1]], + offsetNormal: plan.normal, + offsetDistance: plan.width / 2 + 0.25, + value: plan.length, + renderIn3d: false, + }, + ] +} + +/** + * Drafting beams from the grid events both views send: a click sets the start, the next ends the + * beam and starts the next one there, so a porch's run is drawn corner to corner; Esc ends the run. + * Points snap as walls do (wall ends, faces and crossings, other beams' ends and runs, post + * centres, the grid, the angle steps off the start); Alt places freely. The beam takes the Build + * panel's size, material and top (`useBeamDraft`), or the preset a host armed the tool with. + * `enabled` off, it stands aside. + */ +function useBeamDrafting(enabled: boolean) { + const levelId = useViewer((state) => state.selection.levelId) + const [draft, setDraft] = useState(null) + const [cursor, setCursor] = useState<[number, number, number] | null>(null) + + useEffect(() => { + if (!(enabled && levelId)) return + let start: WallPlanPoint | null = null + let last: WallPlanPoint | null = null + setDraft(null) + + const snap = (event: GridEvent | FloorPlacementClickTriggerEvent): WallPlanPoint => { + const { point, snap: caught } = snapBeamPoint({ + point: [event.localPosition[0], event.localPosition[2]], + nodes: useScene.getState().nodes, + levelId, + start, + free: event.nativeEvent?.altKey === true, + }) + if (caught) useWallSnapIndicator.getState().set({ x: point[0], z: point[1], kind: caught }) + else useWallSnapIndicator.getState().clear() + return point + } + const make = (from: WallPlanPoint, to: WallPlanPoint, fields?: Partial) => + draftBeam( + from, + to, + levelId, + useScene.getState().nodes, + undefined, + useEditor.getState().toolDefaults.beam, + fields, + ) + const show = (next: BeamNode | null) => { + setDraft(next) + if (next) usePlacementPreview.getState().set(next, null, beamDraftDimensions(next)) + else usePlacementPreview.getState().clear() + } + + const onMove = (event: GridEvent) => { + const point = snap(event) + if (last && point[0] === last[0] && point[1] === last[1]) return + last = point + setCursor([point[0], event.localPosition[1], point[1]]) + show(start && isSegmentLongEnough(start, point) ? make(start, point) : null) + } + const onClick = (event: FloorPlacementClickTriggerEvent) => { + const point = last ?? snap(event) + stopPlacementCommitPropagation(event) + if (!start) { + start = point + triggerSFX('sfx:grid-snap') + return + } + if (!isSegmentLongEnough(start, point)) return + const count = Object.values(useScene.getState().nodes).filter( + (node) => node.type === 'beam', + ).length + const beam = make(start, point, { name: `Beam ${count + 1}` }) + useScene.getState().createNode(beam, levelId as AnyNodeId) + triggerSFX('sfx:structure-build') + // The run goes on from the end just placed. + start = point + show(null) + } + const onCancel = () => { + if (!start) return + markToolCancelConsumed() + start = null + show(null) + } + + emitter.on('grid:move', onMove) + emitter.on('tool:cancel', onCancel) + const unsubscribeClicks = subscribeFloorPlacementClicks(onClick) + return () => { + emitter.off('grid:move', onMove) + emitter.off('tool:cancel', onCancel) + unsubscribeClicks() + usePlacementPreview.getState().clear() + useWallSnapIndicator.getState().clear() + setDraft(null) + setCursor(null) + } + }, [enabled, levelId]) + + return { levelId, draft, cursor } +} + +/** + * The beam tool in 2D-only view: the 3D tool lives in the canvas, which never renders while its + * pane is hidden. The plan draws the draft from the placement preview. Split and 3D views keep the + * 3D tool as the one owner. + */ +export function BeamFloorplanTool() { + useBeamDrafting(useEditor((state) => visibleScene(state) === '2d')) + return null +} + +const BeamTool = () => { + const { levelId, draft, cursor } = useBeamDrafting( + useEditor((state) => visibleScene(state) !== '2d'), + ) + const top = useScene((state) => (draft ? beamTopElevation(state.nodes, draft) : 0)) + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const dark = useViewer((state) => getSceneTheme(state.sceneTheme).appearance === 'dark') + const placed = draft ? beamBox(draft, top) : null + const length = placed?.size[0] ?? 0 + const geometry = useMemo( + () => (draft && length > 0 ? buildBeamGeometry(draft, length) : null), + [draft, length], + ) + useEffect(() => () => geometry?.dispose(), [geometry]) + const level = levelId ? sceneRegistry.nodes.get(levelId) : undefined + + if (!levelId) return null + return ( + <> + {cursor && } + {placed && + geometry && + level && + createPortal( + + {}} + renderOrder={2} + rotation-y={placed.rotationY} + /> + + , + level, + )} + + ) +} + +export default BeamTool diff --git a/packages/nodes/src/ceiling/build-up-section.tsx b/packages/nodes/src/ceiling/build-up-section.tsx new file mode 100644 index 0000000000..179198c140 --- /dev/null +++ b/packages/nodes/src/ceiling/build-up-section.tsx @@ -0,0 +1,122 @@ +'use client' + +import { + type CeilingBuildUp, + type CeilingFraming, + type CeilingNode, + ceilingBuildUpThickness, + LUMBER_DEPTH, +} from '@pascal-app/core' +import { PanelSection, SelectControl } from '@pascal-app/editor' +import { + BuildUpReadout, + editedBuildUp, + FinishFields, + FramingSizeFields, + InsulationFields, + NONE, + PanelFields, +} from '../shared/build-up-controls' +import { formatLayerDepth } from '../shared/layer-thickness' +import { + ceilingBuildUpPresetOptions, + ceilingBuildUpPresetPatch, + ceilingBuildUpPresetValue, +} from './panel-model' + +const INCH = 0.0254 + +const FRAMING_OPTIONS: { label: string; value: CeilingFraming['system'] | typeof NONE }[] = [ + { label: 'None', value: NONE }, + { label: 'Ceiling joists', value: 'joists' }, + { label: 'Truss bottom chords', value: 'truss-chords' }, +] + +const NEW_FRAMING: Record = { + joists: { system: 'joists', size: '2x6', spacing: 16 * INCH }, + 'truss-chords': { system: 'truss-chords', size: '2x4', spacing: 24 * INCH }, +} + +export function CeilingBuildUpSection({ + node, + onUpdate, + unit, +}: { + node: CeilingNode + onUpdate: (patch: Partial) => void + unit: 'metric' | 'imperial' +}) { + const buildUp = node.buildUp + const edit = (change: Partial) => { + if (buildUp) onUpdate({ buildUp: editedBuildUp(buildUp, change) }) + } + const framing = buildUp?.framing + + return ( + + { + const patch = ceilingBuildUpPresetPatch(id) + if (patch) onUpdate(patch) + }} + options={ceilingBuildUpPresetOptions(ceilingBuildUpPresetValue(buildUp))} + value={ceilingBuildUpPresetValue(buildUp)} + /> + {buildUp && ( + <> + edit({ finish })} + unit={unit} + /> + + edit({ + framing: + system === NONE + ? undefined + : framing + ? { ...framing, system } + : NEW_FRAMING[system], + }) + } + options={FRAMING_OPTIONS} + value={framing?.system ?? NONE} + /> + {framing && ( + edit({ framing: { ...framing, ...change } })} + sizeLabel={framing.system === 'joists' ? 'Joists' : 'Bottom chord'} + unit={unit} + /> + )} + edit({ decking })} + optional + unit={unit} + /> + edit({ insulation })} + unit={unit} + /> + +
+ The height is the finished underside; the layers rise from it. +
+ + )} +
+ ) +} diff --git a/packages/nodes/src/ceiling/definition.ts b/packages/nodes/src/ceiling/definition.ts index 0129eb6a22..295912578b 100644 --- a/packages/nodes/src/ceiling/definition.ts +++ b/packages/nodes/src/ceiling/definition.ts @@ -12,6 +12,7 @@ import { DRAFTING_SURFACE_EXTENSION_KEY, type DraftingSurfaceExtension, type FloorplanNodeExtension, + PANEL_MODEL_EXTENSION, publishStructuralElevationGuide, resolveStructuralElevationSnap, } from '@pascal-app/editor' @@ -28,6 +29,7 @@ import { import { ceilingFloorplanMoveTarget } from './floorplan-move' import { CEILING_HANDLE_MIN_HEIGHT, ceilingHeightRange } from './height-bounds' import { ceilingPaint } from './paint' +import { ceilingPanelModel } from './panel-model' import { ceilingParametrics } from './parametrics' import { CeilingNode } from './schema' @@ -139,6 +141,7 @@ export const ceilingDefinition: NodeDefinition = { 'pascal:editor/floorplan': { selectionCounterparts: sameOutlineSurfaceCounterparts, } satisfies FloorplanNodeExtension, + [PANEL_MODEL_EXTENSION]: ceilingPanelModel, }, // Height-less on purpose: a new ceiling follows the level top until the diff --git a/packages/nodes/src/ceiling/materials.ts b/packages/nodes/src/ceiling/materials.ts index 3a2195e295..058058535e 100644 --- a/packages/nodes/src/ceiling/materials.ts +++ b/packages/nodes/src/ceiling/materials.ts @@ -77,6 +77,27 @@ export function getCeilingMaterials( } } +/** + * The upper face, drawn while the viewer shows ceiling tops: the ceiling's colour, facing up, so + * the ceiling reads as solid from above. + */ +export function getCeilingCapMaterial(color: string, shading: RenderShading): Material { + return createMaterial( + { + preset: 'custom', + properties: { + color, + roughness: 0.9, + metalness: 0, + opacity: 1, + transparent: false, + side: 'front', + }, + }, + shading, + ) +} + /** Resolve through the shared caches, without mutating another surface's material. */ export function resolveCeilingFinish( finish: MaterialSchema | string | undefined, diff --git a/packages/nodes/src/ceiling/multi-layers.test.ts b/packages/nodes/src/ceiling/multi-layers.test.ts new file mode 100644 index 0000000000..46527eba91 --- /dev/null +++ b/packages/nodes/src/ceiling/multi-layers.test.ts @@ -0,0 +1,50 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + CEILING_BUILD_UP_PRESETS, + CeilingNode, + useScene, +} from '@pascal-app/core' +import { getNodePanelModel, type MultiPanelControl } from '@pascal-app/editor' +import { ceilingDefinition } from './definition' + +globalThis.requestAnimationFrame ??= () => 0 +globalThis.cancelAnimationFrame ??= () => {} + +const originalState = useScene.getState() +afterEach(() => useScene.setState(originalState)) + +type Choice = Extract + +const load = (nodes: AnyNode[]) => { + useScene.setState({ + nodes: Object.fromEntries(nodes.map((n) => [n.id, n])) as Record, + rootNodeIds: [], + }) + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() +} +const current = (nodes: T[]) => + nodes.map((n) => useScene.getState().nodes[n.id as AnyNodeId] as T) + +test('multi-selected ceilings take one layers preset; mixed reads as mixed', () => { + const chosen = CEILING_BUILD_UP_PRESETS[0]! + const ceilings = [ + CeilingNode.parse({ polygon: [] }), + CeilingNode.parse({ polygon: [], buildUp: chosen.buildUp }), + ] + load(ceilings) + const controls = () => + getNodePanelModel(ceilingDefinition)!.multiControls!({ + selection: current(ceilings), + nodes: useScene.getState().nodes, + }) as Choice[] + expect(controls()[0]?.value).toBeNull() + controls()[0]!.onChange(chosen.id) + expect(current(ceilings).map((ceiling) => ceiling.buildUp)).toEqual([ + chosen.buildUp, + chosen.buildUp, + ]) + expect(controls()[0]?.value).toBe(chosen.id) +}) diff --git a/packages/nodes/src/ceiling/panel-model.ts b/packages/nodes/src/ceiling/panel-model.ts new file mode 100644 index 0000000000..f86881ca71 --- /dev/null +++ b/packages/nodes/src/ceiling/panel-model.ts @@ -0,0 +1,63 @@ +import { + type AnyNodeId, + CEILING_BUILD_UP_PRESETS, + type CeilingBuildUp, + type CeilingNode, + runAsSingleSceneHistoryStep, + useScene, +} from '@pascal-app/core' +import type { NodePanelModel } from '@pascal-app/editor' + +const NONE = 'none' +const CUSTOM = 'custom' + +/** The Preset choices for the `current` value: none, the presets, and Custom while it is. */ +export function ceilingBuildUpPresetOptions(current: string | null) { + return [ + { label: 'None (a bare surface)', value: NONE }, + ...(current === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ...CEILING_BUILD_UP_PRESETS.map((preset) => ({ label: preset.label, value: preset.id })), + ] +} + +export function ceilingBuildUpPresetValue(buildUp: CeilingBuildUp | undefined): string { + return buildUp ? (buildUp.presetId ?? CUSTOM) : NONE +} + +/** The patch a Preset choice writes; `null` for Custom, which is no choice. */ +export function ceilingBuildUpPresetPatch(id: string): Partial | null { + if (id === NONE) return { buildUp: undefined } + const preset = CEILING_BUILD_UP_PRESETS.find((entry) => entry.id === id) + return preset ? { buildUp: preset.buildUp } : null +} + +/** Multi-selected ceilings take one layers preset together, in one undo step. */ +export const ceilingPanelModel: NodePanelModel = { + rows: () => [], + multiControls: ({ selection }) => { + if (selection.length === 0) return [] + const values = selection.map((ceiling) => ceilingBuildUpPresetValue(ceiling.buildUp)) + const value = values.every((entry) => entry === values[0]) ? values[0]! : null + return [ + { + id: 'ceiling-layers-preset', + kind: 'select', + section: 'Layers', + label: 'Preset', + options: ceilingBuildUpPresetOptions(value), + value, + onChange: (id) => { + const patch = ceilingBuildUpPresetPatch(id) + if (!patch) return + runAsSingleSceneHistoryStep(useScene, () => + useScene + .getState() + .updateNodes( + selection.map((ceiling) => ({ id: ceiling.id as AnyNodeId, data: patch })), + ), + ) + }, + }, + ] + }, +} diff --git a/packages/nodes/src/ceiling/panel.tsx b/packages/nodes/src/ceiling/panel.tsx index 1ee5b20ebd..43fc42d3ae 100644 --- a/packages/nodes/src/ceiling/panel.tsx +++ b/packages/nodes/src/ceiling/panel.tsx @@ -12,7 +12,9 @@ import { ActionButton, ActionGroup, exitCeilingEditToRoom, + formatAreaLabel, formatLinearMeasurement, + getAreaUnitLabel, holeEditScope, PanelSection, PanelWrapper, @@ -20,6 +22,7 @@ import { ShapeChoice, SliderControl, setCeilingHoles, + squareMetersToAreaUnit, startOpeningDraft, triggerSFX, useCeilingEditCeilingId, @@ -31,6 +34,7 @@ import { import { useViewer } from '@pascal-app/viewer' import { Edit, Move, Plus, Trash2 } from 'lucide-react' import { useCallback, useEffect, useRef } from 'react' +import { CeilingBuildUpSection } from './build-up-section' import { CEILING_PANEL_MIN_HEIGHT, ceilingHeightRange, clampCeilingHeight } from './height-bounds' /** @@ -367,10 +371,14 @@ export function CeilingPanel() { )} + +
{i18n._({ id: 'node.ceiling.panel.area', message: 'Area' })} - {area.toFixed(2)} m² + + {formatAreaLabel(area, unit, 2)} +
@@ -416,9 +424,14 @@ export function CeilingPanel() { {i18n._({ id: 'node.holeSummary', message: - '{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}', + '{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}', values: { - area: formatFixed(holeArea, 2, i18n.getLocale()), + area: formatFixed( + squareMetersToAreaUnit(holeArea, unit), + 2, + i18n.getLocale(), + ), + unit: getAreaUnitLabel(unit), count: hole.length, points: String(hole.length), source: isAutoHole diff --git a/packages/nodes/src/ceiling/renderer.tsx b/packages/nodes/src/ceiling/renderer.tsx index 44db9bed66..a7476a200d 100644 --- a/packages/nodes/src/ceiling/renderer.tsx +++ b/packages/nodes/src/ceiling/renderer.tsx @@ -13,6 +13,7 @@ import { useScene, } from '@pascal-app/core' import { + CEILING_CAP_MESH, CEILING_REGION_MESH, type CeilingRegionMaterial, createSurfaceRoleMaterial, @@ -23,14 +24,22 @@ import { useViewer, } from '@pascal-app/viewer' import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react' -import { BackSide, type Mesh } from 'three/webgpu' +import { BackSide, FrontSide, type Mesh } from 'three/webgpu' import { createPlaceholderGeometry } from '../shared/placeholder-geometry' -import { ceilingColorFromRef, getCeilingMaterials, resolveCeilingFinish } from './materials' +import { + ceilingColorFromRef, + getCeilingCapMaterial, + getCeilingMaterials, + resolveCeilingFinish, +} from './materials' function createEmptyGeometry() { return createPlaceholderGeometry() } +// The cap is only seen: a click from above still reaches what it reached without it. +const noopRaycast = () => {} + export const CeilingRenderer = ({ node }: { node: CeilingNode }) => { const ref = useRef(null!) const handlers = useNodeEvents(node, 'ceiling') @@ -52,6 +61,7 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => { const libraryMaterialsVersion = useLibraryMaterialsVersion() const colorPreset = useViewer((s) => s.colorPreset) const sceneTheme = useViewer((s) => s.sceneTheme) + const showTop = useViewer((s) => s.showCeilingTops) // Subscribe to the scene-material library so editing a `scene:` material the // ceiling slot references re-tints it live. const sceneMaterials = useScene((s) => s.materials) @@ -85,11 +95,13 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => { // revealed (placing a ceiling-hosted item, or selecting one of its // children). Without that the top mesh would ship an opaque surface-role // material and a top-down camera would lose everything under the ceiling. + // The opaque top is the cap's, drawn only while the viewer shows ceiling tops. if (!textures) { const ceilingColor = resolveSurfaceColor('ceiling', colorPreset, sceneTheme) return { topMaterial: getCeilingMaterials(ceilingColor).topMaterial, bottomMaterial: createSurfaceRoleMaterial('ceiling', colorPreset, BackSide, sceneTheme), + capMaterial: createSurfaceRoleMaterial('ceiling', colorPreset, FrontSide, sceneTheme), } } @@ -102,6 +114,7 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => { const fallback = getCeilingMaterials(gridColor, shading) return { topMaterial: fallback.topMaterial, + capMaterial: getCeilingCapMaterial(gridColor, shading), bottomMaterial: resolveCeilingFinish(node.slots?.surface, sceneMaterials, shading) ?? resolveCeilingFinish(node.materialPreset, sceneMaterials, shading) ?? @@ -171,6 +184,14 @@ export const CeilingRenderer = ({ node }: { node: CeilingNode }) => { scale={0} visible={false} /> + {(node.children ?? []).map((childId) => ( ))} diff --git a/packages/nodes/src/ceiling/tool.test.ts b/packages/nodes/src/ceiling/tool.test.ts new file mode 100644 index 0000000000..4dbe7261e7 --- /dev/null +++ b/packages/nodes/src/ceiling/tool.test.ts @@ -0,0 +1,162 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearSceneHistory, + emitter, + type GridEvent, + LevelNode, + useScene, +} from '@pascal-app/core' +import { sceneLayout, useEditor } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import { CeilingTool } from './tool' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } + +afterEach(() => { + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) +}) + +const ceilings = () => Object.values(useScene.getState().nodes).filter((n) => n.type === 'ceiling') + +const click = (x: number, z: number) => + act(async () => { + const event = { + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: {} as GridEvent['nativeEvent'], + } satisfies GridEvent + emitter.emit('grid:move', event) + emitter.emit('grid:click', event) + }) + +async function drawSquare() { + for (const [x, z] of [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], + [0, 0], + ] as const) + await click(x, z) +} + +function seedLevel() { + const building = BuildingNode.parse({}) + const level = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + building.children = [level.id] + useScene + .getState() + .setScene( + { [building.id]: building, [level.id]: level } as Record, + [building.id as AnyNodeId], + { installedPlugins: [], hasExplicitPluginInstallState: false }, + ) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: level.id }) +} + +const showScene = (scene: '2d' | '3d' | 'split') => + act(async () => useEditor.setState({ viewLayouts: { edit: sceneLayout(scene) } })) + +// The 3D tool stays mounted behind the hidden canvas once 3D has been shown, and hears the plan's +// clicks; in 2D-only view the plan commits the ceiling too, so each one was made twice. +test('the 3D ceiling tool leaves a 2D-only plan its clicks, and commits beside a plan', async () => { + seedLevel() + useEditor.setState({ + mode: 'build', + tool: null, + workspaceMode: 'edit', + viewLayouts: { edit: sceneLayout('2d') }, + }) + const renderer = await create(createElement(CeilingTool)) + try { + await drawSquare() + expect(ceilings()).toHaveLength(0) + + await showScene('split') + await drawSquare() + expect(ceilings()).toHaveLength(1) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// The plan finishes a ceiling begun beside it once the view goes 2D-only; corners the 3D tool kept +// would start the next ceiling drawn back in split view, which then never closed. +test('a ceiling begun in split view and finished on the 2D-only plan leaves no corners behind', async () => { + seedLevel() + useEditor.setState({ + mode: 'build', + tool: null, + workspaceMode: 'edit', + viewLayouts: { edit: sceneLayout('split') }, + }) + const renderer = await create(createElement(CeilingTool)) + try { + await click(10, 10) + await click(14, 10) + await showScene('2d') + for (const [x, z] of [ + [14, 14], + [10, 14], + [10, 10], + ] as const) + await click(x, z) + await showScene('split') + await drawSquare() + expect(ceilings().map((ceiling) => ceiling.polygon)).toEqual([ + [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], + ], + ]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// The plan starts its own copy of the draft empty whenever it appears, so corners this tool kept +// from 3D-only view closed a different polygon than the one the plan beside it showed. +test('a ceiling drawn beside the plan starts from the corners the plan shows', async () => { + seedLevel() + useEditor.setState({ + mode: 'build', + tool: null, + workspaceMode: 'edit', + viewLayouts: { edit: sceneLayout('3d') }, + }) + const renderer = await create(createElement(CeilingTool)) + try { + await click(10, 10) + await click(14, 10) + await showScene('split') + await drawSquare() + expect(ceilings().map((ceiling) => ceiling.polygon)).toEqual([ + [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], + ], + ]) + } finally { + await act(async () => renderer.unmount()) + } +}) diff --git a/packages/nodes/src/ceiling/tool.tsx b/packages/nodes/src/ceiling/tool.tsx index df6f85e562..999907b230 100644 --- a/packages/nodes/src/ceiling/tool.tsx +++ b/packages/nodes/src/ceiling/tool.tsx @@ -20,6 +20,7 @@ import { triggerSFX, useEditor, useFloorplanDraftPreview, + visibleScene, } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { useEffect, useMemo, useRef, useState } from 'react' @@ -94,6 +95,21 @@ export const CeilingTool: React.FC = () => { [], ) + // The plan keeps its own copy of the corners, started empty on every view change, and finishes a + // polygon in 2D-only view without this tool. Corners kept here once the plan shows (from split + // or 3D-only view) would close a different polygon than the one the plan draws. + useEffect(() => { + let shownScene = visibleScene(useEditor.getState()) + return useEditor.subscribe((state) => { + const nextScene = visibleScene(state) + if (nextScene === shownScene) return + shownScene = nextScene + if (nextScene === '3d') return + setPoints([]) + clearCeilingSnapFeedback() + }) + }, []) + const verticalGeo = useMemo( () => new BufferGeometry().setFromPoints([ @@ -111,8 +127,13 @@ export const CeilingTool: React.FC = () => { useEffect(() => { if (!currentLevelId) return + // In 2D-only view the plan drafts and commits the ceiling; this tool, mounted behind the + // hidden canvas from an earlier visit, still hears the plan's grid events, so it stands aside + // (it made every ceiling twice). + const planOwnsInput = () => visibleScene(useEditor.getState()) === '2d' + const onGridMove = (event: GridEvent) => { - if (!(cursorRef.current && gridCursorRef.current)) return + if (!(cursorRef.current && gridCursorRef.current) || planOwnsInput()) return const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]] // Honour the active snapping mode: grid lattice + 15° angle lock are each // gated on the mode (off / lines → free), like the slab tool. @@ -151,7 +172,7 @@ export const CeilingTool: React.FC = () => { } const onGridClick = (_event: GridEvent) => { - if (!currentLevelId) return + if (!currentLevelId || planOwnsInput()) return const clickPoint = previousSnappedPointRef.current ?? cursorPosition const firstPoint = points[0] if ( @@ -173,7 +194,7 @@ export const CeilingTool: React.FC = () => { } const onGridDoubleClick = (_event: GridEvent) => { - if (!currentLevelId) return + if (!currentLevelId || planOwnsInput()) return if (points.length >= 3) { const ceilingId = commitCeilingDrawing(currentLevelId, points) setSelection({ selectedIds: [ceilingId] }) diff --git a/packages/nodes/src/column/definition.ts b/packages/nodes/src/column/definition.ts index a24e8d0148..dd2cd6cf22 100644 --- a/packages/nodes/src/column/definition.ts +++ b/packages/nodes/src/column/definition.ts @@ -20,6 +20,7 @@ import { columnFloorplanMoveTarget } from './floorplan-move' import { columnHostedPolicy } from './hosted-resize' import { columnPaint } from './paint' import { columnParametrics } from './parametrics' +import { columnPlaceOption, postBaseOption } from './post-options' import { ColumnNode } from './schema' import { columnSurfaceProvider } from './surface' @@ -439,6 +440,8 @@ export const columnDefinition: NodeDefinition = { // bare sphere the legacy editor-side `ColumnTool` showed. `ToolManager`'s // registry-first path mounts this and skips the legacy ``. tool: () => import('./tool'), + // One family with the Post tool: a column, or a framing post that stands up to the beam over it. + toolOptions: [columnPlaceOption, postBaseOption], toolHints: [ { key: 'Left click', label: 'Place column' }, { key: 'Esc', label: 'Cancel' }, diff --git a/packages/nodes/src/column/floorplan.ts b/packages/nodes/src/column/floorplan.ts index 548ebc97b2..8332975d88 100644 --- a/packages/nodes/src/column/floorplan.ts +++ b/packages/nodes/src/column/floorplan.ts @@ -146,6 +146,28 @@ export function buildColumnFloorplan( metadata: floorplanGeometryMetadata({ annotationRole: 'column-center' }), }, ) + // A framing post reads as a framing plan draws one: its own square (not its base's) crossed + // corner to corner. + if (node.role === 'post') { + const square = getRotatedRectanglePolygon( + { x: centerX, y: centerZ }, + node.width, + node.depth, + -node.rotation, + ) + for (const [a, b] of [[square[0]!, square[2]!] as const, [square[1]!, square[3]!] as const]) + children.push({ + kind: 'line', + x1: a.x, + y1: a.y, + x2: b.x, + y2: b.y, + stroke, + strokeWidth: 0.9, + vectorEffect: 'non-scaling-stroke', + pointerEvents: 'none', + }) + } const levelData = ctx.levelData as ColumnFloorplanLevelData | undefined const gridReference = resolveStructuralGridReference( diff --git a/packages/nodes/src/column/panel.tsx b/packages/nodes/src/column/panel.tsx index 39007a0bf3..62248ce401 100644 --- a/packages/nodes/src/column/panel.tsx +++ b/packages/nodes/src/column/panel.tsx @@ -27,6 +27,7 @@ import { useCallback } from 'react' import { AuthoredParams } from '../item/authored-params' import { planColumnEdit } from './hosted-resize' +import { FramingPostSection } from './post-section' const SELECT_CLASS = 'h-10 w-full rounded-lg border border-border/50 bg-black/4 dark:bg-[#2C2C2E] px-3 text-sm text-foreground outline-none transition-colors hover:bg-black/8 dark:hover:bg-[#3e3e3e] focus:ring-1 focus:ring-border' @@ -404,7 +405,10 @@ export default function ColumnPanel() { title={node.name || i18n._({ id: 'node.column.panel.column', message: 'Column' })} width={300} > - {node.source ? ( + {/* A framing post is a sawn post, not a styled column: its own short section. */} + {node.role === 'post' ? ( + + ) : node.source ? ( ) : ( <> diff --git a/packages/nodes/src/column/post-options.ts b/packages/nodes/src/column/post-options.ts new file mode 100644 index 0000000000..d3773ca7ad --- /dev/null +++ b/packages/nodes/src/column/post-options.ts @@ -0,0 +1,84 @@ +import { + type ColumnPostBase, + FRAMING_POST_BASE_LABELS, + type FramingPostSize, + type ToolOption, +} from '@pascal-app/core' +import { useEditor } from '@pascal-app/editor' +import { + armFramingPostTool, + dropInflightColumnPlacement, + framingPostToolChoices, +} from './post-placement' + +const subscribe = (onChange: () => void) => useEditor.subscribe(() => onChange()) + +/** The Post tool's size, or null while the column tool places ornamental columns. */ +const postChoices = () => framingPostToolChoices(useEditor.getState().toolDefaults.column) + +/** + * "Place" for the column tool, one family with the Post tool: a column of the chosen style, or a + * pressure-treated framing post of a size, which stands up to the beam over it. Picking across + * the family switches what the tool places. + */ +export const columnPlaceOption: ToolOption = { + id: 'column-place', + label: 'Place', + choices: [ + { + value: 'column', + label: 'Column', + description: 'A column of a chosen style: round, square or tapered, or a support frame', + }, + { + value: '4x4', + label: '4x4 post', + description: 'A treated 4x4 framing post, for a light porch or a deck post up to 8 ft', + }, + { + value: '6x6', + label: '6x6 post', + description: 'A treated 6x6 framing post: the post under a porch or a deck beam', + }, + { + value: '8x8', + label: '8x8 post', + description: 'A treated 8x8 framing post, for a tall post or a heavy load', + }, + ], + subscribe, + value: () => postChoices()?.size ?? 'column', + set: (value) => { + if (value !== 'column') { + armFramingPostTool(value as FramingPostSize) + return + } + // Already placing columns (perhaps a preset following the cursor): nothing to switch. + if (postChoices() === null) return + dropInflightColumnPlacement() + const editor = useEditor.getState() + editor.setToolDefaults('column', null) + if (!(editor.mode === 'build' && editor.tool === 'column')) + editor.armToolMode({ mode: 'build', tool: 'column' }) + }, +} + +/** What a framing post stands on, shown while the tool places posts. */ +export const postBaseOption: ToolOption = { + id: 'post-base', + label: 'Base', + choices: (['none', 'post-base', 'pier'] as const).map((base) => ({ + value: base, + label: FRAMING_POST_BASE_LABELS[base], + description: + base === 'none' + ? 'Straight on the floor or the footing under it' + : base === 'post-base' + ? 'A steel post base holding the wood off the concrete' + : 'A concrete pier 8 in tall under the post', + })), + subscribe, + value: () => postChoices()?.base ?? 'none', + set: (value) => armFramingPostTool(undefined, value as ColumnPostBase), + visible: { subscribe, value: () => postChoices() !== null }, +} diff --git a/packages/nodes/src/column/post-placement.ts b/packages/nodes/src/column/post-placement.ts new file mode 100644 index 0000000000..3f0b4ab261 --- /dev/null +++ b/packages/nodes/src/column/post-placement.ts @@ -0,0 +1,236 @@ +import { + type AnyNode, + type AnyNodeId, + type BeamNode, + beamBottomElevation, + type ColumnNode, + type ColumnPostBase, + createFramingPost, + DEFAULT_FRAMING_POST_SIZE, + emitter, + FRAMING_POST_SIZES, + type FramingPostSize, + framingPostFields, + framingPostSize, + framingPostTopAt, + getFloorPlacedElevation, + postStationsAlong, + type WallNode, +} from '@pascal-app/core' +import { + getMovingNode, + isGridSnapActive, + snapBuildingLocalToWorldGrid, + snapWallDraftPointDetailed, + useEditor, + type WallDraftSnapKind, + type WallPlanPoint, +} from '@pascal-app/editor' + +type Nodes = Readonly> + +/** Posts no further apart than this under a beam the panel posts: 8 ft, as a porch's are. */ +export const FRAMING_POST_SPACING = 8 * 12 * 0.0254 + +/** The post choices the Post tool draws with, kept on the column tool's defaults. */ +export type FramingPostToolChoices = { size: FramingPostSize; base: ColumnPostBase } + +/** + * The Post tool is the column tool armed with a framing post's fields (`toolDefaults.column`, + * `role: 'post'`): its size and base, or null while it places ornamental columns. + */ +export function framingPostToolChoices( + defaults: Readonly> | null | undefined, +): FramingPostToolChoices | null { + if (defaults?.role !== 'post') return null + const width = typeof defaults.width === 'number' ? defaults.width : undefined + const size = + (width !== undefined && framingPostSize({ width, depth: width })) || DEFAULT_FRAMING_POST_SIZE + const base = defaults.postBase + return { + size, + base: base === 'post-base' || base === 'pier' ? base : 'none', + } +} + +/** The column tool's defaults that make it the Post tool: a post of this size and base. */ +export function framingPostToolDefaults( + size: FramingPostSize = DEFAULT_FRAMING_POST_SIZE, + base: ColumnPostBase = 'none', +): Record { + return { ...framingPostFields(size, base) } +} + +/** + * Drops a preset still following the cursor (a column preset's clone on the move tool): while it + * moves, no build tool mounts. + */ +export function dropInflightColumnPlacement(): void { + if (!getMovingNode()) return + emitter.emit('tool:cancel') + useEditor.getState().setMovingNode(null) +} + +/** + * The size and base last picked, kept apart from the tool's defaults: a host clears those when it + * arms the column tool from a tile, and the Post tile should come back as the user left it. + */ +let lastChoices: FramingPostToolChoices = { size: DEFAULT_FRAMING_POST_SIZE, base: 'none' } + +/** + * Arms the column tool as the Post tool (the Build panel's Post tile): a post of `size` on `base`, + * else as the tool has them, else as they were last picked. + */ +export function armFramingPostTool(size?: FramingPostSize, base?: ColumnPostBase): void { + dropInflightColumnPlacement() + const editor = useEditor.getState() + const current = framingPostToolChoices(editor.toolDefaults.column) ?? lastChoices + lastChoices = { size: size ?? current.size, base: base ?? current.base } + editor.setToolDefaults('column', framingPostToolDefaults(lastChoices.size, lastChoices.base)) + if (!(editor.mode === 'build' && editor.tool === 'column')) + editor.armToolMode({ mode: 'build', tool: 'column' }) +} + +/** A point the wall snap reads as a wall: a segment of no length catches at the point. */ +function pointReference(id: string, parentId: string | null, at: WallPlanPoint): WallNode { + return { + object: 'node', + id: id as WallNode['id'], + type: 'wall', + parentId, + visible: true, + metadata: {}, + children: [], + start: at, + end: at, + frontSide: 'unknown', + backSide: 'unknown', + } +} + +/** + * What a post set on `levelId` snaps to: each beam's centreline (its ends, its middle and its run, + * so a post lands under a beam end or anywhere along it) and the other posts' and columns' + * centres. + */ +export function postSnapTargets( + nodes: Nodes, + levelId: string, + ignoreIds: readonly string[] = [], +): WallNode[] { + const targets: WallNode[] = [] + for (const node of Object.values(nodes)) { + if (node.parentId !== levelId || node.visible === false || ignoreIds.includes(node.id)) continue + if (node.type === 'beam') { + targets.push({ ...pointReference(node.id, node.parentId, node.start), end: node.end }) + } else if (node.type === 'column') { + targets.push(pointReference(node.id, node.parentId, [node.position[0], node.position[2]])) + } + } + return targets +} + +/** + * A post's plan point, snapped: a beam end, a point along a beam or another post catch it within + * the snap's reach, otherwise the grid while it snaps. `free` (Alt) places it as it is. + */ +export function snapPostPoint(args: { + point: WallPlanPoint + nodes: Nodes + levelId: string + ignoreIds?: readonly string[] + free?: boolean +}): { point: WallPlanPoint; snap: WallDraftSnapKind | null } { + if (args.free) return { point: args.point, snap: null } + const step = useEditor.getState().gridSnapStep + const grid = isGridSnapActive() + const result = snapWallDraftPointDetailed({ + point: args.point, + walls: postSnapTargets(args.nodes, args.levelId, args.ignoreIds), + // Beams and posts always catch a post; the grid is the snapping mode's to give. + magnetic: true, + gridSnap: (point) => (grid ? snapBuildingLocalToWorldGrid(point, step) : point), + }) + return { point: result.point, snap: result.snap } +} + +/** What a framing post stands on at a point: the support the floor stacking elects under it. */ +export type PostFoot = (post: ColumnNode) => number + +/** The runtime foot: the elected support's top under the post, plus its own lift. */ +export const electedPostFoot = + (nodes: Nodes, levelId: string): PostFoot => + (post) => + getFloorPlacedElevation({ + node: post as AnyNode, + nodes: nodes as Record, + position: post.position, + levelId, + }) + post.position[1] + +/** + * A framing post at a plan point, standing from its foot up to the underside of the beam over it, + * else to the ceiling plane: what the Post tool places, and its ghost. + */ +export function framingPostAt(args: { + nodes: Nodes + levelId: string + point: WallPlanPoint + choices: FramingPostToolChoices + foot: PostFoot +}): { post: ColumnNode; beamId: string | null } { + const draft = createFramingPost({ + position: args.point, + size: args.choices.size, + base: args.choices.base, + levelId: args.levelId, + }) + const base = args.foot(draft) + const { top, beamId } = framingPostTopAt(args.nodes, args.levelId, args.point, { + baseElevation: base, + side: FRAMING_POST_SIZES[args.choices.size], + }) + return { + post: createFramingPost({ + position: args.point, + size: args.choices.size, + base: args.choices.base, + levelId: args.levelId, + height: top - base, + }), + beamId, + } +} + +/** + * Posts under a beam: one at each end and evenly between, no further apart than `spacing`, each + * from its foot up to this beam's underside. Skips a station a post already stands at. + */ +export function framingPostsUnderBeam(args: { + nodes: Nodes + beam: BeamNode + choices: FramingPostToolChoices + foot: PostFoot + spacing?: number +}): ColumnNode[] { + const levelId = args.beam.parentId + if (!levelId) return [] + const standing = Object.values(args.nodes).filter( + (node): node is ColumnNode => node.type === 'column' && node.parentId === levelId, + ) + const underside = beamBottomElevation(args.nodes, args.beam) + return postStationsAlong(args.beam.start, args.beam.end, args.spacing ?? FRAMING_POST_SPACING) + .filter( + (point) => + !standing.some( + (post) => + Math.hypot(post.position[0] - point[0], post.position[2] - point[1]) < + Math.max(post.width, FRAMING_POST_SIZES[args.choices.size]), + ), + ) + .map((point) => { + const fields = { position: point, ...args.choices, levelId } + const foot = args.foot(createFramingPost(fields)) + return createFramingPost({ ...fields, height: underside - foot }) + }) +} diff --git a/packages/nodes/src/column/post-section.tsx b/packages/nodes/src/column/post-section.tsx new file mode 100644 index 0000000000..f8bf0f630d --- /dev/null +++ b/packages/nodes/src/column/post-section.tsx @@ -0,0 +1,163 @@ +'use client' + +import { + type AnyNode, + type AnyNodeId, + type ColumnNode, + type ColumnPostBase, + FRAMING_POST_BASE_LABELS, + FRAMING_POST_MATERIAL_REF, + FRAMING_POST_SIZES, + type FramingPostSize, + framingPostBasePatch, + framingPostHeightLimit, + framingPostSize, + framingPostTopAt, + useScene, +} from '@pascal-app/core' +import { + formatLinearMeasurement, + MetricControl, + PanelSection, + SegmentedControl, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { electedPostFoot } from './post-placement' + +/** Sawn lumber for a post kept dry inside; a post's default is treated (`FRAMING_POST_MATERIAL_REF`). */ +const UNTREATED_REF = 'library:wood-woodfine2' +const CUSTOM = 'custom' + +/** The fields that make a post another square size, its base kept in proportion. */ +export function framingPostSizePatch(post: ColumnNode, size: FramingPostSize): Partial { + const side = FRAMING_POST_SIZES[size] + return { + width: side, + depth: side, + radius: side / 2, + ...framingPostBasePatch( + { width: side, depth: side, slots: post.slots }, + post.postBase ?? 'none', + ), + } +} + +/** Where a post's top goes: the beam over it, else the ceiling plane, and its height to it from its foot. */ +export function framingPostFit( + nodes: Record, + post: ColumnNode, + foot: number, +): { height: number; beamId: string | null } | null { + if (!post.parentId) return null + const { top, beamId } = framingPostTopAt( + nodes, + post.parentId, + [post.position[0], post.position[2]], + { baseElevation: foot, side: post.width }, + ) + return { height: top - foot, beamId } +} + +/** + * A framing post's settings in the column panel, in place of a column's styles: its size, its + * base, its wood, its height with a fit to the beam over it, and the deck-post height check. + */ +export function FramingPostSection({ + node, + onUpdate, +}: { + node: ColumnNode + onUpdate: (updates: Partial) => void +}) { + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const nodes = useScene((state) => state.nodes) + const size = framingPostSize(node) ?? CUSTOM + const treated = (node.slots?.shaft ?? FRAMING_POST_MATERIAL_REF) === FRAMING_POST_MATERIAL_REF + const limit = framingPostHeightLimit(node) + const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation) + const fit = + node.parentId !== null + ? framingPostFit(nodes, node, electedPostFoot(nodes, node.parentId)(node)) + : null + const fitBeam = fit?.beamId ? nodes[fit.beamId as AnyNodeId] : undefined + + return ( + + { + if (next !== CUSTOM) onUpdate(framingPostSizePatch(node, next as FramingPostSize)) + }} + options={[ + ...(Object.keys(FRAMING_POST_SIZES) as FramingPostSize[]).map((value) => ({ + label: value, + value, + })), + ...(size === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ]} + value={size} + /> + onUpdate(framingPostBasePatch(node, base))} + options={(['none', 'post-base', 'pier'] as const).map((value) => ({ + label: FRAMING_POST_BASE_LABELS[value], + value, + }))} + value={node.postBase ?? 'none'} + /> + + onUpdate({ + slots: { + ...node.slots, + shaft: wood === 'treated' ? FRAMING_POST_MATERIAL_REF : UNTREATED_REF, + }, + }) + } + options={[ + { label: 'Treated', value: 'treated' }, + { label: 'Untreated', value: 'untreated' }, + ]} + value={treated ? 'treated' : 'untreated'} + /> + onUpdate({ height })} + precision={3} + step={0.01} + unit="m" + value={node.height} + /> + {fit && Math.abs(fit.height - node.height) > 0.001 && fit.height > 0.3 && ( + + )} + {limit && ( +

+ A {limit.size} deck post stands no taller than {length(limit.limit)} (IRC Table R507.4, by + species and the area it carries): check it, or use the next size up. +

+ )} + +
+ ) +} diff --git a/packages/nodes/src/column/post-tool.tsx b/packages/nodes/src/column/post-tool.tsx new file mode 100644 index 0000000000..ca9f008222 --- /dev/null +++ b/packages/nodes/src/column/post-tool.tsx @@ -0,0 +1,135 @@ +'use client' + +import { + type AnyNodeId, + type ColumnNode, + ColumnNode as ColumnNodeSchema, + emitter, + type GridEvent, + resolveSupportSlabPatch, + useScene, +} from '@pascal-app/core' +import { + getFloorStackPreviewPosition, + triggerSFX, + useEditor, + usePlacementPreview, + useWallSnapIndicator, + type WallPlanPoint, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { useEffect, useRef, useState } from 'react' +import type { Group } from 'three' +import { + type FloorPlacementClickTriggerEvent, + stopPlacementCommitPropagation, + subscribeFloorPlacementClicks, +} from '../shared/floor-placement' +import { + electedPostFoot, + type FramingPostToolChoices, + framingPostAt, + snapPostPoint, +} from './post-placement' +import { ColumnPreview } from './renderer' + +type Placed = { point: WallPlanPoint; post: ColumnNode; beamId: string | null } + +/** + * The Post tool: the column tool armed with a framing post (`framingPostToolChoices`). The post + * snaps under a beam's end, anywhere along a beam, onto another post, else to the grid (Alt free); + * it stands from what it lands on up to the underside of the beam over it, else to the ceiling + * plane, and the ghost shows that height before the click. Repeats as the column tool does. + */ +export function FramingPostTool({ choices }: { choices: FramingPostToolChoices }) { + const levelId = useViewer((state) => state.selection.levelId) + const cursorRef = useRef(null) + const [ghost, setGhost] = useState(null) + + useEffect(() => { + if (!levelId) return + let last: Placed | null = null + setGhost(null) + + const place = (raw: WallPlanPoint, free: boolean): Placed => { + const nodes = useScene.getState().nodes + const { point, snap } = snapPostPoint({ point: raw, nodes, levelId, free }) + if (snap) useWallSnapIndicator.getState().set({ x: point[0], z: point[1], kind: snap }) + else useWallSnapIndicator.getState().clear() + return { + point, + ...framingPostAt({ nodes, levelId, point, choices, foot: electedPostFoot(nodes, levelId) }), + } + } + const show = (placed: Placed) => { + const visual = getFloorStackPreviewPosition({ + node: placed.post, + position: placed.post.position, + rotation: 0, + levelId, + }) + cursorRef.current?.position.set(...visual) + usePlacementPreview.getState().set(placed.post) + setGhost(placed.post) + } + + const onMove = (event: GridEvent) => { + const next = place( + [event.localPosition[0], event.localPosition[2]], + event.nativeEvent?.altKey === true, + ) + if ( + last && + last.point[0] === next.point[0] && + last.point[1] === next.point[1] && + Math.abs(last.post.height - next.post.height) < 1e-6 + ) + return + if (!last || last.point[0] !== next.point[0] || last.point[1] !== next.point[1]) + triggerSFX('sfx:grid-snap') + last = next + show(next) + } + + const onClick = (event: FloorPlacementClickTriggerEvent) => { + // A click on a beam or a post is a node event, its point not the level's: use the move's. + const placed = + last ?? + place([event.localPosition[0], event.localPosition[2]], event.nativeEvent?.altKey === true) + stopPlacementCommitPropagation(event) + const nodes = useScene.getState().nodes + const posts = Object.values(nodes).filter( + (node) => node.type === 'column' && node.role === 'post', + ).length + const post = ColumnNodeSchema.parse({ + ...placed.post, + name: `Post ${posts + 1}`, + ...resolveSupportSlabPatch(placed.post, { ...nodes, [placed.post.id]: placed.post }), + }) + useScene.getState().createNode(post, levelId as AnyNodeId) + triggerSFX('sfx:structure-build') + last = null + if (useEditor.getState().getContinuation('point') === 'repeat') return + useViewer.getState().setSelection({ selectedIds: [post.id] }) + usePlacementPreview.getState().clear() + useEditor.getState().setTool(null) + useEditor.getState().setMode('select') + } + + emitter.on('grid:move', onMove) + const unsubscribeClicks = subscribeFloorPlacementClicks(onClick) + return () => { + emitter.off('grid:move', onMove) + unsubscribeClicks() + usePlacementPreview.getState().clear() + useWallSnapIndicator.getState().clear() + } + }, [levelId, choices]) + + if (!levelId) return null + return ( + + {ghost && } + + ) +} diff --git a/packages/nodes/src/column/post.test.ts b/packages/nodes/src/column/post.test.ts new file mode 100644 index 0000000000..3317883303 --- /dev/null +++ b/packages/nodes/src/column/post.test.ts @@ -0,0 +1,201 @@ +import { afterEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BeamNode, + BuildingNode, + ColumnNode, + createFramingPost, + type FloorplanGeometry, + type GeometryContext, + LevelNode, +} from '@pascal-app/core' +import { useEditor } from '@pascal-app/editor' +import { buildColumnFloorplan } from './floorplan' +import { columnPlaceOption, postBaseOption } from './post-options' +import { + armFramingPostTool, + framingPostAt, + framingPostsUnderBeam, + framingPostToolChoices, + framingPostToolDefaults, + postSnapTargets, + snapPostPoint, +} from './post-placement' +import { framingPostFit, framingPostSizePatch } from './post-section' + +const IN = 0.0254 +type Nodes = Record +const previous = useEditor.getState() + +afterEach(() => { + useEditor.setState(previous, true) +}) + +/** A header 6x8 over a porch's front, 8 ft of it, its top at the 2.7 m plane. */ +const HEADER = BeamNode.parse({ + id: 'beam_h', + parentId: 'level_g', + start: [0, 0], + end: [4.8768, 0], + width: 5.5 * IN, + depth: 7.5 * IN, +}) + +function storey(extra: AnyNode[] = []): Nodes { + return Object.fromEntries( + [ + BuildingNode.parse({ id: 'building_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + height: 2.7, + children: [HEADER.id, ...extra.map((node) => node.id)], + }), + HEADER, + ...extra, + ].map((node) => [node.id, node]), + ) as Nodes +} + +/** Every post stands on the floor plane here: what the floor stacking elects in the editor. */ +const onFloor = (post: ColumnNode) => post.position[1] + +describe('the Post tool', () => { + test('is the column tool armed with a framing post, at the size and base picked', () => { + expect(framingPostToolChoices(null)).toBeNull() + expect(framingPostToolChoices({ thickness: 1 })).toBeNull() + expect(framingPostToolChoices(framingPostToolDefaults('8x8', 'pier'))).toEqual({ + size: '8x8', + base: 'pier', + }) + armFramingPostTool('4x4') + const editor = useEditor.getState() + expect(editor).toMatchObject({ mode: 'build', tool: 'column' }) + expect(framingPostToolChoices(editor.toolDefaults.column)).toEqual({ + size: '4x4', + base: 'none', + }) + // the base row keeps the size; the place row reads both + postBaseOption.set('post-base') + expect(framingPostToolChoices(useEditor.getState().toolDefaults.column)).toEqual({ + size: '4x4', + base: 'post-base', + }) + expect(columnPlaceOption.value()).toBe('4x4') + expect(postBaseOption.visible!.value()).toBe(true) + // back to columns: the ornamental column tool, the base row hidden + columnPlaceOption.set('column') + expect(useEditor.getState().toolDefaults.column).toBeUndefined() + expect(columnPlaceOption.value()).toBe('column') + expect(postBaseOption.visible!.value()).toBe(false) + // the tile comes back as it was left + armFramingPostTool() + expect(framingPostToolChoices(useEditor.getState().toolDefaults.column)).toEqual({ + size: '4x4', + base: 'post-base', + }) + }) + + test("snaps under a beam's end, along a beam, and onto another post", () => { + const post = createFramingPost({ position: [2, 3], levelId: 'level_g' }) + const nodes = storey([post]) + expect( + postSnapTargets(nodes, 'level_g') + .map((target) => target.id) + .sort(), + ).toEqual([HEADER.id, post.id].sort()) + const at = (point: [number, number]) => + snapPostPoint({ point, nodes, levelId: 'level_g' }).point + expect(at([4.83, 0.05])).toEqual([4.8768, 0]) + const along = at([1.3, 0.08]) + expect(along[0]).toBeCloseTo(1.3) + expect(along[1]).toBeCloseTo(0) + expect(at([2.04, 2.97])).toEqual([2, 3]) + expect(snapPostPoint({ point: [1.3, 0.08], nodes, levelId: 'level_g', free: true })).toEqual({ + point: [1.3, 0.08], + snap: null, + }) + }) + + test('a post stands up to the underside of the beam over it, else the ceiling plane', () => { + const nodes = storey() + const under = framingPostAt({ + nodes, + levelId: 'level_g', + point: [0, 0], + choices: { size: '6x6', base: 'none' }, + foot: onFloor, + }) + expect(under.beamId).toBe(HEADER.id) + expect(under.post.height).toBeCloseTo(2.7 - 7.5 * IN) + expect(under.post).toMatchObject({ role: 'post', parentId: 'level_g', position: [0, 0, 0] }) + const open = framingPostAt({ + nodes, + levelId: 'level_g', + point: [3, 3], + choices: { size: '4x4', base: 'pier' }, + foot: () => 0.05, + }) + expect(open.beamId).toBeNull() + expect(open.post.height).toBeCloseTo(2.65) + expect(open.post.postBase).toBe('pier') + }) + + test('a beam gets posts at each end and no further apart than 8 ft, skipping one standing', () => { + const standing = createFramingPost({ position: [0, 0], levelId: 'level_g' }) + const posts = framingPostsUnderBeam({ + nodes: storey([standing]), + beam: { ...HEADER, end: [6, 0] }, + choices: { size: '6x6', base: 'none' }, + foot: onFloor, + }) + // 6 m is three bays of 2 m (two of 8 ft would not reach); the near end has its post already + expect(posts.map((post) => [post.position[0], post.position[2]])).toEqual([ + [2, 0], + [4, 0], + [6, 0], + ]) + for (const post of posts) expect(post.height).toBeCloseTo(2.7 - 7.5 * IN) + }) +}) + +describe('a framing post in the column panel and the plan', () => { + test('changes size with its base in proportion, and fits to the beam over it', () => { + const post = createFramingPost({ position: [1, 0], size: '4x4', base: 'pier', height: 2 }) + const patch = framingPostSizePatch(post, '6x6') + expect(patch.width).toBeCloseTo(5.5 * IN) + expect(patch.width! * patch.baseWidthScale!).toBeCloseTo(11.5 * IN) + const fit = framingPostFit(storey(), { ...post, parentId: 'level_g' }, 0) + expect(fit?.beamId).toBe(HEADER.id) + expect(fit?.height).toBeCloseTo(2.7 - 7.5 * IN) + }) + + test('is drawn as a framing plan draws a post: its own square crossed corner to corner', () => { + const view = { + viewState: { selected: false, highlighted: false, hovered: false, moving: false }, + } as unknown as GeometryContext + const lines = (node: ColumnNode) => { + const flat = (g: FloorplanGeometry | null): FloorplanGeometry[] => + !g ? [] : g.kind === 'group' ? g.children.flatMap(flat) : [g] + return flat(buildColumnFloorplan(node, view)).filter((g) => g.kind === 'line') + } + const post = createFramingPost({ position: [2, 2], size: '6x6', base: 'pier' }) + const cross = lines(post).filter( + (line) => line.kind === 'line' && Math.abs(Math.abs(line.x2 - line.x1) - 5.5 * IN) < 1e-9, + ) + expect(cross).toHaveLength(2) + // an ornamental column keeps its small centre mark only + expect(lines(ColumnNode.parse({ position: [2, 0, 2] }))).toHaveLength(2) + // the post's plan footprint is the post, not a capital's or a base's spread + const bare = createFramingPost({ position: [2, 2], size: '6x6' }) + const outline = (() => { + const flat = (g: FloorplanGeometry | null): FloorplanGeometry[] => + !g ? [] : g.kind === 'group' ? g.children.flatMap(flat) : [g] + return flat(buildColumnFloorplan(bare, view)).find((g) => g.kind === 'polygon') + })() + const xs = outline?.kind === 'polygon' ? outline.points.map((point) => point[0]) : [] + expect(Math.max(...xs) - Math.min(...xs)).toBeCloseTo(5.5 * IN) + }) +}) diff --git a/packages/nodes/src/column/tool.tsx b/packages/nodes/src/column/tool.tsx index c77cbd2fd9..29f7ffc70a 100644 --- a/packages/nodes/src/column/tool.tsx +++ b/packages/nodes/src/column/tool.tsx @@ -40,6 +40,8 @@ import { collectStructuralGridAxes, resolveStructuralGridSnap, } from '../structural-grid/coordination' +import { framingPostToolChoices } from './post-placement' +import { FramingPostTool } from './post-tool' import { ColumnPreview } from './renderer' const DEFAULT_COLUMN_PRESET_ID = 'basicPillar' satisfies ColumnPresetId @@ -65,7 +67,7 @@ function createColumnFromPreset(presetId: ColumnPresetId, position: [number, num * `nodes`. Wired via `def.tool`, so `ToolManager`'s registry-first path * mounts it and the legacy `` branch no longer fires. */ -const ColumnTool = () => { +const PillarTool = () => { const activeLevelId = useViewer((state) => state.selection.levelId) const camera = useThree((state) => state.camera) const cameraRef = useRef(camera) @@ -261,4 +263,17 @@ const ColumnTool = () => { ) } +/** + * The column tool, or the Post tool while the Build panel's Post tile has armed it with a framing + * post's fields (`toolDefaults.column`, `framingPostToolChoices`). + */ +const ColumnTool = () => { + const size = useEditor((state) => framingPostToolChoices(state.toolDefaults.column)?.size ?? null) + const base = useEditor( + (state) => framingPostToolChoices(state.toolDefaults.column)?.base ?? 'none', + ) + const choices = useMemo(() => (size ? { size, base } : null), [size, base]) + return choices ? : +} + export default ColumnTool diff --git a/packages/nodes/src/door/actions.tsx b/packages/nodes/src/door/actions.tsx new file mode 100644 index 0000000000..1e2cb13dad --- /dev/null +++ b/packages/nodes/src/door/actions.tsx @@ -0,0 +1,52 @@ +'use client' +import { type AnyNodeId, type DoorNode, useScene } from '@pascal-app/core' +import { ActionMenuButton, triggerSFX } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { ArrowLeftRight, ArrowUpDown } from 'lucide-react' +import { canSwapDoorHinge, canSwapDoorSwing, swapDoorHingePatch, swapDoorSwingPatch } from './swing' + +function applyDoorPatch(door: DoorNode, patch: Partial) { + const scene = useScene.getState() + scene.updateNode(door.id, patch) + scene.dirtyNodes.add(door.id as AnyNodeId) + if (door.parentId) scene.dirtyNodes.add(door.parentId as AnyNodeId) + triggerSFX('sfx:item-rotate') +} + +/** + * The door's action-menu contributions, in 2D and 3D: swap the hinge side and swap the swing + * side without opening the panel's Swing section. One undo step each. + */ +export default function DoorActions() { + const selected = useViewer((s) => s.selection.selectedIds) + const door = useScene((s) => { + const node = selected.length === 1 ? s.nodes[selected[0] as AnyNodeId] : undefined + return node?.type === 'door' ? node : undefined + }) + const readOnly = useScene((s) => s.readOnly) + if (!door || readOnly || !canSwapDoorSwing(door)) return null + return ( + <> + {canSwapDoorHinge(door) ? ( + { + event.stopPropagation() + applyDoorPatch(door, swapDoorHingePatch(door)) + }} + > + + + ) : null} + { + event.stopPropagation() + applyDoorPatch(door, swapDoorSwingPatch(door)) + }} + > + + + + ) +} diff --git a/packages/nodes/src/door/definition.ts b/packages/nodes/src/door/definition.ts index 4c6ba27b87..428cb2c99a 100644 --- a/packages/nodes/src/door/definition.ts +++ b/packages/nodes/src/door/definition.ts @@ -223,6 +223,8 @@ export const doorDefinition: NodeDefinition = { 'pascal:editor/floorplan': { contextualDimensions: buildDoorContextualDimensions, schedule: buildDoorFloorplanSchedule, + // Swap hinge / swing side from the action bar, in 2D and 3D. + actionMenu: { actions: () => import('./actions') }, } satisfies FloorplanNodeExtension, }, surfaceRole: 'joinery', diff --git a/packages/nodes/src/door/floorplan-move.ts b/packages/nodes/src/door/floorplan-move.ts index b6be1f28ed..5c53a006e6 100644 --- a/packages/nodes/src/door/floorplan-move.ts +++ b/packages/nodes/src/door/floorplan-move.ts @@ -18,6 +18,7 @@ import { usePlacementPreview, } from '@pascal-app/editor' import { createFloorplanCursorResolver } from '../shared/floorplan-cursor' +import { movedOpeningCasingFaces } from '../shared/opening-casing-move' import { openingPlaneOffsetOnWall } from '../shared/opening-plane-offset' import { getOpeningHostLevelId, getRoofHostedOpeningPlanPoint } from '../shared/roof-opening-host' import { @@ -83,6 +84,8 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) roofSegmentId: undefined roofFace: undefined visible: true + casingFaceA: boolean + casingFaceB: boolean } | null = null // R flips the door's facing (front ↔ back) mid-placement. `apply` re-derives @@ -210,7 +213,14 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) nodes, }) const snappedLocalX = neighborX ?? snapToHalf(hit.localX) - const { clampedX, clampedY } = clampToWall(hit.wall, snappedLocalX, node.width, node.height) + // On a garage's wall to the outside the door comes down to its slab (`doorSillFloorOf`). + const { clampedX, clampedY } = clampToWall( + hit.wall, + snappedLocalX, + node.width, + node.height, + nodes, + ) // One click per real position step, keyed on the SNAPPED along-wall value // so it ticks only when the door actually moves to a new cell. @@ -219,6 +229,8 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) // Apply the R-flip on top of the wall-derived side. const side: DoorNode['side'] = flipped ? (hit.side === 'front' ? 'back' : 'front') : hit.side const itemRotation = hit.itemRotation + (flipped ? Math.PI : 0) + // Another wall may be drawn the other way round: the casing keeps to its side. + const casing = movedOpeningCasingFaces(node, hit.wall.id, [0, itemRotation, 0], nodes) lastValid = { // Same-wall moves keep the wall-local plane offset; another host resets it. @@ -232,6 +244,7 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) roofSegmentId: undefined, roofFace: undefined, visible: true, + ...casing, } setLiveOverride(`wall:${hit.wall.id}:${side}`, { @@ -241,6 +254,7 @@ export const doorFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) roofSegmentId: undefined, roofFace: undefined, visible: true, + ...casing, }) useLiveTransforms.getState().set(nodeId, { position: lastValid.position, diff --git a/packages/nodes/src/door/floorplan.test.ts b/packages/nodes/src/door/floorplan.test.ts index 03751e402d..5ecce3e714 100644 --- a/packages/nodes/src/door/floorplan.test.ts +++ b/packages/nodes/src/door/floorplan.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from 'bun:test' import { DoorNode, type FloorplanGeometry, type GeometryContext, WallNode } from '@pascal-app/core' import { buildDoorFloorplan } from './floorplan' +import { placementSwing, swapDoorHingePatch, swapDoorSwingPatch } from './swing' const wall = WallNode.parse({ id: 'wall_door-plan', @@ -109,3 +110,44 @@ describe('buildDoorFloorplan wall-local plane offset', () => { for (const panel of panels) expect(panel.max).toBeLessThanOrEqual(-0.1 + 1e-9) }) }) + +/** The plan z the open leaf reaches: + is the wall's front (+Z) side, - its back. */ +function leafSide(geometry: FloorplanGeometry[]) { + const leaf = geometry + .filter((item) => item.kind === 'line') + .reduce<{ y1: number; y2: number } | null>((best, item) => { + if (item.kind !== 'line') return best + return !best || Math.abs(item.y2 - item.y1) > Math.abs(best.y2 - best.y1) ? item : best + }, null) + return Math.sign(leaf!.y2 - leaf!.y1) +} + +describe('swing chosen while placing', () => { + const placed = (rotationY: number, cursor: 'front' | 'back', values: Partial = {}) => + buildDoor({ + ...values, + rotation: [0, rotationY, 0], + swingDirection: placementSwing(DoorNode.parse({ ...values }), rotationY, cursor), + }) + + test('the leaf swings into the side of the wall the cursor is on', () => { + expect(leafSide(placed(0, 'front'))).toBe(1) + expect(leafSide(placed(Math.PI, 'back'))).toBe(-1) + }) + + test('a door held facing out (an outside wall) still swings toward the cursor', () => { + expect(leafSide(placed(0, 'back'))).toBe(-1) + expect(leafSide(placed(Math.PI, 'front'))).toBe(1) + expect(leafSide(placed(0, 'front', { doorType: 'double' }))).toBe(1) + }) + + test('swapping the swing sends the leaf to the other side; swapping hinges keeps it there', () => { + const door = DoorNode.parse({ id: 'door_swap', hingesSide: 'left', handleSide: 'right' }) + expect(leafSide(buildDoor({ ...door, ...swapDoorSwingPatch(door) }))).toBe( + -leafSide(buildDoor(door)), + ) + const swapped = swapDoorHingePatch(door) + expect(swapped).toEqual({ hingesSide: 'right', handleSide: 'left' }) + expect(leafSide(buildDoor({ ...door, ...swapped }))).toBe(leafSide(buildDoor(door))) + }) +}) diff --git a/packages/nodes/src/door/floorplan.ts b/packages/nodes/src/door/floorplan.ts index 0cbfbb3a10..b724e08a4c 100644 --- a/packages/nodes/src/door/floorplan.ts +++ b/packages/nodes/src/door/floorplan.ts @@ -11,6 +11,7 @@ import { readFloorplanGeometryMetadata, withFloorplanGeometryMetadata, } from '@pascal-app/editor' +import { buildOpeningCasingPlan } from '../shared/opening-casing' import { buildOpeningMarkAnnotation, type OpeningFloorplanLevelData, @@ -42,6 +43,8 @@ import { resolveOpeningPlanPlane } from '../shared/opening-plane-offset' * with no swing arc. * 4. Center line through the cutout (matches the legacy's * `getOpeningCenterLine` segment for visual continuity). + * 5. Casing legs standing proud of each cased wall face beside the + * cutout (`buildOpeningCasingPlan`). * * Requires `ctx.parent` to be a wall (door.parentId is the wall it's * mounted on). Returns null when the parent isn't a wall (orphaned @@ -160,6 +163,15 @@ export function buildDoorFloorplan(node: DoorNode, ctx: GeometryContext): Floorp }, ] + // Casing legs on the wall faces beside the cutout, as the 3D door has them. + const casing = buildOpeningCasingPlan(node, wall, { + fill: fillColor, + stroke: accentMuted, + strokeWidth: showSelectedChrome ? 1.4 : 1, + vectorEffect: 'non-scaling-stroke', + }) + if (casing) children.push(casing) + if (node.openingShape !== 'rectangle') { const overheadRise = Math.min(width, node.openingShape === 'arch' ? node.archHeight : node.cornerRadius) * 0.2 @@ -305,8 +317,9 @@ export function buildDoorFloorplan(node: DoorNode, ctx: GeometryContext): Floorp // fall through to just the opening footprint (the earlier behaviour). const isSwingDoor = node.doorType === 'hinged' || isDoubleLeaf // A frameless wall opening — drawn as a bare gap, no leaf / arc / panel - // (mirrors the 3D system, which renders only the cutout for openings). - const isOpening = node.openingKind === 'opening' + // (mirrors the 3D system, which renders only the cutout for openings). A + // door on a railing is always one: a gap in the guard. + const isOpening = node.openingKind === 'opening' || wall.wallType === 'railing' if (isOpening) { // Open doorway — a frameless gap in the wall. No leaf, arc, or panel; diff --git a/packages/nodes/src/door/move-tool.tsx b/packages/nodes/src/door/move-tool.tsx index 3adc3472ee..8dd03850d7 100644 --- a/packages/nodes/src/door/move-tool.tsx +++ b/packages/nodes/src/door/move-tool.tsx @@ -41,6 +41,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { BoxGeometry, EdgesGeometry, type Group, Vector3 } from 'three' import { LineBasicNodeMaterial } from 'three/webgpu' import { commitOpeningMove } from '../shared/commit-opening-move' +import { movedOpeningCasingFaces } from '../shared/opening-casing-move' import { clearOpeningGuides3D, publishOpeningGuidesForWallEvent, @@ -162,6 +163,9 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => // Same-wall moves keep the wall-local plane offset; another host resets it. const planeOffsetOn = (wallId: string) => openingPlaneOffsetOnWall(movingDoorNode, wallId) + // Another host may name its faces the other way round: the casing keeps to its side of it. + const casingOn = (hostId: string, rotation: DoorNode['rotation']) => + movedOpeningCasingFaces(movingDoorNode, hostId, rotation, useScene.getState().nodes) let currentHostId: string | null = movingDoorNode.parentId let dragAnchor: { wallId: string; rawX: number; startX: number } | null = null @@ -354,11 +358,14 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => // component lives in `snapToHalf` (itself mode-aware). applySnap: isMagneticSnapActive(), }) + // On a garage's wall to the outside the door comes down to its slab; back on a house wall, + // up to the house's floor (`doorSillFloorOf`). const { clampedX, clampedY } = clampToWall( event.node, localX, movingDoorNode.width, movingDoorNode.height, + useScene.getState().nodes, ) const valid = !hasWallChildOverlap( @@ -468,11 +475,11 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => }) // Forward-facing triangle (editor-side overlay), in the same building-local // frame the ghost renders in. The door's front is its local +Z. Drop it to - // the floor under the wall (the ghost Y is the opening centre, ~1m up). + // the door's sill (the ghost Y is the opening centre, ~1m up). useFacingPose.getState().set({ position: [ ghostWorldPos[0], - getLevelYOffset() + getSlabElevation(target.event), + ghostWorldPos[1] - movingDoorNode.height / 2, ghostWorldPos[2], ], rotationY: ghostYaw, @@ -576,6 +583,7 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => side: target.side, wallId: target.wallId, parentId: target.wallId, + ...casingOn(target.wallId, [0, target.itemRotation, 0]), roofSegmentId: undefined, roofFace: undefined, // The moving node is hidden during free-follow; the committed door @@ -611,6 +619,7 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => side: target.side, parentId: target.wallId, wallId: target.wallId, + ...casingOn(target.wallId, [0, target.itemRotation, 0]), roofSegmentId: undefined, visible: true, }) @@ -847,6 +856,7 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => roofSegmentId: segmentId, roofFace: target.face.id, parentId: segmentId, + ...casingOn(segmentId, [0, 0, 0]), visible: true, }) history.commitStep(() => { @@ -879,6 +889,7 @@ const MoveDoorTool: React.FC<{ node: DoorNode }> = ({ node: movingDoorNode }) => wallId: undefined, roofSegmentId: segmentId, roofFace: target.face.id, + ...casingOn(segmentId, [0, 0, 0]), visible: true, }) }) diff --git a/packages/nodes/src/door/panel.tsx b/packages/nodes/src/door/panel.tsx index f2ee5084a4..ad16082ac0 100644 --- a/packages/nodes/src/door/panel.tsx +++ b/packages/nodes/src/door/panel.tsx @@ -31,6 +31,7 @@ import { Copy, DoorOpen, FlipHorizontal2, Move, Trash2 } from 'lucide-react' import { useCallback, useEffect, useMemo } from 'react' import { AuthoredParams } from '../item/authored-params' import { constrainCurtainOpening, curtainOpeningLimits } from '../shared/curtain-opening-limits' +import { OpeningCasingSection } from '../shared/opening-casing-section' import { DOOR_STYLE_MESSAGES } from '../shared/opening-labels' import { createOpeningPropertyPreview } from '../shared/opening-property-preview' import { openingPropertyPreviewHost } from '../shared/opening-property-preview-host' @@ -213,9 +214,10 @@ export default function DoorPanel() { if (!(node && node.type === 'door' && selectedId)) return null const limits = curtainOpeningLimits(node, useScene.getState().nodes) + // The sill stays where it stands (a garage's slab, the house's floor); the head moves. const heightUpdates = (height: number): Partial => ({ height, - position: [node.position[0], height / 2, node.position[2]], + position: [node.position[0], node.position[1] - node.height / 2 + height / 2, node.position[2]], handleHeight: scaleHandleHeight(node.handleHeight, node.height, height), }) const hSum = node.segments.reduce((s, seg) => s + seg.heightRatio, 0) @@ -1198,6 +1200,8 @@ export default function DoorPanel() { )} + + & + Partial> + +const flip = (side: 'left' | 'right') => (side === 'left' ? 'right' : 'left') + +/** Whether a door swings on hinges (the panel's Swing section), so its swing can be swapped. */ +export function canSwapDoorSwing(door: SwingDoor): boolean { + return ( + !door.source && + door.openingKind !== 'opening' && + (door.doorType === 'hinged' || door.doorType === 'double' || door.doorType === 'french') + ) +} + +/** Whether a door has one hinge side to swap: a single hinged leaf. */ +export function canSwapDoorHinge(door: SwingDoor): boolean { + return canSwapDoorSwing(door) && door.doorType === 'hinged' +} + +/** The leaf hung from the other jamb: hinges and handle trade sides, so the handle stays on the free edge. */ +export function swapDoorHingePatch(door: SwingDoor): Partial { + return { hingesSide: flip(door.hingesSide), handleSide: flip(door.handleSide) } +} + +/** The leaf swinging into the other side of the wall. */ +export function swapDoorSwingPatch(door: SwingDoor): Partial { + return { swingDirection: door.swingDirection === 'inward' ? 'outward' : 'inward' } +} + +/** + * The swing that opens a door's leaf toward `side` of its wall: the face under the cursor while + * placing, so the door swings into the side it is placed from (R flips it to the other side). An + * inward door opens toward the face it is hung facing (`rotationY` 0 faces front, π back), so a + * door facing that side swings inward and one facing away swings outward. A garage door has no + * swing and keeps its own. + */ +export function placementSwing( + door: Pick, + rotationY: number, + side: 'front' | 'back', +): DoorNode['swingDirection'] { + if (door.doorType?.startsWith('garage-')) return door.swingDirection + const facing = Math.cos(rotationY) >= 0 ? 'front' : 'back' + return facing === side ? 'inward' : 'outward' +} diff --git a/packages/nodes/src/door/tool.tsx b/packages/nodes/src/door/tool.tsx index 6231f453aa..5b14f24f70 100644 --- a/packages/nodes/src/door/tool.tsx +++ b/packages/nodes/src/door/tool.tsx @@ -9,8 +9,10 @@ import { getWallThickness, holdHiddenWallPointerEvents, isCurvedWall, + isRailingWall, type RoofEvent, type RoofNode, + railingOpeningFields, resolveOpeningFrameDepth, sceneRegistry, spatialGridManager, @@ -56,6 +58,7 @@ import { import { clampToWall, hasWallChildOverlap, wallLocalToWorld } from './door-math' import { placedDoorFields, useDoorPlacement } from './placement' import DoorPreview from './preview' +import { placementSwing } from './swing' const edgeMaterial = new LineBasicNodeMaterial({ color: 0xef_44_44, @@ -76,7 +79,7 @@ type HostKind = 'wall' | 'roof' | null * Door tool — places DoorNodes on walls and on roof-segment wall faces * (the generated base walls under a roof, including coplanar gable ends). * Doors always sit at floor level (clampedY = height/2 — segment base for - * roof-hosted doors). + * roof-hosted doors), a garage's slab in its walls to the outside. * * The ghost follows the cursor everywhere (like moving an item): over open * floor it floats as an invalid (unplaceable) ghost; the moment the cursor @@ -329,7 +332,14 @@ const DoorTool: React.FC = () => { candidates: alignmentCandidates, applySnap, }) - const { clampedX, clampedY } = clampToWall(wall, localX, width, height) + // A door in a garage's wall to the outside stands on its slab (`doorSillFloorOf`). + const { clampedX, clampedY } = clampToWall( + wall, + localX, + width, + height, + useScene.getState().nodes, + ) const valid = !hasWallChildOverlap( wall.id, useScene.getState().nodes, @@ -351,10 +361,11 @@ const DoorTool: React.FC = () => { rawLocalX: number side: 'front' | 'back' itemRotation: number + swingSide: 'front' | 'back' cursorRotationY: number applySnap: boolean }) => { - const { wall, rawLocalX, side, itemRotation, cursorRotationY, applySnap } = args + const { wall, rawLocalX, side, itemRotation, swingSide, cursorRotationY, applySnap } = args const placed = placedDoorFields() const width = draftRef.current?.width ?? placed.width ?? 0.9 const height = draftRef.current?.height ?? placed.height ?? 2.1 @@ -381,12 +392,15 @@ const DoorTool: React.FC = () => { applySnap, draftRef.current.id, ) + // The leaf swings into the side the cursor is on (R sends it to the other side). + const swingDirection = placementSwing(draftRef.current, itemRotation, swingSide) if (wall.id === draftRef.current.parentId) { useScene.getState().updateNode(draftRef.current.id, { position: [clampedX, clampedY, 0], rotation: [0, itemRotation, 0], side, + swingDirection, }) markHostDirty(wall.id) } else { @@ -394,6 +408,7 @@ const DoorTool: React.FC = () => { position: [clampedX, clampedY, 0], rotation: [0, itemRotation, 0], side, + swingDirection, parentId: wall.id, wallId: wall.id, // The draft may arrive from a roof-segment face hover. @@ -413,7 +428,7 @@ const DoorTool: React.FC = () => { ), cursorRotationY, valid, - -clampedY, + -height / 2, getWallThickness(wall), ) @@ -441,6 +456,7 @@ const DoorTool: React.FC = () => { clampedY: number, side: 'front' | 'back', itemRotation: number, + swingSide: 'front' | 'back', ) => { const draft = draftRef.current if (!draft) return @@ -459,9 +475,15 @@ const DoorTool: React.FC = () => { return w?.parentId === levelId }).length + // On a railing the door becomes a gap in the guard, as tall as the guard. + const railingGap = isRailingWall(wall) ? railingOpeningFields(wall) : null const node = DoorNode.parse({ - name: `Door ${doorCount + 1}`, - position: [clampedX, clampedY, 0], + name: `${railingGap ? 'Opening' : 'Door'} ${doorCount + 1}`, + position: [ + clampedX, + railingGap ? clampedY + (railingGap.height - draft.height) / 2 : clampedY, + 0, + ], rotation: [0, itemRotation, 0], side, wallId: wall.id, @@ -479,7 +501,7 @@ const DoorTool: React.FC = () => { threshold: draft.threshold, thresholdHeight: draft.thresholdHeight, hingesSide: draft.hingesSide, - swingDirection: draft.swingDirection, + swingDirection: placementSwing(draft, itemRotation, swingSide), segments: draft.segments, contentPadding: draft.contentPadding, handle: draft.handle, @@ -488,6 +510,7 @@ const DoorTool: React.FC = () => { doorCloser: draft.doorCloser, panicBar: draft.panicBar, panicBarHeight: draft.panicBarHeight, + ...railingGap, }) useScene.getState().createNode(node, wall.id as AnyNodeId) @@ -519,8 +542,11 @@ const DoorTool: React.FC = () => { } lastWallEvent = event - // An outside wall's door faces out whichever face is hovered, as add_door places it. - const faceSide = placedDoorFace(event.node, getSideFromNormal(event.normal)) + // An outside wall's door faces out whichever face is hovered, as add_door places it; the + // leaf still swings into the hovered side (R flips facing and swing together). + const hovered = getSideFromNormal(event.normal) + const swingSide = sideFlip ? (hovered === 'front' ? 'back' : 'front') : hovered + const faceSide = placedDoorFace(event.node, hovered) const side = sideFlip ? (faceSide === 'front' ? 'back' : 'front') : faceSide const flipOffset = sideFlip ? Math.PI : 0 const itemRotation = (faceSide === 'back' ? Math.PI : 0) + flipOffset @@ -536,6 +562,7 @@ const DoorTool: React.FC = () => { rawLocalX: event.localPosition[0], side, itemRotation, + swingSide, cursorRotationY: cursorRotation, applySnap: isMagneticSnapActive(), }) @@ -552,7 +579,9 @@ const DoorTool: React.FC = () => { return } - const faceSide = placedDoorFace(event.node, getSideFromNormal(event.normal)) + const hovered = getSideFromNormal(event.normal) + const swingSide = sideFlip ? (hovered === 'front' ? 'back' : 'front') : hovered + const faceSide = placedDoorFace(event.node, hovered) const side = sideFlip ? (faceSide === 'front' ? 'back' : 'front') : faceSide const itemRotation = (faceSide === 'back' ? Math.PI : 0) + (sideFlip ? Math.PI : 0) const { clampedX, clampedY, valid } = resolveWallPlacement( @@ -566,7 +595,7 @@ const DoorTool: React.FC = () => { // Alt force-places over a collision (the draft stays red as a warning). if (!valid && event.nativeEvent?.altKey !== true) return - commitDoorAtWall(event.node, clampedX, clampedY, side, itemRotation) + commitDoorAtWall(event.node, clampedX, clampedY, side, itemRotation, swingSide) event.stopPropagation() } diff --git a/packages/nodes/src/fence/drafting.ts b/packages/nodes/src/fence/drafting.ts index f4c6c41d39..4960a4c31f 100644 --- a/packages/nodes/src/fence/drafting.ts +++ b/packages/nodes/src/fence/drafting.ts @@ -244,9 +244,8 @@ export function snapFenceDraftPoint(args: { step?: number /** * Optional grid-snap function. When provided, replaces the default - * local-axis snap — lets the 2D floor-plan keep snapping to the - * world XZ grid even when the building is rotated. Wall / fence - * endpoint snap precedence is preserved. + * local-axis snap (the floor plan passes its building-grid snap, the + * same lattice). Wall / fence endpoint snap precedence is preserved. */ gridSnap?: (point: FencePlanPoint) => FencePlanPoint }): FencePlanPoint { @@ -280,9 +279,8 @@ export function snapFenceDraftPoint(args: { if (rawTarget) return rawTarget } - // The angle path snaps the distance ALONG the 15° ray — a scalar, the - // same in world and local frames — so the `gridSnap` world-grid override - // only applies when the angle lock is off. + // The angle path snaps the distance ALONG the 15° ray from `start`, so the + // `gridSnap` override only applies when the angle lock is off. const basePoint: FencePlanPoint = start && angleSnap ? [...snapPointAlongAngleRay(start, point, DEFAULT_ANGLE_STEP, gridStep)] diff --git a/packages/nodes/src/floor-opening/floorplan.test.ts b/packages/nodes/src/floor-opening/floorplan.test.ts index 805efd4faf..450ee558bb 100644 --- a/packages/nodes/src/floor-opening/floorplan.test.ts +++ b/packages/nodes/src/floor-opening/floorplan.test.ts @@ -1,6 +1,19 @@ import { expect, test } from 'bun:test' -import { FloorOpeningNode, type FloorplanGeometry, type GeometryContext } from '@pascal-app/core' -import { buildFloorOpeningFloorplan } from './floorplan' +import { + type AnyNodeId, + FloorOpeningNode, + type FloorplanGeometry, + type GeometryContext, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + buildFloorOpeningFloorplan, + floorOpeningMoveVertexAffordance, + moveRectangleEdge, + openingRectangle, + resizeRectangleCorner, +} from './floorplan' const opening = FloorOpeningNode.parse({ polygon: [ @@ -28,3 +41,99 @@ test('an opening draws as a dashed outline, with corner handles only while selec expect(selected.filter((kind) => kind === 'endpoint-handle')).toHaveLength(4) expect(selected.filter((kind) => kind === 'edge-handle')).toHaveLength(4) }) + +test('a stair opening stays a box: solid outline, side grips, corners and sides resize it square', () => { + const rounded = FloorOpeningNode.parse({ + source: 'stair', + ownerId: 'stair_box', + // A legacy opening cut with rounded corners. + polygon: [ + [0.1, 0], + [1.9, 0], + [2, 0.1], + [2, 2.9], + [1.9, 3], + [0.1, 3], + [0, 2.9], + [0, 0.1], + ], + }) + const geometry = buildFloorOpeningFloorplan(rounded, context(true)) + const selected = kinds(geometry) + expect(selected.filter((kind) => kind === 'midpoint-handle')).toHaveLength(0) + expect(selected.filter((kind) => kind === 'edge-handle')).toHaveLength(4) + // Four side grips and four corners. + expect(selected.filter((kind) => kind === 'endpoint-handle')).toHaveLength(8) + const box = openingRectangle(rounded.polygon) + expect(box).toHaveLength(4) + const xs = box.map(([x]) => x) + const zs = box.map(([, z]) => z) + expect([Math.min(...xs), Math.max(...xs), Math.min(...zs), Math.max(...zs)]).toEqual([0, 2, 0, 3]) + const corner = box.findIndex(([x, z]) => x === 2 && z === 3) + const grown = resizeRectangleCorner(box, corner, [2.5, 3.4]) + const square = (ring: [number, number][]) => + ring.every((a, i) => { + const b = ring[(i + 1) % 4]! + const c = ring[(i + 2) % 4]! + return Math.abs((b[0] - a[0]) * (c[0] - b[0]) + (b[1] - a[1]) * (c[1] - b[1])) < 1e-9 + }) + expect(square(grown)).toBe(true) + expect(grown).toContainEqual([2.5, 3.4]) + expect(grown).toContainEqual([0, 0]) + const edge = box.findIndex(([x], i) => x === 2 && box[(i + 1) % 4]![0] === 2) + const wider = moveRectangleEdge(box, edge, 0.5) + expect(square(wider)).toBe(true) + expect(Math.max(...wider.map(([x]) => x))).toBeCloseTo(2.5) + // Never collapses past the minimum side. + expect(Math.max(...moveRectangleEdge(box, edge, -5).map(([x]) => x))).toBeCloseTo(0.2) +}) + +test("an elevator's opening keeps the free polygon handles", () => { + const elevator = FloorOpeningNode.parse({ ...opening, source: 'elevator', ownerId: 'elevator_x' }) + const selected = kinds(buildFloorOpeningFloorplan(elevator, context(true))) + expect(selected.filter((kind) => kind === 'midpoint-handle')).toHaveLength(4) + expect(selected.filter((kind) => kind === 'endpoint-handle')).toHaveLength(4) +}) + +test('a stair opening drag previews live and writes the scene once, on release', () => { + const stairOpening = FloorOpeningNode.parse({ + ...opening, + id: 'floor-opening_drag', + source: 'stair', + ownerId: 'stair_drag', + }) + const previous = useScene.getState() + const frame = globalThis.requestAnimationFrame + const cancelFrame = globalThis.cancelAnimationFrame + globalThis.requestAnimationFrame = (() => 0) as typeof requestAnimationFrame + globalThis.cancelAnimationFrame = () => {} + useScene.setState({ nodes: { [stairOpening.id]: stairOpening } as never }) + let writes = 0 + const stop = useScene.subscribe((state, before) => { + if (state.nodes !== before.nodes) writes++ + }) + try { + const nodes = useScene.getState().nodes + const session = floorOpeningMoveVertexAffordance.start({ + node: stairOpening, + payload: { vertexIndex: 2 }, + nodes, + initialPlanPoint: [2, 1], + } as never) + session.apply({ planPoint: [3, 2], modifiers: {} } as never) + session.apply({ planPoint: [3, 1.5], modifiers: {} } as never) + expect(writes).toBe(0) + expect(useLiveNodeOverrides.getState().get(stairOpening.id)?.polygon).toContainEqual([3, 1.5]) + expect(session.canCommit()).toBe(true) + session.commit?.() + expect(writes).toBe(1) + const written = useScene.getState().nodes[stairOpening.id as AnyNodeId] as FloorOpeningNode + expect(written.polygon).toContainEqual([3, 1.5]) + expect(useLiveNodeOverrides.getState().get(stairOpening.id)).toBeUndefined() + } finally { + stop() + useScene.setState({ nodes: previous.nodes }) + globalThis.requestAnimationFrame = frame + globalThis.cancelAnimationFrame = cancelFrame + } +}) diff --git a/packages/nodes/src/floor-opening/floorplan.ts b/packages/nodes/src/floor-opening/floorplan.ts index 7ebc223c41..848fe07c87 100644 --- a/packages/nodes/src/floor-opening/floorplan.ts +++ b/packages/nodes/src/floor-opening/floorplan.ts @@ -1,4 +1,15 @@ -import type { FloorOpeningNode, FloorplanGeometry, GeometryContext } from '@pascal-app/core' +import { + type AnyNodeId, + authorStairOpeningPatches, + type FloorOpeningNode, + type FloorplanAffordance, + type FloorplanAffordanceSession, + type FloorplanGeometry, + type GeometryContext, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { getSegmentGridStep, snapPointToGrid, snapScalarToGrid } from '@pascal-app/editor' import { createPolygonAddVertexAffordance, createPolygonDeleteVertexAffordance, @@ -7,17 +18,172 @@ import { } from '../shared/polygon-vertex-affordance' const COLOR = '#f59e0b' +/** A side of a stair opening never shrinks below this (metres). */ +const MIN_OPENING_SIDE = 0.2 + +type Ring = [number, number][] + +/** + * A stair's opening: a box its stair cut, resized as a box. An elevator's opening keeps the free + * polygon handles: its cut comes from the car, so a drawn shape would not stick. + */ +export function isOwnedOpening(node: FloorOpeningNode): boolean { + return node.source === 'stair' && !!node.ownerId +} + +/** + * The square-cornered rectangle an owned opening is edited as: the ring itself when it already is + * one, otherwise (an opening cut with rounded corners before they were square) the box around it, + * aligned with its longest side. + */ +export function openingRectangle(polygon: ReadonlyArray): Ring { + const ring = polygon.map(([x, z]) => [x, z] as [number, number]) + let longest = 0 + let axis: [number, number] = [1, 0] + for (const [i, a] of ring.entries()) { + const b = ring[(i + 1) % ring.length]! + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (length > longest) { + longest = length + axis = [(b[0] - a[0]) / length, (b[1] - a[1]) / length] + } + } + const normal: [number, number] = [-axis[1], axis[0]] + const along = ring.map(([x, z]) => x * axis[0] + z * axis[1]) + const across = ring.map(([x, z]) => x * normal[0] + z * normal[1]) + const [u0, u1, v0, v1] = [ + Math.min(...along), + Math.max(...along), + Math.min(...across), + Math.max(...across), + ] + const at = (u: number, v: number): [number, number] => [ + u * axis[0] + v * normal[0], + u * axis[1] + v * normal[1], + ] + return [at(u0, v0), at(u1, v0), at(u1, v1), at(u0, v1)] +} + +/** + * `rectangle` with corner `index` dragged to `point`: the opposite corner stays, the two sides + * through the dragged corner keep their directions, so the box stays a box. + */ +export function resizeRectangleCorner( + rectangle: Ring, + index: number, + point: readonly [number, number], +): Ring { + const at = (offset: number) => rectangle[(index + offset) % 4]! + const origin = at(2) + const unit = (to: [number, number]) => { + const dx = to[0] - origin[0] + const dz = to[1] - origin[1] + const length = Math.hypot(dx, dz) || 1 + return [dx / length, dz / length] as const + } + const a = unit(at(1)) + const b = unit(at(3)) + const dx = point[0] - origin[0] + const dz = point[1] - origin[1] + const sideA = Math.max(MIN_OPENING_SIDE, dx * a[0] + dz * a[1]) + const sideB = Math.max(MIN_OPENING_SIDE, dx * b[0] + dz * b[1]) + const next = [...rectangle] as Ring + next[(index + 1) % 4] = [origin[0] + a[0] * sideA, origin[1] + a[1] * sideA] + next[(index + 3) % 4] = [origin[0] + b[0] * sideB, origin[1] + b[1] * sideB] + next[index] = [origin[0] + a[0] * sideA + b[0] * sideB, origin[1] + a[1] * sideA + b[1] * sideB] + return next +} + +/** `rectangle` with side `edgeIndex` moved parallel by `distance` along its outward normal. */ +export function moveRectangleEdge(rectangle: Ring, edgeIndex: number, distance: number): Ring { + const start = rectangle[edgeIndex]! + const end = rectangle[(edgeIndex + 1) % 4]! + const opposite = rectangle[(edgeIndex + 2) % 4]! + const length = Math.hypot(end[0] - start[0], end[1] - start[1]) || 1 + let normal: [number, number] = [-(end[1] - start[1]) / length, (end[0] - start[0]) / length] + // Point the normal away from the box, whichever way the ring winds. + if ((opposite[0] - start[0]) * normal[0] + (opposite[1] - start[1]) * normal[1] > 0) + normal = [-normal[0], -normal[1]] + const depth = (opposite[0] - start[0]) * -normal[0] + (opposite[1] - start[1]) * -normal[1] + const travel = Math.max(MIN_OPENING_SIDE - depth, distance) + return rectangle.map((point, i) => + i === edgeIndex || i === (edgeIndex + 1) % 4 + ? ([point[0] + normal[0] * travel, point[1] + normal[1] * travel] as [number, number]) + : point, + ) +} + +/** Every write for `ring` on a stair's opening: its floor and ceiling cuts take it as drawn. */ +function ownedOpeningUpdates(id: AnyNodeId, ring: Ring) { + const authored = authorStairOpeningPatches(useScene.getState().nodes, id, ring) + return authored.length ? authored : [{ id, data: { polygon: ring } }] +} + +function ownedOpeningSession( + node: FloorOpeningNode, + next: (rectangle: Ring, planPoint: readonly [number, number]) => Ring, +): FloorplanAffordanceSession { + const rectangle = openingRectangle(node.polygon) + const affectedIds = [ + ...new Set([ + node.id as AnyNodeId, + ...authorStairOpeningPatches(useScene.getState().nodes, node.id, rectangle).map( + (patch) => patch.id, + ), + ]), + ] + let ring: Ring | null = null + // The drag previews through live overrides; the scene is written once, on release. + return { + affectedIds, + apply({ planPoint }) { + ring = next(rectangle, planPoint) + useLiveNodeOverrides + .getState() + .setMany( + ownedOpeningUpdates(node.id as AnyNodeId, ring).map( + ({ id, data }) => [id, data as Record] as const, + ), + ) + }, + canCommit: () => ring !== null, + commit() { + const overrides = useLiveNodeOverrides.getState() + if (ring) + useScene.getState().updateNodes(ownedOpeningUpdates(node.id as AnyNodeId, ring) as never) + for (const id of affectedIds) overrides.clear(id) + }, + } +} + +/** Owned openings resize as a box; every other opening keeps the free polygon handles. */ +function ownedOr( + owned: FloorplanAffordance['start'], + free: FloorplanAffordance, +): FloorplanAffordance { + return { start: (args) => (isOwnedOpening(args.node) ? owned(args) : free.start(args)) } +} + +const inert = (node: FloorOpeningNode): FloorplanAffordanceSession => ({ + affectedIds: [node.id as AnyNodeId], + apply() {}, + canCommit: () => false, +}) /** * A floor opening on the plan: a dashed amber outline (the void the surfaces * around it show), and — while selected — the same corner, midpoint and edge - * handles slabs and zones use. + * handles slabs and zones use. A stair's opening is a box: its + * selected outline is solid, its sides carry a grip each to move them + * parallel and its corners resize it, all at a fixed screen size; it has no + * midpoint handles to add a vertex. */ export function buildFloorOpeningFloorplan( node: FloorOpeningNode, ctx: GeometryContext, ): FloorplanGeometry | null { - const polygon = node.polygon + const owned = isOwnedOpening(node) + const polygon = owned && ctx.viewState?.selected ? openingRectangle(node.polygon) : node.polygon if (polygon.length < 3) return null const selected = ctx.viewState?.selected ?? false const d = `M ${polygon.map(([x, z]) => `${x} ${z}`).join(' L ')} Z` @@ -28,9 +194,9 @@ export function buildFloorOpeningFloorplan( fill: COLOR, fillOpacity: selected ? 0.22 : 0.1, stroke: COLOR, - strokeWidth: selected ? 0.04 : 0.03, + strokeWidth: selected ? (owned ? 0.06 : 0.04) : 0.03, strokeOpacity: 0.95, - strokeDasharray: '0.12 0.08', + ...(selected && owned ? {} : { strokeDasharray: '0.12 0.08' }), }, ] if (selected) { @@ -48,18 +214,31 @@ export function buildFloorOpeningFloorplan( }) polygon.forEach((a, i) => { const b = polygon[(i + 1) % polygon.length]! - children.push({ - kind: 'midpoint-handle', - point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], - affordance: 'add-vertex', - payload: { edgeIndex: i }, - }) + const point: [number, number] = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2] + children.push( + owned + ? { + kind: 'endpoint-handle', + point, + state: 'idle', + screenSized: true, + affordance: 'move-edge', + payload: { edgeIndex: i }, + } + : { + kind: 'midpoint-handle', + point, + affordance: 'add-vertex', + payload: { edgeIndex: i }, + }, + ) }) polygon.forEach(([x, z], i) => { children.push({ kind: 'endpoint-handle', point: [x, z], state: 'idle', + ...(owned ? { screenSized: true } : {}), affordance: 'move-vertex', payload: { vertexIndex: i }, }) @@ -68,11 +247,39 @@ export function buildFloorOpeningFloorplan( return { kind: 'group', children } } -export const floorOpeningMoveVertexAffordance = - createPolygonVertexAffordance('floor-opening') -export const floorOpeningAddVertexAffordance = - createPolygonAddVertexAffordance('floor-opening') -export const floorOpeningMoveEdgeAffordance = - createPolygonMoveEdgeAffordance('floor-opening') -export const floorOpeningDeleteVertexAffordance = - createPolygonDeleteVertexAffordance('floor-opening') +export const floorOpeningMoveVertexAffordance = ownedOr(({ node, payload }) => { + const { vertexIndex } = payload as { vertexIndex: number } + return ownedOpeningSession(node, (rectangle, planPoint) => + resizeRectangleCorner( + rectangle, + vertexIndex % 4, + snapPointToGrid([planPoint[0], planPoint[1]], getSegmentGridStep()), + ), + ) +}, createPolygonVertexAffordance('floor-opening')) +export const floorOpeningAddVertexAffordance = ownedOr( + ({ node }) => inert(node), + createPolygonAddVertexAffordance('floor-opening'), +) +export const floorOpeningMoveEdgeAffordance = ownedOr(({ node, payload, initialPlanPoint }) => { + const { edgeIndex } = payload as { edgeIndex: number } + return ownedOpeningSession(node, (rectangle, planPoint) => { + const start = rectangle[edgeIndex % 4]! + const end = rectangle[(edgeIndex + 1) % 4]! + const opposite = rectangle[(edgeIndex + 2) % 4]! + const length = Math.hypot(end[0] - start[0], end[1] - start[1]) || 1 + let normal: [number, number] = [-(end[1] - start[1]) / length, (end[0] - start[0]) / length] + if ((opposite[0] - start[0]) * normal[0] + (opposite[1] - start[1]) * normal[1] > 0) + normal = [-normal[0], -normal[1]] + const distance = snapScalarToGrid( + (planPoint[0] - initialPlanPoint[0]) * normal[0] + + (planPoint[1] - initialPlanPoint[1]) * normal[1], + getSegmentGridStep(), + ) + return moveRectangleEdge(rectangle, edgeIndex % 4, distance) + }) +}, createPolygonMoveEdgeAffordance('floor-opening')) +export const floorOpeningDeleteVertexAffordance = ownedOr( + ({ node }) => inert(node), + createPolygonDeleteVertexAffordance('floor-opening'), +) diff --git a/packages/nodes/src/footing/definition.ts b/packages/nodes/src/footing/definition.ts new file mode 100644 index 0000000000..96218e4ccd --- /dev/null +++ b/packages/nodes/src/footing/definition.ts @@ -0,0 +1,88 @@ +import type { NodeDefinition } from '@pascal-app/core' +import { FLOORPLAN_NODE_EXTENSION_KEY, type FloorplanNodeExtension } from '@pascal-app/editor' +import { foundationFamilyOption } from '../foundation/family' +import { buildFootingFloorplan } from './floorplan' +import { + footingEndpointAffordance, + footingRotateAffordance, + footingSizeAffordance, + footingWidthAffordance, +} from './floorplan-affordances' +import { footingFloorplanMoveTarget } from './floorplan-move' +import { footingParametrics } from './parametrics' +import { FootingNode } from './schema' + +/** + * Footing — a concrete footing drawn by hand under the building's lowest level: a strip between + * two points or a pad at one. Its height is derived from the building's foundation record + * (`footingDesign` in core), so the renderer, Bones and the structural sheets read one footing. + * + * No `movable`: a strip has no `position` to translate; the plan moves both shapes through + * `floorplanMoveTarget`. + */ +export const footingDefinition: NodeDefinition = { + kind: 'footing', + snapProfile: 'structural', + schemaVersion: 1, + schema: FootingNode, + category: 'structure', + extensions: { + [FLOORPLAN_NODE_EXTENSION_KEY]: { + // In 2D-only view the 3D tool never mounts: the plan drafts footings itself. + tool: () => import('./tool').then((module) => ({ default: module.FootingFloorplanTool })), + // A footing lies inside a room: a plan click on it picks the footing, not the room. + planHitSelectsNode: true, + } satisfies FloorplanNodeExtension, + }, + + defaults: () => { + const { id: _id, type: _type, ...rest } = FootingNode.parse({ start: [0, 0], end: [1, 0] }) + return rest + }, + + capabilities: { + selectable: { hitVolume: 'bbox' }, + surfaces: { hosting: false }, + surfacePlacement: 'floor-only', + duplicable: true, + deletable: true, + drawTool: true, + }, + + // The renderer reads the scene and live overrides itself; nothing consumes a dirty mark. + dirtyTracking: false, + rendersChildren: false, + renderer: { kind: 'parametric', module: () => import('./renderer') }, + parametrics: footingParametrics, + + tool: () => import('./tool'), + // One family with the Foundation tool: a footing, or an outline poured as a slab or a raised floor. + toolOptions: [foundationFamilyOption], + toolHints: [ + { key: 'Left click', label: 'Set footing start / end, or place a pad' }, + { key: 'Alt', label: 'Free place' }, + { key: 'Esc', label: 'Cancel' }, + ], + + floorplan: buildFootingFloorplan, + floorplanMoveTarget: footingFloorplanMoveTarget, + floorplanAffordances: { + 'move-endpoint': footingEndpointAffordance, + width: footingWidthAffordance, + size: footingSizeAffordance, + rotate: footingRotateAffordance, + }, + + presentation: { + label: 'Footing', + description: 'A concrete strip or pad footing poured under the lowest floor.', + icon: { kind: 'url', src: '/icons/floor.webp' }, + paletteSection: 'structure', + paletteOrder: 71, + }, + + mcp: { + description: + 'A concrete footing on the lowest level: a strip (start, end, width) or a pad (center, width, length, rotation), with thickness and an optional depth under grade; its height follows the building foundation.', + }, +} diff --git a/packages/nodes/src/footing/drafting.ts b/packages/nodes/src/footing/drafting.ts new file mode 100644 index 0000000000..31b2a53bb0 --- /dev/null +++ b/packages/nodes/src/footing/drafting.ts @@ -0,0 +1,68 @@ +import { + type AnyNode, + type AnyNodeId, + FOOTING_SIZES, + FootingNode, + type FootingShape, + footingPlan, + type WallNode, +} from '@pascal-app/core' +import type { WallPlanPoint } from '@pascal-app/editor' + +/** + * A footing as the wall snap reads a wall: a strip's centreline, a pad's centre (a segment of no + * length, so its ends and middle are the centre). The wall snap's corners, midpoints, crossings + * and bodies then catch footings as they catch walls. + */ +function snapReference(footing: FootingNode): WallNode | null { + const plan = footingPlan(footing) + if (!plan) return null + const [start, end] = plan.shape === 'strip' ? [plan.start, plan.end] : [plan.center, plan.center] + return { + object: 'node', + id: footing.id as unknown as WallNode['id'], + type: 'wall', + parentId: footing.parentId, + visible: true, + metadata: {}, + children: [], + start, + end, + frontSide: 'unknown', + backSide: 'unknown', + } +} + +/** + * What a footing drafted on `levelId` snaps to, through the wall snap (`snapWallDraftPoint`): the + * level's wall centrelines and its footings. + */ +export function footingSnapTargets( + nodes: Readonly>, + levelId: string, + ignoreIds: readonly string[] = [], +): WallNode[] { + const targets: WallNode[] = [] + for (const node of Object.values(nodes)) { + if (node.parentId !== levelId || node.visible === false || ignoreIds.includes(node.id)) continue + if (node.type === 'wall') targets.push(node) + else if (node.type === 'footing') { + const reference = snapReference(node) + if (reference) targets.push(reference) + } + } + return targets +} + +/** The footing a draft makes: a strip `start` → `end`, or a pad at `end`, at its shape's sizes. */ +export function draftFooting( + shape: FootingShape, + start: WallPlanPoint | null, + end: WallPlanPoint, + fields: Partial = {}, +): FootingNode | null { + if (shape === 'pad') + return FootingNode.parse({ ...FOOTING_SIZES.pad, ...fields, shape, center: end }) + if (!start) return null + return FootingNode.parse({ ...FOOTING_SIZES.strip, ...fields, shape, start, end }) +} diff --git a/packages/nodes/src/footing/floorplan-affordances.ts b/packages/nodes/src/footing/floorplan-affordances.ts new file mode 100644 index 0000000000..1933e3bff4 --- /dev/null +++ b/packages/nodes/src/footing/floorplan-affordances.ts @@ -0,0 +1,145 @@ +import { + type AnyNodeId, + type FloorplanAffordance, + type FloorplanAffordanceSession, + type FootingNode, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + getSegmentGridStep, + isAngleSnapActive, + isGridSnapActive, + isMagneticSnapActive, + isSegmentLongEnough, + snapBuildingLocalToWorldGrid, + snapScalarToGrid, + snapWallDraftPoint, + type WallPlanPoint, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { rotateAffordanceDelta } from '../shared/rotate-affordance' +import { footingSnapTargets } from './drafting' +import type { FootingEndpointPayload, FootingSizePayload, FootingWidthPayload } from './floorplan' + +/** The least a drag sizes a footing to: what still draws, not a code minimum (the panel warns of that). */ +const MIN_SIZE = 0.05 +const INCH = 0.0254 + +/** + * A footing size off a drag: to the whole inch (a centimetre on a metric plan) while the grid + * snaps, since the plan's grid step (half a metre by default) is no footing size; raw with Alt. + */ +function snapFootingSize(size: number, free: boolean): number { + const step = + free || !isGridSnapActive() ? 0 : useViewer.getState().unit === 'imperial' ? INCH : 0.01 + return Math.max(MIN_SIZE, snapScalarToGrid(size, step)) +} + +/** One drag that previews a patch on the footing and writes the last one on release. */ +function patchSession( + node: FootingNode, + next: (planPoint: WallPlanPoint, altKey: boolean) => Partial, + valid: (patch: Partial) => boolean = () => true, +): FloorplanAffordanceSession { + const id = node.id as AnyNodeId + let last: Partial = {} + return { + affectedIds: [id], + apply({ planPoint, modifiers }) { + last = next([planPoint[0], planPoint[1]], modifiers.altKey) + useLiveNodeOverrides.getState().set(id, last) + }, + canCommit() { + return Object.keys(last).length > 0 && valid(last) + }, + commit() { + useScene.getState().updateNodes([{ id, data: last }]) + useLiveNodeOverrides.getState().clear(id) + }, + } +} + +/** A strip's end, dragged: snapped as the tool snaps, angle-locked off the other end. */ +export const footingEndpointAffordance: FloorplanAffordance = { + start({ node, payload, nodes }) { + const { endpoint } = payload as FootingEndpointPayload + const fixed = (endpoint === 'start' ? node.end : node.start) ?? [0, 0] + const targets = node.parentId ? footingSnapTargets(nodes, node.parentId, [node.id]) : [] + return patchSession( + node, + (point, free) => { + const step = getSegmentGridStep() + const angleSnap = isAngleSnapActive() + const snapped = free + ? point + : snapWallDraftPoint({ + point, + walls: targets, + start: angleSnap ? fixed : undefined, + angleSnap, + magnetic: isMagneticSnapActive(), + gridSnap: (p) => snapBuildingLocalToWorldGrid(p, step), + }) + return { [endpoint]: snapped } + }, + (patch) => isSegmentLongEnough(patch.start ?? fixed, patch.end ?? fixed), + ) + }, +} + +/** A strip's side, dragged across it: the width grows twice the drag, the centreline stays. */ +export const footingWidthAffordance: FloorplanAffordance = { + start({ node, payload, initialPlanPoint }) { + const { side } = payload as FootingWidthPayload + const a: WallPlanPoint = node.start ?? [0, 0] + const b: WallPlanPoint = node.end ?? [1, 0] + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) || 1 + const normal: WallPlanPoint = [ + (-(b[1] - a[1]) / length) * side, + ((b[0] - a[0]) / length) * side, + ] + return patchSession(node, (point, free) => { + const delta = + (point[0] - initialPlanPoint[0]) * normal[0] + (point[1] - initialPlanPoint[1]) * normal[1] + return { width: snapFootingSize(node.width + delta * 2, free) } + }) + }, +} + +/** A pad's side, dragged along its axis: the side grows twice the drag about the centre. */ +export const footingSizeAffordance: FloorplanAffordance = { + start({ node, payload, initialPlanPoint }) { + const { dim, planAxis } = payload as FootingSizePayload + const initial = dim === 'width' ? node.width : (node.length ?? node.width) + return patchSession(node, (point, free) => { + const delta = + (point[0] - initialPlanPoint[0]) * planAxis[0] + + (point[1] - initialPlanPoint[1]) * planAxis[1] + return { [dim]: snapFootingSize(initial + delta * 2, free) } + }) + }, +} + +/** A pad turned about its centre, in the angle steps unless snapping to angles is off. */ +export const footingRotateAffordance: FloorplanAffordance = { + start({ node, initialPlanPoint }) { + const center = node.center ?? [0, 0] + const initialRotation = node.rotation ?? 0 + const initialAngle = Math.atan2( + initialPlanPoint[1] - center[1], + initialPlanPoint[0] - center[0], + ) + return patchSession(node, (point) => ({ + // The plan turns a pad by -rotation (three.js turns Y the other way), as columns do. + rotation: + initialRotation - + rotateAffordanceDelta({ + center: [center[0], center[1]], + initialAngle, + planPoint: point, + free: !isAngleSnapActive(), + }), + })) + }, +} diff --git a/packages/nodes/src/footing/floorplan-move.ts b/packages/nodes/src/footing/floorplan-move.ts new file mode 100644 index 0000000000..0c466bc291 --- /dev/null +++ b/packages/nodes/src/footing/floorplan-move.ts @@ -0,0 +1,71 @@ +import { + type AnyNodeId, + type FloorplanMoveTarget, + type FootingNode, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + getSegmentGridStep, + isMagneticSnapActive, + snapBuildingLocalToWorldGrid, + snapWallDraftPoint, + triggerSFX, + type WallPlanPoint, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { footingSnapTargets } from './drafting' + +/** + * A footing moved in the plan: its reference point (a pad's centre, a strip's start) follows the + * pointer and snaps as the tool snaps; the rest of it travels with it. Previews through live + * overrides, writes once on release. + */ +export const footingFloorplanMoveTarget: FloorplanMoveTarget = ({ node, nodes }) => { + const id = node.id as AnyNodeId + const pad = node.shape === 'pad' + const origin: WallPlanPoint = (pad ? node.center : node.start) ?? [0, 0] + const targets = node.parentId ? footingSnapTargets(nodes, node.parentId, [node.id]) : [] + let anchor: WallPlanPoint | null = null + let delta: WallPlanPoint = [0, 0] + const moved = (point: readonly [number, number] | undefined): WallPlanPoint | undefined => + point && [point[0] + delta[0], point[1] + delta[1]] + const patch = (): Partial => + pad ? { center: moved(node.center) } : { start: moved(node.start), end: moved(node.end) } + + return { + affectedIds: [id], + apply({ planPoint, modifiers }) { + if (!anchor) { + anchor = [planPoint[0], planPoint[1]] + return + } + const raw: WallPlanPoint = [ + origin[0] + planPoint[0] - anchor[0], + origin[1] + planPoint[1] - anchor[1], + ] + const step = getSegmentGridStep() + const at = modifiers.altKey + ? raw + : snapWallDraftPoint({ + point: raw, + walls: targets, + magnetic: isMagneticSnapActive(), + gridSnap: (p) => snapBuildingLocalToWorldGrid(p, step), + }) + const next: WallPlanPoint = [at[0] - origin[0], at[1] - origin[1]] + if (next[0] === delta[0] && next[1] === delta[1]) return + delta = next + triggerSFX('sfx:grid-snap') + useLiveNodeOverrides.getState().set(id, patch()) + }, + canCommit() { + return delta[0] !== 0 || delta[1] !== 0 + }, + commit() { + useScene.getState().updateNodes([{ id, data: patch() }]) + useLiveNodeOverrides.getState().clear(id) + useViewer.getState().setSelection({ selectedIds: [id] }) + }, + } +} diff --git a/packages/nodes/src/footing/floorplan.test.ts b/packages/nodes/src/footing/floorplan.test.ts new file mode 100644 index 0000000000..c422f7f511 --- /dev/null +++ b/packages/nodes/src/footing/floorplan.test.ts @@ -0,0 +1,86 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type FloorplanGeometry, + FootingNode, + type GeometryContext, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { readFloorplanGeometryMetadata, useEditor, useInteractionScope } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { buildFootingFloorplan } from './floorplan' +import { footingSizeAffordance, footingWidthAffordance } from './floorplan-affordances' + +const IN = 0.0254 +const modifiers = { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false } +const STRIP = FootingNode.parse({ + id: 'footing_strip', + parentId: 'level_g', + start: [0, 0], + end: [3.048, 0], +}) + +afterEach(() => { + useLiveNodeOverrides.getState().clearAll() + useInteractionScope.getState().end() + useViewer.setState({ unit: 'metric' }) +}) + +function labels(geometry: FloorplanGeometry | null): string[] { + if (!geometry) return [] + if (geometry.kind === 'group') return geometry.children.flatMap(labels) + return geometry.kind === 'dimension-label' ? [geometry.text] : [] +} + +test("a selected strip's length reads in the plan's unit", () => { + const view = { selected: true, highlighted: false, hovered: false, moving: false } + const ctx = (unit: 'metric' | 'imperial') => + ({ viewState: { ...view, unit } }) as unknown as GeometryContext + expect(labels(buildFootingFloorplan(STRIP, ctx('imperial')))).toEqual([`10'0"`]) + expect(labels(buildFootingFloorplan(STRIP, ctx('metric')))).toEqual(['3.05m']) +}) + +test('a footing drag sizes to the whole inch while the grid snaps, not to the grid step; Alt is free', () => { + useViewer.setState({ unit: 'imperial' }) + useEditor.setState((state) => ({ + gridSnapStep: 0.5, + snappingModeByContext: { ...state.snappingModeByContext, polygon: 'grid' }, + })) + useInteractionScope + .getState() + .begin({ kind: 'reshaping', nodeId: STRIP.id, reshape: 'width', driver: 'tool' }) + const drag = (altKey: boolean) => { + const session = footingWidthAffordance.start({ + node: STRIP, + payload: { side: 1 }, + nodes: useScene.getState().nodes, + initialPlanPoint: [1.5, 6 * IN], + gridSnapStep: 0.5, + }) + // Out 1.3 in: the width grows 2.6 in, from 12. + session.apply({ planPoint: [1.5, 7.3 * IN], modifiers: { ...modifiers, altKey } }) + return useLiveNodeOverrides.getState().overrides.get(STRIP.id)?.width ?? 0 + } + expect(drag(false) / IN).toBeCloseTo(15, 6) + expect(drag(true) / IN).toBeCloseTo(14.6, 6) + + const pad = FootingNode.parse({ id: 'footing_pad', shape: 'pad', center: [0, 0], width: 0.6096 }) + const session = footingSizeAffordance.start({ + node: pad, + payload: { dim: 'length', planAxis: [0, 1] }, + nodes: useScene.getState().nodes, + initialPlanPoint: [0, 0.3], + gridSnapStep: 0.5, + }) + session.apply({ planPoint: [0, 0.3 + 2.2 * IN], modifiers }) + expect((useLiveNodeOverrides.getState().overrides.get(pad.id)?.length ?? 0) / IN).toBeCloseTo( + 28, + 6, + ) +}) + +test("a room's name and area move off a footing's outline", () => { + const plan = buildFootingFloorplan(STRIP, { viewState: undefined } as unknown as GeometryContext) + const outline = plan?.kind === 'group' ? plan.children.find((c) => c.kind === 'polygon') : null + expect(outline && readFloorplanGeometryMetadata(outline).annotationObstacle).toBe('outline') +}) diff --git a/packages/nodes/src/footing/floorplan.ts b/packages/nodes/src/footing/floorplan.ts new file mode 100644 index 0000000000..49873195a6 --- /dev/null +++ b/packages/nodes/src/footing/floorplan.ts @@ -0,0 +1,159 @@ +import { + type FloorplanGeometry, + type FootingNode, + footingOutline, + footingPlan, + type GeometryContext, +} from '@pascal-app/core' +import { + floorplanGeometryMetadata, + formatLinearMeasurement, + readFloorplanMetricNotationOverride, +} from '@pascal-app/editor' + +/** Hidden work: footings are drawn dashed, as a plan draws what is under the floor. */ +const HIDDEN_DASH = '6 4' +const CENTRELINE_DASH = '12 3 2 3' +const HANDLE_OFFSET = 0.12 +const ROTATE_OFFSET = 0.22 + +export type FootingEndpointPayload = { endpoint: 'start' | 'end' } +export type FootingWidthPayload = { side: 1 | -1 } +export type FootingSizePayload = { dim: 'width' | 'length'; planAxis: [number, number] } + +/** + * A footing in the plan: its outline and centreline dashed as hidden lines, over a clear hit + * area. Selected, a strip gets its end handles, side width handles and the two move arrows; a pad + * its move dot, a size arrow per side and the rotate arrow. + */ +export function buildFootingFloorplan( + node: FootingNode, + ctx: GeometryContext, +): FloorplanGeometry | null { + const plan = footingPlan(node) + if (!plan) return null + const view = ctx.viewState + const selected = view?.selected ?? false + const chrome = selected || (view?.highlighted ?? false) + const stroke = + chrome && view?.palette + ? view.palette.selectedStroke + : view?.hovered && view.palette + ? view.palette.wallHoverStroke + : '#6b7280' + const outline = footingOutline(plan) + const line = (a: [number, number], b: [number, number]): FloorplanGeometry => ({ + kind: 'line', + x1: a[0], + y1: a[1], + x2: b[0], + y2: b[1], + stroke, + strokeWidth: 1, + strokeDasharray: CENTRELINE_DASH, + vectorEffect: 'non-scaling-stroke', + pointerEvents: 'none', + }) + + const children: FloorplanGeometry[] = [ + { + kind: 'polygon', + points: outline, + fill: chrome ? '#fed7aa' : 'transparent', + fillOpacity: chrome ? 0.35 : undefined, + stroke, + strokeWidth: chrome ? 1.75 : 1.25, + strokeDasharray: HIDDEN_DASH, + vectorEffect: 'non-scaling-stroke', + // The room's name and area move off a footing rather than hide it. + metadata: floorplanGeometryMetadata({ annotationObstacle: 'outline' }), + }, + ] + + if (plan.shape === 'strip') { + children.push(line(plan.start, plan.end)) + if (selected) { + const dx = plan.end[0] - plan.start[0] + const dz = plan.end[1] - plan.start[1] + const length = Math.hypot(dx, dz) + const [nx, nz] = length > 1e-9 ? [-dz / length, dx / length] : [0, 1] + const mid: [number, number] = [ + (plan.start[0] + plan.end[0]) / 2, + (plan.start[1] + plan.end[1]) / 2, + ] + for (const endpoint of ['start', 'end'] as const) + children.push({ + kind: 'endpoint-handle', + point: plan[endpoint], + state: 'idle', + affordance: 'move-endpoint', + payload: { endpoint } satisfies FootingEndpointPayload, + }) + for (const side of [1, -1] as const) { + children.push({ + kind: 'endpoint-handle', + point: [mid[0] + nx * (plan.width / 2) * side, mid[1] + nz * (plan.width / 2) * side], + state: 'idle', + affordance: 'width', + payload: { side } satisfies FootingWidthPayload, + }) + // No affordance: the layer hands the footing to `floorplanMoveTarget`. + const offset = plan.width / 2 + HANDLE_OFFSET + 0.05 + children.push({ + kind: 'move-arrow', + point: [mid[0] + nx * offset * side, mid[1] + nz * offset * side], + angle: Math.atan2(nz * side, nx * side), + }) + } + if (length >= 0.1) + children.push({ + kind: 'dimension-label', + cx: mid[0], + cy: mid[1], + text: formatLinearMeasurement( + length, + view?.unit ?? 'metric', + readFloorplanMetricNotationOverride(ctx) ?? 'meters', + ), + angle: Math.atan2(dz, dx), + }) + } + return { kind: 'group', children } + } + + const { center, width, length, rotation } = plan + const ax: [number, number] = [Math.cos(rotation), -Math.sin(rotation)] + const az: [number, number] = [Math.sin(rotation), Math.cos(rotation)] + const along = (axis: [number, number], distance: number): [number, number] => [ + center[0] + axis[0] * distance, + center[1] + axis[1] * distance, + ] + children.push(line(along(ax, -width / 2), along(ax, width / 2))) + children.push(line(along(az, -length / 2), along(az, length / 2))) + if (selected) { + children.push({ kind: 'move-handle', point: center }) + for (const [dim, axis, half] of [ + ['width', ax, width / 2], + ['length', az, length / 2], + ] as const) + children.push({ + kind: 'move-arrow', + point: along(axis, half + HANDLE_OFFSET), + angle: Math.atan2(axis[1], axis[0]), + affordance: 'size', + payload: { dim, planAxis: [axis[0], axis[1]] } satisfies FootingSizePayload, + }) + const corner: [number, number] = [ + center[0] + ax[0] * (width / 2 + ROTATE_OFFSET) + az[0] * (length / 2 + ROTATE_OFFSET), + center[1] + ax[1] * (width / 2 + ROTATE_OFFSET) + az[1] * (length / 2 + ROTATE_OFFSET), + ] + children.push({ + kind: 'rotate-arrow', + point: corner, + angle: Math.atan2(corner[1] - center[1], corner[0] - center[0]), + affordance: 'rotate', + pivot: center, + }) + } + return { kind: 'group', children } +} diff --git a/packages/nodes/src/footing/index.ts b/packages/nodes/src/footing/index.ts new file mode 100644 index 0000000000..76ff81ce69 --- /dev/null +++ b/packages/nodes/src/footing/index.ts @@ -0,0 +1 @@ +export { footingDefinition } from './definition' diff --git a/packages/nodes/src/footing/panel.test.ts b/packages/nodes/src/footing/panel.test.ts new file mode 100644 index 0000000000..40eecd141e --- /dev/null +++ b/packages/nodes/src/footing/panel.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FootingNode, + footingDesign, + LevelNode, + SiteNode, + SlabNode, +} from '@pascal-app/core' +import { footingDepthPatch, footingDepthReach, footingDepthShown } from './panel' + +const IN = 0.0254 + +// Under a slab no depth is stored, and the field read 0 beside a bottom 8 in under grade. +test('the depth field reads how far under grade the bottom is poured', () => { + expect(footingDepthShown({}, { gradeY: -0.05, bottomY: -0.25 })).toBeCloseTo(0.2, 9) + expect(footingDepthShown({ depth: 0.3 }, { gradeY: 0, bottomY: -0.3 })).toBeCloseTo(0.3, 9) + expect(footingDepthShown({ depth: 0.3 }, null)).toBe(0.3) +}) + +function house(foundation?: Record) { + const strip = FootingNode.parse({ + id: 'footing_strip', + parentId: 'level_g', + shape: 'strip', + start: [2, 4], + end: [8, 4], + }) + const nodes = Object.fromEntries( + [ + SiteNode.parse({ id: 'site_a', children: ['building_a'] }), + BuildingNode.parse({ + id: 'building_a', + parentId: 'site_a', + children: ['level_g'], + ...(foundation ? { metadata: { foundation } } : {}), + }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: ['slab_g', strip.id], + }), + SlabNode.parse({ + id: 'slab_g', + parentId: 'level_g', + plateRole: 'base', + polygon: [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ], + elevation: 0.05, + thickness: 0.05, + }), + strip, + ].map((node) => [node.id, node]), + ) as Record + return { nodes, strip } +} + +// A depth shallower than the slab and the footing reach was stored and did nothing: the field +// snapped back and offered to clear it. +test('under a slab a depth only counts past what the slab and the footing reach', () => { + const { nodes, strip } = house() + const design = footingDesign(nodes, strip)! + const reach = footingDepthReach(nodes, strip) + expect(reach).toBeCloseTo(design.gradeY - design.bottomY, 9) + expect(reach).toBeGreaterThan(4 * IN) + expect(footingDepthPatch(4 * IN, reach, true)).toEqual({ depth: undefined }) + expect(footingDepthPatch(reach, reach, true)).toEqual({ depth: undefined }) + expect(footingDepthPatch(12 * IN, reach, true)).toEqual({ depth: 12 * IN }) + const deeper = { ...strip, depth: 12 * IN } + expect(footingDepthShown(deeper, footingDesign(nodes, deeper))).toBeCloseTo(12 * IN, 6) +}) + +// Where no depth is the building's, a shallow one typed must be kept: dropping it poured the +// building's deeper one instead. +test('on piles the shallowest depth typed is poured, not the building’s', () => { + const { nodes, strip } = house({ + enabled: true, + type: 'raised', + support: 'piles', + footingDepthIn: 40, + }) + const reach = footingDepthReach(nodes, strip) + expect(reach).toBeGreaterThan(0) + expect(reach).toBeLessThan(40 * IN) + for (const typed of [reach, 2 * IN]) { + const set = { ...strip, ...footingDepthPatch(typed, reach, false) } + expect(footingDepthShown(set, footingDesign(nodes, set))).toBeCloseTo(reach, 6) + } +}) + +test('under a raised floor a footing goes the depth typed under the crawl space’s dirt', () => { + const { nodes, strip } = house({ enabled: true, type: 'raised', footingDepthIn: 40 }) + // Measured from the dug-out dirt, nothing takes it deeper than its own depth. + const reach = footingDepthReach(nodes, strip) + expect(reach).toBeCloseTo(0, 6) + for (const typed of [6 * IN, 18 * IN]) { + const set = { ...strip, ...footingDepthPatch(typed, reach, false) } + expect(footingDepthShown(set, footingDesign(nodes, set))).toBeCloseTo(typed, 6) + } +}) diff --git a/packages/nodes/src/footing/panel.tsx b/packages/nodes/src/footing/panel.tsx new file mode 100644 index 0000000000..79900f7c5d --- /dev/null +++ b/packages/nodes/src/footing/panel.tsx @@ -0,0 +1,244 @@ +'use client' + +import { + type AnyNodeId, + FOOTING_MINIMUM, + FOOTING_SIZES, + type FootingDesign, + type FootingNode, + type FootingShape, + footingDesign, + footingPlan, + useScene, +} from '@pascal-app/core' +import { + ActionButton, + ActionGroup, + formatLinearMeasurement, + MetricControl, + PanelSection, + PanelWrapper, + SegmentedControl, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { Trash2 } from 'lucide-react' +import { useState } from 'react' + +const IN = 0.0254 + +/** A footing size in metres, shown in inches on an imperial plan (feet would show 8 in as 0.67). */ +function SizeField({ + label, + value, + min = 0.01, + onCommit, +}: { + label: string + value: number + min?: number + onCommit: (meters: number) => void +}) { + const [draft, setDraft] = useState(null) + const imperial = useViewer((state) => state.unit) === 'imperial' + const toShown = (meters: number) => (imperial ? meters / IN : meters) + return ( + setDraft(next)} + onCommit={(next) => { + setDraft(null) + onCommit(imperial ? next * IN : next) + }} + precision={imperial ? 2 : 3} + restoreOnCommit={false} + step={imperial ? 0.5 : 0.5 * IN} + unit={imperial ? 'in' : 'm'} + value={draft ?? toShown(value)} + /> + ) +} + +/** The footing a shape change leaves: in the same place, at the new shape's sizes. */ +function reshaped(footing: FootingNode, shape: FootingShape): Partial { + const plan = footingPlan(footing) + if (!plan || plan.shape === shape) return {} + if (plan.shape === 'strip') + return { + shape, + ...FOOTING_SIZES.pad, + center: [(plan.start[0] + plan.end[0]) / 2, (plan.start[1] + plan.end[1]) / 2], + rotation: -Math.atan2(plan.end[1] - plan.start[1], plan.end[0] - plan.start[0]), + start: undefined, + end: undefined, + } + // A strip along the pad's local x, its width long. + const half = plan.width / 2 + const [ux, uz] = [Math.cos(plan.rotation), -Math.sin(plan.rotation)] + return { + shape, + ...FOOTING_SIZES.strip, + start: [plan.center[0] - ux * half, plan.center[1] - uz * half], + end: [plan.center[0] + ux * half, plan.center[1] + uz * half], + center: undefined, + length: undefined, + rotation: undefined, + } +} + +/** + * The depth field shows how far under grade the bottom goes as poured: a depth is the least, and a + * thickened slab (or a grade beam) may reach deeper than the one stored or asked. + */ +export function footingDepthShown( + footing: Pick, + design: Pick | null, +): number { + return design ? design.gradeY - design.bottomY : (footing.depth ?? 0) +} + +/** + * How far under grade the footing goes with no depth of its own: a thickened slab or a grade beam + * reaches past the grade by its own size, so a depth only counts beyond this. + */ +export function footingDepthReach( + nodes: Parameters[0], + footing: FootingNode, +) { + const shallowest = footingDesign(nodes, { ...footing, depth: 0 }) + return shallowest ? Math.max(0, shallowest.gradeY - shallowest.bottomY) : 0 +} + +/** + * The depth to store for one typed, never under the reach. Under a slab no depth means the reach, + * so one that goes no further is none; elsewhere no depth is the building's, which may be deeper. + */ +export function footingDepthPatch( + meters: number, + reach: number, + underSlab: boolean, +): Pick { + if (underSlab) return { depth: meters > reach + 0.001 ? meters : undefined } + return { depth: Math.max(meters, reach) } +} + +export default function FootingPanel() { + const selectedId = useViewer((state) => state.selection.selectedIds[0]) + const setSelection = useViewer((state) => state.setSelection) + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const nodes = useScene((state) => state.nodes) + const footing = selectedId ? nodes[selectedId as AnyNodeId] : undefined + if (footing?.type !== 'footing') return null + const design = footingDesign(nodes, footing) + const reach = footingDepthReach(nodes, footing) + const length = (meters: number) => formatLinearMeasurement(meters, unit, metricNotation) + const update = (patch: Partial) => useScene.getState().updateNode(footing.id, patch) + const against = (y: number) => + y > design!.gradeY + ? `${length(y - design!.gradeY)} over grade` + : `${length(design!.gradeY - y)} under grade` + // Under a slab a footing is an interior one: no depth is asked of it, it goes down to the grade. + const underSlab = design?.foundation.type === 'slab' && design.foundation.support === 'footing' + const small = + footing.width < FOOTING_MINIMUM.width - 1e-6 || + footing.thickness < FOOTING_MINIMUM.thickness - 1e-6 || + (footing.shape === 'pad' && (footing.length ?? footing.width) < FOOTING_MINIMUM.width - 1e-6) + + return ( + setSelection({ selectedIds: [] })} + title={footing.name ?? 'Footing'} + width={300} + > + + update(reshaped(footing, shape))} + options={[ + { label: 'Strip', value: 'strip' }, + { label: 'Pad', value: 'pad' }, + ]} + value={footing.shape} + /> + update({ width })} value={footing.width} /> + {footing.shape === 'pad' && ( + update({ length })} + value={footing.length ?? footing.width} + /> + )} + update({ thickness })} + value={footing.thickness} + /> + update(footingDepthPatch(depth, reach, underSlab))} + value={footingDepthShown(footing, design)} + /> + {reach > 0.01 && ( +

+ At least {length(reach)}:{' '} + {design?.foundation.support === 'piles' + ? 'the grade beam reaches this far.' + : design?.foundation.type === 'raised' + ? 'the footing’s thickness under the floor reaches this far.' + : 'the slab and the footing’s thickness reach this far.'} +

+ )} + {footing.depth !== undefined && ( + + )} + {design && ( +

+ Top {against(design.topY)}, bottom {against(design.bottomY)}.{' '} + {design.foundation.support === 'piles' + ? footing.shape === 'strip' + ? 'A grade beam on piles, as the building is.' + : 'A pile cap on one pile, as the building is.' + : design.foundation.type === 'slab' + ? 'A thickened slab under the floor slab.' + : footing.shape === 'strip' + ? 'A stem wall carries the floor.' + : 'A pier carries the floor.'} +

+ )} + {small && ( +

+ IRC R403.1 asks a footing at least 12 in wide and 6 in thick. +

+ )} +
+ + + } + label="Delete" + onClick={() => { + useScene.getState().deleteNode(footing.id) + setSelection({ selectedIds: [] }) + }} + /> + + +
+ ) +} diff --git a/packages/nodes/src/footing/parametrics.ts b/packages/nodes/src/footing/parametrics.ts new file mode 100644 index 0000000000..180ef83aa7 --- /dev/null +++ b/packages/nodes/src/footing/parametrics.ts @@ -0,0 +1,6 @@ +import type { FootingNode, ParametricDescriptor } from '@pascal-app/core' + +export const footingParametrics: ParametricDescriptor = { + groups: [], + customPanel: () => import('./panel'), +} diff --git a/packages/nodes/src/footing/renderer.tsx b/packages/nodes/src/footing/renderer.tsx new file mode 100644 index 0000000000..f74360d257 --- /dev/null +++ b/packages/nodes/src/footing/renderer.tsx @@ -0,0 +1,72 @@ +'use client' + +import { + type FootingDesign, + type FootingNode, + type FoundationMember, + footingDesign, + useLiveNodeOverrides, + useRegistry, + useScene, +} from '@pascal-app/core' +import { concreteMaterial, useNodeEvents, useViewer } from '@pascal-app/viewer' +import { useEffect, useMemo, useRef } from 'react' +import { type BufferGeometry, type Group, Matrix4, Quaternion, Vector3 } from 'three' +import { sameFoundation } from '../site/generic-foundation' +import { buildFoundationMembersMesh, geometryOf } from '../site/generic-foundation-geometry' + +const material = concreteMaterial() +const UP = new Vector3(0, 1, 0) +const UNIT = new Vector3(1, 1, 1) + +/** World → level-local, the level's frame inverted. */ +function toLevel({ origin, yaw }: FootingDesign['levelFrame']): Matrix4 { + return new Matrix4() + .compose(new Vector3(...origin), new Quaternion().setFromAxisAngle(UP, yaw), UNIT) + .invert() +} + +/** + * A footing design's concrete as one level-local geometry, rebuilt only when its members or the + * level's frame change. The members are world; the mesh is moved into the level so it rides the + * level's stacking. + */ +export function useFootingGeometry(design: FootingDesign | null): BufferGeometry | null { + const kept = useRef<{ members: readonly FoundationMember[]; frame: string }>({ + members: [], + frame: '', + }) + const frame = design ? JSON.stringify(design.levelFrame) : '' + const members = design?.members ?? [] + if (frame !== kept.current.frame || !sameFoundation(members, kept.current.members)) + kept.current = { members, frame } + const current = kept.current + const geometry = useMemo(() => { + const built = geometryOf(buildFoundationMembersMesh(current.members)) + if (built && current.frame) built.applyMatrix4(toLevel(JSON.parse(current.frame))) + return built + }, [current]) + useEffect(() => () => geometry?.dispose(), [geometry]) + return geometry +} + +/** A footing's concrete (`footingDesign` in core), drawn with the foundation's look. */ +export default function FootingRenderer({ node: stored }: { node: FootingNode }) { + const ref = useRef(null!) + useRegistry(stored.id, 'footing', ref) + const live = useLiveNodeOverrides((state) => state.overrides.get(stored.id)) + const node = useMemo( + () => (live ? ({ ...stored, ...live } as FootingNode) : stored), + [stored, live], + ) + const handlers = useNodeEvents(node, 'footing') + const nodes = useScene((state) => state.nodes) + const geometry = useFootingGeometry(useMemo(() => footingDesign(nodes, node), [nodes, node])) + const shown = useViewer((state) => !state.walkthroughMode) + + return ( + + {geometry && } + + ) +} diff --git a/packages/nodes/src/footing/schema.ts b/packages/nodes/src/footing/schema.ts new file mode 100644 index 0000000000..7ed9bbe321 --- /dev/null +++ b/packages/nodes/src/footing/schema.ts @@ -0,0 +1 @@ +export { FootingNode } from '@pascal-app/core' diff --git a/packages/nodes/src/footing/tool.test.ts b/packages/nodes/src/footing/tool.test.ts new file mode 100644 index 0000000000..633334a594 --- /dev/null +++ b/packages/nodes/src/footing/tool.test.ts @@ -0,0 +1,132 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearSceneHistory, + emitter, + type FootingNode, + type GridEvent, + LevelNode, + useScene, +} from '@pascal-app/core' +import { + getFloorplanNodeExtension, + sceneLayout, + useEditor, + usePlacementPreview, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import { footingDefinition } from './definition' +import { FootingFloorplanTool } from './tool' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } + +afterEach(() => { + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) + usePlacementPreview.getState().clear() +}) + +function setScene() { + const building = BuildingNode.parse({}) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 3 }) + building.children = [ground.id, upper.id] + const nodes = Object.fromEntries([building, ground, upper].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: upper.id }) + useEditor.setState({ + mode: 'build', + tool: 'footing', + toolDefaults: { ...useEditor.getState().toolDefaults, footing: { shape: 'strip' } }, + workspaceMode: 'edit', + }) + return { ground, upper } +} + +const footings = () => + Object.values(useScene.getState().nodes).filter( + (node): node is FootingNode => node.type === 'footing', + ) + +const gridEvent = (x: number, z: number) => + ({ + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: { altKey: true } as GridEvent['nativeEvent'], + }) satisfies GridEvent + +const move = (x: number, z: number) => act(async () => emitter.emit('grid:move', gridEvent(x, z))) +const click = (x: number, z: number) => + act(async () => { + emitter.emit('grid:move', gridEvent(x, z)) + emitter.emit('grid:click', gridEvent(x, z)) + }) + +// The footing tool was 3D-only: in 2D-only view its canvas never renders, so nothing drew. Drawn +// with an upper floor shown, the footing landed on the ground level, off the plan on screen. +test('in 2D-only view the plan draws footings, on the lowest level, which it shows', async () => { + const { ground } = setScene() + useEditor.setState({ viewLayouts: { edit: sceneLayout('2d') } }) + const renderer = await create(createElement(FootingFloorplanTool)) + try { + expect(useViewer.getState().selection.levelId).toBe(ground.id) + await click(0, 0) + await move(3, 0) + expect(usePlacementPreview.getState().node).toMatchObject({ + type: 'footing', + start: [0, 0], + end: [3, 0], + }) + await click(3, 0) + expect(footings()).toHaveLength(1) + expect(footings()[0]).toMatchObject({ parentId: ground.id, start: [0, 0], end: [3, 0] }) + + // Split view: the 3D tool owns the plan's clicks. + await act(async () => useEditor.setState({ viewLayouts: { edit: sceneLayout('split') } })) + await click(0, 2) + await click(3, 2) + expect(footings()).toHaveLength(1) + } finally { + await act(async () => renderer.unmount()) + } +}) + +test('the plan tool the footing registers draws a strip and a pad in 2D-only view', async () => { + const { ground } = setScene() + useEditor.setState({ viewLayouts: { edit: sceneLayout('2d') } }) + const extension = getFloorplanNodeExtension(footingDefinition) + // A plan click on a footing inside a room picks the footing. + expect(extension?.planHitSelectsNode).toBe(true) + const { default: PlanTool } = await extension!.tool!() + const renderer = await create(createElement(PlanTool as () => null)) + try { + await click(1, 1) + await click(4, 1) + await act(async () => useEditor.getState().setToolDefaults('footing', { shape: 'pad' })) + await click(6, 3) + expect(footings().map(({ shape, parentId }) => ({ shape, parentId }))).toEqual([ + { shape: 'strip', parentId: ground.id }, + { shape: 'pad', parentId: ground.id }, + ]) + expect(footings()[1]).toMatchObject({ center: [6, 3] }) + } finally { + await act(async () => renderer.unmount()) + } +}) diff --git a/packages/nodes/src/footing/tool.tsx b/packages/nodes/src/footing/tool.tsx new file mode 100644 index 0000000000..ea2ef0f732 --- /dev/null +++ b/packages/nodes/src/footing/tool.tsx @@ -0,0 +1,185 @@ +'use client' + +import { + type AnyNodeId, + emitter, + type FootingNode, + type FootingShape, + footingDesign, + footingLevelOf, + type GridEvent, + type LevelNode, + sceneRegistry, + useScene, +} from '@pascal-app/core' +import { + CursorSphere, + getSegmentGridStep, + isAngleSnapActive, + isMagneticSnapActive, + isSegmentLongEnough, + markToolCancelConsumed, + snapBuildingLocalToWorldGrid, + snapWallDraftPoint, + triggerSFX, + useEditor, + usePlacementPreview, + visibleScene, + type WallPlanPoint, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { createPortal } from '@react-three/fiber' +import { useEffect, useMemo, useState } from 'react' +import { DoubleSide } from 'three' +import { MeshBasicNodeMaterial } from 'three/webgpu' +import { + type FloorPlacementClickTriggerEvent, + stopPlacementCommitPropagation, + subscribeFloorPlacementClicks, +} from '../shared/floor-placement' +import { draftFooting, footingSnapTargets } from './drafting' +import { useFootingGeometry } from './renderer' + +const ghostMaterial = new MeshBasicNodeMaterial({ + color: '#f59e0b', + depthWrite: false, + opacity: 0.45, + side: DoubleSide, + transparent: true, +}) + +/** + * Drafting a footing: a strip from two clicks, a pad from one (the tool's shape choice), from the + * grid events both views send. Footings go on the building's lowest level, so the tool shows that + * level: its walls are the ones footings snap to and its plan is where they are drawn. Points snap + * as walls do: wall and footing ends, midpoints, crossings and centrelines, the grid, and from a + * strip's first point the angle steps. Alt places freely. `enabled` off, it stands aside. + */ +function useFootingDrafting(enabled: boolean) { + const activeLevelId = useViewer((state) => state.selection.levelId) + const shape = useEditor((state) => + state.toolDefaults.footing?.shape === 'pad' ? 'pad' : 'strip', + ) as FootingShape + const levelId = useScene((state) => + activeLevelId ? (footingLevelOf(state.nodes, activeLevelId)?.id ?? null) : null, + ) + const [draft, setDraft] = useState(null) + const [cursor, setCursor] = useState<[number, number, number] | null>(null) + + useEffect(() => { + if (enabled && levelId && levelId !== activeLevelId) { + useViewer.getState().setSelection({ levelId: levelId as LevelNode['id'] }) + } + }, [enabled, levelId, activeLevelId]) + + useEffect(() => { + if (!(enabled && levelId)) return + let start: WallPlanPoint | null = null + let last: WallPlanPoint | null = null + setDraft(null) + + const snap = (event: GridEvent | FloorPlacementClickTriggerEvent): WallPlanPoint => { + const raw: WallPlanPoint = [event.localPosition[0], event.localPosition[2]] + if (event.nativeEvent?.altKey) return raw + const step = getSegmentGridStep() + return snapWallDraftPoint({ + point: raw, + walls: footingSnapTargets(useScene.getState().nodes, levelId), + start: start ?? undefined, + angleSnap: start !== null && isAngleSnapActive(), + magnetic: isMagneticSnapActive(), + gridSnap: (point) => snapBuildingLocalToWorldGrid(point, step), + }) + } + const show = (next: FootingNode | null) => { + setDraft(next) + if (next) usePlacementPreview.getState().set({ ...next, parentId: levelId }) + else usePlacementPreview.getState().clear() + } + + const onMove = (event: GridEvent) => { + const point = snap(event) + if (last && point[0] === last[0] && point[1] === last[1]) return + last = point + setCursor([point[0], event.localPosition[1], point[1]]) + show(draftFooting(shape, start, point)) + } + const onClick = (event: FloorPlacementClickTriggerEvent) => { + const point = last ?? snap(event) + stopPlacementCommitPropagation(event) + if (shape === 'strip' && !start) { + start = point + triggerSFX('sfx:grid-snap') + return + } + if (start && !isSegmentLongEnough(start, point)) return + const nodes = useScene.getState().nodes + const count = Object.values(nodes).filter((node) => node.type === 'footing').length + const footing = draftFooting(shape, start, point, { name: `Footing ${count + 1}` }) + if (!footing) return + useScene.getState().createNode(footing, levelId as AnyNodeId) + triggerSFX('sfx:structure-build') + start = null + show(null) + } + const onCancel = () => { + if (!start) return + markToolCancelConsumed() + start = null + show(last ? draftFooting(shape, null, last) : null) + } + + emitter.on('grid:move', onMove) + emitter.on('tool:cancel', onCancel) + const unsubscribeClicks = subscribeFloorPlacementClicks(onClick) + return () => { + emitter.off('grid:move', onMove) + emitter.off('tool:cancel', onCancel) + unsubscribeClicks() + usePlacementPreview.getState().clear() + setDraft(null) + setCursor(null) + } + }, [enabled, levelId, shape]) + + return { levelId, draft, cursor } +} + +/** + * The footing tool in 2D-only view: the 3D tool lives in the canvas, which never renders while its + * pane is hidden. The plan draws the draft from the placement preview. Split and 3D views keep the + * 3D tool as the one owner. + */ +export function FootingFloorplanTool() { + useFootingDrafting(useEditor((state) => visibleScene(state) === '2d')) + return null +} + +const FootingTool = () => { + const { levelId, draft, cursor } = useFootingDrafting( + useEditor((state) => visibleScene(state) !== '2d'), + ) + const nodes = useScene((state) => state.nodes) + const ghost = useFootingGeometry( + useMemo( + () => (draft && levelId ? footingDesign(nodes, { ...draft, parentId: levelId }) : null), + [draft, levelId, nodes], + ), + ) + const level = levelId ? sceneRegistry.nodes.get(levelId) : undefined + + if (!levelId) return null + return ( + <> + {cursor && } + {ghost && + level && + createPortal( + {}} renderOrder={2} />, + level, + )} + + ) +} + +export default FootingTool diff --git a/packages/nodes/src/foundation/boundary-editor.tsx b/packages/nodes/src/foundation/boundary-editor.tsx new file mode 100644 index 0000000000..72c476c872 --- /dev/null +++ b/packages/nodes/src/foundation/boundary-editor.tsx @@ -0,0 +1,164 @@ +'use client' + +import { + type AnyNode, + type FoundationNode, + resolveLevelId, + useLiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + boundaryReshapeScope, + clearSlabSnapFeedback, + getSegmentGridStep, + PolygonEditor, + type PolygonEditorPlanPointSnapContext, + snapScalarToGrid, + useInteractionScope, +} from '@pascal-app/editor' +import { useCallback, useEffect } from 'react' +import { squareToNeighbours } from './floorplan-affordances' +import { snapEdgeToWallFaces, snapPointToWallFaces, wallFacesOf } from './wall-face-snap' + +/** The editor's handles stand just over the slab's top, where the foundation view shows them. */ +const HANDLE_HEIGHT = 0.12 + +/** + * Where a 3D drag of a foundation outline lands, as the plan's drags do: a side, square to itself + * on the grid, onto a wall's outside face (`faces`) it runs with when near; a corner onto the faces + * or squared to its neighbours, else the grid. `faces` `null` (Alt): the grid alone. A whole-outline + * move keeps to the grid: its cursor is no corner to snap. + */ +export function foundationEditorPlanPoint( + context: PolygonEditorPlanPointSnapContext, + faces: [number, number][][] | null, +): [number, number] { + if (context.mode === 'edge' && context.edgeIndex !== undefined) { + const a = context.initialPolygon[context.edgeIndex] + const b = context.initialPolygon[(context.edgeIndex + 1) % context.initialPolygon.length] + if (a && b) { + const dx = b[0] - a[0] + const dz = b[1] - a[1] + const length = Math.hypot(dx, dz) + if (length > 1e-6) { + const normalX = -dz / length + const normalZ = dx / length + const rawDelta = + (context.rawPoint[0] - context.initialPosition[0]) * normalX + + (context.rawPoint[1] - context.initialPosition[1]) * normalZ + const projection = snapScalarToGrid(rawDelta, getSegmentGridStep()) + const snap = faces + ? snapEdgeToWallFaces(faces, [ + [a[0] + normalX * projection, a[1] + normalZ * projection], + [b[0] + normalX * projection, b[1] + normalZ * projection], + ]) + : null + const distance = snap + ? (snap[0][0] - a[0]) * normalX + (snap[0][1] - a[1]) * normalZ + : projection + return [ + context.initialPosition[0] + normalX * distance, + context.initialPosition[1] + normalZ * distance, + ] + } + } + } + if (!faces || context.mode !== 'vertex' || context.vertexIndex === null) return context.gridPoint + return ( + snapPointToWallFaces(faces, context.rawPoint) ?? + squareToNeighbours( + context.rawPoint, + context.gridPoint, + context.initialPolygon, + context.vertexIndex, + ) + ) +} + +/** + * The foundation outline's 3D editor: the slab's own `PolygonEditor` (drag a corner, slide a side + * square to itself), snapping as the plan does: onto the walls' outside faces, where the traced + * foundation runs, a corner squaring to its neighbours, else the grid; Alt drags free. The drag previews + * through the live overrides, which the foundation's concrete follows at pointer rate, and lands as + * one undo step on release. Mounted by the tool manager for a sole selected outline. + */ +export const FoundationBoundaryEditor: React.FC<{ foundationId: FoundationNode['id'] }> = ({ + foundationId, +}) => { + const stored = useScene((state) => state.nodes[foundationId]) + const node = stored?.type === 'foundation' ? stored : null + const levelId = node ? resolveLevelId(node, useScene.getState().nodes) : null + + const handlePolygonChange = useCallback( + (polygon: Array<[number, number]>) => { + clearSlabSnapFeedback() + useScene.getState().updateNode(foundationId, { polygon }) + }, + [foundationId], + ) + + const handlePolygonPreview = useCallback( + (preview: ReadonlyArray | null) => { + if (preview) + useLiveNodeOverrides.getState().set(foundationId, { + polygon: preview.map(([x, z]) => [x, z] as [number, number]), + }) + else { + clearSlabSnapFeedback() + useLiveNodeOverrides.getState().clear(foundationId) + } + }, + [foundationId], + ) + + const handleDragStateChange = useCallback( + (isDragging: boolean) => { + const scope = useInteractionScope.getState() + if (isDragging) scope.begin(boundaryReshapeScope(foundationId)) + else scope.endIf((s) => s.kind === 'reshaping' && s.reshape === 'boundary') + }, + [foundationId], + ) + + const resolvePlanPoint = useCallback( + (context: PolygonEditorPlanPointSnapContext) => + foundationEditorPlanPoint( + context, + context.nativeEvent?.altKey === true + ? null + : wallFacesOf(useScene.getState().nodes as Record, levelId), + ), + [levelId], + ) + + useEffect( + () => () => { + clearSlabSnapFeedback() + useLiveNodeOverrides.getState().clear(foundationId) + useInteractionScope + .getState() + .endIf((s) => s.kind === 'reshaping' && s.reshape === 'boundary') + }, + [foundationId], + ) + + if (!node || node.polygon.length < 3) return null + + return ( + + ) +} + +export default FoundationBoundaryEditor diff --git a/packages/nodes/src/foundation/definition.ts b/packages/nodes/src/foundation/definition.ts new file mode 100644 index 0000000000..c7a36cc4da --- /dev/null +++ b/packages/nodes/src/foundation/definition.ts @@ -0,0 +1,89 @@ +import type { NodeDefinition } from '@pascal-app/core' +import { FLOORPLAN_NODE_EXTENSION_KEY, type FloorplanNodeExtension } from '@pascal-app/editor' +import { foundationFamilyOption } from './family' +import { buildFoundationFloorplan } from './floorplan' +import { + foundationAddVertexAffordance, + foundationDeleteVertexAffordance, + foundationMoveEdgeAffordance, + foundationMoveVertexAffordance, +} from './floorplan-affordances' +import { FoundationNode } from './schema' + +/** + * Foundation — the outline a building's foundation is poured to, in place of the one traced from + * its exterior walls: traced from them and stretched (a porch slab, a bump-out), or drawn from + * scratch, corner by corner. It stores the outline only; the concrete under it (slab and its + * turned-down edge, stem wall and footing, grade beam on piles) is the building's designed + * foundation (`genericFoundation` in core), sized by its record. Shown and edited in the foundation + * view: corners and sides drag in the plan and in 3D. + */ +export const foundationDefinition: NodeDefinition = { + kind: 'foundation', + snapProfile: 'structural', + schemaVersion: 1, + schema: FoundationNode, + category: 'structure', + extensions: { + [FLOORPLAN_NODE_EXTENSION_KEY]: { + // In 2D-only view the 3D tool never mounts: the plan drafts outlines itself. + tool: () => import('./tool').then((module) => ({ default: module.FoundationFloorplanTool })), + } satisfies FloorplanNodeExtension, + }, + + defaults: () => { + const { id: _id, type: _type, ...rest } = FoundationNode.parse({}) + return rest + }, + + capabilities: { + selectable: { hitVolume: 'bbox' }, + surfaces: { hosting: false }, + duplicable: false, + deletable: true, + drawTool: true, + }, + + // The site's foundation reads the scene itself; nothing consumes a dirty mark. + dirtyTracking: false, + rendersChildren: false, + renderer: { kind: 'parametric', module: () => import('./renderer') }, + parametrics: { + groups: [], + customPanel: () => import('./panel'), + }, + + tool: () => import('./tool'), + toolOptions: [foundationFamilyOption], + toolHints: [ + { key: 'Left click', label: 'Place a corner of the foundation' }, + { key: 'Enter', label: 'Close the outline', minDraftVertices: 3 }, + { key: 'Alt', label: 'Free place' }, + { key: 'Esc', label: 'Cancel' }, + ], + affordanceTools: { + 'boundary-edit': () => import('./boundary-editor'), + }, + + floorplan: buildFoundationFloorplan, + floorplanAffordances: { + 'move-vertex': foundationMoveVertexAffordance, + 'add-vertex': foundationAddVertexAffordance, + 'move-edge': foundationMoveEdgeAffordance, + 'delete-vertex': foundationDeleteVertexAffordance, + }, + + presentation: { + label: 'Foundation', + description: + 'The outline the foundation is poured to: drawn, or traced from the walls and stretched.', + icon: { kind: 'url', src: '/icons/floor.webp' }, + paletteSection: 'structure', + paletteOrder: 70, + }, + + mcp: { + description: + "A building's foundation outline on its lowest level (polygon, level-local plan points at the foundation's outside face), in place of the outline traced from its exterior walls; the section and sizes come from the building foundation record.", + }, +} diff --git a/packages/nodes/src/foundation/family.ts b/packages/nodes/src/foundation/family.ts new file mode 100644 index 0000000000..d83bb6fc35 --- /dev/null +++ b/packages/nodes/src/foundation/family.ts @@ -0,0 +1,80 @@ +import type { FoundationConstructionChoice, ToolOption } from '@pascal-app/core' +import { useEditor } from '@pascal-app/editor' +import { create } from 'zustand' + +/** + * What the Foundation tool pours, kept apart from the editor's tool defaults: a host clears those + * when it arms a tool from its tile, which would forget the choice just picked. + */ +export const useFoundationDraw = create<{ + construction: FoundationConstructionChoice + setConstruction: (construction: FoundationConstructionChoice) => void +}>((set) => ({ + construction: 'building', + setConstruction: (construction) => set({ construction }), +})) + +/** One of the foundation family's draws: an outline poured as one of three, or a footing. */ +export type FoundationDraw = FoundationConstructionChoice | 'strip' | 'pad' + +/** What the family draws now: the footing's shape while the Footing tool is armed, else the outline's construction. */ +export function foundationDraw(): FoundationDraw { + const editor = useEditor.getState() + if (editor.tool === 'footing') + return editor.toolDefaults.footing?.shape === 'pad' ? 'pad' : 'strip' + return useFoundationDraw.getState().construction +} + +/** + * "Draw" for the Foundation and Footing tools, one family: an outline poured as the house's + * foundation, a slab of its own (a porch, a patio, a garage pad) or a raised floor of its own, and + * a strip or pad footing. Picking across the family switches between the two tools. + */ +export const foundationFamilyOption: ToolOption = { + id: 'draw', + label: 'Draw', + choices: [ + { + value: 'building', + label: 'Foundation', + description: "An outline poured as the house's foundation, in place of the walls' outline", + }, + { + value: 'slab', + label: 'Slab', + description: 'A slab of its own: a porch, a patio, a garage pad', + }, + { value: 'raised', label: 'Raised', description: 'A raised floor on a stem wall of its own' }, + { + value: 'strip', + label: 'Strip footing', + description: 'A continuous footing under a bearing wall: click its start and end', + }, + { + value: 'pad', + label: 'Pad footing', + description: 'A footing under a post: click where it goes', + }, + ], + subscribe: (onChange) => { + const editor = useEditor.subscribe(onChange) + const draw = useFoundationDraw.subscribe(onChange) + return () => { + editor() + draw() + } + }, + value: foundationDraw, + set: (value) => { + const editor = useEditor.getState() + if (value === 'strip' || value === 'pad') { + editor.setToolDefaults('footing', { ...editor.toolDefaults.footing, shape: value }) + if (editor.tool !== 'footing') editor.armToolMode({ mode: 'build', tool: 'footing' }) + return + } + useFoundationDraw + .getState() + .setConstruction(value === 'slab' || value === 'raised' ? value : 'building') + if (editor.tool !== 'foundation') editor.armToolMode({ mode: 'build', tool: 'foundation' }) + }, +} diff --git a/packages/nodes/src/foundation/floorplan-affordances.test.ts b/packages/nodes/src/foundation/floorplan-affordances.test.ts new file mode 100644 index 0000000000..0bf4b896a6 --- /dev/null +++ b/packages/nodes/src/foundation/floorplan-affordances.test.ts @@ -0,0 +1,153 @@ +import { expect, test } from 'bun:test' +import { type AnyNodeId, FoundationNode, type GeometryContext, useScene } from '@pascal-app/core' +import { movedEdgeKeepingNeighbours } from '../shared/polygon-vertex-affordance' +import { buildFoundationFloorplan } from './floorplan' +import { + foundationMoveEdgeAffordance, + foundationMoveVertexAffordance, + squareToNeighbours, +} from './floorplan-affordances' + +globalThis.requestAnimationFrame ??= () => 1 +globalThis.cancelAnimationFrame ??= () => {} + +const BOX: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] + +test('a side pulled out keeps its square neighbours square: the box grows', () => { + // The east side (1 → 2) out 2 m, along +x. + expect(movedEdgeKeepingNeighbours(BOX, 1, [1, 0], 2)).toEqual([ + [0, 0], + [12, 0], + [12, 8], + [0, 8], + ]) +}) + +test('pulling out one piece of a split side makes a square bump-out', () => { + // The south side split at x 4: its east piece (1 → 2) out 1.5 m, to -z. + const split: Array<[number, number]> = [ + [0, 0], + [4, 0], + [10, 0], + [10, 8], + [0, 8], + ] + expect(movedEdgeKeepingNeighbours(split, 1, [0, -1], 1.5)).toEqual([ + [0, 0], + [4, 0], + [4, -1.5], + [10, -1.5], + [10, 8], + [0, 8], + ]) +}) + +test('a slanted neighbour keeps its angle, running on to meet the side', () => { + // A trapezoid: the east side leans in; the north side pulled 1 m to +z. + const trapezoid: Array<[number, number]> = [ + [0, 0], + [10, 0], + [8, 8], + [0, 8], + ] + const [, , east, north] = movedEdgeKeepingNeighbours(trapezoid, 2, [0, 1], 1) + expect(north).toEqual([0, 9]) + // On the same line as before, from (10, 0) through (8, 8): 1 m further, a quarter across. + expect(east?.[0]).toBeCloseTo(7.75, 9) + expect(east?.[1]).toBeCloseTo(9, 9) +}) + +test('a still side stays as it was, no hair-width jog', () => { + const split: Array<[number, number]> = [ + [0, 0], + [4, 0], + [10, 0], + [10, 8], + [0, 8], + ] + expect(movedEdgeKeepingNeighbours(split, 1, [0, -1], 0.0004)).toEqual(split) +}) + +test('a dragged corner squares to its neighbours, and a straight pull stays straight', () => { + // The corner (10, 0) pulled east and a little off: the south side stays square. + expect(squareToNeighbours([12, 0.06], [12, 0.06], BOX, 1)).toEqual([12, 0]) + // Pulled straight south: it stays on its own x. + expect(squareToNeighbours([10.08, -2], [10.08, -2], BOX, 1)).toEqual([10, -2]) + // Near both neighbours' lines at once: square to both. + expect(squareToNeighbours([0.05, 7.9], [0.05, 7.9], BOX, 3)).toEqual([0, 8]) + // Well off square, the grid's point. + expect(squareToNeighbours([13, 3], [13, 3], BOX, 1)).toEqual([13, 3]) +}) + +test('a picked outline carries a push arrow out of each side', () => { + const node = FoundationNode.parse({ parentId: 'level_a', polygon: BOX }) + const ctx = { viewState: { selected: true } } as unknown as GeometryContext + const plan = buildFoundationFloorplan(node, ctx) + const arrows = plan?.kind === 'group' ? plan.children.filter((c) => c.kind === 'move-arrow') : [] + expect(arrows).toHaveLength(4) + const east = arrows.find( + (arrow) => + arrow.kind === 'move-arrow' && (arrow.payload as { edgeIndex: number }).edgeIndex === 1, + ) + // Out of the east side, pointing east, driving that side's push. + expect(east).toMatchObject({ point: [10.3, 4], affordance: 'move-edge' }) + expect(east?.kind === 'move-arrow' && Math.abs(east.angle)).toBeCloseTo(0, 9) +}) + +test('in the plan a dragged corner lands square, and a piece of a split side pulls out square', () => { + const previous = useScene.getState() + try { + const node = FoundationNode.parse({ id: 'foundation_drag', polygon: BOX }) + useScene.setState({ nodes: { [node.id]: node }, rootNodeIds: [] } as never) + const modifiers = { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false } + const polygon = () => + (useScene.getState().nodes['foundation_drag' as AnyNodeId] as FoundationNode).polygon + const corner = foundationMoveVertexAffordance.start({ + node, + payload: { vertexIndex: 1 }, + nodes: useScene.getState().nodes, + initialPlanPoint: [10, 0], + gridSnapStep: 0, + }) + corner.apply({ planPoint: [12.03, 0.07], modifiers } as never) + expect(polygon()[1]).toEqual([12.03, 0]) + // Alt drags it free. + corner.apply({ planPoint: [12.03, 0.07], modifiers: { ...modifiers, altKey: true } } as never) + expect(polygon()[1]).toEqual([12.03, 0.07]) + + const split = FoundationNode.parse({ + id: 'foundation_drag', + polygon: [ + [0, 0], + [4, 0], + [10, 0], + [10, 8], + [0, 8], + ], + }) + useScene.setState({ nodes: { [split.id]: split } } as never) + const side = foundationMoveEdgeAffordance.start({ + node: split, + payload: { edgeIndex: 1 }, + nodes: useScene.getState().nodes, + initialPlanPoint: [7, 0], + gridSnapStep: 0, + }) + side.apply({ planPoint: [7, -1.5], modifiers } as never) + expect(polygon()).toEqual([ + [0, 0], + [4, 0], + [4, -1.5], + [10, -1.5], + [10, 8], + [0, 8], + ]) + } finally { + useScene.setState(previous, true) + } +}) diff --git a/packages/nodes/src/foundation/floorplan-affordances.ts b/packages/nodes/src/foundation/floorplan-affordances.ts new file mode 100644 index 0000000000..9d1181acdb --- /dev/null +++ b/packages/nodes/src/foundation/floorplan-affordances.ts @@ -0,0 +1,107 @@ +import { type AnyNode, type FoundationNode, resolveLevelId } from '@pascal-app/core' +import { + createPolygonAddVertexAffordance, + createPolygonDeleteVertexAffordance, + createPolygonMoveEdgeAffordance, + createPolygonVertexAffordance, + type PolygonAffordanceSnapContext, + type PolygonEdgeSnapContext, +} from '../shared/polygon-vertex-affordance' +import { snapEdgeToWallFaces, snapPointToWallFaces, wallFacesOf } from './wall-face-snap' + +type P = readonly [number, number] + +/** A corner's side within this angle of square (to the plan's axes) snaps square. */ +const SQUARE_SNAP = Math.tan((10 * Math.PI) / 180) +/** The least reach of that snap, metres: close to a neighbour, a hand's width still counts. */ +const SQUARE_SNAP_MIN = 0.12 + +/** + * A dragged corner squared up: each of its coordinates locks onto a neighbour's, or onto its own + * where the drag began, when the side to it stands within ~10° of square, so its sides run square + * and a straight pull stays straight. A coordinate nothing locks takes `fallback`'s (the grid's). + */ +export function squareToNeighbours( + raw: P, + fallback: P, + ring: ReadonlyArray

, + index: number, +): [number, number] { + const count = ring.length + const anchors = [ + ring[(index - 1 + count) % count], + ring[(index + 1) % count], + ring[index], + ].filter((anchor): anchor is P => anchor !== undefined) + const lock = (axis: 0 | 1) => { + let best: number | null = null + let gap = Number.POSITIVE_INFINITY + for (const anchor of anchors) { + const reach = Math.max( + SQUARE_SNAP_MIN, + SQUARE_SNAP * Math.hypot(raw[0] - anchor[0], raw[1] - anchor[1]), + ) + const off = Math.abs(raw[axis] - anchor[axis]) + if (off <= reach && off < gap) { + best = anchor[axis] + gap = off + } + } + return best ?? fallback[axis] + } + return [lock(0), lock(1)] +} + +/** The outside faces of the walls over `node`'s foundation (`wallFacesOf`). */ +const facesFor = (node: FoundationNode, nodes: Record) => + wallFacesOf(nodes, resolveLevelId(node, nodes)) + +/** + * The foundation outline's plan edits, the slab's own (`move-vertex`, `add-vertex`, `move-edge`, + * `delete-vertex`), snapping onto the walls' outside faces, where the foundation traced from them + * runs: a corner onto a corner of them or a face, a side onto a face it runs with, when near, and + * else to the grid. A dragged corner squares up to its neighbours; a dragged side keeps its + * neighbours' angles, and pulling out part of a split side makes a square bump-out. Alt drags free + * of both snaps. + */ +const foundationSnapOptions = { + keepNeighbourAngles: true, + resolvePlanPoint({ + node, + nodes, + rawPoint, + fallbackPoint, + modifiers, + mode, + ring, + vertexIndex, + }: PolygonAffordanceSnapContext) { + if (mode === 'move-edge' || modifiers.altKey) return fallbackPoint + const face = snapPointToWallFaces(facesFor(node, nodes as Record), rawPoint) + if (face) return face + return ring && vertexIndex !== undefined + ? squareToNeighbours(rawPoint, fallbackPoint, ring, vertexIndex) + : fallbackPoint + }, + snapEdge({ node, nodes, edge, modifiers }: PolygonEdgeSnapContext) { + if (modifiers.altKey) return null + return snapEdgeToWallFaces(facesFor(node, nodes as Record), edge) + }, +} + +export const foundationMoveVertexAffordance = createPolygonVertexAffordance( + 'foundation', + foundationSnapOptions, +) +export const foundationAddVertexAffordance = createPolygonAddVertexAffordance( + 'foundation', + foundationSnapOptions, +) +export const foundationMoveEdgeAffordance = createPolygonMoveEdgeAffordance( + 'foundation', + foundationSnapOptions, +) +export const foundationDeleteVertexAffordance = createPolygonDeleteVertexAffordance( + 'foundation', + foundationSnapOptions, +) diff --git a/packages/nodes/src/foundation/floorplan.ts b/packages/nodes/src/foundation/floorplan.ts new file mode 100644 index 0000000000..8ae31a1706 --- /dev/null +++ b/packages/nodes/src/foundation/floorplan.ts @@ -0,0 +1,137 @@ +import type { + FloorplanGeometry, + FloorplanPoint, + FoundationNode, + GeometryContext, +} from '@pascal-app/core' +import { formatLinearMeasurement, readFloorplanMetricNotationOverride } from '@pascal-app/editor' + +/** The tool's draft, shown before the outline closes: drawn in full, open or closed. */ +export const FOUNDATION_DRAFT_ID = 'foundation_draft' + +const OUTLINE = '#475569' +/** A side's push arrow stands this far out of the outline, metres, clear of its midpoint handle. */ +const ARROW_OFFSET = 0.3 +/** The push arrow at this times the usual size: a foundation is seen whole, zoomed out. */ +const ARROW_SCALE = 2.5 + +/** + * A foundation outline in the plan, only ever shown in the foundation view (the concrete itself is + * the foundation layer's drawing). At rest it is its own hit area, clear; hovered or selected its + * edge shows, and selected it carries the polygon editor: a corner handle at every corner (it + * squares up to its neighbours), a midpoint handle that adds one, each side draggable square to + * itself by its line or by the arrow out of it, every side's length. The tool's draft draws as a + * solid line, open until it closes. + */ +export function buildFoundationFloorplan( + node: FoundationNode, + ctx: GeometryContext, +): FloorplanGeometry | null { + const polygon = node.polygon + const view = ctx.viewState + const draft = node.id === FOUNDATION_DRAFT_ID + if (polygon.length < (draft ? 2 : 3)) return null + const selected = view?.selected ?? false + const chrome = selected || (view?.highlighted ?? false) + const points: FloorplanPoint[] = polygon.map(([x, z]) => [x, z]) + const closed = polygon.length >= 3 + + const stroke = + chrome && view?.palette + ? view.palette.selectedStroke + : view?.hovered && view.palette + ? view.palette.wallHoverStroke + : draft + ? OUTLINE + : 'none' + const children: FloorplanGeometry[] = [ + closed + ? { + kind: 'polygon', + points, + fill: chrome ? (view?.palette?.selectedFill ?? 'transparent') : 'transparent', + fillOpacity: chrome ? 0.12 : undefined, + stroke, + strokeWidth: chrome ? 2 : 1.6, + vectorEffect: 'non-scaling-stroke', + } + : { + kind: 'line', + x1: points[0]![0], + y1: points[0]![1], + x2: points[1]![0], + y2: points[1]![1], + stroke, + strokeWidth: 1.6, + vectorEffect: 'non-scaling-stroke', + pointerEvents: 'none', + }, + ] + + if (selected && !draft) { + const unit = view?.unit ?? 'metric' + const notation = readFloorplanMetricNotationOverride(ctx) ?? 'meters' + // Out of the outline: to the right of each side as a counter-clockwise ring winds (x, z). + let area = 0 + for (let i = 0; i < points.length; i++) { + const a = points[i]! + const b = points[(i + 1) % points.length]! + area += a[0] * b[1] - b[0] * a[1] + } + const out = area > 0 ? 1 : -1 + for (let i = 0; i < points.length; i++) { + const a = points[i]! + const b = points[(i + 1) % points.length]! + children.push({ + kind: 'edge-handle', + x1: a[0], + y1: a[1], + x2: b[0], + y2: b[1], + affordance: 'move-edge', + payload: { edgeIndex: i }, + }) + const side = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (side >= 0.2) { + const [nx, nz] = [((b[1] - a[1]) / side) * out, (-(b[0] - a[0]) / side) * out] + children.push({ + kind: 'move-arrow', + point: [(a[0] + b[0]) / 2 + nx * ARROW_OFFSET, (a[1] + b[1]) / 2 + nz * ARROW_OFFSET], + angle: Math.atan2(nz, nx), + affordance: 'move-edge', + payload: { edgeIndex: i }, + scale: ARROW_SCALE, + }) + } + const length = Math.hypot(b[0] - a[0], b[1] - a[1]) + if (length >= 0.2) + children.push({ + kind: 'dimension-label', + cx: (a[0] + b[0]) / 2, + cy: (a[1] + b[1]) / 2, + text: formatLinearMeasurement(length, unit, notation), + angle: Math.atan2(b[1] - a[1], b[0] - a[0]), + }) + } + for (let i = 0; i < points.length; i++) { + const a = points[i]! + const b = points[(i + 1) % points.length]! + children.push({ + kind: 'midpoint-handle', + point: [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], + affordance: 'add-vertex', + payload: { edgeIndex: i }, + }) + } + points.forEach((point, i) => { + children.push({ + kind: 'endpoint-handle', + point, + state: 'idle', + affordance: 'move-vertex', + payload: { vertexIndex: i }, + }) + }) + } + return { kind: 'group', children } +} diff --git a/packages/nodes/src/foundation/foundation.test.ts b/packages/nodes/src/foundation/foundation.test.ts new file mode 100644 index 0000000000..f0f2ab91f2 --- /dev/null +++ b/packages/nodes/src/foundation/foundation.test.ts @@ -0,0 +1,228 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearSceneHistory, + emitter, + type FloorplanGeometry, + FoundationNode, + type GridEvent, + LevelNode, + type LiveNodeOverrides, + useScene, +} from '@pascal-app/core' +import { + getFloorplanNodeExtension, + sceneLayout, + useEditor, + usePlacementPreview, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import { withLiveOutlines } from '../site/generic-foundation' +import { foundationDefinition } from './definition' +import { foundationFamilyOption, useFoundationDraw } from './family' +import { buildFoundationFloorplan, FOUNDATION_DRAFT_ID } from './floorplan' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } + +afterEach(() => { + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) + usePlacementPreview.getState().clear() + useFoundationDraw.getState().setConstruction('building') +}) + +function setScene() { + const building = BuildingNode.parse({}) + const ground = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 3 }) + building.children = [ground.id, upper.id] + const nodes = Object.fromEntries([building, ground, upper].map((node) => [node.id, node])) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: upper.id }) + useEditor.setState({ + mode: 'build', + tool: 'foundation', + workspaceMode: 'edit', + viewLayouts: { edit: sceneLayout('2d') }, + }) + return { ground } +} + +const outlines = () => + Object.values(useScene.getState().nodes).filter( + (node): node is FoundationNode => node.type === 'foundation', + ) + +const gridEvent = (x: number, z: number) => + ({ + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: { altKey: true } as GridEvent['nativeEvent'], + }) satisfies GridEvent + +const move = (x: number, z: number) => act(async () => emitter.emit('grid:move', gridEvent(x, z))) +const click = (x: number, z: number) => + act(async () => { + emitter.emit('grid:move', gridEvent(x, z)) + emitter.emit('grid:click', gridEvent(x, z)) + }) + +test('in 2D-only view a foundation is drawn corner by corner on the lowest level, closed on its first corner', async () => { + const { ground } = setScene() + // The tool listens for Enter on the window; the test runs without a DOM, so lend it one. + const scope = globalThis as { window?: EventTarget } + const lent = scope.window === undefined + if (lent) scope.window = new EventTarget() + const { default: PlanTool } = await getFloorplanNodeExtension(foundationDefinition)!.tool!() + const renderer = await create(createElement(PlanTool as () => null)) + try { + expect(useViewer.getState().selection.levelId).toBe(ground.id) + await click(0, 0) + await click(6, 0) + await move(6, 4) + // The plan's preview: the corners so far and the cursor, a draft. + expect(usePlacementPreview.getState().node).toMatchObject({ + type: 'foundation', + polygon: [ + [0, 0], + [6, 0], + [6, 4], + ], + id: FOUNDATION_DRAFT_ID, + }) + await click(6, 4) + await click(0, 4) + expect(outlines()).toHaveLength(0) + await click(0.1, 0.1) + expect(outlines()).toHaveLength(1) + expect(outlines()[0]).toMatchObject({ + parentId: ground.id, + name: 'Foundation 1', + construction: 'building', + polygon: [ + [0, 0], + [6, 0], + [6, 4], + [0, 4], + ], + }) + expect(useViewer.getState().selection.selectedIds).toEqual([outlines()[0]!.id]) + expect(usePlacementPreview.getState().node).toBeNull() + + // Enter closes it from three corners; Draw › Slab pours it as a slab of its own. + useFoundationDraw.getState().setConstruction('slab') + await click(10, 0) + await click(12, 0) + await click(12, 2) + await act(async () => { + scope.window!.dispatchEvent(Object.assign(new Event('keydown'), { key: 'Enter' })) + }) + expect(outlines()).toHaveLength(2) + expect(outlines()[1]).toMatchObject({ name: 'Slab 2', construction: 'slab' }) + } finally { + await act(async () => renderer.unmount()) + if (lent) delete scope.window + } +}) + +test('Draw picks across the family: an outline’s construction, or a footing’s shape, arming the tool that draws it', () => { + useEditor.setState({ mode: 'select', tool: null }) + foundationFamilyOption.set('pad') + expect(useEditor.getState()).toMatchObject({ mode: 'build', tool: 'footing' }) + expect(useEditor.getState().toolDefaults.footing?.shape).toBe('pad') + expect(foundationFamilyOption.value()).toBe('pad') + foundationFamilyOption.set('slab') + expect(useEditor.getState()).toMatchObject({ mode: 'build', tool: 'foundation' }) + expect(useFoundationDraw.getState().construction).toBe('slab') + expect(foundationFamilyOption.value()).toBe('slab') + foundationFamilyOption.set('strip') + expect(foundationFamilyOption.value()).toBe('strip') +}) + +const kinds = (geometry: FloorplanGeometry | null): string[] => + geometry?.kind === 'group' ? geometry.children.map((child) => child.kind) : [] + +test('the plan shows an outline clear at rest, its polygon editor picked, and a draft in full', () => { + const node = FoundationNode.parse({ + parentId: 'level_a', + polygon: [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], + ], + }) + const ctx = (selected: boolean) => + ({ viewState: { selected, unit: 'metric' } }) as Parameters[1] + const rest = buildFoundationFloorplan(node, ctx(false)) + expect(kinds(rest)).toEqual(['polygon']) + expect(rest?.kind === 'group' && rest.children[0]).toMatchObject({ + fill: 'transparent', + stroke: 'none', + }) + const picked = kinds(buildFoundationFloorplan(node, ctx(true))) + expect(picked.filter((kind) => kind === 'endpoint-handle')).toHaveLength(4) + expect(picked.filter((kind) => kind === 'midpoint-handle')).toHaveLength(4) + expect(picked.filter((kind) => kind === 'edge-handle')).toHaveLength(4) + expect(picked.filter((kind) => kind === 'dimension-label')).toHaveLength(4) + + // A two-corner draft is a line; a closed outline needs three corners. + const draft = { ...node, id: FOUNDATION_DRAFT_ID, polygon: node.polygon.slice(0, 2) } + expect(kinds(buildFoundationFloorplan(draft, ctx(false)))).toEqual(['line']) + expect( + buildFoundationFloorplan({ ...node, polygon: node.polygon.slice(0, 2) }, ctx(false)), + ).toBeNull() + expect(foundationDefinition.floorplanAffordances).toHaveProperty('move-edge') +}) + +test('a dragged outline reaches the concrete through its live shape; the scene stays as it is otherwise', () => { + const outline = FoundationNode.parse({ + parentId: 'level_a', + polygon: [ + [0, 0], + [4, 0], + [4, 3], + ], + }) + const nodes = { [outline.id]: outline } as Record + expect(withLiveOutlines(nodes, new Map())).toBe(nodes) + const wall = new Map([ + ['wall_a', { start: [0, 0] } as LiveNodeOverrides], + ]) + expect(withLiveOutlines(nodes, wall)).toBe(nodes) + const dragged = withLiveOutlines( + nodes, + new Map([ + [ + outline.id, + { + polygon: [ + [0, 0], + [5, 0], + [5, 3], + ], + } as LiveNodeOverrides, + ], + ]), + ) + expect(dragged).not.toBe(nodes) + expect((dragged[outline.id as AnyNodeId] as FoundationNode).polygon[1]).toEqual([5, 0]) + expect(nodes[outline.id as AnyNodeId]).toBe(outline) +}) diff --git a/packages/nodes/src/foundation/index.ts b/packages/nodes/src/foundation/index.ts new file mode 100644 index 0000000000..0a08c11b97 --- /dev/null +++ b/packages/nodes/src/foundation/index.ts @@ -0,0 +1 @@ +export { foundationDefinition } from './definition' diff --git a/packages/nodes/src/foundation/panel.tsx b/packages/nodes/src/foundation/panel.tsx new file mode 100644 index 0000000000..af8a866cee --- /dev/null +++ b/packages/nodes/src/foundation/panel.tsx @@ -0,0 +1,10 @@ +'use client' + +import { FoundationViewPanel } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' + +/** A picked foundation outline shows the building's Foundation panel; closing it lets the outline go. */ +export default function FoundationPanel() { + const setSelection = useViewer((state) => state.setSelection) + return setSelection({ selectedIds: [] })} /> +} diff --git a/packages/nodes/src/foundation/renderer.tsx b/packages/nodes/src/foundation/renderer.tsx new file mode 100644 index 0000000000..aac406ce43 --- /dev/null +++ b/packages/nodes/src/foundation/renderer.tsx @@ -0,0 +1,61 @@ +'use client' + +import { type FoundationNode, useLiveNodeOverrides, useRegistry } from '@pascal-app/core' +import { useNodeEvents, useViewer } from '@pascal-app/viewer' +import { useEffect, useMemo, useRef } from 'react' +import { DoubleSide, type Group, Shape, ShapeGeometry } from 'three' +import { MeshBasicNodeMaterial } from 'three/webgpu' + +/** Over the level plane, about where the slab's top stands: what a click on the foundation meets. */ +const PICK_HEIGHT = 0.1 + +// Writes nothing (no colour, no depth, so no ink outline either): it is only there to be hit. +const pickMaterial = new MeshBasicNodeMaterial({ + colorWrite: false, + depthWrite: false, + side: DoubleSide, + transparent: true, + opacity: 0, +}) + +/** + * A foundation outline in 3D draws no concrete of its own: the site's foundation draws it from the + * outline (`genericFoundation`). In the foundation view it lays an unseen sheet over its outline, + * so a click on the foundation picks it and its corners and sides can be dragged. + */ +export default function FoundationRenderer({ node: stored }: { node: FoundationNode }) { + const ref = useRef(null!) + useRegistry(stored.id, 'foundation', ref) + const live = useLiveNodeOverrides((state) => state.overrides.get(stored.id)) + const node = useMemo( + () => (live ? ({ ...stored, ...live } as FoundationNode) : stored), + [stored, live], + ) + const handlers = useNodeEvents(node, 'foundation') + const pickable = useViewer((state) => state.foundationView && !state.walkthroughMode) + const geometry = useMemo(() => { + if (!pickable || node.polygon.length < 3) return null + const shape = new Shape() + node.polygon.forEach(([x, z], index) => { + if (index === 0) shape.moveTo(x, -z) + else shape.lineTo(x, -z) + }) + shape.closePath() + return new ShapeGeometry(shape) + }, [pickable, node.polygon]) + useEffect(() => () => geometry?.dispose(), [geometry]) + + return ( + + {geometry && ( + + )} + + ) +} diff --git a/packages/nodes/src/foundation/schema.ts b/packages/nodes/src/foundation/schema.ts new file mode 100644 index 0000000000..5873f9b9b3 --- /dev/null +++ b/packages/nodes/src/foundation/schema.ts @@ -0,0 +1 @@ +export { FoundationNode } from '@pascal-app/core' diff --git a/packages/nodes/src/foundation/tool.tsx b/packages/nodes/src/foundation/tool.tsx new file mode 100644 index 0000000000..b1f5d19d53 --- /dev/null +++ b/packages/nodes/src/foundation/tool.tsx @@ -0,0 +1,246 @@ +'use client' + +import { + type AnyNode, + type AnyNodeId, + DEFAULT_ANGLE_STEP, + emitter, + FoundationNode, + footingLevelOf, + type GridEvent, + type LevelNode, + sceneRegistry, + snapPointAlongAngleRay, + useScene, +} from '@pascal-app/core' +import { + CursorSphere, + clearSlabSnapFeedback, + getSegmentGridStep, + isAngleSnapActive, + markToolCancelConsumed, + snapBuildingLocalToWorldGrid, + triggerSFX, + useEditor, + usePlacementPreview, + visibleScene, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { createPortal } from '@react-three/fiber' +import { useEffect, useMemo, useState } from 'react' +import { BufferGeometry, Line, Vector3 } from 'three' +import { LineBasicNodeMaterial } from 'three/webgpu' +import { + type FloorPlacementClickTriggerEvent, + stopPlacementCommitPropagation, + subscribeFloorPlacementClicks, +} from '../shared/floor-placement' +import { useFoundationDraw } from './family' +import { FOUNDATION_DRAFT_ID } from './floorplan' +import { snapPointToWallFaces, wallFacesOf } from './wall-face-snap' + +type P = [number, number] + +/** A click this close to the first corner closes the outline. */ +const CLOSE_DISTANCE = 0.25 +/** The draft line stands just over the level plane, where the slab's top will be. */ +const DRAFT_HEIGHT = 0.12 + +const draftMaterial = new LineBasicNodeMaterial({ + color: '#475569', + depthTest: false, + depthWrite: false, +}) + +const OUTLINE_NAMES = { building: 'Foundation', slab: 'Slab', raised: 'Raised floor' } as const + +/** The outline drawn so far as a foundation node, for the plan's placement preview. */ +function draftNode(points: P[], levelId: string) { + return { + ...FoundationNode.parse({ + id: FOUNDATION_DRAFT_ID, + polygon: points, + construction: useFoundationDraw.getState().construction, + }), + parentId: levelId, + } +} + +/** + * Drawing a foundation outline, corner by corner, from the grid events both views send: a click + * places a corner, a click on the first corner (or Enter, from three corners) closes it. Corners + * snap onto the walls' outside faces and their corners (where the traced foundation runs), else to + * the grid and, from the second, along the angle steps; Alt places freely; Esc drops the corners + * placed. Outlines go on the building's lowest + * level, as footings do, so the tool shows that level. `enabled` off, it stands aside. + */ +function useFoundationDrafting(enabled: boolean) { + const activeLevelId = useViewer((state) => state.selection.levelId) + const levelId = useScene((state) => + activeLevelId ? (footingLevelOf(state.nodes, activeLevelId)?.id ?? null) : null, + ) + const [points, setPoints] = useState([]) + const [cursor, setCursor] = useState<[number, number, number] | null>(null) + + useEffect(() => { + if (enabled && levelId && levelId !== activeLevelId) + useViewer.getState().setSelection({ levelId: levelId as LevelNode['id'] }) + }, [enabled, levelId, activeLevelId]) + + useEffect(() => { + if (!(enabled && levelId)) return + let placed: P[] = [] + let last: P | null = null + // The walls' outside faces, where the foundation traced from them runs: corners snap there first. + const faces = wallFacesOf(useScene.getState().nodes as Record, levelId) + + const snap = (event: GridEvent | FloorPlacementClickTriggerEvent): P => { + const raw: P = [event.localPosition[0], event.localPosition[2]] + if (event.nativeEvent?.altKey) return raw + const face = snapPointToWallFaces(faces, raw) + if (face) return face + // Off the faces, the grid (a slab's snap would take the walls' centrelines). + const step = getSegmentGridStep() + const previous = placed[placed.length - 1] + return previous && isAngleSnapActive() + ? [...snapPointAlongAngleRay(previous, raw, DEFAULT_ANGLE_STEP, step)] + : [...snapBuildingLocalToWorldGrid(raw, step)] + } + const show = (at: P | null) => { + const outline = at ? [...placed, at] : [...placed] + if (outline.length >= 2) usePlacementPreview.getState().set(draftNode(outline, levelId)) + else usePlacementPreview.getState().clear() + setPoints([...placed]) + useEditor.getState().setDraftVertexCount(placed.length) + } + const finish = () => { + if (placed.length < 3) return false + const nodes = useScene.getState().nodes + const count = Object.values(nodes).filter((node) => node.type === 'foundation').length + const construction = useFoundationDraw.getState().construction + const outline = FoundationNode.parse({ + name: `${OUTLINE_NAMES[construction]} ${count + 1}`, + polygon: placed, + construction, + }) + useScene.getState().createNode(outline, levelId as AnyNodeId) + triggerSFX('sfx:structure-build') + clearSlabSnapFeedback() + placed = [] + show(null) + useViewer.getState().setSelection({ selectedIds: [outline.id] }) + return true + } + + const onMove = (event: GridEvent) => { + const point = snap(event) + if (last && point[0] === last[0] && point[1] === last[1]) return + last = point + setCursor([point[0], event.localPosition[1], point[1]]) + show(point) + } + const onClick = (event: FloorPlacementClickTriggerEvent) => { + const point = last ?? snap(event) + stopPlacementCommitPropagation(event) + const first = placed[0] + if ( + placed.length >= 3 && + first && + Math.hypot(point[0] - first[0], point[1] - first[1]) < CLOSE_DISTANCE + ) { + finish() + return + } + const previous = placed[placed.length - 1] + if (previous && Math.hypot(point[0] - previous[0], point[1] - previous[1]) < 1e-6) return + placed.push(point) + triggerSFX('sfx:structure-build-start') + show(point) + } + const onKeyDown = (event: KeyboardEvent) => { + if (event.key !== 'Enter' || event.repeat) return + if (finish()) event.preventDefault() + } + const onCancel = () => { + if (placed.length === 0) return + markToolCancelConsumed() + placed = [] + show(null) + } + + emitter.on('grid:move', onMove) + emitter.on('tool:cancel', onCancel) + const keys = typeof window === 'undefined' ? null : window + keys?.addEventListener('keydown', onKeyDown) + const unsubscribeClicks = subscribeFloorPlacementClicks(onClick) + return () => { + emitter.off('grid:move', onMove) + emitter.off('tool:cancel', onCancel) + keys?.removeEventListener('keydown', onKeyDown) + unsubscribeClicks() + usePlacementPreview.getState().clear() + useEditor.getState().setDraftVertexCount(0) + clearSlabSnapFeedback() + setPoints([]) + setCursor(null) + } + }, [enabled, levelId]) + + return { levelId, points, cursor } +} + +/** + * The foundation tool in 2D-only view: the 3D tool lives in the canvas, which never renders while + * its pane is hidden. The plan draws the draft from the placement preview. + */ +export function FoundationFloorplanTool() { + useFoundationDrafting(useEditor((state) => visibleScene(state) === '2d')) + return null +} + +const FoundationTool = () => { + const { levelId, points, cursor } = useFoundationDrafting( + useEditor((state) => visibleScene(state) !== '2d'), + ) + const draft = useMemo(() => { + const line = new Line(new BufferGeometry(), draftMaterial) + line.frustumCulled = false + line.renderOrder = 2 + return line + }, []) + useEffect(() => { + const corners = cursor ? [...points, [cursor[0], cursor[2]] as P] : points + const previous = draft.geometry + draft.geometry = new BufferGeometry().setFromPoints( + corners.map(([x, z]) => new Vector3(x, DRAFT_HEIGHT, z)), + ) + draft.visible = corners.length >= 2 + previous.dispose() + }, [draft, points, cursor]) + useEffect(() => () => draft.geometry.dispose(), [draft]) + const level = levelId ? sceneRegistry.nodes.get(levelId as AnyNodeId) : undefined + + if (!levelId) return null + return ( + <> + {cursor && } + {level && + createPortal( + <> + + {points.map(([x, z], index) => ( + + ))} + , + level, + )} + + ) +} + +export default FoundationTool diff --git a/packages/nodes/src/foundation/wall-face-snap.test.ts b/packages/nodes/src/foundation/wall-face-snap.test.ts new file mode 100644 index 0000000000..513cf5c3bd --- /dev/null +++ b/packages/nodes/src/foundation/wall-face-snap.test.ts @@ -0,0 +1,175 @@ +import { expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FoundationNode, + LevelNode, + useScene, + WallNode, +} from '@pascal-app/core' +import { foundationEditorPlanPoint } from './boundary-editor' +import { foundationMoveEdgeAffordance } from './floorplan-affordances' +import { snapEdgeToWallFaces, snapPointToWallFaces, wallFacesOf } from './wall-face-snap' + +globalThis.requestAnimationFrame ??= () => 1 +globalThis.cancelAnimationFrame ??= () => {} + +/** The outside faces of a 10 × 8 m box of 0.1 m walls, a millimetre out, as the foundation traces them. */ +const FACES: Array> = [ + [ + [-0.051, -0.051], + [10.051, -0.051], + [10.051, 8.051], + [-0.051, 8.051], + ], +] + +test('a side pushed back within a foot of a wall lands on its outside face; further off it stays', () => { + const near = snapEdgeToWallFaces(FACES, [ + [10.25, -0.051], + [10.25, 8.051], + ]) + expect(near?.[0][0]).toBeCloseTo(10.051, 9) + expect(near?.[1][0]).toBeCloseTo(10.051, 9) + expect( + snapEdgeToWallFaces(FACES, [ + [10.5, -0.051], + [10.5, 8.051], + ]), + ).toBeNull() + // A side running across the faces, not with them, stays. + expect( + snapEdgeToWallFaces(FACES, [ + [10.2, 0], + [12, 1], + ]), + ).toBeNull() +}) + +test('a corner near a corner of the faces lands on it; near a face, on the face', () => { + expect(snapPointToWallFaces(FACES, [10.2, 8.2])).toEqual([10.051, 8.051]) + const onFace = snapPointToWallFaces(FACES, [10.12, 4]) + expect(onFace?.[0]).toBeCloseTo(10.051, 9) + expect(onFace?.[1]).toBeCloseTo(4, 9) + expect(snapPointToWallFaces(FACES, [11, 4])).toBeNull() +}) + +test('in the plan a side of an outline pushed out and back snaps onto the wall over it', () => { + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const walls = corners.map((start, index) => + WallNode.parse({ + id: `wall_${index}`, + parentId: 'level_g', + start, + end: corners[(index + 1) % 4], + thickness: 0.1, + }), + ) + const outline = FoundationNode.parse({ + id: 'foundation_patio', + parentId: 'level_g', + polygon: [ + [-0.051, -0.051], + [12, -0.051], + [12, 8.051], + [-0.051, 8.051], + ], + }) + const nodes = Object.fromEntries( + [ + BuildingNode.parse({ id: 'building_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: [...walls.map((wall) => wall.id), outline.id], + }), + ...walls, + outline, + ].map((node) => [node.id, node]), + ) as Record + const previous = useScene.getState() + try { + useScene.setState({ nodes, rootNodeIds: [] } as never) + expect(wallFacesOf(nodes as Record, 'level_g')).toHaveLength(1) + const session = foundationMoveEdgeAffordance.start({ + node: outline, + payload: { edgeIndex: 1 }, + nodes, + initialPlanPoint: [12, 4], + gridSnapStep: 0, + }) + const modifiers = { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false } + session.apply({ planPoint: [10.2, 4], modifiers } as never) + const polygon = () => + (useScene.getState().nodes['foundation_patio' as AnyNodeId] as FoundationNode).polygon + expect(polygon()[1]?.[0]).toBeCloseTo(10.051, 6) + expect(polygon()[2]?.[0]).toBeCloseTo(10.051, 6) + // Alt keeps it where the pointer is. + session.apply({ planPoint: [10.2, 4], modifiers: { ...modifiers, altKey: true } } as never) + expect(polygon()[1]?.[0]).toBeCloseTo(10.2, 6) + } finally { + useScene.setState(previous, true) + } +}) + +test('in 3D a side pushed back near a wall lands on its outside face too, a corner on its corner', () => { + const outline: Array<[number, number]> = [ + [-0.051, -0.051], + [12, -0.051], + [12, 8.051], + [-0.051, 8.051], + ] + const base = { + initialPolygon: outline, + vertexIndex: null, + nativeEvent: undefined, + } + // The east side (edge 1) grabbed at x 12 and let go at x 10.2. + const side = foundationEditorPlanPoint( + { + ...base, + mode: 'edge', + edgeIndex: 1, + initialPosition: [12, 4], + rawPoint: [10.2, 4], + gridPoint: [10.2, 4], + }, + FACES, + ) + expect(side[0]).toBeCloseTo(10.051, 9) + // Alt (no faces): where the pointer is. + expect( + foundationEditorPlanPoint( + { + ...base, + mode: 'edge', + edgeIndex: 1, + initialPosition: [12, 4], + rawPoint: [10.2, 4], + gridPoint: [10.2, 4], + }, + null, + )[0], + ).toBeCloseTo(10.2, 9) + // A corner let go near a corner of the faces lands on it. + expect( + foundationEditorPlanPoint( + { + ...base, + mode: 'vertex', + vertexIndex: 1, + initialPosition: [12, -0.051], + rawPoint: [10.2, 0.1], + gridPoint: [10.2, 0.1], + }, + FACES, + ), + ).toEqual([10.051, -0.051]) +}) diff --git a/packages/nodes/src/foundation/wall-face-snap.ts b/packages/nodes/src/foundation/wall-face-snap.ts new file mode 100644 index 0000000000..a48637b934 --- /dev/null +++ b/packages/nodes/src/foundation/wall-face-snap.ts @@ -0,0 +1,107 @@ +import { type AnyNode, type AnyNodeId, traceFoundationOutlines } from '@pascal-app/core' + +type P = [number, number] + +/** How near the walls' outside faces a foundation corner or side snaps onto them, metres (~1 ft). */ +export const WALL_FACE_SNAP = 0.3 +/** A side within this sine of a face's line runs with it. */ +const PARALLEL_SINE = 0.02 + +const facesMemo = new WeakMap>() + +/** + * The outside faces of the walls over the foundation of `levelId`'s building, as the foundation + * traces them (`traceFoundationOutlines`): closed loops, level-local plan. A drag reads one scene + * from start to end, so they are worked out once a drag. + */ +export function wallFacesOf(nodes: Record, levelId: string | null): P[][] { + const level = levelId ? nodes[levelId] : undefined + const buildingId = level?.type === 'level' ? level.parentId : null + if (!buildingId) return [] + let byBuilding = facesMemo.get(nodes) + const hit = byBuilding?.get(buildingId) + if (hit) return hit + const faces = traceFoundationOutlines(nodes as Record, buildingId) + if (!byBuilding) { + byBuilding = new Map() + facesMemo.set(nodes, byBuilding) + } + byBuilding.set(buildingId, faces) + return faces +} + +const sides = (loop: P[]) => loop.map((a, i) => [a, loop[(i + 1) % loop.length]!] as const) + +/** + * A foundation corner onto the walls' outside faces: a corner of them within `WALL_FACE_SNAP`, else + * the nearest point of a face within half that. `null` off them. + */ +export function snapPointToWallFaces(faces: P[][], point: readonly [number, number]): P | null { + let corner: P | null = null + let cornerGap = WALL_FACE_SNAP + for (const loop of faces) + for (const vertex of loop) { + const gap = Math.hypot(point[0] - vertex[0], point[1] - vertex[1]) + if (gap <= cornerGap) { + corner = [vertex[0], vertex[1]] + cornerGap = gap + } + } + if (corner) return corner + let onFace: P | null = null + let faceGap = WALL_FACE_SNAP / 2 + for (const loop of faces) + for (const [a, b] of sides(loop)) { + const dx = b[0] - a[0] + const dy = b[1] - a[1] + const length2 = dx * dx + dy * dy + if (length2 < 1e-12) continue + const t = ((point[0] - a[0]) * dx + (point[1] - a[1]) * dy) / length2 + if (t < 0 || t > 1) continue + const near: P = [a[0] + dx * t, a[1] + dy * t] + const gap = Math.hypot(point[0] - near[0], point[1] - near[1]) + if (gap <= faceGap) { + onFace = near + faceGap = gap + } + } + return onFace +} + +/** + * A foundation side onto the walls' outside faces: moved square to itself onto the line of a face + * it runs with and overlaps, when within `WALL_FACE_SNAP` of it. `null` off them. + */ +export function snapEdgeToWallFaces( + faces: P[][], + edge: readonly [readonly [number, number], readonly [number, number]], +): [P, P] | null { + const [a, b] = edge + const dx = b[0] - a[0] + const dy = b[1] - a[1] + const length = Math.hypot(dx, dy) + if (length < 1e-9) return null + const u: P = [dx / length, dy / length] + const n: P = [-u[1], u[0]] + let best: number | null = null + for (const loop of faces) + for (const [p, q] of sides(loop)) { + const fx = q[0] - p[0] + const fy = q[1] - p[1] + const faceLength = Math.hypot(fx, fy) + if (faceLength < 1e-9 || Math.abs(u[0] * fy - u[1] * fx) / faceLength > PARALLEL_SINE) + continue + // Overlapping along the side, not a face further along its line. + const s0 = (p[0] - a[0]) * u[0] + (p[1] - a[1]) * u[1] + const s1 = (q[0] - a[0]) * u[0] + (q[1] - a[1]) * u[1] + if (Math.max(s0, s1) < 0 || Math.min(s0, s1) > length) continue + const shift = (p[0] - a[0]) * n[0] + (p[1] - a[1]) * n[1] + if (Math.abs(shift) <= WALL_FACE_SNAP && (best === null || Math.abs(shift) < Math.abs(best))) + best = shift + } + if (best === null) return null + return [ + [a[0] + n[0] * best, a[1] + n[1] * best], + [b[0] + n[0] * best, b[1] + n[1] * best], + ] +} diff --git a/packages/nodes/src/index.ts b/packages/nodes/src/index.ts index 50687c9914..84b1b22225 100644 --- a/packages/nodes/src/index.ts +++ b/packages/nodes/src/index.ts @@ -1,4 +1,5 @@ import type { AnyNodeDefinition, Plugin } from '@pascal-app/core' +import { beamDefinition } from './beam' import { blockDefinition } from './block/definition' import { boxVentDefinition } from './box-vent' import { buildingDefinition } from './building' @@ -19,6 +20,8 @@ import { eyebrowVentDefinition } from './eyebrow-vent' import { fenceDefinition } from './fence' import { fenceGateDefinition, fenceOpeningDefinition } from './fence-feature/definition' import { floorOpeningDefinition } from './floor-opening/definition' +import { footingDefinition } from './footing' +import { foundationDefinition } from './foundation' import { guideDefinition } from './guide' import { gutterDefinition } from './gutter' import { hvacEquipmentDefinition } from './hvac-equipment' @@ -96,6 +99,9 @@ export const builtinPlugin: Plugin = { // Stage A — wrap-exports the legacy renderer + system. Legacy // panels / move tools / floorplan branches still serve these. columnDefinition as unknown as AnyNodeDefinition, + beamDefinition as unknown as AnyNodeDefinition, + footingDefinition as unknown as AnyNodeDefinition, + foundationDefinition as unknown as AnyNodeDefinition, elevatorDefinition as unknown as AnyNodeDefinition, unitDefinition as unknown as AnyNodeDefinition, roofDefinition as unknown as AnyNodeDefinition, @@ -137,6 +143,8 @@ export const builtinPlugin: Plugin = { ], } +export { beamDefinition } from './beam' +export { useBeamDraft } from './beam/draft-settings' export { applyBlockCommand, type BlockCommand, @@ -177,6 +185,13 @@ export { export { ceilingDefinition } from './ceiling' export { chimneyDefinition } from './chimney' export { columnDefinition } from './column' +export { + armFramingPostTool, + type FramingPostToolChoices, + framingPostsUnderBeam, + framingPostToolChoices, + framingPostToolDefaults, +} from './column/post-placement' export { constructionDimensionDefinition } from './construction-dimension' export { cupolaDefinition } from './cupola' export { doorDefinition } from './door' @@ -190,6 +205,8 @@ export { eyebrowVentDefinition } from './eyebrow-vent' export { beginFenceFeaturePlacement, fenceDefinition } from './fence' export { fenceGateDefinition, fenceOpeningDefinition } from './fence-feature/definition' export { floorOpeningDefinition } from './floor-opening/definition' +export { footingDefinition } from './footing' +export { foundationDefinition } from './foundation' export { guideDefinition } from './guide' export { gutterDefinition } from './gutter' export { hvacEquipmentDefinition } from './hvac-equipment' diff --git a/packages/nodes/src/roof-segment/build-up-section.tsx b/packages/nodes/src/roof-segment/build-up-section.tsx new file mode 100644 index 0000000000..62e69e4834 --- /dev/null +++ b/packages/nodes/src/roof-segment/build-up-section.tsx @@ -0,0 +1,290 @@ +'use client' + +import { + LUMBER_DEPTH, + ROOFING_THICKNESS, + type RoofBuildUp, + type RoofingKind, + type RoofSegmentNode, + roofBuildUpDepths, + roofBuildUpPatch, + UNDERLAYMENT_THICKNESS, + type UnderlaymentKind, +} from '@pascal-app/core' +import { ActionButton, PanelSection, SegmentedControl, SelectControl } from '@pascal-app/editor' +import { useState } from 'react' +import { + BuildUpReadout, + editedBuildUp, + FinishFields, + FramingSizeFields, + formatSpacing, + InsulationFields, + NONE, + PanelFields, +} from '../shared/build-up-controls' +import { formatLayerDepth, formatLayerThickness } from '../shared/layer-thickness' +import { + keptName, + namedLayer, + roofBuildUpOfDeck, + roofBuildUpPresetOptions, + roofBuildUpPresetPatch, + roofBuildUpPresetValue, +} from './panel-model' + +const ROOFING_OPTIONS: { label: string; value: RoofingKind }[] = [ + { label: 'Asphalt 3-tab', value: 'asphalt-3-tab' }, + { label: 'Architectural shingle', value: 'architectural-shingle' }, + { label: 'Standing-seam metal', value: 'standing-seam-metal' }, + { label: 'Concrete tile', value: 'concrete-tile' }, + { label: 'Clay tile', value: 'clay-tile' }, + { label: 'Wood shake', value: 'wood-shake' }, + { label: 'Slate', value: 'slate' }, +] + +const UNDERLAYMENT_OPTIONS: { label: string; value: UnderlaymentKind | typeof NONE }[] = [ + { label: 'None', value: NONE }, + { label: '#15 felt', value: 'felt-15' }, + { label: '#30 felt', value: 'felt-30' }, + { label: 'Synthetic', value: 'synthetic' }, + { label: 'Ice and water shield', value: 'ice-and-water' }, +] + +/** What each kind is called where the layer has no name of its own. */ +const KIND_LABELS: Record = { + ...Object.fromEntries( + [...ROOFING_OPTIONS, ...UNDERLAYMENT_OPTIONS].map((o) => [o.value, o.label]), + ), + osb: 'OSB', + plywood: 'Plywood', + batt: 'Batt', + blown: 'Blown', + rigid: 'Rigid board', + drywall: 'Drywall', + plaster: 'Plaster', + 'wood-plank': 'Wood plank', +} + +type NamedLayerKey = 'roofing' | 'underlayment' | 'sheathing' | 'insulation' | 'finish' + +/** + * A layer's own name for its material ("GAF Timberline HDZ"), under its kind: the kind's name + * shows until one is typed, and emptied the name goes. + */ +function LayerNameField({ + label, + layer, + onCommit, +}: { + label: string + layer: { kind: string; name?: string } + onCommit: (name: string) => void +}) { + const [draft, setDraft] = useState(null) + return ( + + ) +} + +/** A layer as a line of the readout: its name, else its kind's. */ +const layerLabel = (layer: { kind: string; name?: string }) => + layer.name ?? KIND_LABELS[layer.kind] ?? layer.kind + +/** + * A plane without layers draws its deck and shingle thicknesses as they are. The readout says + * which layers that deck is (`roofBuildUpOfDeck`: the default preset over the framing it holds), + * and "Edit layers" gives the plane those layers to edit. + */ +function DrawnLayers({ + node, + onUpdate, + unit, +}: { + node: RoofSegmentNode + onUpdate: (patch: Partial) => void + unit: 'metric' | 'imperial' +}) { + const drawn = roofBuildUpOfDeck(node.deckThickness) + const { structure } = drawn + return ( + <> + + {drawn.underlayment && ( + + )} + + + + + onUpdate(roofBuildUpPatch(drawn))} + /> + + ) +} + +export function RoofBuildUpSection({ + node, + onApplyToRoof, + onUpdate, + unit, +}: { + node: RoofSegmentNode + /** Present when the plane's roof has other planes to take the same layers. */ + onApplyToRoof?: () => void + onUpdate: (patch: Partial) => void + unit: 'metric' | 'imperial' +}) { + const buildUp = node.buildUp + const edit = (change: Partial) => { + if (buildUp) onUpdate(roofBuildUpPatch(editedBuildUp(buildUp, change))) + } + // a name is no change of material: the preset the layers came from stays theirs + const rename = (key: NamedLayerKey, name: string) => { + const layer = buildUp?.[key] + if (buildUp && layer) onUpdate(roofBuildUpPatch({ ...buildUp, [key]: namedLayer(layer, name) })) + } + const depths = buildUp ? roofBuildUpDepths(buildUp) : null + + return ( + + { + const patch = roofBuildUpPresetPatch(id) + if (patch) onUpdate(patch) + }} + options={roofBuildUpPresetOptions(roofBuildUpPresetValue(buildUp))} + value={roofBuildUpPresetValue(buildUp)} + /> + {!buildUp && } + {buildUp && depths && ( + <> + edit({ roofing: { kind, thickness: ROOFING_THICKNESS[kind] } })} + options={ROOFING_OPTIONS} + value={buildUp.roofing.kind} + /> + rename('roofing', name)} + /> + + edit({ + underlayment: + kind === NONE ? undefined : { kind, thickness: UNDERLAYMENT_THICKNESS[kind] }, + }) + } + options={UNDERLAYMENT_OPTIONS} + value={buildUp.underlayment?.kind ?? NONE} + /> + {buildUp.underlayment && ( + rename('underlayment', name)} + /> + )} + + sheathing && edit({ sheathing: keptName(buildUp.sheathing, sheathing) }) + } + unit={unit} + /> + rename('sheathing', name)} + /> + edit({ structure: { ...buildUp.structure, system } })} + options={[ + { label: 'Rafters', value: 'rafters' }, + { label: 'Trusses', value: 'trusses' }, + ]} + value={buildUp.structure.system} + /> + edit({ structure: { ...buildUp.structure, ...change } })} + sizeLabel={buildUp.structure.system === 'trusses' ? 'Top chord' : 'Rafters'} + unit={unit} + /> + + edit({ insulation: keptName(buildUp.insulation, insulation) }) + } + unit={unit} + /> + {buildUp.insulation && ( + rename('insulation', name)} + /> + )} + edit({ finish: keptName(buildUp.finish, finish) })} + unit={unit} + /> + {buildUp.finish && ( + rename('finish', name)} + /> + )} + + + {onApplyToRoof && ( + + )} + + )} + + ) +} diff --git a/packages/nodes/src/roof-segment/definition.test.ts b/packages/nodes/src/roof-segment/definition.test.ts index 4ec752d38d..8cceaf9705 100644 --- a/packages/nodes/src/roof-segment/definition.test.ts +++ b/packages/nodes/src/roof-segment/definition.test.ts @@ -1,16 +1,23 @@ -import { describe, expect, test } from 'bun:test' +import { afterEach, describe, expect, test } from 'bun:test' import { + type AnyNode, + type AnyNodeId, + BuildingNode, getActiveRoofHeight, type HandleDescriptor, + LevelNode, type LinearResizeHandle, + nodeRegistry, type RadialResizeHandle, + RoofNode, type RoofSegmentNode, + registerNode, + type SceneApi, + useScene, + WallNode, } from '@pascal-app/core' -import { builtinPlugin } from '@pascal-app/nodes' - -const roofSegmentDefinition = builtinPlugin.nodes.find( - (definition) => definition.kind === 'roof-segment', -)! +import { useEditor, useInteractionScope } from '@pascal-app/editor' +import { roofSegmentDefinition } from './definition' function segment(overrides: Partial = {}): RoofSegmentNode { return { @@ -156,3 +163,115 @@ test('barrel handles preserve rise on span resize and adjust the crown without c expect(getActiveRoofHeight(raised)).toBe(1.5) expect(raised.pitch).toBe(node.pitch) }) + +describe('roof-segment paint: one gable at a time', () => { + const paint = roofSegmentDefinition.capabilities!.paint! + const red = { preset: 'custom', properties: { color: '#aa2222' } } as const + + test('the wall-band slots resolve to the heel band and each gable', () => { + const node = segment({ roofType: 'gable' }) + const role = (materialIndex: number) => paint.resolveRole({ node, materialIndex }) + expect([role(0), role(4), role(5), role(6)]).toEqual([ + 'edge', + 'heel', + 'gable-left', + 'gable-right', + ]) + }) + + test('painting one gable leaves the other gable and the edge role alone', () => { + const node = segment({ roofType: 'gable' }) + const patch = paint.buildPatch({ + node, + role: 'gable-left', + material: red, + }) as Partial + expect(patch).toEqual({ gableLeftMaterial: red, gableLeftMaterialPreset: undefined }) + const painted = { ...node, ...patch } as RoofSegmentNode + expect(paint.getEffectiveMaterial?.({ node: painted, role: 'gable-left' })).toEqual({ + material: red, + materialPreset: undefined, + }) + // the other gable still shows the edge role (here unpainted) + expect(paint.getEffectiveMaterial?.({ node: painted, role: 'gable-right' })).toEqual({ + material: undefined, + materialPreset: undefined, + }) + }) +}) + +describe('roof-segment 3D side arrows onto the walls below', () => { + afterEach(() => { + useInteractionScope.getState().end() + useScene.setState({ nodes: {}, rootNodeIds: [] } as never) + }) + + // A room just off the grid, its roof on the Roof level, wall line x 1..11 by z 0..8. + function scene(mode: 'grid' | 'off', thickness?: number) { + const corners: [number, number][] = [ + [0.2, 0], + [10.2, 0], + [10.2, 8], + [0.2, 8], + ] + const walls = corners.map((start, i) => + WallNode.parse({ parentId: 'level_g', start, end: corners[(i + 1) % 4], thickness }), + ) + const node = segment({ parentId: 'roof_w', position: [0, 0, 0], width: 10, depth: 8 }) + const roof = RoofNode.parse({ + id: 'roof_w', + parentId: 'level_r', + position: [6, 0, 4], + children: [node.id], + }) + const ground = LevelNode.parse({ + id: 'level_g', + parentId: 'building_w', + level: 0, + children: walls.map((wall) => wall.id), + }) + const roofLevel = LevelNode.parse({ + id: 'level_r', + parentId: 'building_w', + level: 1, + children: [roof.id], + }) + const building = BuildingNode.parse({ id: 'building_w', children: [ground.id, roofLevel.id] }) + const nodes = Object.fromEntries( + [building, ground, roofLevel, ...walls, roof, node].map((n) => [n.id, n as AnyNode]), + ) as Record + useScene.setState({ nodes, rootNodeIds: [building.id] } as never) + if (!nodeRegistry.has(roofSegmentDefinition.kind)) registerNode(roofSegmentDefinition) + useEditor.setState((state) => ({ + gridSnapStep: 0.5, + snappingModeByContext: { ...state.snappingModeByContext, polygon: mode }, + })) + useInteractionScope.getState().begin({ kind: 'handle-drag', nodeId: node.id, handle: 'resize' }) + const sceneApi = { + get: (id: AnyNodeId) => nodes[id], + nodes: () => nodes, + } as unknown as SceneApi + return { node, sceneApi } + } + + test('the left arrow lands its side on the wall centre near it, else on the grid', () => { + const { node, sceneApi } = scene('grid') + const snap = linear('x', 'max').connectionSnap! + expect(snap(node, 10.7, sceneApi)).toBeCloseTo(10.8) + expect(snap(node, 10.3, sceneApi)).toBeCloseTo(10.5) + }) + + test('with snapping off the side follows the cursor', () => { + const { node, sceneApi } = scene('off') + expect(linear('x', 'max').connectionSnap!(node, 10.7, sceneApi)).toBeCloseTo(10.7) + }) + + test('sized onto the walls its gable and heel walls take their thickness, off them its own', () => { + const { node, sceneApi } = scene('grid', 0.1826) + const handle = linear('x', 'max') + expect(handle.apply(node, 10.8, sceneApi).wallThickness).toBeCloseTo(0.1826) + // A metre off the walls along z, and its left side pulled in off the wall at x 0.2. + const off = { ...node, position: [0, 0, 1] as [number, number, number] } + expect(handle.apply(off, 9, sceneApi).wallThickness).toBeCloseTo(0.1) + }) +}) diff --git a/packages/nodes/src/roof-segment/definition.ts b/packages/nodes/src/roof-segment/definition.ts index 17024a27eb..b2abad9376 100644 --- a/packages/nodes/src/roof-segment/definition.ts +++ b/packages/nodes/src/roof-segment/definition.ts @@ -1,12 +1,29 @@ import { + type AnyNodeId, getActiveRoofHeight, getPitchFromActiveRoofHeight, getRoofSegmentResizePatch, type HandleDescriptor, type NodeDefinition, + type RoofNode, RoofSegmentNode as RoofSegmentNodeSchema, type RoofSegmentNode as RoofSegmentNodeType, + type SceneApi, + snapScalar, } from '@pascal-app/core' +import { + getSegmentGridStep, + isGridSnapActive, + isMagneticSnapActive, + PANEL_MODEL_EXTENSION, +} from '@pascal-app/editor' +import { roofFloorplanAffectedIds } from '../roof/floorplan-overrides' +import { + bearingWallThickness, + catchRoofSideOnWalls, + roofCatchWalls, + segmentWallLine, +} from '../roof/wall-catch' import { buildRoofSegmentFloorplan } from './floorplan' import { roofSegmentMoveTarget, @@ -15,6 +32,7 @@ import { } from './floorplan-affordances' import { matchRoofSegmentMeasurementFeature, roofSegmentMeasurementFeatures } from './measurement' import { roofSegmentPaint, roofSegmentSlots } from './paint' +import { roofSegmentPanelModel } from './panel-model' import { roofSegmentParametrics } from './parametrics' import { RoofSegmentNode } from './schema' @@ -49,6 +67,49 @@ function getSideResizeHandleY(n: RoofSegmentNodeType, localZ: number): number { return Math.max(roofY, MIN_WALL_DISPLAY) + ROOF_HANDLE_CLEARANCE } +function roofOf(n: RoofSegmentNodeType, sceneApi: SceneApi | undefined): RoofNode | undefined { + const roof = n.parentId ? sceneApi?.get(n.parentId as AnyNodeId) : undefined + return roof?.type === 'roof' ? roof : undefined +} + +// A dragged side lands on a wall under it in grid and lines, ahead of the grid, as a move does. +// The arrows leave `gridSnap` off so the catch sees the raw size, and grid it here when nothing +// catches; Alt skips this hook, so the side follows the cursor. +function snapSideOntoWalls(axis: 'x' | 'z', side: 1 | -1) { + return (n: RoofSegmentNodeType, value: number, sceneApi: SceneApi): number => { + const grid = isGridSnapActive() + if (!grid && !isMagneticSnapActive()) return value + const roof = roofOf(n, sceneApi) + if (roof) { + const caught = catchRoofSideOnWalls({ + line: segmentWallLine(roof, n), + axis, + side, + value, + walls: roofCatchWalls(sceneApi.nodes(), roof.parentId), + }) + if (caught !== null) return caught + } + return grid ? snapScalar(value, getSegmentGridStep()) : value + } +} + +// Sized onto the walls under it, a segment's gable and heel walls take their thickness; off them, +// its own (written either way, so a drag back off the walls drops the thickness it previewed). +function onBearingWalls( + n: RoofSegmentNodeType, + patch: Partial, + sceneApi: SceneApi | undefined, +): Partial { + const roof = roofOf(n, sceneApi) + if (!(roof && sceneApi)) return patch + const thickness = bearingWallThickness( + segmentWallLine(roof, { ...n, ...patch }), + roofCatchWalls(sceneApi.nodes(), roof.parentId), + ) + return { ...patch, wallThickness: thickness ?? n.wallThickness } +} + // Width arrow on the +X (right) or -X (left) side. Asymmetric resize: // dragging one arrow grows the segment outward from its own edge while // the opposite edge stays world-fixed — the same pattern doors use @@ -70,9 +131,10 @@ function roofSegmentWidthHandle(side: 'left' | 'right'): HandleDescriptor n.width, - apply: (initial, newWidth) => getRoofSegmentResizePatch(initial, 'x', sign, newWidth), + apply: (initial, newWidth, sceneApi) => + onBearingWalls(initial, getRoofSegmentResizePatch(initial, 'x', sign, newWidth), sceneApi), placement: { position: (n) => [sign * (n.width / 2 + SIDE_HANDLE_OFFSET), getSideResizeHandleY(n, 0), 0], // Flip the left chevron so it points outward toward -X. The @@ -99,9 +161,10 @@ function roofSegmentDepthHandle(side: 'front' | 'back'): HandleDescriptor n.depth, - apply: (initial, newDepth) => getRoofSegmentResizePatch(initial, 'z', sign, newDepth), + apply: (initial, newDepth, sceneApi) => + onBearingWalls(initial, getRoofSegmentResizePatch(initial, 'z', sign, newDepth), sceneApi), placement: { position: (n) => [ 0, @@ -273,6 +336,9 @@ export const roofSegmentDefinition: NodeDefinition = { // snapping chip and honours the active mode like every other structural // move. Resize / rotate run through their own reshaping scope. snapProfile: 'structural', + extensions: { + [PANEL_MODEL_EXTENSION]: roofSegmentPanelModel, + }, defaults: () => { const stub = RoofSegmentNodeSchema.parse({ @@ -322,6 +388,8 @@ export const roofSegmentDefinition: NodeDefinition = { module: () => import('./renderer'), }, floorplan: buildRoofSegmentFloorplan, + // Its roof draws the outline and creases from it: a live edit rebuilds that roof's plan. + floorplanAffectedIds: roofFloorplanAffectedIds, measurement: { features: (node, ctx) => roofSegmentMeasurementFeatures(node, ctx.parent?.type === 'roof' ? ctx.parent : null), diff --git a/packages/nodes/src/roof-segment/floorplan-affordances.test.ts b/packages/nodes/src/roof-segment/floorplan-affordances.test.ts index f2da90de9c..8fd3790df7 100644 --- a/packages/nodes/src/roof-segment/floorplan-affordances.test.ts +++ b/packages/nodes/src/roof-segment/floorplan-affordances.test.ts @@ -4,17 +4,19 @@ import { type AnyNodeId, BuildingNode, LevelNode, + nodeRegistry, RoofNode, RoofSegmentNode, + registerNode, useLiveNodeOverrides, useScene, WallNode, ZoneNode, } from '@pascal-app/core' +import { useEditor, useInteractionScope } from '@pascal-app/editor' import { builtinPlugin } from '@pascal-app/nodes' - -const roofSegmentResizeAffordance = builtinPlugin.nodes.find((def) => def.kind === 'roof-segment')! - .floorplanAffordances!['roof-segment-resize']! +import { roofSegmentDefinition } from './definition' +import { roofSegmentMoveTarget, roofSegmentResizeAffordance } from './floorplan-affordances' globalThis.requestAnimationFrame ??= (callback) => { callback(0) @@ -25,6 +27,7 @@ globalThis.cancelAnimationFrame ??= () => {} const modifiers = { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false } afterEach(() => { + useInteractionScope.getState().end() useLiveNodeOverrides.getState().clearAll() useScene.setState({ nodes: {}, rootNodeIds: [] } as never) }) @@ -146,3 +149,147 @@ describe('roof-segment floor-plan resize onto the walls', () => { expect(after[segment.id]).toMatchObject({ width: 9 }) }) }) + +describe('roof-segment floor-plan move and resize onto the walls below', () => { + // A room just off the grid, and its roof on the Roof level above, 1 m off to the +x. + function scene(thickness?: number) { + const corners: [number, number][] = [ + [0.2, 0], + [10.2, 0], + [10.2, 8], + [0.2, 8], + ] + const walls = corners.map((start, i) => + WallNode.parse({ + parentId: 'level_g', + start, + end: corners[(i + 1) % 4], + height: 2.7, + thickness, + }), + ) + const segment = RoofSegmentNode.parse({ + id: 'rseg_move', + parentId: 'roof_move', + width: 10, + depth: 8, + }) + const roof = RoofNode.parse({ + id: 'roof_move', + parentId: 'level_r', + position: [6, 0, 4], + children: [segment.id], + }) + const ground = LevelNode.parse({ + id: 'level_g', + parentId: 'building_m', + level: 0, + height: 3, + children: walls.map((wall) => wall.id), + }) + const roofLevel = LevelNode.parse({ + id: 'level_r', + parentId: 'building_m', + level: 1, + metadata: { role: 'roof' }, + children: [roof.id], + }) + const building = BuildingNode.parse({ id: 'building_m', children: [ground.id, roofLevel.id] }) + const nodes = Object.fromEntries( + [building, ground, roofLevel, ...walls, roof, segment].map((node) => [ + node.id, + node as AnyNode, + ]), + ) as Record + useScene.setState({ nodes, rootNodeIds: [building.id] } as never) + if (!nodeRegistry.has(roofSegmentDefinition.kind)) registerNode(roofSegmentDefinition) + useEditor.setState((state) => ({ + gridSnapStep: 0.5, + snappingModeByContext: { ...state.snappingModeByContext, polygon: 'grid' }, + })) + useInteractionScope.getState().begin({ + kind: 'moving', + node: segment, + nodeId: segment.id, + nodeType: segment.type, + view: '2d', + }) + return { nodes, segment } + } + + test('its wall line lands on the wall centres instead of the grid near them', () => { + const { nodes, segment } = scene() + const session = roofSegmentMoveTarget({ node: segment, nodes, sceneApi: {} as never }) + session.apply({ planPoint: [6, 4], modifiers }) + session.apply({ planPoint: [5.15, 4.1], modifiers }) + const position = useLiveNodeOverrides.getState().get(segment.id as AnyNodeId)?.position as + | number[] + | undefined + // Edges at x 0.2 / 10.2 and z 0 / 8: the room's walls, not the grid's 0 / 10. + expect(position?.[0]).toBeCloseTo(-0.8) + expect(position?.[2]).toBeCloseTo(0) + }) + + test('moved onto the walls, its gable and heel walls take their thickness', () => { + const { nodes, segment } = scene(0.1826) + const session = roofSegmentMoveTarget({ node: segment, nodes, sceneApi: {} as never }) + session.apply({ planPoint: [6, 4], modifiers }) + session.apply({ planPoint: [5.15, 4.1], modifiers }) + expect(useLiveNodeOverrides.getState().get(segment.id as AnyNodeId)?.wallThickness).toBeCloseTo( + 0.1826, + ) + // Off the walls again, it goes back to its own. + session.apply({ planPoint: [8, 6], modifiers }) + expect(useLiveNodeOverrides.getState().get(segment.id as AnyNodeId)?.wallThickness).toBeCloseTo( + 0.1, + ) + }) + + test('Alt holds the raw cursor, past the walls and the grid', () => { + const { nodes, segment } = scene() + const session = roofSegmentMoveTarget({ node: segment, nodes, sceneApi: {} as never }) + const alt = { ...modifiers, altKey: true } + session.apply({ planPoint: [6, 4], modifiers: alt }) + session.apply({ planPoint: [5.15, 4.1], modifiers: alt }) + const position = useLiveNodeOverrides.getState().get(segment.id as AnyNodeId)?.position as + | number[] + | undefined + expect(position?.[0]).toBeCloseTo(-0.85) + expect(position?.[2]).toBeCloseTo(0.1) + }) + + // Its wall line runs x 1..11: the -x arrow pulled out to 0.3, near the wall at 0.2. + function pullLeftSide(altKey: boolean, thickness?: number) { + const { nodes, segment } = scene(thickness) + useInteractionScope.getState().end() + useInteractionScope + .getState() + .begin({ kind: 'reshaping', nodeId: segment.id, reshape: 'boundary', driver: 'floorplan' }) + const session = roofSegmentResizeAffordance.start({ + node: segment, + payload: { axis: 'x', side: -1 }, + nodes, + initialPlanPoint: [1, 4], + gridSnapStep: 0.5, + }) + session.apply({ planPoint: [0.3, 4], modifiers: { ...modifiers, altKey } }) + return useLiveNodeOverrides.getState().get(segment.id as AnyNodeId) as + | { width: number; position: number[]; wallThickness?: number } + | undefined + } + + test('a side arrow pulled near a wall lands that side on the wall centre, not the grid', () => { + const live = pullLeftSide(false) + // Grid alone would give 10.5; the wall at x 0.2 gives 10.8, the +x side held at 11. + expect(live?.width).toBeCloseTo(10.8) + expect(live?.position[0]).toBeCloseTo(-0.4) + }) + + test('Alt pulls the side to the raw cursor', () => { + expect(pullLeftSide(true)?.width).toBeCloseTo(10.7) + }) + + test('a side pulled onto the walls gives its gable and heel walls their thickness', () => { + expect(pullLeftSide(false, 0.1826)?.wallThickness).toBeCloseTo(0.1826) + }) +}) diff --git a/packages/nodes/src/roof-segment/floorplan-affordances.ts b/packages/nodes/src/roof-segment/floorplan-affordances.ts index 9f9ae25e69..d407cd568a 100644 --- a/packages/nodes/src/roof-segment/floorplan-affordances.ts +++ b/packages/nodes/src/roof-segment/floorplan-affordances.ts @@ -9,8 +9,21 @@ import { useLiveNodeOverrides, useScene, } from '@pascal-app/core' -import { getSegmentGridStep, isAngleSnapActive, isGridSnapActive } from '@pascal-app/editor' +import { + getSegmentGridStep, + isAngleSnapActive, + isGridSnapActive, + isMagneticSnapActive, +} from '@pascal-app/editor' import { commitSeatedRoofEdit } from '../roof/roof-level-target' +import { + bearingWallThickness, + catchRoofOnWalls, + catchRoofSideOnWalls, + placeRoofOnWalls, + roofCatchWalls, + segmentWallLine, +} from '../roof/wall-catch' import { createFloorplanCursorResolver } from '../shared/floorplan-cursor' import { rotateAffordanceDelta } from '../shared/rotate-affordance' @@ -101,6 +114,16 @@ export const roofSegmentResizeAffordance: FloorplanAffordance = const { axis, side } = resize const initialValue = axis === 'x' ? node.width : node.depth const { effRot } = resolveSegmentFrame(node, nodes) + const roofId = (node as unknown as { parentId?: AnyNodeId | null }).parentId + const roof = roofId ? (nodes[roofId] as RoofNode | undefined) : undefined + const walls = roofCatchWalls(nodes, roof?.parentId) + const line = { + center: [cx, cz] as const, + angle: effRot, + hw: node.width / 2, + hd: node.depth / 2, + wallThickness: node.wallThickness, + } const cosEff = Math.cos(effRot) const sinEff = Math.sin(effRot) // Project (planPoint - center) onto the segment's local X or Z axis @@ -113,23 +136,38 @@ export const roofSegmentResizeAffordance: FloorplanAffordance = } const initialLocal = projectLocalAxis(initialPlanPoint[0], initialPlanPoint[1]) let lastValue = initialValue + // Sized onto the walls under it, its gable and heel walls take their thickness; off them, it + // keeps its own (the live override merges, so it is always written). + const resized = (value: number): Partial => { + const patch = getRoofSegmentResizePatch(node, axis, side, value) + const thickness = bearingWallThickness( + segmentWallLine(roof ?? { position: [0, 0, 0], rotation: 0 }, { ...node, ...patch }), + walls, + ) + return { ...patch, wallThickness: thickness ?? node.wallThickness } + } return { affectedIds: [segmentId], - apply({ planPoint }) { + apply({ planPoint, modifiers }) { const currentLocal = projectLocalAxis(planPoint[0], planPoint[1]) const delta = (currentLocal - initialLocal) * side const rawValue = initialValue + delta // Mode-aware grid step (0 outside grid mode, so `lines` / `off` resize // freely — the "smooth" behaviour that used to need a held Shift). The // reshaping scope opened by the dispatcher resolves the `polygon` set. - const step = isGridSnapActive() ? getSegmentGridStep() : 0 - const snappedValue = step > 0 ? snapScalar(rawValue, step) : rawValue + // Alt holds the raw cursor, as in 3D. + const free = modifiers.altKey + const step = !free && isGridSnapActive() ? getSegmentGridStep() : 0 + let snappedValue = step > 0 ? snapScalar(rawValue, step) : rawValue + // The dragged side lands on a wall under it in grid and lines, as a move does. + if (!free && (isGridSnapActive() || isMagneticSnapActive())) { + snappedValue = + catchRoofSideOnWalls({ line, axis, side, value: rawValue, walls }) ?? snappedValue + } const newValue = Math.max(MIN_ROOF_DIM, snappedValue) lastValue = newValue - useLiveNodeOverrides - .getState() - .set(segmentId, getRoofSegmentResizePatch(node, axis, side, newValue)) + useLiveNodeOverrides.getState().set(segmentId, resized(newValue)) useScene.getState().markDirty(segmentId) }, canCommit() { @@ -137,10 +175,7 @@ export const roofSegmentResizeAffordance: FloorplanAffordance = }, commit() { useLiveNodeOverrides.getState().clear(segmentId) - commitSeatedRoofEdit( - [{ id: segmentId, data: getRoofSegmentResizePatch(node, axis, side, lastValue) }], - segmentId, - ) + commitSeatedRoofEdit([{ id: segmentId, data: resized(lastValue) }], segmentId) }, } }, @@ -198,7 +233,7 @@ export const roofSegmentRotateAffordance: FloorplanAffordance = export const roofSegmentMoveTarget: FloorplanMoveTarget = ({ node, nodes }) => { const segmentId = node.id as AnyNodeId const initialY = node.position[1] - const { cx, cz, roofRot, cosRoof, sinRoof } = resolveSegmentFrame(node, nodes) + const { cx, cz, effRot, cosRoof, sinRoof } = resolveSegmentFrame(node, nodes) const roofId = (node as unknown as { parentId?: AnyNodeId | null }).parentId const roof = roofId ? (nodes[roofId] as RoofNode | undefined) : undefined const roofPosX = roof?.position[0] ?? 0 @@ -207,27 +242,56 @@ export const roofSegmentMoveTarget: FloorplanMoveTarget = ({ no original: [cx, cz], metadata: node.metadata, }) - // Inverse of the forward transform `[cosRoof, -sinRoof; sinRoof, cosRoof]` - // is `[cosRoof, sinRoof; -sinRoof, cosRoof]`. Used to project world cursor - // back into roof-local coords. - void roofRot + const walls = roofCatchWalls(nodes, roof?.parentId) let lastLocal: [number, number, number] = [node.position[0], node.position[1], node.position[2]] + // Off them again, it keeps its own (the live override merges, so it is always written). + let lastThickness = node.wallThickness + const moved = (): Partial => ({ + position: lastLocal, + wallThickness: lastThickness, + }) return { affectedIds: [segmentId], - apply({ planPoint }) { + apply({ planPoint, modifiers }) { // Mode-aware: `getSegmentGridStep()` is 0 outside grid mode (so `lines` / // `off` move freely), and the `moving` scope resolves the `polygon` set - // via the kind's `snapProfile` — no held-Shift bypass. - const step = isGridSnapActive() ? getSegmentGridStep() : 0 + // via the kind's `snapProfile` — no held-Shift bypass. Alt holds the raw cursor, as in 3D. + const free = modifiers.altKey + const step = !free && isGridSnapActive() ? getSegmentGridStep() : 0 const snap = (value: number) => snapScalar(value, step) - const worldPoint = resolveCursor(planPoint, { snap }) + const raw = resolveCursor(planPoint) + let worldPoint = resolveCursor(planPoint, { snap }) + // Its wall line lands on the walls under it in grid and lines, its outer face on a porch's + // invisible walls or a deck's railings. + if (!free && (isGridSnapActive() || isMagneticSnapActive())) { + const caught = catchRoofOnWalls({ + lines: [ + { + center: raw, + angle: effRot, + hw: node.width / 2, + hd: node.depth / 2, + wallThickness: node.wallThickness, + }, + ], + walls, + angle: effRot, + }) + worldPoint = placeRoofOnWalls(raw, worldPoint, caught, effRot) + } const dx = worldPoint[0] - roofPosX const dz = worldPoint[1] - roofPosZ let localX = dx * cosRoof + dz * sinRoof let localZ = -dx * sinRoof + dz * cosRoof lastLocal = [localX, initialY, localZ] - useLiveNodeOverrides.getState().set(segmentId, { position: lastLocal }) + // Moved onto the walls under it, its gable and heel walls take their thickness. + lastThickness = + bearingWallThickness( + { center: worldPoint, angle: effRot, hw: node.width / 2, hd: node.depth / 2 }, + walls, + ) ?? node.wallThickness + useLiveNodeOverrides.getState().set(segmentId, moved()) useScene.getState().markDirty(segmentId) }, canCommit() { @@ -236,7 +300,7 @@ export const roofSegmentMoveTarget: FloorplanMoveTarget = ({ no commit() { useLiveNodeOverrides.getState().clear(segmentId) // Dragged onto the walls that carry it, its roof goes to the level above them. - commitSeatedRoofEdit([{ id: segmentId, data: { position: lastLocal } }], segmentId) + commitSeatedRoofEdit([{ id: segmentId, data: moved() }], segmentId) }, } } diff --git a/packages/nodes/src/roof-segment/floorplan.test.ts b/packages/nodes/src/roof-segment/floorplan.test.ts index 418b161c5d..5b737a018a 100644 --- a/packages/nodes/src/roof-segment/floorplan.test.ts +++ b/packages/nodes/src/roof-segment/floorplan.test.ts @@ -2,6 +2,7 @@ import { describe, expect, test } from 'bun:test' import { type FloorplanGeometry, type GeometryContext, + getRoofSegmentVisibleTopBounds, RoofNode, type RoofSegmentNode, } from '@pascal-app/core' @@ -215,3 +216,37 @@ describe('getRoofSegmentPlanLinework', () => { ]) }) }) + +describe("a selected roof plane's handles", () => { + const roof = RoofNode.parse({ id: 'roof_test', children: ['rseg_test'] }) + const node = dutchSegment({ parentId: roof.id, roofType: 'gable' }) + const geometry = buildRoofSegmentFloorplan(node, { + parent: roof, + viewState: { selected: true }, + } as GeometryContext) as Extract + const eave = getRoofSegmentVisibleTopBounds(node) + + test('the rotate handle sits past the eave corner, sized to a roof', () => { + const rotate = geometry.children.find((child) => child.kind === 'rotate-arrow') + expect(rotate).toMatchObject({ scale: 2 }) + const [x, z] = (rotate as Extract).point + expect(x).toBeGreaterThan(eave.maxX) + expect(z).toBeGreaterThan(eave.maxZ) + }) + + test('the resize arrows sit past the eaves and rakes', () => { + const arrows = geometry.children.filter( + (child): child is Extract => + child.kind === 'move-arrow', + ) + expect(arrows).toHaveLength(4) + for (const { point } of arrows) { + expect( + point[0] > eave.maxX || + point[0] < eave.minX || + point[1] > eave.maxZ || + point[1] < eave.minZ, + ).toBe(true) + } + }) +}) diff --git a/packages/nodes/src/roof-segment/floorplan.ts b/packages/nodes/src/roof-segment/floorplan.ts index 2034c115e0..cc9dd6eb55 100644 --- a/packages/nodes/src/roof-segment/floorplan.ts +++ b/packages/nodes/src/roof-segment/floorplan.ts @@ -4,6 +4,7 @@ import { type GeometryContext, getConicalRoofCoverage, getDutchRoofMetrics, + getRoofSegmentVisibleTopBounds, type RoofNode, type RoofSegmentNode, } from '@pascal-app/core' @@ -68,7 +69,8 @@ export function buildRoofSegmentFloorplan( const palette = view?.palette const isSelected = view?.selected ?? false const isHighlighted = view?.highlighted ?? false - const showSelectedChrome = isSelected || isHighlighted + // Moved too: the wall line shows whether it lands on the walls' centre lines. + const showSelectedChrome = isSelected || isHighlighted || (view?.moving ?? false) // Black architectural outline by default; palette accent on select. // Mirrors the elevator / column style so all structural elements read @@ -154,9 +156,8 @@ export function buildRoofSegmentFloorplan( }) const sideArrowOffset = 0.12 - const rotateCornerOffset = 0.22 + const rotateCornerOffset = 0.3 const halfW = node.width / 2 - const halfD = node.depth / 2 // Effective rotation = parent roof rotation + segment-local rotation. // Reuse `cos` / `sin` from the corner computation above (they were // computed for the same `rotation` value). @@ -175,16 +176,18 @@ export function buildRoofSegmentFloorplan( payload: { mode: 'radial' }, }) } else { + // Past the eaves and rakes the plan outlines, so the arrows sit clear of the roof's edge. + const eave = getRoofSegmentVisibleTopBounds(node) const sides: Array<{ local: [number, number] localAngle: number axis: 'x' | 'z' side: 1 | -1 }> = [ - { local: [halfW + sideArrowOffset, 0], localAngle: 0, axis: 'x', side: 1 }, - { local: [-(halfW + sideArrowOffset), 0], localAngle: Math.PI, axis: 'x', side: -1 }, - { local: [0, halfD + sideArrowOffset], localAngle: Math.PI / 2, axis: 'z', side: 1 }, - { local: [0, -(halfD + sideArrowOffset)], localAngle: -Math.PI / 2, axis: 'z', side: -1 }, + { local: [eave.maxX + sideArrowOffset, 0], localAngle: 0, axis: 'x', side: 1 }, + { local: [eave.minX - sideArrowOffset, 0], localAngle: Math.PI, axis: 'x', side: -1 }, + { local: [0, eave.maxZ + sideArrowOffset], localAngle: Math.PI / 2, axis: 'z', side: 1 }, + { local: [0, eave.minZ - sideArrowOffset], localAngle: -Math.PI / 2, axis: 'z', side: -1 }, ] for (const side of sides) { const [ox, oz] = rotateLocal(side.local[0], side.local[1]) @@ -199,11 +202,15 @@ export function buildRoofSegmentFloorplan( } } - // Rotate-arrow at the +X / +Z corner. Local angle π/4 puts the - // curved arrow's bow at the diagonal corner so it reads as a - // rotation gizmo around the segment centre. + // Rotate-arrow off the +X / +Z corner of the eaves, clear of the plan's + // outline, sized to a roof. Local angle π/4 puts the curved arrow's bow + // on the diagonal so it reads as a rotation gizmo around the centre. if (node.roofType !== 'conical') { - const [cornerX, cornerZ] = rotateLocal(halfW + rotateCornerOffset, halfD + rotateCornerOffset) + const eave = getRoofSegmentVisibleTopBounds(node) + const [cornerX, cornerZ] = rotateLocal( + eave.maxX + rotateCornerOffset, + eave.maxZ + rotateCornerOffset, + ) const [radialX, radialZ] = rotateLocal(1, 1) children.push({ kind: 'rotate-arrow', @@ -211,6 +218,7 @@ export function buildRoofSegmentFloorplan( angle: Math.atan2(radialZ, radialX), affordance: 'roof-segment-rotate', pivot: [cx, cz], + scale: 2, }) } } diff --git a/packages/nodes/src/roof-segment/multi-layers.test.ts b/packages/nodes/src/roof-segment/multi-layers.test.ts new file mode 100644 index 0000000000..c08bff77f9 --- /dev/null +++ b/packages/nodes/src/roof-segment/multi-layers.test.ts @@ -0,0 +1,110 @@ +import { afterEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + LUMBER_DEPTH, + ROOF_BUILD_UP_PRESETS, + RoofSegmentNode, + roofBuildUpDepths, + roofBuildUpPatch, + useScene, +} from '@pascal-app/core' +import { getNodePanelModel, type MultiPanelControl } from '@pascal-app/editor' +import { roofSegmentDefinition } from './definition' +import { keptName, namedLayer, roofBuildUpOfDeck, roofBuildUpPresetValue } from './panel-model' + +globalThis.requestAnimationFrame ??= () => 0 +globalThis.cancelAnimationFrame ??= () => {} + +const originalState = useScene.getState() +afterEach(() => useScene.setState(originalState)) + +type Choice = Extract + +const load = (nodes: AnyNode[]) => { + useScene.setState({ + nodes: Object.fromEntries(nodes.map((n) => [n.id, n])) as Record, + rootNodeIds: [], + }) + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() +} +const current = (nodes: T[]) => + nodes.map((n) => useScene.getState().nodes[n.id as AnyNodeId] as T) + +test('multi-selected roof planes take one layers preset, their deck and covering with it, in one step', () => { + const planes = [RoofSegmentNode.parse({}), RoofSegmentNode.parse({ deckThickness: 0.2 })] + load(planes) + const [preset] = getNodePanelModel(roofSegmentDefinition)!.multiControls!({ + selection: planes, + nodes: useScene.getState().nodes, + }) as Choice[] + expect(preset).toMatchObject({ section: 'Layers', value: 'none' }) + const chosen = ROOF_BUILD_UP_PRESETS[2]! + preset!.onChange(chosen.id) + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + const { deck, covering } = roofBuildUpDepths(chosen.buildUp) + for (const plane of current(planes)) { + expect(plane.buildUp).toEqual(chosen.buildUp) + expect([plane.deckThickness, plane.shingleThickness]).toEqual([deck, covering]) + } + preset!.onChange('none') + for (const plane of current(planes)) expect(plane.buildUp).toBeUndefined() +}) + +test("a layer's own name rides the plane: through the schema, a save and the preset it came from; emptied it goes", () => { + const preset = ROOF_BUILD_UP_PRESETS[0]!.buildUp + const named = { ...preset, roofing: namedLayer(preset.roofing, ' GAF Timberline HDZ ') } + const plane = RoofSegmentNode.parse(roofBuildUpPatch(named)) + expect(plane.buildUp?.roofing).toEqual({ ...preset.roofing, name: 'GAF Timberline HDZ' }) + // a name is no change of material: still the preset + expect(roofBuildUpPresetValue(plane.buildUp)).toBe(preset.presetId!) + expect([plane.deckThickness, plane.shingleThickness]).toEqual([ + roofBuildUpDepths(preset).deck, + roofBuildUpDepths(preset).covering, + ]) + expect(RoofSegmentNode.parse(JSON.parse(JSON.stringify(plane))).buildUp).toEqual(plane.buildUp) + // emptied, the kind's own name stands for it again + expect(namedLayer(plane.buildUp!.roofing, ' ')).toEqual(preset.roofing) + expect(namedLayer(preset.roofing, 'x'.repeat(80)).name).toHaveLength(60) + expect(() => + RoofSegmentNode.parse( + roofBuildUpPatch({ ...preset, roofing: { ...preset.roofing, name: 'x'.repeat(61) } }), + ), + ).toThrow() + // a new thickness of the same panel keeps its name; another material does not + const sheathing = namedLayer(preset.sheathing, 'AdvanTech') + expect(keptName(sheathing, { kind: 'osb' as const, thickness: 0.0127 })).toEqual({ + kind: 'osb', + thickness: 0.0127, + name: 'AdvanTech', + }) + expect(keptName(sheathing, { kind: 'plywood' as const, thickness: 0.0127 })).toEqual({ + kind: 'plywood', + thickness: 0.0127, + }) +}) + +test('a plane without layers reads as the layers its deck holds, which Edit layers writes with its deck', () => { + const preset = ROOF_BUILD_UP_PRESETS[0]!.buildUp + // the default trusses on their heel, and the schema's 0.100 m deck: one chord before the heel + // counted, which Edit layers deepens to the heel + expect(roofBuildUpOfDeck(roofBuildUpDepths(preset).deck)).toBe(preset) + expect(roofBuildUpOfDeck(RoofSegmentNode.parse({}).deckThickness)).toBe(preset) + expect(roofBuildUpDepths(preset).deck).toBeCloseTo( + 2 * LUMBER_DEPTH['2x4'] + preset.sheathing.thickness, + 9, + ) + // a generated roof's 2x6 rafters and their sheathing + const generated = LUMBER_DEPTH['2x6'] + preset.sheathing.thickness + const layers = roofBuildUpOfDeck(generated) + expect(layers.structure).toEqual({ + system: 'rafters', + size: '2x6', + spacing: preset.structure.spacing, + }) + expect(layers.presetId).toBeUndefined() + expect(roofBuildUpDepths(layers).deck).toBeCloseTo(generated, 9) + // a deck no framing makes reads as the default preset + expect(roofBuildUpOfDeck(0.25)).toBe(preset) +}) diff --git a/packages/nodes/src/roof-segment/paint.ts b/packages/nodes/src/roof-segment/paint.ts index 9044248a4a..b69a9db103 100644 --- a/packages/nodes/src/roof-segment/paint.ts +++ b/packages/nodes/src/roof-segment/paint.ts @@ -3,9 +3,12 @@ import { getEffectiveRoofSurfaceMaterial, getEffectiveSegmentSurfaceMaterial, getRoofGlazing, + getRoofWallBandMaterial, isGlazedRoofSegment, type PaintCapability, type RoofSegmentNode, + type RoofWallBand, + roofWallBandFields, sceneRegistry, useScene, } from '@pascal-app/core' @@ -52,10 +55,37 @@ const glazingPaint = createSlotPaintCapability({ }, }) +// Slots 4–6 are the wall bands split off the wall/trim slot (the viewer's `ROOF_HEEL_MATERIAL_INDEX`…). +const WALL_BAND_BY_INDEX: Record = { + 4: 'heel', + 5: 'gable-left', + 6: 'gable-right', +} +const WALL_BAND_LABELS: Record = { + heel: 'Heel band', + 'gable-left': 'Left gable', + 'gable-right': 'Right gable', +} + function standardRole(index: number | null) { + if (index !== null && WALL_BAND_BY_INDEX[index]) return WALL_BAND_BY_INDEX[index] return index === 3 ? 'top' : index === 0 ? 'edge' : index === 1 || index === 2 ? 'wall' : null } +function isWallBand(role: string): role is RoofWallBand { + return role === 'heel' || role === 'gable-left' || role === 'gable-right' +} + +/** A heel or gable paints on its own: its own finish, cleared back to its fallback by the eraser. */ +export function wallBandPatch( + band: RoofWallBand, + material: RoofSegmentNode['material'], + materialPreset: string | undefined, +): Partial { + const fields = roofWallBandFields(band) + return { [fields.material]: material, [fields.materialPreset]: materialPreset } +} + export const roofSegmentPaint: PaintCapability = { materialTarget: 'roof', resolveRole(args) { @@ -63,9 +93,15 @@ export const roofSegmentPaint: PaintCapability = { ? glazingPaint.resolveRole(args) : standardRole(args.materialIndex) }, + roleLabel(node, role) { + return isWallBand(role) && !isGlazedRoofSegment(node as RoofSegmentNode) + ? WALL_BAND_LABELS[role] + : null + }, buildPatch(args) { if (isGlazedRoofSegment(args.node as RoofSegmentNode)) return glazingPaint.buildPatch(args) const role = args.role + if (isWallBand(role)) return wallBandPatch(role, args.material, args.materialPreset) if (role !== 'top' && role !== 'edge' && role !== 'wall') return {} return buildRoofSegmentSurfaceMaterialPatch( args.node as RoofSegmentNode, @@ -93,8 +129,24 @@ export const roofSegmentPaint: PaintCapability = { const node = args.node as RoofSegmentNode if (isGlazedRoofSegment(node)) return glazingPaint.getEffectiveMaterial?.(args) ?? null const role = args.role - if (role !== 'top' && role !== 'edge' && role !== 'wall') return null const parent = node.parentId ? args.nodes?.[node.parentId as AnyNodeId] : undefined + if (isWallBand(role)) { + // Unpainted, a gable shows the edge role and the heel band the roof's (else the walls'). + const own = + getRoofWallBandMaterial(node, role) ?? + (role === 'heel' && parent?.type === 'roof' + ? getRoofWallBandMaterial(parent, 'heel') + : null) + if (own) return { material: own.material, materialPreset: own.materialPreset } + if (role === 'heel') return null + const edge = getEffectiveSegmentSurfaceMaterial( + node, + 'edge', + parent?.type === 'roof' ? getEffectiveRoofSurfaceMaterial(parent, 'edge') : undefined, + ) + return { material: edge.material, materialPreset: edge.materialPreset } + } + if (role !== 'top' && role !== 'edge' && role !== 'wall') return null const spec = getEffectiveSegmentSurfaceMaterial( node, role, diff --git a/packages/nodes/src/roof-segment/panel-model.ts b/packages/nodes/src/roof-segment/panel-model.ts new file mode 100644 index 0000000000..1dc790d130 --- /dev/null +++ b/packages/nodes/src/roof-segment/panel-model.ts @@ -0,0 +1,83 @@ +import { + type AnyNodeId, + isGlazedRoofSegment, + ROOF_BUILD_UP_PRESETS, + type RoofBuildUp, + type RoofSegmentNode, + roofBuildUpPatch, + runAsSingleSceneHistoryStep, + useScene, +} from '@pascal-app/core' +import type { NodePanelModel } from '@pascal-app/editor' + +const NONE = 'none' +const CUSTOM = 'custom' + +/** The Preset choices for the `current` value: none, the presets, and Custom while it is. */ +export function roofBuildUpPresetOptions(current: string | null) { + return [ + { label: 'None (deck and shingle)', value: NONE }, + ...(current === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ...ROOF_BUILD_UP_PRESETS.map((preset) => ({ label: preset.label, value: preset.id })), + ] +} + +export function roofBuildUpPresetValue(buildUp: RoofBuildUp | undefined): string { + return buildUp ? (buildUp.presetId ?? CUSTOM) : NONE +} + +/** The patch a Preset choice writes; `null` for Custom, which is no choice. */ +export function roofBuildUpPresetPatch(id: string): Partial | null { + if (id === NONE) return { buildUp: undefined } + const preset = ROOF_BUILD_UP_PRESETS.find((entry) => entry.id === id) + return preset ? roofBuildUpPatch(preset.buildUp) : null +} + +// The layers a plane without any is drawn as live in core, where Bones frames such a plane by them. +export { roofBuildUpOfDeck } from '@pascal-app/core' + +/** A layer with `name`, trimmed; empty takes the name off, the kind's own name standing for it. */ +export function namedLayer(layer: T, name: string): T { + const { name: _name, ...rest } = layer + const trimmed = name.trim().slice(0, 60) + return (trimmed ? { ...rest, name: trimmed } : rest) as T +} + +/** `next` keeps `previous`'s name while it is the same kind of material (a new thickness, say). */ +export function keptName( + previous: T | undefined, + next: T | undefined, +): T | undefined { + if (!next || next.name !== undefined || previous?.name === undefined) return next + return next.kind === previous.kind ? { ...next, name: previous.name } : next +} + +/** Multi-selected planes take one layers preset together, in one undo step. */ +export const roofSegmentPanelModel: NodePanelModel = { + rows: () => [], + multiControls: ({ selection }) => { + const planes = selection.filter((plane) => !isGlazedRoofSegment(plane)) + if (planes.length === 0) return [] + const values = planes.map((plane) => roofBuildUpPresetValue(plane.buildUp)) + const value = values.every((entry) => entry === values[0]) ? values[0]! : null + return [ + { + id: 'roof-segment-layers-preset', + kind: 'select', + section: 'Layers', + label: 'Preset', + options: roofBuildUpPresetOptions(value), + value, + onChange: (id) => { + const patch = roofBuildUpPresetPatch(id) + if (!patch) return + runAsSingleSceneHistoryStep(useScene, () => + useScene + .getState() + .updateNodes(planes.map((plane) => ({ id: plane.id as AnyNodeId, data: patch }))), + ) + }, + }, + ] + }, +} diff --git a/packages/nodes/src/roof-segment/panel.tsx b/packages/nodes/src/roof-segment/panel.tsx index bb521f9896..2332c4cc0f 100644 --- a/packages/nodes/src/roof-segment/panel.tsx +++ b/packages/nodes/src/roof-segment/panel.tsx @@ -14,9 +14,12 @@ import { type MessageDescriptor, normalizeRoofSegmentTrim, ROOF_SHAPE_DEFAULTS, + type RoofEaveCut, type RoofSegmentNode, type RoofSegmentTrim, type RoofType, + roofBuildUpPatch, + roofSegmentEaveProfile, roofTypeHasRakes, usePascalI18n, useScene, @@ -39,6 +42,7 @@ import { useCallback } from 'react' import { BarrelRoofPanel } from '../roof/barrel-panel' import { RoofGlazingPanel } from '../roof/glazing-panel' import { commitSeatedRoofEdit } from '../roof/roof-level-target' +import { RoofBuildUpSection } from './build-up-section' const ROOF_TYPE_OPTIONS: { label: MessageDescriptor; value: RoofType }[] = [ { label: /* i18n */ { id: 'node.roofSegment.panel.hip', message: 'Hip' }, value: 'hip' }, @@ -67,6 +71,19 @@ const ROOF_TYPE_OPTIONS_3: { label: MessageDescriptor; value: RoofType }[] = [ { label: /* i18n */ { id: 'node.roofSegment.panel.barrel', message: 'Barrel' }, value: 'barrel' }, ] +// How the rafter tails are cut at the eaves, the fascia and soffit with them (core's `roofEaveProfile`). +const EAVE_CUT_OPTIONS: { label: MessageDescriptor; value: RoofEaveCut }[] = [ + { label: /* i18n */ { id: 'node.roofSegment.panel.plumb', message: 'Plumb' }, value: 'plumb' }, + { label: /* i18n */ { id: 'node.roofSegment.panel.square', message: 'Square' }, value: 'square' }, + { + label: /* i18n */ { id: 'node.roofSegment.panel.plumbLevel', message: 'Plumb + level' }, + value: 'plumb-level', + }, +] + +/** The sloped forms whose tails are cut at their eaves by the plane's eave cut. */ +const EAVE_CUT_ROOF_TYPES: ReadonlySet = new Set(['gable', 'hip', 'shed']) + // Carpenter / roofer convention: rise over a 12" run, converted to degrees. // atan(3/12) ≈ 14.04°, atan(6/12) ≈ 26.57°, atan(9/12) ≈ 36.87°, atan(12/12) = 45°. const PITCH_PRESETS: { label: string; deg: number }[] = [ @@ -95,6 +112,17 @@ const EMPTY_TRIM: RoofSegmentTrim = { backRightZ: 0, } +/** The planes of a roof that take layers: its segments, glazed ones aside. */ +function roofLayerPlaneIds( + children: readonly string[], + nodes: Record, +): AnyNodeId[] { + return children.filter((id) => { + const child = nodes[id as AnyNodeId] + return child?.type === 'roof-segment' && !isGlazedRoofSegment(child) + }) as AnyNodeId[] +} + function hasSegmentTrim(node: RoofSegmentNode): boolean { return Object.values(normalizeRoofSegmentTrim(node)).some((value) => value > 0) } @@ -122,6 +150,7 @@ export default function RoofSegmentPanel() { const updateNode = useScene((s) => s.updateNode) const setMovingNode = useEditor((s) => s.setMovingNode) const setRoofHostDragArmedId = useEditor((s) => s.setRoofHostDragArmedId) + const unit = useViewer((s) => s.unit) const node = useScene((s) => selectedId ? (s.nodes[selectedId as AnyNode['id']] as RoofSegmentNode | undefined) : undefined, @@ -133,6 +162,14 @@ export default function RoofSegmentPanel() { if (current?.type !== 'roof-segment') return false return isAutoRidgeVentEnabled(current, s.nodes) }) + // How many planes of this roof take layers: "Apply to all planes" shows once there are two. + const roofPlaneCount = useScene((s) => { + const current = selectedId + ? (s.nodes[selectedId as AnyNode['id']] as RoofSegmentNode | undefined) + : undefined + const roof = current?.parentId ? s.nodes[current.parentId as AnyNodeId] : undefined + return roof?.type === 'roof' ? roofLayerPlaneIds(roof.children, s.nodes).length : 0 + }) const autoGutterEnabled = useScene((s) => { const current = selectedId ? (s.nodes[selectedId as AnyNode['id']] as RoofSegmentNode | undefined) @@ -153,6 +190,18 @@ export default function RoofSegmentPanel() { [selectedId], ) + const handleApplyToRoof = useCallback(() => { + const scene = useScene.getState() + const current = selectedId ? scene.nodes[selectedId as AnyNodeId] : undefined + if (current?.type !== 'roof-segment' || !current.buildUp) return + const roof = current.parentId ? scene.nodes[current.parentId as AnyNodeId] : undefined + if (roof?.type !== 'roof') return + const patch = roofBuildUpPatch(current.buildUp) + scene.updateNodes( + roofLayerPlaneIds(roof.children, scene.nodes).map((id) => ({ id, data: patch })), + ) + }, [selectedId]) + const handleRoofTypeChange = useCallback( (roofType: RoofType) => { if (isManagedLeanToRoofSegment(node?.metadata)) return @@ -679,7 +728,8 @@ export default function RoofSegmentPanel() { unit="m" value={node.wallThickness} /> - {!isGlazedRoofSegment(node) && ( + {/* With layers, the Layers section sets it and shows it. */} + {!(isGlazedRoofSegment(node) || node.buildUp) && ( )} - {!isGlazedRoofSegment(node) && ( + {!(isGlazedRoofSegment(node) || node.buildUp) && ( )} + {EAVE_CUT_ROOF_TYPES.has(node.roofType) && node.pitch > 0 && ( +

+
+ {i18n._({ id: 'node.roofSegment.panel.eaveCut', message: 'Eave cut' })} +
+ handleUpdate({ eaveCut: v })} + options={EAVE_CUT_OPTIONS.map((option) => ({ + ...option, + label: i18n._(option.label), + }))} + value={node.eaveCut ?? 'plumb'} + /> +
+ )} )}
+ {!isGlazedRoofSegment(node) && ( + 1 ? handleApplyToRoof : undefined} + onUpdate={handleUpdate} + unit={unit} + /> + )} + = { min: 0.04, max: 0.3, step: 0.01, + // Layers set it: edited here it would part from them. + visibleIf: (node) => !node.buildUp, }, { key: 'overhang', kind: 'number', unit: 'm', min: 0, max: 1, step: 0.05 }, { @@ -161,6 +163,8 @@ export const roofSegmentParametrics: ParametricDescriptor = { min: 0.02, max: 0.3, step: 0.01, + // Layers set it: edited here it would part from them. + visibleIf: (node) => !node.buildUp, }, { key: 'wallShell', diff --git a/packages/nodes/src/roof-segment/renderer.tsx b/packages/nodes/src/roof-segment/renderer.tsx index 779f20e069..d9603838c6 100644 --- a/packages/nodes/src/roof-segment/renderer.tsx +++ b/packages/nodes/src/roof-segment/renderer.tsx @@ -5,6 +5,7 @@ import { getEffectiveNode, getEffectiveRoofSurfaceMaterial, getEffectiveSegmentSurfaceMaterial, + getRoofWallBandMaterial, getSegmentFasciaMaterial, isGlazedRoofSegment, type RoofNode, @@ -18,6 +19,7 @@ import { createMaterial, getRoofMaterialArray, levelWallCladdingRef, + levelWallFaceRef, resolveMaterialRef, SHADOW_ONLY_LAYER, useNodeEvents, @@ -76,10 +78,15 @@ export const RoofSegmentRenderer = ({ node: storedNode }: { node: RoofSegmentNod // slot 1 → 'wall' (deck top & shingle eave bands) // slot 2 → 'wall' (interior) // slot 3 → 'top' (shingle / roof surface) + // slot 4 → the heel band (its own, the roof's, else the walls' below) + // slot 5 / 6 → the left / right gable (its own, else slot 0's) // The gable band is clad like the walls below it (their assemblies). const wallCladdingRef = useScene((state) => parentNode ? levelWallCladdingRef(state.nodes, parentNode) : null, ) + const wallFaceRef = useScene((state) => + parentNode ? levelWallFaceRef(state.nodes, parentNode) : null, + ) // biome-ignore lint/correctness/useExhaustiveDependencies: deps deliberately list the build inputs; depending on the whole object would rebuild on unrelated field changes. const customMaterial = useMemo(() => { const resolveSlot = (role: RoofSegmentSurfaceMaterialRole): THREE.Material | null => { @@ -104,9 +111,19 @@ export const RoofSegmentRenderer = ({ node: storedNode }: { node: RoofSegmentNod sceneTheme, wallCladdingRef, sceneMaterials, + wallFaceRef, ) : null + const resolveSpec = (spec: ReturnType) => + spec + ? (resolveMaterialRef(spec.materialPreset, sceneMaterials, shading) ?? + (spec.material !== undefined ? createMaterial(spec.material, shading) : null)) + : null + const heel = resolveSpec(getRoofWallBandMaterial(node, 'heel')) + const gableLeft = resolveSpec(getRoofWallBandMaterial(node, 'gable-left')) + const gableRight = resolveSpec(getRoofWallBandMaterial(node, 'gable-right')) + const edge = resolveSlot('edge') const wall = resolveSlot('wall') const top = resolveSlot('top') @@ -117,7 +134,7 @@ export const RoofSegmentRenderer = ({ node: storedNode }: { node: RoofSegmentNod (fasciaSpec.material !== undefined ? createMaterial(fasciaSpec.material, shading) : null)) : null - if (!(edge || wall || top || fascia)) { + if (!(edge || wall || top || fascia || heel || gableLeft || gableRight)) { return themedArray } @@ -126,11 +143,15 @@ export const RoofSegmentRenderer = ({ node: storedNode }: { node: RoofSegmentNod // Per-role only, then the themed parent slot — no cross-role fallback, so // painting one segment surface never bleeds onto its other surfaces. const slot = (i: number) => themedArray?.[i] ?? new THREE.MeshStandardMaterial() + const trim = edge ?? slot(0) return [ - edge ?? slot(0), + trim, wall ?? slot(1), fascia ?? wall ?? slot(2), top ?? slot(3), + heel ?? slot(4), + gableLeft ?? trim, + gableRight ?? trim, ] as THREE.Material[] }, [ node.material, @@ -144,6 +165,13 @@ export const RoofSegmentRenderer = ({ node: storedNode }: { node: RoofSegmentNod node.wallMaterialPreset, node.fasciaMaterial, node.fasciaMaterialPreset, + node.heelMaterial, + node.heelMaterialPreset, + node.gableLeftMaterial, + node.gableLeftMaterialPreset, + node.gableRightMaterial, + node.gableRightMaterialPreset, + wallFaceRef, parentNode, sceneMaterials, shading, diff --git a/packages/nodes/src/roof/definition.test.ts b/packages/nodes/src/roof/definition.test.ts index 3364bd07e8..1a49078d02 100644 --- a/packages/nodes/src/roof/definition.test.ts +++ b/packages/nodes/src/roof/definition.test.ts @@ -1,7 +1,16 @@ -import { describe, expect, test } from 'bun:test' -import type { HandleDescriptor, RoofNode } from '@pascal-app/core' +import { afterEach, beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + getEffectiveRoofSurfaceMaterial, + type HandleDescriptor, + type RoofNode, + type RoofSegmentNode, + RoofSegmentNode as RoofSegmentSchema, + useScene, +} from '@pascal-app/core' +import { useEditor } from '@pascal-app/editor' import { roofDefinition } from './definition' -import useRoofPlacementMode from './roof-placement-mode' function roof(overrides: Partial = {}): RoofNode { return { @@ -73,14 +82,23 @@ describe('roof tool registration', () => { const placementHint = roofDefinition.toolHints?.find((hint) => hint.key === 'P') const rotationHint = roofDefinition.toolHints?.find((hint) => hint.key === 'R') - useRoofPlacementMode.setState({ conical: false, mode: 'auto' }) - expect(placementHint?.visible?.value()).toBe(false) - expect(rotationHint?.visible?.value()).toBe(true) + const previousDefaults = useEditor.getState().toolDefaults + let changes = 0 + const unsubscribe = placementHint?.visible?.subscribe(() => changes++) + try { + useEditor.getState().setToolDefaults('roof', { roofType: 'gable' }) + expect(placementHint?.visible?.value()).toBe(false) + expect(rotationHint?.visible?.value()).toBe(true) - useRoofPlacementMode.setState({ conical: true }) - expect(placementHint?.visible?.value()).toBe(true) - expect(rotationHint?.visible?.value()).toBe(false) - useRoofPlacementMode.setState({ conical: false, mode: 'auto' }) + // Armed with a cone and no tool mounted yet, as in a 2D-only session. + useEditor.getState().setToolDefaults('roof', { roofType: 'conical' }) + expect(placementHint?.visible?.value()).toBe(true) + expect(rotationHint?.visible?.value()).toBe(false) + expect(changes).toBe(1) + } finally { + unsubscribe?.() + useEditor.setState({ toolDefaults: previousDefaults }) + } }) }) @@ -93,3 +111,82 @@ describe('roof assembly host', () => { expect(host?.body(roof())).toBeNull() }) }) + +describe('roof paint: gables on the merged shell', () => { + const paint = roofDefinition.capabilities!.paint! + const red = { preset: 'custom', properties: { color: '#aa2222' } } as const + let previousRaf: typeof requestAnimationFrame + let previousCancelRaf: typeof cancelAnimationFrame + + beforeEach(() => { + previousRaf = globalThis.requestAnimationFrame + previousCancelRaf = globalThis.cancelAnimationFrame + globalThis.requestAnimationFrame = () => 0 + globalThis.cancelAnimationFrame = () => {} + }) + + afterEach(() => { + useScene.getState().unloadScene() + useScene.temporal.getState().clear() + globalThis.requestAnimationFrame = previousRaf + globalThis.cancelAnimationFrame = previousCancelRaf + }) + + function seed() { + const left = RoofSegmentSchema.parse({ + id: 'rseg_left', + type: 'roof-segment', + parentId: 'roof_test', + roofType: 'gable', + gableLeftMaterialPreset: 'library:brick', + }) + const right = RoofSegmentSchema.parse({ + id: 'rseg_right', + type: 'roof-segment', + parentId: 'roof_test', + roofType: 'gable', + }) + const node = roof({ children: [left.id, right.id] as RoofNode['children'] }) + useScene.setState({ + nodes: Object.fromEntries( + [node, left, right].map((entry) => [entry.id, entry as AnyNode]), + ) as Record, + rootNodeIds: [node.id as AnyNodeId], + readOnly: false, + } as never) + return node + } + const segmentOf = (id: string) => useScene.getState().nodes[id as AnyNodeId] as RoofSegmentNode + + test('a gable offers itself or every gable of the roof', () => { + const scopes = paint.scopes?.(roof(), 'gable-right:rseg_right') + expect(scopes?.scopes).toEqual(['single', 'matching']) + expect(scopes?.labels).toEqual({ matching: 'All gables' }) + expect(paint.roleLabel?.(roof(), 'gable-right:rseg_right')).toBe('Right gable') + expect(paint.roleLabel?.(roof(), 'heel')).toBe('Heel band') + // the roof's own surfaces keep the derived single scope + expect(paint.scopes?.(roof(), 'edge')).toBeNull() + }) + + test('one gable paints only its segment', () => { + const node = seed() + const role = paint.scopes?.(node, 'gable-right:rseg_right')?.roleFor?.('single') + paint.commit?.({ node, role: role!, material: red, materialPreset: undefined }) + expect(segmentOf('rseg_right').gableRightMaterial).toMatchObject(red) + expect(segmentOf('rseg_left').gableLeftMaterialPreset).toBe('library:brick') + const after = useScene.getState().nodes[node.id as AnyNodeId] as RoofNode + expect(getEffectiveRoofSurfaceMaterial(after, 'edge')).toEqual( + getEffectiveRoofSurfaceMaterial(node, 'edge'), + ) + }) + + test('every gable paints the roof edge and clears each segment gable', () => { + const node = seed() + const role = paint.scopes?.(node, 'gable-right:rseg_right')?.roleFor?.('matching') + paint.commit?.({ node, role: role!, material: red, materialPreset: undefined }) + const after = useScene.getState().nodes[node.id as AnyNodeId] as RoofNode + expect(getEffectiveRoofSurfaceMaterial(after, 'edge').material).toMatchObject(red) + expect(segmentOf('rseg_left').gableLeftMaterialPreset).toBeUndefined() + expect(segmentOf('rseg_right').gableRightMaterial).toBeUndefined() + }) +}) diff --git a/packages/nodes/src/roof/definition.ts b/packages/nodes/src/roof/definition.ts index 657b5f7718..4c339490ea 100644 --- a/packages/nodes/src/roof/definition.ts +++ b/packages/nodes/src/roof/definition.ts @@ -10,16 +10,24 @@ import { import { DRAFTING_SURFACE_EXTENSION_KEY, type DraftingSurfaceExtension, + FLOORPLAN_NODE_EXTENSION_KEY, + type FloorplanNodeExtension, PANEL_MODEL_EXTENSION, } from '@pascal-app/editor' import { buildRoofFloorplan } from './floorplan' +import { roofFloorplanAffectedIds, roofFloorplanSiblingOverrides } from './floorplan-overrides' +import { roofPaint } from './paint' import { roofPanelModel } from './panel-model' import { roofParametrics } from './parametrics' -import useRoofFootprintSource from './roof-footprint-source' +import useRoofFootprintSource, { + irregularRoomHintVisibility, + noRoomHintVisibility, +} from './roof-footprint-source' import useRoofPlacementMode, { conicalRoofToolHintVisibility, standardRoofToolHintVisibility, } from './roof-placement-mode' +import useRoofRidgeMode from './roof-ridge-mode' import { RoofNode } from './schema' const MOVE_FRONT_OFFSET = 0.35 @@ -126,6 +134,9 @@ export const roofDefinition: NodeDefinition = { [DRAFTING_SURFACE_EXTENSION_KEY]: { kind: 'roof', } satisfies DraftingSurfaceExtension, + [FLOORPLAN_NODE_EXTENSION_KEY]: { + tool: () => import('./floorplan-tool'), + } satisfies FloorplanNodeExtension, }, defaults: () => { @@ -136,6 +147,7 @@ export const roofDefinition: NodeDefinition = { capabilities: { exterior: true, + paint: roofPaint, reveal: { phase: 'roof', style: 'assemble', @@ -229,9 +241,30 @@ export const roofDefinition: NodeDefinition = { }, { key: 'R', - label: 'Rotate roof direction 90°', + label: 'Ridge direction', visible: standardRoofToolHintVisibility, + chip: { + subscribe: (onChange) => useRoofRidgeMode.subscribe(onChange), + value: () => useRoofRidgeMode.getState().mode, + cycle: () => useRoofRidgeMode.getState().toggle(), + labels: { + long: 'Ridge: Long side', + short: 'Ridge: Short side', + }, + icons: { + long: 'lucide:rectangle-horizontal', + short: 'lucide:rectangle-vertical', + }, + tooltip: 'Ridge direction - click or press R to turn it 90°', + }, + }, + // Room makes nothing where there is no room to roof: say why, where a click would do nothing. + { + key: 'Room', + label: 'Not a rectangle: draw this roof instead', + visible: irregularRoomHintVisibility, }, + { key: 'Room', label: 'Point inside a room, porch or deck', visible: noRoomHintVisibility }, { key: 'Esc', label: 'Cancel' }, ], toolOptions: [ @@ -262,6 +295,8 @@ export const roofDefinition: NodeDefinition = { parametrics: roofParametrics, handles: resolveRoofHandles, floorplan: buildRoofFloorplan, + floorplanSiblingOverrides: roofFloorplanSiblingOverrides, + floorplanAffectedIds: roofFloorplanAffectedIds, rendersChildren: false, renderer: { diff --git a/packages/nodes/src/roof/floorplan-overrides.test.ts b/packages/nodes/src/roof/floorplan-overrides.test.ts new file mode 100644 index 0000000000..95c61b86ea --- /dev/null +++ b/packages/nodes/src/roof/floorplan-overrides.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + LevelNode, + RoofNode, + RoofSegmentNode, + WallNode, +} from '@pascal-app/core' +import { roofFloorplanAffectedIds, roofFloorplanSiblingOverrides } from './floorplan-overrides' + +function scene() { + const level = LevelNode.parse({ + id: 'level_roof', + type: 'level', + children: ['roof_main', 'roof_wing', 'wall_a'], + }) + const main = RoofNode.parse({ + id: 'roof_main', + type: 'roof', + parentId: level.id, + children: ['rseg_main'], + }) + const wing = RoofNode.parse({ + id: 'roof_wing', + type: 'roof', + parentId: level.id, + children: ['rseg_wing'], + }) + const mainSegment = RoofSegmentNode.parse({ + id: 'rseg_main', + type: 'roof-segment', + parentId: main.id, + }) + const wingSegment = RoofSegmentNode.parse({ + id: 'rseg_wing', + type: 'roof-segment', + parentId: wing.id, + }) + const wall = WallNode.parse({ + id: 'wall_a', + type: 'wall', + parentId: level.id, + start: [0, 0], + end: [4, 0], + }) + return Object.fromEntries( + [level, main, wing, mainSegment, wingSegment, wall].map((node) => [node.id, node]), + ) as Record +} + +const project = ( + nodes: Record, + live: { + overrides?: [string, Record][] + transforms?: [string, { position: [number, number, number]; rotation: number }][] + }, +) => + roofFloorplanSiblingOverrides({ + nodeId: 'roof_main' as AnyNodeId, + nodes, + liveOverrides: new Map(live.overrides ?? []), + liveTransforms: new Map(live.transforms ?? []), + }) + +describe('roof plan during a live edit', () => { + test("a segment's live move reaches the roof's builder snapshot", () => { + const nodes = scene() + const projected = project(nodes, { overrides: [['rseg_main', { position: [2, 0, 1] }]] }) + expect((projected['rseg_main' as AnyNodeId] as RoofSegmentNode).position).toEqual([2, 0, 1]) + expect(projected['rseg_wing' as AnyNodeId]).toBe(nodes['rseg_wing' as AnyNodeId]) + }) + + test("a roof's live transform from the 3D move tool moves it in the snapshot", () => { + const nodes = scene() + const projected = project(nodes, { + transforms: [['roof_wing', { position: [1, 0, 3], rotation: 0.5 }]], + }) + const wing = projected['roof_wing' as AnyNodeId] as RoofNode + expect(wing.position).toEqual([1, 0, 3]) + expect(wing.rotation).toBe(0.5) + }) + + test('nothing live, or only other kinds live, leaves the scene as it is', () => { + const nodes = scene() + expect(project(nodes, {})).toBe(nodes) + expect(project(nodes, { overrides: [['wall_a', { start: [1, 1] }]] })).toBe(nodes) + }) + + test("a segment's edit rebuilds its roof and the level's other roofs", () => { + const nodes = scene() + const affected = roofFloorplanAffectedIds({ node: nodes['rseg_wing' as AnyNodeId]!, nodes }) + expect([...affected].sort()).toEqual(['roof_main', 'roof_wing']) + expect(roofFloorplanAffectedIds({ node: nodes['wall_a' as AnyNodeId]!, nodes })).toEqual([]) + }) +}) diff --git a/packages/nodes/src/roof/floorplan-overrides.ts b/packages/nodes/src/roof/floorplan-overrides.ts new file mode 100644 index 0000000000..478a82e9b0 --- /dev/null +++ b/packages/nodes/src/roof/floorplan-overrides.ts @@ -0,0 +1,57 @@ +import type { AnyNode, AnyNodeId, LiveTransformLike } from '@pascal-app/core' + +const isRoofPart = (node: AnyNode | undefined) => + node?.type === 'roof' || node?.type === 'roof-segment' + +/** + * A roof's plan is drawn by the roof node from its segments (`ctx.children`) and its level's + * other roofs (`ctx.siblings`), which a drag moves through live overrides and transforms, not the + * scene. Projecting those into the builder's snapshot keeps the outline, ridges, hips and valleys + * on the segment while it is moved, resized or turned, in 2D or 3D. + */ +export function roofFloorplanSiblingOverrides(args: { + nodeId: AnyNodeId + nodes: Record + liveTransforms: Map + liveOverrides: Map> +}): Record { + const { nodes, liveOverrides, liveTransforms } = args + if (liveOverrides.size === 0 && liveTransforms.size === 0) return nodes + + let out: Record | null = null + for (const [id, live] of liveTransforms) { + const existing = nodes[id as AnyNodeId] + if (!isRoofPart(existing)) continue + out ??= { ...nodes } + out[id as AnyNodeId] = { + ...existing, + position: live.position, + rotation: live.rotation, + } as AnyNode + } + for (const [id, override] of liveOverrides) { + const existing = out?.[id as AnyNodeId] ?? nodes[id as AnyNodeId] + if (!isRoofPart(existing) || Object.keys(override).length === 0) continue + out ??= { ...nodes } + out[id as AnyNodeId] = { ...existing, ...override } as AnyNode + } + return out ?? nodes +} + +/** + * The roofs whose plan a live roof or segment edit changes: every roof on its level, since a + * wing's ridge runs on to the main roof's slope and their valleys are drawn by one of the two. + */ +export function roofFloorplanAffectedIds(args: { + node: AnyNode + nodes: Record +}): readonly AnyNodeId[] { + const { node, nodes } = args + const roof = + node.type === 'roof-segment' && node.parentId ? nodes[node.parentId as AnyNodeId] : node + if (roof?.type !== 'roof') return [] + const level = roof.parentId ? nodes[roof.parentId as AnyNodeId] : undefined + const onLevel = (level as { children?: AnyNodeId[] } | undefined)?.children ?? [] + const roofs = onLevel.filter((id) => id !== roof.id && nodes[id]?.type === 'roof') + return [roof.id as AnyNodeId, ...roofs] +} diff --git a/packages/nodes/src/roof/floorplan-tool.test.ts b/packages/nodes/src/roof/floorplan-tool.test.ts new file mode 100644 index 0000000000..acb89d20b5 --- /dev/null +++ b/packages/nodes/src/roof/floorplan-tool.test.ts @@ -0,0 +1,516 @@ +import { afterEach, beforeEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearSceneHistory, + createSceneApi, + emitter, + type GridEvent, + getLevelElevations, + getWallCurveFrameAt, + LevelNode, + type RoofNode, + type RoofSegmentNode, + SeparatorNode, + useScene, + WallNode, + ZoneNode, +} from '@pascal-app/core' +import { + type FloorplanToolContext, + sceneLayout, + useEditor, + useFloorplanDraftPreview, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import RoofFloorplanTool from './floorplan-tool' +import useRoofFootprintSource from './roof-footprint-source' +import { segmentWallLine } from './wall-catch' + +type RafFn = (cb: (time: number) => void) => number +;(globalThis as unknown as { requestAnimationFrame?: RafFn }).requestAnimationFrame ??= (cb) => { + cb(0) + return 0 +} +;(globalThis as unknown as { cancelAnimationFrame?: (id: number) => void }).cancelAnimationFrame ??= + () => {} +;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true + +const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } +const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window') + +beforeEach(() => { + globalThis.window = new EventTarget() as Window & typeof globalThis +}) + +afterEach(() => { + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) + useRoofFootprintSource.setState({ source: 'draw', roomPick: null }) + if (windowDescriptor) Object.defineProperty(globalThis, 'window', windowDescriptor) + else Reflect.deleteProperty(globalThis, 'window') +}) + +const RECTANGLE: Array<[number, number]> = [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], +] + +function setScene( + polygon: Array<[number, number]>, + { upperLevel = true, rotation = 0 }: { upperLevel?: boolean; rotation?: number } = {}, +) { + const building = BuildingNode.parse({ rotation: [0, rotation, 0] }) + const level = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 3 }) + building.children = upperLevel ? [level.id, upper.id] : [level.id] + const walls = polygon.map((start, index) => + WallNode.parse({ + parentId: level.id, + start, + end: polygon[(index + 1) % polygon.length], + height: 2.5, + }), + ) + level.children = walls.map((wall) => wall.id) + const nodes = Object.fromEntries( + [building, level, ...(upperLevel ? [upper] : []), ...walls].map((n) => [n.id, n]), + ) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: level.id }) + useEditor.setState({ phase: 'structure', mode: 'build', tool: 'roof', workspaceMode: 'edit' }) + return { level, upper } +} + +const roofs = () => Object.values(useScene.getState().nodes).filter((n) => n.type === 'roof') + +const showViews = (scene: '2d' | 'split') => + act(async () => useEditor.setState({ viewLayouts: { edit: sceneLayout(scene) } })) + +async function mountTool(levelId: LevelNode['id']) { + const context: FloorplanToolContext = { + sceneApi: createSceneApi(useScene), + activeLevelId: levelId, + unit: 'metric', + metricNotation: 'meters', + gridSnapStep: 0.5, + toolDefaults: null, + selectNode: (id) => useViewer.getState().setSelection({ selectedIds: [id] }), + finishTool: () => {}, + } + return create(createElement(RoofFloorplanTool, context)) +} + +const gridEvent = (x: number, z: number) => + ({ + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: {} as GridEvent['nativeEvent'], + }) satisfies GridEvent + +const move = (x: number, z: number) => act(async () => emitter.emit('grid:move', gridEvent(x, z))) + +const click = (x: number, z: number) => + act(async () => { + emitter.emit('grid:move', gridEvent(x, z)) + emitter.emit('grid:click', gridEvent(x, z)) + }) + +// The 3D roof tool lives in the canvas, which never renders while its pane is hidden: in 2D-only +// view a roof drawn on the plan was never written. In split view the 3D tool stays its one owner. +test('a roof drawn on the plan in 2D-only view is made; split view leaves it to the 3D tool', async () => { + const { level } = setScene(RECTANGLE) + await showViews('2d') + const renderer = await mountTool(level.id) + try { + await click(0, 0) + expect(roofs()).toHaveLength(0) + await click(4, 3) + expect(roofs()).toHaveLength(1) + expect(useViewer.getState().selection.selectedIds).toEqual([roofs()[0]!.id]) + + await act(async () => useViewer.getState().setSelection({ selectedIds: [] })) + await showViews('split') + await click(6, 0) + await click(9, 3) + expect(roofs()).toHaveLength(1) + } finally { + await act(async () => renderer.unmount()) + } +}) + +test('Room roofs the room clicked, and says why an L-shaped one makes nothing', async () => { + await showViews('2d') + useRoofFootprintSource.setState({ source: 'room' }) + const rectangle = await mountTool(setScene(RECTANGLE).level.id) + try { + await click(2, 1) + expect(roofs()).toHaveLength(1) + expect(useRoofFootprintSource.getState().roomPick).toBe('room') + await click(9, 9) + expect(useRoofFootprintSource.getState().roomPick).toBe('none') + } finally { + await act(async () => rectangle.unmount()) + } + + const ell = await mountTool( + setScene([ + [0, 0], + [6, 0], + [6, 2], + [2, 2], + [2, 6], + [0, 6], + ]).level.id, + ) + try { + await click(1, 1) + expect(roofs()).toHaveLength(0) + expect(useRoofFootprintSource.getState().roomPick).toBe('irregular') + } finally { + await act(async () => ell.unmount()) + } + expect(useRoofFootprintSource.getState().roomPick).toBeNull() +}) + +// Switching to 2D-only with a rectangle open in 3D left the plan's draft with a first corner this +// tool never had: every click then started one and ended the other, a roof each second click. +test('a rectangle left open by the 3D tool is dropped when the plan takes over', async () => { + const { level } = setScene(RECTANGLE) + await showViews('2d') + useFloorplanDraftPreview.getState().setRoofDraftStart([1, 1]) + useFloorplanDraftPreview.getState().setRoofDraftEnd([2, 2]) + const renderer = await mountTool(level.id) + try { + expect(useFloorplanDraftPreview.getState().roofDraftStart).toBeNull() + expect(useFloorplanDraftPreview.getState().roofDraftEnd).toBeNull() + } finally { + await act(async () => renderer.unmount()) + } +}) + +// Split view's 3D tool snaps the drafted corners onto the walls below; the plan tool took the +// plan's point as it came, so on the Roof level the same clicks made a roof a few cm off the house. +test('a roof drawn on the level over the walls snaps to their corners, as in split view', async () => { + const { upper } = setScene(RECTANGLE) + await showViews('2d') + const renderer = await mountTool(upper.id) + try { + await click(0.03, 0.02) + await click(3.97, 2.98) + const roof = roofs()[0] as RoofNode + expect(roof.position[0]).toBeCloseTo(2) + expect(roof.position[2]).toBeCloseTo(1.5) + const segment = useScene.getState().nodes[roof.children[0]!] as RoofSegmentNode + expect([segment.width, segment.depth].sort()).toEqual([3, 4]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// The plan drew its draft from its own grid point while the roof committed at the corner the tool +// snapped: the dashed rectangle must be the roof the next click makes. +test('the plan’s draft rectangle runs between the corners the roof commits at', async () => { + const { upper } = setScene(RECTANGLE) + await showViews('2d') + const renderer = await mountTool(upper.id) + const draft = () => { + const { roofDraftStart, roofDraftEnd } = useFloorplanDraftPreview.getState() + return { roofDraftStart, roofDraftEnd } + } + try { + await click(0.03, 0.02) + expect(draft()).toEqual({ roofDraftStart: [0, 0], roofDraftEnd: [0, 0] }) + await move(3.97, 2.98) + expect(draft()).toEqual({ roofDraftStart: [0, 0], roofDraftEnd: [4, 3] }) + await click(3.97, 2.98) + expect(roofs()[0]!.position[0]).toBeCloseTo(2) + expect(draft()).toEqual({ roofDraftStart: null, roofDraftEnd: null }) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// The plan clears its draft when Room is picked, but the tool kept its first corner: back on Draw, +// the next click made a roof from a corner nothing showed. +test('a first corner is dropped when the footprint source changes', async () => { + const { level } = setScene(RECTANGLE) + await showViews('2d') + const renderer = await mountTool(level.id) + try { + await click(0, 0) + await act(async () => useRoofFootprintSource.setState({ source: 'room' })) + await act(async () => useRoofFootprintSource.setState({ source: 'draw' })) + await click(6, 5) + expect(roofs()).toHaveLength(0) + expect(useFloorplanDraftPreview.getState().roofDraftStart).toEqual([6, 5]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// Reported on a turned building: the roof came out thin and the Roof level at grade. Drawn on the +// plan over the top storey's walls, it is the rectangle drawn, on a Roof level made at the +// storey's top, its eave on the wall tops. +test('on a turned building, a roof drawn over the top storey lands on a new Roof level', async () => { + const { level } = setScene(RECTANGLE, { upperLevel: false, rotation: Math.PI / 5 }) + await showViews('2d') + const renderer = await mountTool(level.id) + try { + await click(0, 0) + await click(4, 3) + const nodes = useScene.getState().nodes + const roof = roofs()[0] as RoofNode + const roofLevel = nodes[roof.parentId as AnyNodeId] as LevelNode + expect(roofLevel).toMatchObject({ name: 'Roof', level: 1, metadata: { role: 'roof' } }) + expect(getLevelElevations(nodes).get(roofLevel.id)?.baseY).toBeCloseTo(3) + // 2.5 m walls under a 3 m storey: the eave 0.5 m below the roof level's floor. + expect(roof).toMatchObject({ support: { kind: 'walls' }, rotation: 0 }) + expect(roof.position[1]).toBeCloseTo(-0.5) + const segment = nodes[roof.children[0]!] as RoofSegmentNode + expect([segment.width, segment.depth].sort()).toEqual([3, 4]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// A cone builds over a curved wall, and the plan raises no wall events for one: in a 2D-only +// session the clicks made nothing. +test('a cone is built over the curved wall clicked on the plan in 2D-only view', async () => { + const { level } = setScene(RECTANGLE) + const curved = WallNode.parse({ + parentId: level.id, + start: [8, 0], + end: [12, 0], + curveOffset: 1, + height: 2.5, + }) + useScene.getState().createNode(curved, level.id) + clearSceneHistory() + useEditor.getState().setToolDefaults('roof', { roofType: 'conical' }) + await showViews('2d') + const renderer = await mountTool(level.id) + try { + await click(2, 1) + expect(roofs()).toHaveLength(0) + + const onArc = getWallCurveFrameAt(curved, 0.5).point + await move(onArc.x, onArc.y + 0.1) + expect(useViewer.getState().previewSelectedIds).toEqual([curved.id]) + await click(onArc.x, onArc.y + 0.1) + expect(roofs().map((roof) => roof.metadata)).toEqual([ + expect.objectContaining({ conicalSourceWallId: curved.id }), + ]) + expect(useViewer.getState().previewSelectedIds).toEqual([]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +const RAIL = 3.5 * 0.0254 +const H = RAIL / 2 + +// A porch: a 10 x 8 m house of 0.2 m walls, a porch off its south wall (z 0) +// closed by invisible walls at x 2.2 and 7.8 and z -2.4, or a deck fenced there by railings. +function setPorchScene(open: 'separators' | 'railings') { + const building = BuildingNode.parse({}) + const level = LevelNode.parse({ parentId: building.id, level: 0, height: 3 }) + const upper = LevelNode.parse({ parentId: building.id, level: 1, height: 3 }) + building.children = [level.id, upper.id] + const corners: Array<[number, number]> = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], + ] + const house = corners.map((start, index) => + WallNode.parse({ + parentId: level.id, + start, + end: corners[(index + 1) % 4], + height: 2.5, + thickness: 0.2, + }), + ) + const sides: Array<[[number, number], [number, number]]> = [ + [ + [2.2, 0], + [2.2, -2.4], + ], + [ + [2.2, -2.4], + [7.8, -2.4], + ], + [ + [7.8, -2.4], + [7.8, 0], + ], + ] + const edges = sides.map(([start, end]) => + open === 'separators' + ? SeparatorNode.parse({ parentId: level.id, start, end }) + : WallNode.parse({ + parentId: level.id, + start, + end, + wallType: 'railing', + thickness: RAIL, + height: 36 * 0.0254, + }), + ) + const rooms = [ + ZoneNode.parse({ parentId: level.id, name: 'Living', polygon: corners }), + ZoneNode.parse({ + parentId: level.id, + name: open === 'separators' ? 'Porch' : 'Deck', + roomType: open === 'separators' ? 'porch' : 'deck', + polygon: [ + [2.2, 0], + [2.2, -2.4], + [7.8, -2.4], + [7.8, 0], + ], + }), + ] + level.children = [...house, ...edges, ...rooms].map((node) => node.id) as LevelNode['children'] + const nodes = Object.fromEntries( + [building, level, upper, ...house, ...edges, ...rooms].map((node) => [node.id, node]), + ) + useScene.getState().setScene(nodes as Record, [building.id as AnyNodeId], { + installedPlugins: [], + hasExplicitPluginInstallState: false, + }) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: upper.id }) + useEditor.setState({ + phase: 'structure', + mode: 'build', + tool: 'roof', + workspaceMode: 'edit', + snappingModeByContext: { ...useEditor.getState().snappingModeByContext, polygon: 'lines' }, + }) + return { level, upper, edges } +} + +/** A roof's one segment's wall line in the plan: its x and z extents. */ +function wallLineExtents(roof: RoofNode) { + const segment = useScene.getState().nodes[roof.children[0]!] as RoofSegmentNode + const line = segmentWallLine(roof, segment) + const along = Math.abs(Math.cos(line.angle)) > 0.5 + const [hx, hz] = along ? [line.hw, line.hd] : [line.hd, line.hw] + return { + x: [line.center[0] - hx, line.center[0] + hx], + z: [line.center[1] - hz, line.center[1] + hz], + wallThickness: segment.wallThickness, + } +} + +const expectExtents = ( + actual: ReturnType, + x: [number, number], + z: [number, number], +) => { + for (const [i, value] of [...actual.x, ...actual.z].entries()) + expect(value).toBeCloseTo([...x, ...z][i]!, 6) +} + +// A roof drawn over a porch from the Roof level: before, its corners caught the house's +// walls below but not the porch's invisible walls, and the roof's walls stood out past them. +test("a roof drawn over a porch snaps to its invisible walls, its outer faces on the porch's edge", async () => { + const { upper } = setPorchScene('separators') + await showViews('2d') + const renderer = await mountTool(upper.id) + try { + await click(2.23, -2.37) + await click(7.77, 0.03) + const roof = roofs()[0] as RoofNode + expect(roof.parentId).toBe(upper.id) + // The house wall's centre line at z 0; 0.2 m walls, their outer faces on x 2.2, 7.8, z -2.4. + const extents = wallLineExtents(roof) + expect(extents.wallThickness).toBeCloseTo(0.2, 6) + expectExtents(extents, [2.3, 7.7], [-2.3, 0]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +// The owner's porch clause still holds: drawn on the porch's own storey, the roof stays there on +// its posts rather than going up to the Roof level, its outer faces on the porch's edge. +test("drawn on the porch's own storey, the roof stays on it, flush with the porch", async () => { + const { level } = setPorchScene('separators') + await showViews('2d') + const renderer = await mountTool(level.id) + try { + await click(2.23, -2.37) + await click(7.77, 0.03) + const roof = roofs()[0] as RoofNode + expect(roof.parentId).toBe(level.id) + expect(roof.support).toEqual({ kind: 'level' }) + expectExtents(wallLineExtents(roof), [2.3, 7.7], [-2.3, 0]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +test("a roof drawn over a deck snaps to its railings' outside faces and corners", async () => { + const { upper } = setPorchScene('railings') + await showViews('2d') + const renderer = await mountTool(upper.id) + try { + await click(2.2 - H + 0.03, -2.4 - H + 0.03) + await click(7.8 + H - 0.03, 0.03) + const extents = wallLineExtents(roofs()[0] as RoofNode) + expect(extents.wallThickness).toBeCloseTo(0.2, 6) + expectExtents(extents, [2.3 - H, 7.7 + H], [-2.3 - H, 0]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +test("Room over a porch roofs it to its outside edge, on the house wall's top", async () => { + const { upper, edges } = setPorchScene('separators') + await showViews('2d') + useRoofFootprintSource.setState({ source: 'room' }) + const renderer = await mountTool(upper.id) + try { + await move(5, -1) + expect(useRoofFootprintSource.getState().roomPick).toBe('room') + expect(useViewer.getState().previewSelectedIds).toEqual( + expect.arrayContaining(edges.map((edge) => edge.id)), + ) + await click(5, -1) + const roof = roofs()[0] as RoofNode + expect(roof.parentId).toBe(upper.id) + // 2.5 m walls under a 3 m storey: the eave on the house wall's top, 0.5 m below the level. + expect(roof.position[1]).toBeCloseTo(-0.5, 6) + expectExtents(wallLineExtents(roof), [2.3, 7.7], [-2.3, 0]) + } finally { + await act(async () => renderer.unmount()) + } +}) + +test("Room over a deck roofs it to its railings' outside faces, never on their tops", async () => { + const { upper } = setPorchScene('railings') + await showViews('2d') + useRoofFootprintSource.setState({ source: 'room' }) + const renderer = await mountTool(upper.id) + try { + await click(5, -1) + const roof = roofs()[0] as RoofNode + expect(roof.position[1]).toBeCloseTo(-0.5, 6) + const extents = wallLineExtents(roof) + expect(extents.wallThickness).toBeCloseTo(0.2, 6) + expectExtents(extents, [2.3 - H, 7.7 + H], [-2.3 - H, 0]) + } finally { + await act(async () => renderer.unmount()) + } +}) diff --git a/packages/nodes/src/roof/floorplan-tool.tsx b/packages/nodes/src/roof/floorplan-tool.tsx new file mode 100644 index 0000000000..bd6a8017dd --- /dev/null +++ b/packages/nodes/src/roof/floorplan-tool.tsx @@ -0,0 +1,226 @@ +'use client' + +import { + type AnyNodeId, + emitter, + type GridEvent, + type LevelNode, + RoofType as RoofTypeSchema, +} from '@pascal-app/core' +import { + clearSurfacePlanSnapFeedback, + type FloorplanToolContext, + markToolCancelConsumed, + triggerSFX, + useEditor, + useFloorplanDraftPreview, + visibleScene, +} from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { useEffect } from 'react' +import { createConicalRoofSectorAboveWall } from './conical-roof' +import { + parseRoofFootprintSource, + pickConicalRoofWall, + pickRoofRoom, + roofFootprintPreviewIds, +} from './roof-footprint' +import useRoofFootprintSource from './roof-footprint-source' +import useRoofPlacementMode from './roof-placement-mode' +import useRoofRidgeMode, { ridgeQuarterTurn } from './roof-ridge-mode' +import { + collectRoofAlignmentAnchors, + commitRoofFootprint, + commitRoofPlacement, + resolveRoofDraftPoint, +} from './tool' + +/** + * The roof tool in 2D-only view. The 3D `RoofTool` lives in the canvas, which never renders while + * its pane is hidden, so in 2D-only view this commits instead, from the plan's own grid events: it + * snaps the corners, writes them into the plan's draft rectangle (or hovers the room, or for a cone + * the curved wall) and writes the roof. Split and 3D views keep the 3D tool as the one owner. + */ +export default function RoofFloorplanTool({ + activeLevelId, + sceneApi, + selectNode, +}: FloorplanToolContext) { + const planOwnsInput = useEditor((state) => visibleScene(state) === '2d') + const levelId = planOwnsInput ? (activeLevelId as LevelNode['id'] | null) : null + const roofType = useEditor((state) => { + const parsed = RoofTypeSchema.safeParse(state.toolDefaults.roof?.roofType) + return parsed.success ? parsed.data : 'gable' + }) + // A new source or roof type starts over, as the 3D tool does: a first corner kept across it + // would end the next rectangle with no draft on the plan to show it. + const source = useRoofFootprintSource((state) => parseRoofFootprintSource(state.source, roofType)) + + useEffect(() => { + if (!levelId) return + let corner1: [number, number, number] | null = null + const draft = useFloorplanDraftPreview.getState + // A rectangle the 3D tool opened before the view changed has no first corner here: drop it, or + // the plan's draft and this tool's corner fall out of step (each click starts one, ends the other). + const clearDraft = () => { + draft().setRoofDraftStart(null) + draft().setRoofDraftEnd(null) + draft().setRoofDraftQuarterTurn(false) + } + clearDraft() + // Snapped as the 3D tool snaps in split view (onto the walls below too), so the same clicks + // make the same roof in either view. + let alignmentCandidates = collectRoofAlignmentAnchors(sceneApi.nodes(), levelId, roofType) + const planPoint = (event: GridEvent) => + resolveRoofDraftPoint(event, levelId, sceneApi.nodes(), roofType, alignmentCandidates) + let previewedId: string | null = null + const preview = (target: { id: string; ids: string[] } | null) => { + if ((target?.id ?? null) === previewedId) return + previewedId = target?.id ?? null + useViewer.getState().setPreviewSelectedIds(target?.ids ?? []) + } + const conicalWall = (event: GridEvent) => + pickConicalRoofWall(levelId, sceneApi.nodes(), [ + event.localPosition[0], + event.localPosition[2], + ]) + + const onMove = (event: GridEvent) => { + if (source === 'walls') { + const wall = conicalWall(event) + preview(wall && { id: wall.id, ids: [wall.id] }) + return + } + if (source === 'room') { + const room = pickRoofRoom(levelId, sceneApi.nodes(), planPoint(event)) + useRoofFootprintSource.getState().setRoomPick(room.pick) + preview(room.target && { id: room.target.id, ids: roofFootprintPreviewIds(room.target) }) + return + } + const [x, z] = planPoint(event) + if (!corner1) return + draft().setRoofDraftEnd([x, z]) + draft().setRoofDraftQuarterTurn( + ridgeQuarterTurn( + Math.abs(x - corner1[0]), + Math.abs(z - corner1[2]), + useRoofRidgeMode.getState().mode, + ), + ) + } + + const onClick = (event: GridEvent) => { + if (source === 'walls') { + const wall = conicalWall(event) + if (!wall) return + preview(null) + const segmentId = createConicalRoofSectorAboveWall( + wall, + sceneApi.nodes(), + sceneApi, + levelId, + ) + if (segmentId) selectNode(segmentId) + return + } + if (source === 'room') { + const { target, pick } = pickRoofRoom(levelId, sceneApi.nodes(), planPoint(event)) + useRoofFootprintSource.getState().setRoomPick(pick) + if (!target) return + const roofId = commitRoofFootprint( + sceneApi, + levelId, + target, + useRoofRidgeMode.getState().mode, + ) + if (roofId) selectNode(roofId as AnyNodeId) + return + } + const [x, z] = planPoint(event) + const corner: [number, number, number] = [x, event.localPosition[1], z] + if (!corner1) { + corner1 = corner + draft().setRoofDraftStart([x, z]) + draft().setRoofDraftEnd([x, z]) + triggerSFX('sfx:structure-build-start') + return + } + const roofId = commitRoofPlacement( + sceneApi, + levelId, + corner1, + corner, + useViewer.getState().selection.selectedIds, + useRoofRidgeMode.getState().mode, + useRoofPlacementMode.getState().mode, + ) + corner1 = null + clearDraft() + alignmentCandidates = collectRoofAlignmentAnchors(sceneApi.nodes(), levelId, roofType) + clearSurfacePlanSnapFeedback() + if (roofId) selectNode(roofId as AnyNodeId) + } + + const onCancel = () => { + if (!corner1) return + markToolCancelConsumed() + corner1 = null + clearDraft() + } + + const onKeyDown = (event: KeyboardEvent) => { + const target = event.target + if ( + target instanceof HTMLInputElement || + target instanceof HTMLTextAreaElement || + (target instanceof HTMLElement && target.isContentEditable) + ) + return + if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return + const key = event.key.toLowerCase() + // As in 3D: P cycles a cone's placement surface, R turns a drawn roof's ridge. + if (source === 'walls') { + if (key !== 'p') return + event.preventDefault() + useRoofPlacementMode.getState().cycleMode() + triggerSFX('sfx:grid-snap') + return + } + if (key !== 'r') return + event.preventDefault() + useRoofRidgeMode.getState().toggle() + triggerSFX('sfx:item-rotate') + } + // The HUD chip flips the ridge too; the draft's ridge line follows either way. + const unsubscribeRidgeMode = useRoofRidgeMode.subscribe(() => { + const start = draft().roofDraftStart + const end = draft().roofDraftEnd + if (!(corner1 && start && end)) return + draft().setRoofDraftQuarterTurn( + ridgeQuarterTurn( + Math.abs(end[0] - start[0]), + Math.abs(end[1] - start[1]), + useRoofRidgeMode.getState().mode, + ), + ) + }) + + emitter.on('grid:move', onMove) + emitter.on('grid:click', onClick) + emitter.on('tool:cancel', onCancel) + window.addEventListener('keydown', onKeyDown) + return () => { + emitter.off('grid:move', onMove) + emitter.off('grid:click', onClick) + emitter.off('tool:cancel', onCancel) + window.removeEventListener('keydown', onKeyDown) + unsubscribeRidgeMode() + useRoofFootprintSource.getState().setRoomPick(null) + preview(null) + clearDraft() + clearSurfacePlanSnapFeedback() + } + }, [levelId, roofType, sceneApi, selectNode, source]) + + return null +} diff --git a/packages/nodes/src/roof/floorplan.test.ts b/packages/nodes/src/roof/floorplan.test.ts index 6e79655063..9acd0e4045 100644 --- a/packages/nodes/src/roof/floorplan.test.ts +++ b/packages/nodes/src/roof/floorplan.test.ts @@ -4,12 +4,19 @@ import { type AnyNodeId, type FloorplanGeometry, type GeometryContext, + getRoofSegmentVisibleTopBounds, RoofNode, RoofSegmentNode, } from '@pascal-app/core' import { createFloorplanContextExtensions } from '@pascal-app/editor' import { buildRoofFloorplan } from './floorplan' +/** + * No overhang, wall or shingle reach: the eave sits on the wall line, so a test about clipping or + * creases reads in round numbers. Otherwise the editor plan runs out to the eave. + */ +const FLUSH = { overhang: 0, wallThickness: 0, shingleThickness: 0 } + function buildContext( node: ReturnType, children: AnyNode[], @@ -90,6 +97,7 @@ describe('buildRoofFloorplan roof intersections', () => { roofType: 'mansard', width: 10, depth: 8, + ...FLUSH, }) const enteringSegment = RoofSegmentNode.parse({ id: 'rseg_entering', @@ -98,6 +106,7 @@ describe('buildRoofFloorplan roof intersections', () => { roofType: 'gable', width: 8, depth: 4, + ...FLUSH, }) const nodes = { [hostRoof.id]: hostRoof, @@ -175,6 +184,167 @@ describe('buildRoofFloorplan roof intersections', () => { }) }) +/** Every straight line the plan draws, as rounded endpoint pairs in either order. */ +function lineKey(from: [number, number], to: [number, number]): string { + const key = (point: [number, number]) => + point.map((value) => (Math.abs(value) < 0.005 ? 0 : value).toFixed(2)).join(',') + const a = key(from) + const b = key(to) + return a < b ? `${a} ${b}` : `${b} ${a}` +} + +function planLines(geometry: FloorplanGeometry | null): string[] { + return flat(geometry).flatMap((child) => + child.kind === 'line' ? [lineKey([child.x1, child.y1], [child.x2, child.y2])] : [], + ) +} + +describe('roof plan creases from the roof surface', () => { + // A 10 × 8 gable, ridge along x, and a 4-wide wing roof running into it from +z, + // its ridge along z: both on the same plate at the same pitch. + function wingedHouse() { + const main = RoofNode.parse({ id: 'roof_main', type: 'roof', children: ['rseg_main'] }) + const wing = RoofNode.parse({ + id: 'roof_wing', + type: 'roof', + position: [0, 0, 5], + children: ['rseg_wing'], + }) + const mainSegment = RoofSegmentNode.parse({ + id: 'rseg_main', + type: 'roof-segment', + parentId: main.id, + roofType: 'gable', + width: 10, + depth: 8, + pitch: 30, + wallHeight: 0, + + ...FLUSH, + }) + const wingSegment = RoofSegmentNode.parse({ + id: 'rseg_wing', + type: 'roof-segment', + parentId: wing.id, + roofType: 'gable', + width: 10, + depth: 4, + pitch: 30, + wallHeight: 0, + rotation: Math.PI / 2, + + ...FLUSH, + }) + const nodes = { + [main.id]: main, + [wing.id]: wing, + [mainSegment.id]: mainSegment, + [wingSegment.id]: wingSegment, + } + return { + main: buildRoofFloorplan(main, buildContext(main, [mainSegment], [wing], nodes)), + wing: buildRoofFloorplan(wing, buildContext(wing, [wingSegment], [main], nodes)), + } + } + + test("a wing's ridge runs on past the main roof's wall line to where it meets the main slope", () => { + const { wing } = wingedHouse() + expect(planLines(wing)).toContain(lineKey([0, 10], [0, 2])) + }) + + test('the valleys run from where the eaves cross up to the ridge, drawn once', () => { + const { main, wing } = wingedHouse() + expect(planLines(main)).toEqual( + expect.arrayContaining([ + lineKey([-5, 0], [5, 0]), + lineKey([0, 2], [2, 4]), + lineKey([0, 2], [-2, 4]), + ]), + ) + expect(planLines(wing)).not.toContain(lineKey([0, 2], [2, 4])) + }) + + test('a hip draws its ridge and four hips', () => { + const roof = RoofNode.parse({ id: 'roof_h', type: 'roof', children: ['rseg_h'] }) + const segment = RoofSegmentNode.parse({ + id: 'rseg_h', + type: 'roof-segment', + parentId: roof.id, + roofType: 'hip', + width: 10, + depth: 6, + ...FLUSH, + }) + const nodes = { [roof.id]: roof, [segment.id]: segment } + const lines = planLines(buildRoofFloorplan(roof, buildContext(roof, [segment], [], nodes))) + expect(lines).toEqual( + expect.arrayContaining([ + lineKey([-2, 0], [2, 0]), + lineKey([-5, -3], [-2, 0]), + lineKey([-5, 3], [-2, 0]), + lineKey([5, -3], [2, 0]), + lineKey([5, 3], [2, 0]), + ]), + ) + expect(lines).toHaveLength(5) + }) +}) + +describe('the editor plan runs out to the eave', () => { + function singleRoof(roofType: 'gable' | 'hip', drafting = false) { + const roof = RoofNode.parse({ id: 'roof_e', type: 'roof', children: ['rseg_e'] }) + const segment = RoofSegmentNode.parse({ + id: 'rseg_e', + type: 'roof-segment', + parentId: roof.id, + roofType, + width: 10, + depth: 6, + }) + const nodes = { [roof.id]: roof, [segment.id]: segment } + return { + edge: getRoofSegmentVisibleTopBounds(segment), + geometry: buildRoofFloorplan(roof, buildContext(roof, [segment], [], nodes, drafting)), + } + } + + test('the outline is the eave and rake, overhang included, not the wall line', () => { + const { edge, geometry } = singleRoof('gable') + const outline = outlinePoints(geometry) + expect(edge.minX).toBeLessThan(-5.1) + expect(Math.min(...outline.map(([x]) => x))).toBeCloseTo(edge.minX, 6) + expect(Math.max(...outline.map(([x]) => x))).toBeCloseTo(edge.maxX, 6) + expect(Math.min(...outline.map(([, z]) => z))).toBeCloseTo(edge.minZ, 6) + expect(Math.max(...outline.map(([, z]) => z))).toBeCloseTo(edge.maxZ, 6) + }) + + test("a gable's ridge runs out to both rakes", () => { + const { edge, geometry } = singleRoof('gable') + expect(planLines(geometry)).toEqual([lineKey([edge.minX, 0], [edge.maxX, 0])]) + }) + + test("a hip's hips run out to the eave corners", () => { + const { edge, geometry } = singleRoof('hip') + expect(planLines(geometry)).toEqual( + expect.arrayContaining([ + lineKey([-2, 0], [2, 0]), + lineKey([edge.minX, edge.minZ], [-2, 0]), + lineKey([edge.minX, edge.maxZ], [-2, 0]), + lineKey([edge.maxX, edge.minZ], [2, 0]), + lineKey([edge.maxX, edge.maxZ], [2, 0]), + ]), + ) + }) + + test('a drafted sheet still outlines the wall line and dashes the eave past it', () => { + const { edge, geometry } = singleRoof('hip', true) + const outline = outlinePoints(geometry) + expect(Math.min(...outline.map(([x]) => x))).toBeCloseTo(-5, 6) + expect(Math.min(...eavePoints(geometry).map(([x]) => x))).toBeCloseTo(edge.minX, 6) + expect(planLines(geometry)).toContain(lineKey([-5, -3], [-2, 0])) + }) +}) + /** A 46' × 32' gable at a 7:12 pitch. */ function cottageRoof(overrides: Record = {}, drafting = true) { const roof = RoofNode.parse({ id: 'roof_c', type: 'roof', children: ['rseg_c'] }) @@ -250,3 +420,49 @@ describe('roof plan slope annotation', () => { expect(labels(geometry).filter((label) => /:12$/.test(label.text))).toHaveLength(0) }) }) + +describe("the roof's wall line while it is selected or moved", () => { + const BLUE = '#2563eb' + function wallLines(view: { selected?: boolean; moving?: boolean }) { + const roof = RoofNode.parse({ id: 'roof_w', type: 'roof', children: ['rseg_w'] }) + const segment = RoofSegmentNode.parse({ + id: 'rseg_w', + type: 'roof-segment', + parentId: roof.id, + roofType: 'gable', + width: 10, + depth: 6, + }) + const nodes = { [roof.id]: roof, [segment.id]: segment } + const geometry = buildRoofFloorplan(roof, { + ...buildContext(roof, [segment], [], nodes), + viewState: { + selected: false, + highlighted: false, + hovered: false, + moving: false, + unit: 'metric', + ...view, + palette: { selectedStroke: BLUE } as never, + }, + }) + return flat(geometry).filter( + (child) => child.kind === 'polygon' && child.stroke === BLUE && child.fill === 'none', + ) + } + + test('a selected or moved roof draws each segment where it stands on the walls', () => { + for (const view of [{ selected: true }, { moving: true }]) { + const lines = wallLines(view).filter((child) => + child.kind === 'polygon' + ? child.points.every(([x, z]) => Math.abs(x) === 5 && Math.abs(z) === 3) + : false, + ) + expect(lines).toHaveLength(1) + } + }) + + test('at rest it draws only the eaves', () => { + expect(wallLines({})).toHaveLength(0) + }) +}) diff --git a/packages/nodes/src/roof/floorplan.ts b/packages/nodes/src/roof/floorplan.ts index 7d749509de..90e9474402 100644 --- a/packages/nodes/src/roof/floorplan.ts +++ b/packages/nodes/src/roof/floorplan.ts @@ -4,6 +4,7 @@ import { type GeometryContext, getGlazedRoofPlanLines, getRoofSegmentVisibleTopBounds, + getSegmentSlopeFrame, type RoofNode, type RoofSegmentNode, roofPlanOverlapEntryOwns, @@ -12,6 +13,7 @@ import { } from '@pascal-app/core' import { floorplanGeometryMetadata, readFloorplanContext } from '@pascal-app/editor' import { getConicalRoofPlanFootprint, getRoofSegmentPlanLinework } from '../roof-segment/floorplan' +import { getRoofSurfaceCreases, type RoofCreaseSurface, type RoofPlane } from './roof-plan-creases' type Pt = [number, number] type Seg = [Pt, Pt] @@ -23,11 +25,24 @@ type SegPlan = { footprint: Pt[] /** The visible roof edge — the footprint plus the eave / rake overhang. */ eave: Pt[] + /** + * The edge the plan outlines, and the reach of its ridges, hips and + * valleys. The editor draws the roof as the 3D shows it from above, out to + * the eave and rake; a drafted sheet outlines the wall line and dashes the + * overhang past it (`eave`). + */ + outline: Pt[] ridges: Seg[] hips: Seg[] breaks: Seg[] glazing: Seg[] slopes: SlopeArrow[] + /** + * The segment's top as planes, for a form made only of planes (gable, hip, + * shed, flat): the plan then draws its ridges, hips and valleys off the + * unioned surface (`getRoofSurfaceCreases`) instead of `ridges` / `hips`. + */ + surface: RoofCreaseSurface | null } type PlanEntry = { @@ -117,6 +132,35 @@ function overlapEntry(entry: PlanEntry, ctx: GeometryContext) { } } +/** + * A planar form's roof planes in segment-local plan coordinates, each as + * `[gx, gz, c]` with height `c + gx·lx + gz·lz` above the eave: every slope + * rises at the pitch from its eave line. `null` for the forms with curved or + * broken slopes, which keep their own linework. + */ +function localRoofPlanes(seg: RoofSegmentNode): [number, number, number][] | null { + const hw = Math.max(seg.width, 0.01) / 2 + const hd = Math.max(seg.depth, 0.01) / 2 + const tan = getSegmentSlopeFrame(seg).tanTheta + const towardPlusZ: [number, number, number] = [0, -tan, tan * hd] + const towardMinusZ: [number, number, number] = [0, tan, tan * hd] + const towardPlusX: [number, number, number] = [-tan, 0, tan * hw] + const towardMinusX: [number, number, number] = [tan, 0, tan * hw] + switch (seg.roofType) { + case 'flat': + return [[0, 0, 0]] + case 'shed': + // high eave at lz = −hd, falling to +hd, as the 3D builder slopes it + return [towardPlusZ] + case 'gable': + return [towardPlusZ, towardMinusZ] + case 'hip': + return [towardPlusZ, towardMinusZ, towardPlusX, towardMinusX] + default: + return null + } +} + /** A segment's footprint + ridge/hip/break/slope linework, in world plan coords. */ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode, drafting: boolean): SegPlan { const cosRoof = Math.cos(-roof.rotation) @@ -132,19 +176,52 @@ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode, drafting: boolean): ] const hw = Math.max(seg.width, 0.01) / 2 const hd = Math.max(seg.depth, 0.01) / 2 + const conical = seg.roofType === 'conical' + // The drip edge: core's own visible-top bounds, which add the horizontal + // component of `overhang` (plus half the wall thickness and, on the sloped + // faces, the shingle projection) to the footprint on each side. + const edge = getRoofSegmentVisibleTopBounds(seg) + // In the editor a line that ends on the wall line runs on to the eave or + // rake, as the 3D's hips, ridges and slope breaks do. + const toEdge = (v: number, half: number, min: number, max: number) => + Math.abs(v - half) < 1e-6 ? max : Math.abs(v + half) < 1e-6 ? min : v + const reach = drafting || conical ? null : edge + const mapSeg = (s: readonly [readonly [number, number], readonly [number, number]]): Seg => + s.map(([lx, lz]) => + reach + ? tp(toEdge(lx, hw, reach.minX, reach.maxX), toEdge(lz, hd, reach.minZ, reach.maxZ)) + : tp(lx, lz), + ) as Seg const lw = getRoofSegmentPlanLinework(seg) - const mapSeg = (s: readonly [readonly [number, number], readonly [number, number]]): Seg => [ - tp(s[0][0], s[0][1]), - tp(s[1][0], s[1][1]), + const footprint = conical + ? getConicalRoofPlanFootprint(seg).map(([x, z]) => tp(x, z)) + : [tp(-hw, -hd), tp(hw, -hd), tp(hw, hd), tp(-hw, hd)] + const eave = [ + tp(edge.minX, edge.minZ), + tp(edge.maxX, edge.minZ), + tp(edge.maxX, edge.maxZ), + tp(edge.minX, edge.maxZ), ] - const footprint = - seg.roofType === 'conical' - ? getConicalRoofPlanFootprint(seg).map(([x, z]) => tp(x, z)) - : [tp(-hw, -hd), tp(hw, -hd), tp(hw, hd), tp(-hw, hd)] + const outline = reach ? eave : footprint const ridges = lw.ridges.map(mapSeg) const hips = lw.hips.map(mapSeg) const breaks = lw.breaks.map(mapSeg) - const glazing = getGlazedRoofPlanLines(seg).map(mapSeg) + const glazing = getGlazedRoofPlanLines(seg).map(([a, b]): Seg => [tp(a[0], a[1]), tp(b[0], b[1])]) + const local = localRoofPlanes(seg) + // Heights in the roof's level frame, so the planes of two roofs side by side + // compare. The planes run on past the wall line, as the overhang does. + const base = roof.position[1] + seg.position[1] + seg.wallHeight + const surface: RoofCreaseSurface | null = local + ? { + roofId: String(roof.id), + footprint: outline, + planes: local.map(([gx, gz, c0]): RoofPlane => { + const a = gx * cos - gz * sin + const b = gx * sin + gz * cos + return { a, b, c: base + c0 - a * segCx - b * segCz } + }), + } + : null if (!drafting) { const slopes = lw.slope ? [ @@ -155,25 +232,18 @@ function buildSegPlan(roof: RoofNode, seg: RoofSegmentNode, drafting: boolean): }, ] : [] - return { footprint, eave: footprint, ridges, hips, breaks, glazing, slopes } + return { footprint, eave, outline, ridges, hips, breaks, glazing, slopes, surface } } - // The drip edge: core's own visible-top bounds, which add the horizontal - // component of `overhang` (plus half the wall thickness and, on the sloped - // faces, the shingle projection) to the footprint on each side. - const edge = getRoofSegmentVisibleTopBounds(seg) const pitch = seg.roofType === 'barrel' ? '' : roofPitchLabel(seg.pitch) return { footprint, - eave: [ - tp(edge.minX, edge.minZ), - tp(edge.maxX, edge.minZ), - tp(edge.maxX, edge.maxZ), - tp(edge.minX, edge.maxZ), - ], + eave, + outline, ridges, hips, breaks, glazing, + surface, slopes: slopeArrows(seg).map((arrow) => ({ tail: tp(arrow.tail[0], arrow.tail[1]), head: tp(arrow.head[0], arrow.head[1]), @@ -272,25 +342,25 @@ export function buildRoofFloorplan(node: RoofNode, ctx: GeometryContext): Floorp } const currentEntries = entries.filter((entry) => entry.roof.id === node.id) + const owningCandidates = (entry: PlanEntry) => + entries.filter((candidate) => { + if (candidate.segment.id === entry.segment.id) return false + if (candidate.segment.roofType === 'shed') return false + return roofPlanOverlapEntryOwns(overlapEntry(candidate, ctx), overlapEntry(entry, ctx)) + }) const visiblePlans = currentEntries.map((entry) => { - const cutters = entries - .filter((candidate) => { - if (candidate.segment.id === entry.segment.id) return false - if (candidate.segment.roofType === 'shed') return false - return roofPlanOverlapEntryOwns(overlapEntry(candidate, ctx), overlapEntry(entry, ctx)) - }) - .map((candidate) => candidate.plan.footprint) + const cutters = owningCandidates(entry).map((candidate) => candidate.plan.outline) return { plan: entry.plan, cutters, - footprints: subtractPolygonsFromPolygon(entry.plan.footprint, cutters) as Pt[][], + outlines: subtractPolygonsFromPolygon(entry.plan.outline, cutters) as Pt[][], eaves: subtractPolygonsFromPolygon(entry.plan.eave, cutters) as Pt[][], /** True when the drip edge is far enough outside the wall line to draw. */ hasOverhang: drafting && polygonSpread(entry.plan.eave) - polygonSpread(entry.plan.footprint) > 0.04, } }) - const rings = unionPolygons(visiblePlans.flatMap(({ footprints }) => footprints)) as Pt[][] + const rings = unionPolygons(visiblePlans.flatMap(({ outlines }) => outlines)) as Pt[][] if (rings.length === 0) return null const eaveRings = unionPolygons( visiblePlans.flatMap(({ eaves, hasOverhang }) => (hasOverhang ? eaves : [])), @@ -344,7 +414,7 @@ export function buildRoofFloorplan(node: RoofNode, ctx: GeometryContext): Floorp }) } - // Merged outline (wall line under the roof). + // Merged outline: the eave and rake in the editor, the wall line on a sheet. for (const ring of rings) { if (ring.length < 3) continue children.push({ @@ -358,24 +428,65 @@ export function buildRoofFloorplan(node: RoofNode, ctx: GeometryContext): Floorp }) } + // Selected or moved, the wall line each segment stands on: whether the roof sits on the walls' + // centre lines (a selected segment draws its own). + if (!drafting && palette && ((view?.moving ?? false) || showSelectedChrome)) { + for (const { plan } of visiblePlans) { + children.push({ + kind: 'polygon', + points: plan.footprint.map(([x, z]) => [x, z] as FloorplanPoint), + fill: 'none', + stroke: palette.selectedStroke, + strokeWidth: 0.035, + strokeLinejoin: 'miter', + pointerEvents: 'none', + }) + } + } + + // Ridges, hips and valleys of the planar segments, off the surface they make + // together with this level's other roofs: a wing's ridge runs on until it + // meets the main roof's slope, and a valley runs where the two slopes meet. + // The curved and broken forms are not in that surface: where one owns an + // overlap it cuts these lines as before. + const planarEntries = entries.filter((entry) => entry.plan.surface) + const curvedCutters = (entry: PlanEntry) => + owningCandidates(entry) + .filter((candidate) => !candidate.plan.surface) + .map((candidate) => candidate.plan.outline) + for (const crease of getRoofSurfaceCreases(planarEntries.map((entry) => entry.plan.surface!))) { + if (crease.roofId !== String(node.id)) continue + const [i, k] = crease.between + const cutters = [ + ...curvedCutters(planarEntries[i]!), + ...(k === i ? [] : curvedCutters(planarEntries[k]!)), + ] + const width = crease.kind === 'ridge' ? ridgeWidth : hipWidth + for (const visible of clipLineByCutters([crease.from, crease.to], cutters)) + pushLine(visible[0], visible[1], width) + } + for (const { plan, cutters } of visiblePlans) { for (const line of plan.glazing) { for (const visible of clipLineByCutters(line, cutters)) pushLine(visible[0], visible[1], hipWidth) } - for (const line of plan.breaks) { - for (const visible of clipLineByCutters(line, cutters)) { - pushLine(visible[0], visible[1], hipWidth) + // A planar segment's ridges and hips were drawn off the shared surface above. + if (!plan.surface) { + for (const line of plan.breaks) { + for (const visible of clipLineByCutters(line, cutters)) { + pushLine(visible[0], visible[1], hipWidth) + } } - } - for (const line of plan.hips) { - for (const visible of clipLineByCutters(line, cutters)) { - pushLine(visible[0], visible[1], hipWidth) + for (const line of plan.hips) { + for (const visible of clipLineByCutters(line, cutters)) { + pushLine(visible[0], visible[1], hipWidth) + } } - } - for (const line of plan.ridges) { - for (const visible of clipLineByCutters(line, cutters)) { - pushLine(visible[0], visible[1], ridgeWidth) + for (const line of plan.ridges) { + for (const visible of clipLineByCutters(line, cutters)) { + pushLine(visible[0], visible[1], ridgeWidth) + } } } diff --git a/packages/nodes/src/roof/paint.ts b/packages/nodes/src/roof/paint.ts new file mode 100644 index 0000000000..1ae3ef1765 --- /dev/null +++ b/packages/nodes/src/roof/paint.ts @@ -0,0 +1,196 @@ +import { + type AnyNode, + type AnyNodeId, + getEffectiveRoofSurfaceMaterial, + getEffectiveSegmentSurfaceMaterial, + getRoofWallBandMaterial, + type PaintCapability, + type RoofNode, + type RoofSegmentNode, + type RoofSurfaceMaterialRole, + useScene, +} from '@pascal-app/core' +import { buildRoofSurfaceMaterialPatch, resolveRoofWallHit } from '@pascal-app/editor' +import { + getRoofMaterialArray, + levelWallCladdingRef, + levelWallFaceRef, + ROOF_HEEL_MATERIAL_INDEX, + useViewer, +} from '@pascal-app/viewer' +import { type Mesh, Vector3 } from 'three' +import { wallBandPatch } from '../roof-segment/paint' +import { swapPreviewMaterial } from '../shared/swap-preview-material' + +type RoofGableBand = 'gable-left' | 'gable-right' + +/** Every gable of the roof: the `matching` scope of a gable hit. */ +export const ROOF_ALL_GABLES_ROLE = 'gables:all' + +/** One gable of the merged shell: `gable-left:` / `gable-right:`. */ +export function roofGableRole(band: RoofGableBand, segmentId: string): string { + return `${band}:${segmentId}` +} + +export function parseRoofGableRole( + role: string, +): { band: RoofGableBand; segmentId: AnyNodeId } | null { + const split = role.indexOf(':') + if (split < 0) return null + const band = role.slice(0, split) + if (band !== 'gable-left' && band !== 'gable-right') return null + return { band, segmentId: role.slice(split + 1) as AnyNodeId } +} + +const isSurfaceRole = (role: string): role is RoofSurfaceMaterialRole => + role === 'top' || role === 'edge' || role === 'wall' + +const hitWorldPoint = new Vector3() + +/** The merged shell's surface under the cursor: its material slot, else the hit normal. */ +function surfaceRole( + materialIndex: number | null, + normal: readonly [number, number, number] | undefined, +): RoofSurfaceMaterialRole | 'heel' | null { + if (materialIndex === 3) return 'top' + if (materialIndex === ROOF_HEEL_MATERIAL_INDEX) return 'heel' + if (materialIndex === 0) return 'edge' + if (materialIndex === 1 || materialIndex === 2) return 'wall' + + const normalY = normal?.[1] + if (normalY !== undefined && normalY > 0.35) return 'top' + if (normalY !== undefined && Math.abs(normalY) <= 0.35) return 'edge' + if (normalY !== undefined && normalY < -0.35) return 'wall' + return null +} + +/** The merged roof with `role` repainted, so the preview draws it the way a commit would. */ +function previewRoof( + roof: RoofNode, + role: string, + material: RoofNode['material'], + preset?: string, +) { + if (role === 'heel') return { ...roof, ...wallBandPatch('heel', material, preset) } as RoofNode + const surface = role === ROOF_ALL_GABLES_ROLE ? 'edge' : role + if (!isSurfaceRole(surface)) return null + return { ...roof, ...buildRoofSurfaceMaterialPatch(roof, surface, material, preset) } +} + +/** + * The merged roof shell's paint. Its surfaces are the roof's top / edge / wall + * finishes and the heel band; an edge hit on a segment's ±x end wall is that + * gable alone (its segment's gable finish), and the `matching` scope widens it + * to every gable — the roof's edge, with no segment keeping a gable of its own. + */ +export const roofPaint: PaintCapability = { + materialTarget: 'roof', + resolveRole(args) { + const role = surfaceRole(args.materialIndex, args.normal) + if (role !== 'edge' || !(args.localPosition && args.hitObject)) return role + args.hitObject.updateWorldMatrix(true, false) + const position = args.hitObject.localToWorld(hitWorldPoint.set(...args.localPosition)) + const hit = resolveRoofWallHit( + args.node as RoofNode, + [position.x, position.y, position.z], + args.normal as [number, number, number] | undefined, + args.hitObject, + ) + if (!hit || (hit.face.id !== 'left' && hit.face.id !== 'right')) return role + return roofGableRole(hit.face.id === 'left' ? 'gable-left' : 'gable-right', hit.segment.id) + }, + roleLabel(_node, role) { + if (role === 'heel') return 'Heel band' + if (role === ROOF_ALL_GABLES_ROLE) return 'All gables' + const gable = parseRoofGableRole(role) + if (!gable) return null + return gable.band === 'gable-left' ? 'Left gable' : 'Right gable' + }, + scopes(_node, role) { + if (!parseRoofGableRole(role)) return null + return { + scopes: ['single', 'matching'], + labels: { matching: 'All gables' }, + roleFor: (scope) => (scope === 'matching' ? ROOF_ALL_GABLES_ROLE : role), + } + }, + buildPatch({ node, role, material, materialPreset }) { + const roof = node as RoofNode + if (role === 'heel') return wallBandPatch('heel', material, materialPreset) as Partial + if (role === ROOF_ALL_GABLES_ROLE) + return buildRoofSurfaceMaterialPatch(roof, 'edge', material, materialPreset) + if (!isSurfaceRole(role)) return {} + return buildRoofSurfaceMaterialPatch(roof, role, material, materialPreset) + }, + commit(args) { + const scene = useScene.getState() + const gable = parseRoofGableRole(args.role) + if (gable) { + scene.updateNode( + gable.segmentId, + wallBandPatch(gable.band, args.material, args.materialPreset), + ) + return + } + if (args.role !== ROOF_ALL_GABLES_ROLE) { + scene.updateNode(args.node.id as AnyNodeId, roofPaint.buildPatch(args)) + return + } + // Every gable: the roof's edge, and no segment keeping a gable of its own. + const cleared: Partial = { + ...wallBandPatch('gable-left', undefined, undefined), + ...wallBandPatch('gable-right', undefined, undefined), + } + scene.updateNodes([ + { id: args.node.id as AnyNodeId, data: roofPaint.buildPatch(args) }, + ...((args.node as RoofNode).children ?? []).flatMap((childId) => + scene.nodes[childId as AnyNodeId]?.type === 'roof-segment' + ? [{ id: childId as AnyNodeId, data: cleared as Partial }] + : [], + ), + ]) + }, + applyPreview(args) { + // One gable has no surface of its own on the merged shell until it is painted. + if (parseRoofGableRole(args.role)) return null + const mesh = args.root.getObjectByName('merged-roof') as Mesh | undefined + if (!mesh) return null + const roof = args.node as RoofNode + const preview = previewRoof(roof, args.role, args.material, args.materialPreset) + if (!preview) return null + // The renderer's wall-derived defaults, so the surfaces not being painted hold still. + const nodes = args.nodes ?? useScene.getState().nodes + const viewer = useViewer.getState() + const material = getRoofMaterialArray( + preview, + viewer.shading, + viewer.textures, + viewer.colorPreset, + viewer.sceneTheme, + levelWallCladdingRef(nodes, roof), + args.materials ?? useScene.getState().materials, + levelWallFaceRef(nodes, roof), + ) + return material ? swapPreviewMaterial(mesh, material) : null + }, + // The top / edge / wall finishes return null here and resolve through the + // editor's roof surface fallback (`resolveActivePaintMaterialFromSelection`). + getEffectiveMaterial({ node, role, nodes }) { + if (role === 'heel') { + const own = getRoofWallBandMaterial(node as RoofNode, 'heel') + return own ? { material: own.material, materialPreset: own.materialPreset } : null + } + const gable = parseRoofGableRole(role) + const segment = gable ? (nodes?.[gable.segmentId] as RoofSegmentNode | undefined) : undefined + if (!(gable && segment?.type === 'roof-segment')) return null + // Unpainted, a gable shows the segment's edge, falling back to the roof's. + const own = getRoofWallBandMaterial(segment, gable.band) + if (own) return { material: own.material, materialPreset: own.materialPreset } + const edge = getEffectiveSegmentSurfaceMaterial( + segment, + 'edge', + getEffectiveRoofSurfaceMaterial(node as RoofNode, 'edge'), + ) + return { material: edge.material, materialPreset: edge.materialPreset } + }, +} diff --git a/packages/nodes/src/roof/panel.tsx b/packages/nodes/src/roof/panel.tsx index d60ae54981..c429035d6f 100644 --- a/packages/nodes/src/roof/panel.tsx +++ b/packages/nodes/src/roof/panel.tsx @@ -7,6 +7,8 @@ import { type ChimneyNode, type DormerNode, type GutterNode, + getRoofSegmentDeckReach, + getSegmentSlopeFrame, isRoofOnStoreyWalls, type LevelNode, type PascalI18n, @@ -15,6 +17,7 @@ import { type RoofNode, type RoofSegmentNode, roofLevelAbove, + roofTypeHasRakes, type SkylightNode, type SolarPanelNode, type TurbineVentNode, @@ -221,6 +224,19 @@ export default function RoofPanel() { [selectedId], ) + // The eaves are each segment's: the roof's sliders set them on all of its segments at once. + const handleSegmentsUpdate = useCallback( + (patch: (segment: RoofSegmentNode) => Partial | null) => { + if (!selectedId) return + const update = segments.flatMap((segment) => { + const data = patch(segment) + return data ? [{ id: segment.id as AnyNodeId, data: data as Partial }] : [] + }) + if (update.length > 0) commitSeatedRoofEdit(update, selectedId as AnyNodeId) + }, + [segments, selectedId], + ) + const handleClose = useCallback(() => { setSelection({ selectedIds: [] }) }, [setSelection]) @@ -294,6 +310,12 @@ export default function RoofPanel() { if (!(node && node.type === 'roof' && selectedId)) return null + // Shown as the segment panel shows them: in plan, past the walls' outside faces. + const eaveSegment = segments.find((segment) => segment.roofType !== 'barrel') + const rakedSegment = segments.find((segment) => roofTypeHasRakes(segment.roofType)) + const overhangPastWall = (segment: RoofSegmentNode, axis: 'x' | 'z') => + getRoofSegmentDeckReach(segment)[axis] - segment.wallThickness / 2 + return ( + {eaveSegment && ( + + + handleSegmentsUpdate((segment) => + segment.roofType === 'barrel' + ? null + : { overhang: v / getSegmentSlopeFrame(segment).cosTheta }, + ) + } + precision={2} + step={0.05} + unit="m" + value={overhangPastWall(eaveSegment, 'z')} + /> + {rakedSegment && ( + + handleSegmentsUpdate((segment) => + roofTypeHasRakes(segment.roofType) ? { gableOverhang: v } : null, + ) + } + precision={2} + step={0.05} + unit="m" + value={overhangPastWall(rakedSegment, 'x')} + /> + )} + + )} + { // The gable band is clad like the walls below it (their assemblies). const wallCladdingRef = useScene((state) => levelWallCladdingRef(state.nodes, node)) + // The heel band, under the eaves, is wall: it shows what the walls below show outside. + const wallFaceRef = useScene((state) => levelWallFaceRef(state.nodes, node)) const sceneMaterials = useScene((state) => state.materials) const customMaterial = useMemo( () => @@ -133,8 +136,18 @@ export const RoofRenderer = ({ node: rawNode }: { node: RoofNode }) => { sceneTheme, wallCladdingRef, sceneMaterials, + wallFaceRef, ), - [node, shading, textures, colorPreset, sceneTheme, wallCladdingRef, sceneMaterials], + [ + node, + shading, + textures, + colorPreset, + sceneTheme, + wallCladdingRef, + sceneMaterials, + wallFaceRef, + ], ) const material = debugColors diff --git a/packages/nodes/src/roof/roof-footprint-source.ts b/packages/nodes/src/roof/roof-footprint-source.ts index 2d17d69550..0b38a5e502 100644 --- a/packages/nodes/src/roof/roof-footprint-source.ts +++ b/packages/nodes/src/roof/roof-footprint-source.ts @@ -2,9 +2,18 @@ import { create } from 'zustand' export type RoofFootprintSourceChoice = 'draw' | 'room' +/** + * What a Room pick finds under the cursor: a room it can roof, a room that is + * not a rectangle (Room roofs only rectangles), or no closed room at all. + */ +export type RoofRoomPick = 'room' | 'irregular' | 'none' + type RoofFootprintSourceState = { source: RoofFootprintSourceChoice setSource: (source: RoofFootprintSourceChoice) => void + /** The hovered Room pick, for the HUD to say why a click would do nothing; null off Room. */ + roomPick: RoofRoomPick | null + setRoomPick: (roomPick: RoofRoomPick | null) => void } /** @@ -18,6 +27,23 @@ type RoofFootprintSourceState = { const useRoofFootprintSource = create((set) => ({ source: 'draw', setSource: (source) => set({ source }), + roomPick: null, + setRoomPick: (roomPick) => set({ roomPick }), })) +const subscribeToRoomPick = (onChange: () => void) => + useRoofFootprintSource.subscribe((state, previous) => { + if (state.roomPick !== previous.roomPick) onChange() + }) + +export const irregularRoomHintVisibility = { + subscribe: subscribeToRoomPick, + value: () => useRoofFootprintSource.getState().roomPick === 'irregular', +} + +export const noRoomHintVisibility = { + subscribe: subscribeToRoomPick, + value: () => useRoofFootprintSource.getState().roomPick === 'none', +} + export default useRoofFootprintSource diff --git a/packages/nodes/src/roof/roof-footprint.test.ts b/packages/nodes/src/roof/roof-footprint.test.ts index cc1529a4e5..98b231bd57 100644 --- a/packages/nodes/src/roof/roof-footprint.test.ts +++ b/packages/nodes/src/roof/roof-footprint.test.ts @@ -4,8 +4,10 @@ import { fitRoofFootprint, LevelNode, resolveRoomRoofFootprint, + SeparatorNode, type WallEvent, WallNode, + ZoneNode, } from '@pascal-app/core' import { isStandardRoofWallEligible, @@ -13,6 +15,9 @@ import { resolveRoofFootprintElevation, resolveRoofFootprintWorldElevation, resolveRoofWallTopWorldElevation, + roofEdgeAnchors, + roofFootprintPreviewIds, + roofSnapEdges, subscribeToConicalRoofWallClicks, } from './roof-footprint' @@ -274,3 +279,127 @@ describe('roof footprint sources', () => { expect(selected).toEqual([]) }) }) + +const RAIL = 3.5 * 0.0254 +const H = RAIL / 2 +const r6 = (value: number) => Math.round(value * 1e6) / 1e6 + +// A porch 6 m along a 0.2 m house wall at z 0, 2.5 m deep: invisible walls or railings round it. +function porchLevel(open: 'separators' | 'railings') { + const level = LevelNode.parse({ id: 'level_porch', level: 0 }) + const house = WallNode.parse({ + id: 'wall_house', + parentId: level.id, + start: [0, 0], + end: [10, 0], + thickness: 0.2, + }) + const sides: Array<[[number, number], [number, number]]> = [ + [ + [2, 0], + [2, -2.5], + ], + [ + [2, -2.5], + [8, -2.5], + ], + [ + [8, -2.5], + [8, 0], + ], + ] + const edges = sides.map(([start, end], i) => + open === 'separators' + ? SeparatorNode.parse({ id: `separator_${i}`, parentId: level.id, start, end }) + : WallNode.parse({ + id: `wall_rail${i}`, + parentId: level.id, + start, + end, + wallType: 'railing', + thickness: RAIL, + }), + ) + const zone = ZoneNode.parse({ + id: 'zone_porch', + parentId: level.id, + name: 'Porch', + roomType: open === 'separators' ? 'porch' : 'deck', + polygon: [ + [2, 0], + [2, -2.5], + [8, -2.5], + [8, 0], + ], + }) + level.children = [house, ...edges, zone].map((node) => node.id) as LevelNode['children'] + return Object.fromEntries([level, house, ...edges, zone].map((node) => [node.id, node])) +} + +const ends = (wall: WallNode) => [...wall.start, ...wall.end].map(r6) + +describe("the rooms' edges a drawn roof snaps to", () => { + test("a porch's invisible walls snap as walls do, on their lines", () => { + const { references, faced } = roofSnapEdges(porchLevel('separators'), ['level_porch']) + expect(references.map((reference) => reference.id)).toEqual([ + 'separator_0', + 'separator_1', + 'separator_2', + ]) + expect(references.map(ends)).toEqual([ + [2, 0, 2, -2.5], + [2, -2.5, 8, -2.5], + [8, -2.5, 8, 0], + ]) + expect(references.every((reference) => isStandardRoofWallEligible(reference))).toBe(true) + expect(faced.size).toBe(0) + }) + + test("a deck's railings snap at their outside faces, post corner to post corner", () => { + const { references, faced } = roofSnapEdges(porchLevel('railings'), ['level_porch']) + expect(references.map(ends)).toEqual([ + [2 - H, 0, 2 - H, -2.5 - H].map(r6), + [2 - H, -2.5 - H, 8 + H, -2.5 - H].map(r6), + [8 + H, -2.5 - H, 8 + H, 0].map(r6), + ]) + expect([...faced].sort()).toEqual(['wall_rail0', 'wall_rail1', 'wall_rail2']) + }) + + test("a porch's corners and invisible walls' ends line a drawn roof up", () => { + const deck = roofEdgeAnchors(porchLevel('railings'), ['level_porch']) + const corner = deck.find((anchor) => anchor.nodeId === 'zone_porch' && anchor.x > 8) + expect(corner && [r6(corner.x), r6(corner.z)]).toEqual([r6(8 + H), r6(-2.5 - H)]) + const porch = roofEdgeAnchors(porchLevel('separators'), ['level_porch']) + expect(porch).toContainEqual({ nodeId: 'separator_1', kind: 'center', x: 5, z: -2.5 }) + expect(porch).toContainEqual({ nodeId: 'separator_0', kind: 'corner', x: 2, z: -2.5 }) + }) + + test('the Room pick lights up a porch with its edges, a room with its walls', () => { + const porch = resolveRoomRoofFootprint('level_porch', porchLevel('separators'), [5, -1]) + expect(roofFootprintPreviewIds(porch).sort()).toEqual([ + 'separator_0', + 'separator_1', + 'separator_2', + 'wall_house', + ]) + expect(roofFootprintPreviewIds(null)).toEqual([]) + }) + + test("a railing's top never sets its roof's height", () => { + const nodes = porchLevel('railings') + const target = fitRoofFootprint( + 'deck', + [ + [2, 0], + [2, -2.5], + [8, -2.5], + [8, 0], + ], + ['wall_rail1' as WallNode['id']], + )! + const railing = { ...nodes.wall_rail1, height: 0.9 } as WallNode + expect( + resolveRoofFootprintElevation('level_porch', target, { ...nodes, wall_rail1: railing }), + ).toBe(0) + }) +}) diff --git a/packages/nodes/src/roof/roof-footprint.ts b/packages/nodes/src/roof/roof-footprint.ts index a887a957af..2b202a5708 100644 --- a/packages/nodes/src/roof/roof-footprint.ts +++ b/packages/nodes/src/roof/roof-footprint.ts @@ -1,29 +1,152 @@ import { + type AlignmentAnchor, type AnyNode, emitter, getLevelBelow, getLevelElevations, + getWallCurveFrameAt, + getWallCurveStationAtPoint, isCurvedWall, + isOutdoorRoomType, + isRailingWall, type LevelNode, + outdoorRoomRoofOutline, + polygonAnchors, type RoofFootprintTarget, type RoofType, resolveLevelId, resolveRoofWallTopElevation, + resolveRoomRoofFootprint, + type SeparatorNode, type WallEvent, type WallNode, + wallSegmentAnchors, + type ZoneNode, } from '@pascal-app/core' +import type { RoofRoomPick } from './roof-footprint-source' export type RoofFootprintSource = 'room' | 'walls' | 'draw' const ROOF_AXIS_ALIGNMENT_EPSILON = 1e-4 -export function isStandardRoofWallEligible(wall: WallNode): boolean { +export function isStandardRoofWallEligible( + wall: Pick, +): boolean { if (isCurvedWall(wall)) return false const deltaX = Math.abs(wall.end[0] - wall.start[0]) const deltaZ = Math.abs(wall.end[1] - wall.start[1]) return deltaX <= ROOF_AXIS_ALIGNMENT_EPSILON || deltaZ <= ROOF_AXIS_ALIGNMENT_EPSILON } +/** A room's edge as the wall snap reads a wall: its ends, its middle and its run catch a point. */ +function edgeReference( + id: string, + parentId: string | null, + start: [number, number], + end: [number, number], +): WallNode { + return { + object: 'node', + id: id as WallNode['id'], + type: 'wall', + parentId, + visible: true, + metadata: {}, + children: [], + start, + end, + frontSide: 'unknown', + backSide: 'unknown', + } +} + +/** The levels' invisible walls, and their porches', decks' and patios' outside edges. */ +function levelEdges(nodes: Readonly>, levelIds: readonly string[]) { + return levelIds.flatMap((levelId) => { + const level = nodes[levelId] + if (level?.type !== 'level') return [] + const children = level.children.map((id) => nodes[id]) + const walls = children.filter((node): node is WallNode => node?.type === 'wall') + const separators = children.filter((node): node is SeparatorNode => node?.type === 'separator') + const rooms = children + .filter( + (node): node is ZoneNode => + node?.type === 'zone' && isOutdoorRoomType(node.roomType) && node.polygon.length >= 3, + ) + .map((zone) => ({ zone, ...outdoorRoomRoofOutline(zone, walls, separators) })) + return [{ levelId, separators, rooms }] + }) +} + +const snapEdgesMemo = new WeakMap>>() + +function computeRoofSnapEdges( + nodes: Readonly>, + levelIds: readonly string[], +) { + const references: WallNode[] = [] + const faced = new Set() + for (const { levelId, separators, rooms } of levelEdges(nodes, levelIds)) { + for (const separator of separators) + references.push(edgeReference(separator.id, levelId, separator.start, separator.end)) + for (const { polygon, edges } of rooms) + edges.forEach((edge, index) => { + if (edge?.kind !== 'railing') return + faced.add(edge.id) + const next = polygon[(index + 1) % polygon.length]! + references.push(edgeReference(edge.id, levelId, polygon[index]!, next)) + }) + } + return { references, faced } +} + +/** + * The rooms' edges on `levelIds` a drawn roof snaps to, as it snaps to walls (their corners, + * middles, crossings and runs): each invisible wall's line, and each railing round a porch, deck + * or patio at its outside face, from post corner to post corner (`outdoorRoomRoofOutline`), so a + * roof drawn over the room reaches its outside edge as one drawn over the house reaches its walls'. + * `faced` holds those railings: their faces stand in for their centre lines. Kept per scene, so a + * pointer move asks once. + */ +export function roofSnapEdges( + nodes: Readonly>, + levelIds: readonly string[], +): { references: WallNode[]; faced: Set } { + const key = levelIds.join('|') + let byKey = snapEdgesMemo.get(nodes) + if (!byKey) { + byKey = new Map() + snapEdgesMemo.set(nodes, byKey) + } + const cached = byKey.get(key) + if (cached) return cached + const edges = computeRoofSnapEdges(nodes, levelIds) + byKey.set(key, edges) + return edges +} + +/** + * The rooms' edges on `levelIds` as alignment anchors for a roof drawn over them: each straight + * invisible wall's ends and middle (as a wall's), and each porch's, deck's or patio's outside + * corners, railings' post corners included. + */ +export function roofEdgeAnchors( + nodes: Readonly>, + levelIds: readonly string[], +): AlignmentAnchor[] { + return levelEdges(nodes, levelIds).flatMap(({ separators, rooms }) => [ + ...separators + .filter((separator) => isStandardRoofWallEligible(separator)) + .flatMap((separator) => wallSegmentAnchors(separator.id, separator.start, separator.end)), + ...rooms.flatMap(({ zone, polygon }) => polygonAnchors(zone.id, polygon)), + ]) +} + +/** What a Room pick lights up while it hovers a room: its walls, a porch's edges with them. */ +export function roofFootprintPreviewIds(target: RoofFootprintTarget | null): string[] { + return target ? [...target.wallIds, ...(target.edgeIds ?? [])] : [] +} + export function isConicalRoofWallEligible( targetLevelId: LevelNode['id'], wall: WallNode, @@ -36,11 +159,51 @@ export function isConicalRoofWallEligible( return getLevelBelow(targetLevelId, completeNodes)?.id === sourceLevelId } +// As far off the wall's centreline as the plan reaches for a wall a door or window goes in. +const CONICAL_WALL_PICK_DISTANCE = 0.5 + +/** + * The curved wall a cone would be built over from a plan point: the nearest one in reach on the + * level or the level below. The plan raises no wall events, so its tool picks the wall itself. + */ +export function pickConicalRoofWall( + levelId: LevelNode['id'], + nodes: Readonly>, + point: [number, number], +): WallNode | null { + let picked: WallNode | null = null + let pickedDistance = CONICAL_WALL_PICK_DISTANCE + for (const node of Object.values(nodes)) { + if (!(node.type === 'wall' && isCurvedWall(node))) continue + if (!isConicalRoofWallEligible(levelId, node, nodes)) continue + const nearest = getWallCurveFrameAt(node, getWallCurveStationAtPoint(node, point)).point + const distance = Math.hypot(point[0] - nearest.x, point[1] - nearest.y) + if (distance > pickedDistance) continue + picked = node + pickedDistance = distance + } + return picked +} + export function parseRoofFootprintSource(value: unknown, roofType: RoofType): RoofFootprintSource { if (roofType === 'conical') return 'walls' return value === 'room' ? 'room' : 'draw' } +/** + * The room a Room pick at `point` roofs (the smallest one on the level, else on the level below), + * and what the pick found: a rectangle it roofs, a room it can't (not a rectangle), or none. + */ +export function pickRoofRoom( + levelId: LevelNode['id'], + nodes: Readonly>, + point: [number, number], +): { target: RoofFootprintTarget | null; pick: RoofRoomPick } { + const found = resolveRoomRoofFootprint(levelId, nodes, point) + if (!found) return { target: null, pick: 'none' } + return found.rectangular ? { target: found, pick: 'room' } : { target: null, pick: 'irregular' } +} + export function subscribeToConicalRoofWallClicks(options: { footprintSource: RoofFootprintSource currentLevelId: LevelNode['id'] | null @@ -99,9 +262,10 @@ export function resolveRoofFootprintElevation( ): number { const completeNodes = nodes as Record const elevations = getLevelElevations(completeNodes) + // A railing is a guard round a deck or a porch, not a wall: the roof never sits on its top. const tops = target.wallIds.flatMap((id) => { const wall = nodes[id] - return wall?.type === 'wall' + return wall?.type === 'wall' && !isRailingWall(wall) ? [resolveRoofWallTopElevation(targetLevelId, wall, completeNodes, elevations)] : [] }) diff --git a/packages/nodes/src/roof/roof-materials.ts b/packages/nodes/src/roof/roof-materials.ts index 940ae3bf1c..db7676caa6 100644 --- a/packages/nodes/src/roof/roof-materials.ts +++ b/packages/nodes/src/roof/roof-materials.ts @@ -8,7 +8,7 @@ import { import * as THREE from 'three' // Production materials — match the rest of the scene (white walls, light-gray slabs). -// Indices: 0 = Wall/Trim, 1 = Deck, 2 = Interior, 3 = Shingle +// Indices: 0 = Wall/Trim, 1 = Deck, 2 = Interior, 3 = Shingle, 4 = Heel band, 5/6 = Gables const roofMaterialsCache = new Map() export function getRoofMaterials( @@ -28,12 +28,18 @@ export function getRoofMaterials( resolveSlotDefaultMaterial('library:preset-softwhite', shading), // 1: Deck resolveSlotDefaultMaterial('library:preset-softwhite', shading), // 2: Interior resolveSlotDefaultMaterial('library:roof-terracottatiles', shading), // 3: Shingle + resolveSlotDefaultMaterial('library:concrete-drywall', shading), // 4: Heel band + resolveSlotDefaultMaterial('library:concrete-drywall', shading), // 5: Left gable + resolveSlotDefaultMaterial('library:concrete-drywall', shading), // 6: Right gable ] : [ createSurfaceRoleMaterial('roof', colorPreset), createSurfaceRoleMaterial('ceiling', colorPreset), createSurfaceRoleMaterial('ceiling', colorPreset), createSurfaceRoleMaterial('roof', colorPreset), + createSurfaceRoleMaterial('wall', colorPreset), + createSurfaceRoleMaterial('roof', colorPreset), + createSurfaceRoleMaterial('roof', colorPreset), ] roofMaterialsCache.set(cacheKey, materials) return materials @@ -51,6 +57,9 @@ export function getRoofDebugMaterials(shading: RenderShading = 'rendered'): THRE createDefaultMaterial('#000000', 0.9, shading, THREE.FrontSide), // 1: Deck createDefaultMaterial('#dddddd', 0.9, shading, THREE.DoubleSide), // 2: Interior createDefaultMaterial('#4ade80', 0.9, shading, THREE.FrontSide), // 3: Shingle + createDefaultMaterial('#f5d0a9', 0.8, shading, THREE.DoubleSide), // 4: Heel band + createDefaultMaterial('#93c5fd', 0.8, shading, THREE.DoubleSide), // 5: Left gable + createDefaultMaterial('#c4b5fd', 0.8, shading, THREE.DoubleSide), // 6: Right gable ] roofDebugMaterialsCache.set(shading, materials) return materials diff --git a/packages/nodes/src/roof/roof-placement-mode.test.ts b/packages/nodes/src/roof/roof-placement-mode.test.ts index 01b0abe48c..9fe5a4ca18 100644 --- a/packages/nodes/src/roof/roof-placement-mode.test.ts +++ b/packages/nodes/src/roof/roof-placement-mode.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, test } from 'bun:test' import useRoofPlacementMode from './roof-placement-mode' describe('roof placement mode', () => { - beforeEach(() => useRoofPlacementMode.setState({ conical: false, mode: 'auto' })) + beforeEach(() => useRoofPlacementMode.setState({ mode: 'auto' })) test('cycles through auto, ground, and roof placement', () => { const state = useRoofPlacementMode.getState() @@ -13,11 +13,4 @@ describe('roof placement mode', () => { useRoofPlacementMode.getState().cycleMode() expect(useRoofPlacementMode.getState().mode).toBe('auto') }) - - test('publishes whether conical placement hints apply', () => { - useRoofPlacementMode.getState().setConical(true) - expect(useRoofPlacementMode.getState().conical).toBe(true) - useRoofPlacementMode.getState().setConical(false) - expect(useRoofPlacementMode.getState().conical).toBe(false) - }) }) diff --git a/packages/nodes/src/roof/roof-placement-mode.ts b/packages/nodes/src/roof/roof-placement-mode.ts index 605bc1948c..9a223cd1c7 100644 --- a/packages/nodes/src/roof/roof-placement-mode.ts +++ b/packages/nodes/src/roof/roof-placement-mode.ts @@ -1,3 +1,4 @@ +import { useEditor } from '@pascal-app/editor' import { create } from 'zustand' export type RoofPlacementMode = 'auto' | 'ground' | 'roof' @@ -5,35 +6,36 @@ export type RoofPlacementMode = 'auto' | 'ground' | 'roof' const MODES: RoofPlacementMode[] = ['auto', 'ground', 'roof'] type RoofPlacementModeState = { - conical: boolean mode: RoofPlacementMode cycleMode: () => void - setConical: (conical: boolean) => void } const useRoofPlacementMode = create((set, get) => ({ - conical: false, mode: 'auto', cycleMode: () => { const current = MODES.indexOf(get().mode) set({ mode: MODES[(current + 1) % MODES.length] ?? 'auto' }) }, - setConical: (conical) => set({ conical }), })) +// Read from the roof type the tool is armed with, not from whichever roof tool mounted: in a +// 2D-only session the 3D tool never mounts, and the HUD showed a cone the standard rows. +const isConicalRoof = (state: ReturnType) => + state.toolDefaults.roof?.roofType === 'conical' + const subscribeToRoofKind = (onChange: () => void) => - useRoofPlacementMode.subscribe((state, previous) => { - if (state.conical !== previous.conical) onChange() + useEditor.subscribe((state, previous) => { + if (isConicalRoof(state) !== isConicalRoof(previous)) onChange() }) export const conicalRoofToolHintVisibility = { subscribe: subscribeToRoofKind, - value: () => useRoofPlacementMode.getState().conical, + value: () => isConicalRoof(useEditor.getState()), } export const standardRoofToolHintVisibility = { subscribe: subscribeToRoofKind, - value: () => !useRoofPlacementMode.getState().conical, + value: () => !isConicalRoof(useEditor.getState()), } export default useRoofPlacementMode diff --git a/packages/nodes/src/roof/roof-plan-creases.ts b/packages/nodes/src/roof/roof-plan-creases.ts new file mode 100644 index 0000000000..81d9710c81 --- /dev/null +++ b/packages/nodes/src/roof/roof-plan-creases.ts @@ -0,0 +1,228 @@ +/** + * The ridges, hips and valleys a roof plan draws, read off the roof surface + * itself rather than off each segment on its own. + * + * A planar segment (gable, hip, shed, flat) is a convex solid: over its + * footprint its top is the lowest of its planes. Segments that overlap are + * unioned, so the roof's top is the highest of the segments there. The lines + * a plan draws are that surface's creases: + * + * - two planes of one segment meeting where both are its top: a ridge + * (level) or a hip (sloped), drawn wherever no other segment rises over + * it, so a wing's ridge runs on past the main roof's wall line until it + * dives under the main roof's slope; + * - two segments' tops meeting: always a valley (the higher of two planes + * is a V), from the corner where their eaves cross up to where it meets + * the ridges. + * + * Everything is linear, so each crease is solved exactly along its line. + */ + +export type PlanPoint = [number, number] + +/** A plane over the plan: its height is `a·x + b·z + c`. */ +export type RoofPlane = { a: number; b: number; c: number } + +export type RoofCreaseSurface = { + /** The roof node the segment belongs to: valleys between two roofs are drawn by one of them. */ + roofId: string + /** The segment's footprint in plan: a convex polygon. */ + footprint: PlanPoint[] + /** The segment's roof planes; its top over the footprint is the lowest of them. */ + planes: RoofPlane[] +} + +export type RoofCreaseKind = 'ridge' | 'hip' | 'valley' + +export type RoofCrease = { + kind: RoofCreaseKind + from: PlanPoint + to: PlanPoint + /** The roof that draws the line: the segment's own roof, or the lesser id of a valley's two. */ + roofId: string + /** The indices of the surfaces that meet there (the same one twice for a ridge or hip). */ + between: [number, number] +} + +type Interval = [number, number] + +const EPS = 1e-6 +/** Shorter than this, a crease is a point where planes meet, not a line. */ +const MIN_CREASE = 0.01 + +const heightAt = (plane: RoofPlane, x: number, z: number) => plane.a * x + plane.b * z + plane.c + +/** The `t` where `slope·t + offset >= 0`, as an interval (empty as `null`). */ +function halfLine(slope: number, offset: number): Interval | null { + if (Math.abs(slope) <= 1e-12) return offset >= -EPS ? [-Infinity, Infinity] : null + const root = -offset / slope + return slope > 0 ? [root, Infinity] : [-Infinity, root] +} + +function intersect(a: Interval | null, b: Interval | null): Interval | null { + if (!(a && b)) return null + const lo = Math.max(a[0], b[0]) + const hi = Math.min(a[1], b[1]) + return hi > lo ? [lo, hi] : null +} + +function subtract(intervals: Interval[], cut: Interval): Interval[] { + return intervals.flatMap(([lo, hi]) => { + if (cut[1] <= lo || cut[0] >= hi) return [[lo, hi] as Interval] + const kept: Interval[] = [] + if (cut[0] > lo) kept.push([lo, cut[0]]) + if (cut[1] < hi) kept.push([cut[1], hi]) + return kept + }) +} + +type Line = { origin: PlanPoint; dir: PlanPoint } + +/** The plan line along which two planes stand at one height, or `null` when they never cross. */ +function meetingLine(p: RoofPlane, q: RoofPlane): Line | null { + const na = p.a - q.a + const nb = p.b - q.b + const norm2 = na * na + nb * nb + if (norm2 <= 1e-12) return null + const dc = p.c - q.c + const norm = Math.sqrt(norm2) + return { + origin: [(-dc * na) / norm2, (-dc * nb) / norm2], + dir: [-nb / norm, na / norm], + } +} + +/** The stretch of a line inside a convex polygon. */ +function clipToPolygon(line: Line, polygon: PlanPoint[]): Interval | null { + if (polygon.length < 3) return null + let area = 0 + for (let i = 0; i < polygon.length; i++) { + const [x0, z0] = polygon[i]! + const [x1, z1] = polygon[(i + 1) % polygon.length]! + area += x0 * z1 - x1 * z0 + } + const sign = area >= 0 ? 1 : -1 + let range: Interval | null = [-Infinity, Infinity] + for (let i = 0; i < polygon.length && range; i++) { + const [x0, z0] = polygon[i]! + const [x1, z1] = polygon[(i + 1) % polygon.length]! + const ex = x1 - x0 + const ez = z1 - z0 + // inside: sign · cross(edge, point − start) >= 0, linear in t + const slope = sign * (ex * line.dir[1] - ez * line.dir[0]) + const offset = sign * (ex * (line.origin[1] - z0) - ez * (line.origin[0] - x0)) + range = intersect(range, halfLine(slope, offset + EPS)) + } + return range +} + +/** Along a line, the height of a plane as `slope·t + offset`. */ +function along(plane: RoofPlane, line: Line): [number, number] { + return [ + plane.a * line.dir[0] + plane.b * line.dir[1], + heightAt(plane, line.origin[0], line.origin[1]), + ] +} + +/** Where `plane` is the lowest of the surface's planes, so the surface's top, along the line. */ +function activeRange(surface: RoofCreaseSurface, plane: RoofPlane, line: Line): Interval | null { + const [ps, po] = along(plane, line) + let range: Interval | null = [-Infinity, Infinity] + for (const other of surface.planes) { + if (other === plane) continue + const [os, oo] = along(other, line) + range = intersect(range, halfLine(os - ps, oo - po + EPS)) + if (!range) return null + } + return range +} + +/** Where `surface` stands higher than the crease (on `plane`) along the line. */ +function buriedRange(surface: RoofCreaseSurface, plane: RoofPlane, line: Line): Interval | null { + const [ps, po] = along(plane, line) + let range = clipToPolygon(line, surface.footprint) + for (const other of surface.planes) { + if (!range) return null + const [os, oo] = along(other, line) + range = intersect(range, halfLine(os - ps, oo - po - EPS * 10)) + } + return range +} + +function pointAt(line: Line, t: number): PlanPoint { + return [line.origin[0] + line.dir[0] * t, line.origin[1] + line.dir[1] * t] +} + +/** The visible stretches of the line where plane `p` of surface `i` meets plane `q` of surface `k`. */ +function visibleStretches( + surfaces: RoofCreaseSurface[], + i: number, + p: RoofPlane, + k: number, + q: RoofPlane, + line: Line, +): Interval[] { + const si = surfaces[i]! + const sk = surfaces[k]! + let range = intersect(clipToPolygon(line, si.footprint), activeRange(si, p, line)) + if (k !== i) range = intersect(range, clipToPolygon(line, sk.footprint)) + range = intersect(range, activeRange(sk, q, line)) + if (!range) return [] + let stretches: Interval[] = [range] + for (let m = 0; m < surfaces.length && stretches.length > 0; m++) { + if (m === i || m === k) continue + const buried = buriedRange(surfaces[m]!, p, line) + if (buried) stretches = subtract(stretches, buried) + } + return stretches.filter(([lo, hi]) => hi - lo >= MIN_CREASE) +} + +/** + * Every ridge, hip and valley of the roof surface the planar segments make + * together. Each crease is reported once, under the roof that draws it. + */ +export function getRoofSurfaceCreases(surfaces: RoofCreaseSurface[]): RoofCrease[] { + const creases: RoofCrease[] = [] + for (let i = 0; i < surfaces.length; i++) { + const si = surfaces[i]! + // Within one segment: ridges and hips. + for (let pi = 0; pi < si.planes.length; pi++) { + for (let qi = pi + 1; qi < si.planes.length; qi++) { + const p = si.planes[pi]! + const q = si.planes[qi]! + const line = meetingLine(p, q) + if (!line) continue + const level = Math.abs(along(p, line)[0]) <= 1e-6 + for (const [lo, hi] of visibleStretches(surfaces, i, p, i, q, line)) { + creases.push({ + kind: level ? 'ridge' : 'hip', + from: pointAt(line, lo), + to: pointAt(line, hi), + roofId: si.roofId, + between: [i, i], + }) + } + } + } + // Between two segments: valleys. + for (let k = i + 1; k < surfaces.length; k++) { + const sk = surfaces[k]! + for (const p of si.planes) { + for (const q of sk.planes) { + const line = meetingLine(p, q) + if (!line) continue + for (const [lo, hi] of visibleStretches(surfaces, i, p, k, q, line)) { + creases.push({ + kind: 'valley', + from: pointAt(line, lo), + to: pointAt(line, hi), + roofId: si.roofId < sk.roofId ? si.roofId : sk.roofId, + between: [i, k], + }) + } + } + } + } + } + return creases +} diff --git a/packages/nodes/src/roof/roof-ridge-mode.ts b/packages/nodes/src/roof/roof-ridge-mode.ts new file mode 100644 index 0000000000..29159e61a2 --- /dev/null +++ b/packages/nodes/src/roof/roof-ridge-mode.ts @@ -0,0 +1,36 @@ +import { create } from 'zustand' + +/** + * Which way a placed roof's ridge runs over the drawn footprint: along its + * long side (the default, as a wing's roof usually does) or across it. The + * roof tool's R key and HUD chip flip it while placing. + */ +export type RoofRidgeMode = 'long' | 'short' + +type RoofRidgeModeState = { + mode: RoofRidgeMode + toggle: () => void + reset: () => void +} + +const useRoofRidgeMode = create((set, get) => ({ + mode: 'long', + toggle: () => set({ mode: get().mode === 'long' ? 'short' : 'long' }), + reset: () => set({ mode: 'long' }), +})) + +/** + * Whether the segment turns a quarter so its ridge runs along z. A segment's + * ridge runs along its own x (its width), so a footprint deeper than it is + * wide turns for a long-side ridge; a square one keeps x. + */ +export function ridgeQuarterTurn( + footprintWidth: number, + footprintDepth: number, + mode: RoofRidgeMode, +): boolean { + const alongDepth = footprintDepth > footprintWidth + 1e-6 + return mode === 'long' ? alongDepth : !alongDepth +} + +export default useRoofRidgeMode diff --git a/packages/nodes/src/roof/tool.test.ts b/packages/nodes/src/roof/tool.test.ts index bc921f3d64..71a57af696 100644 --- a/packages/nodes/src/roof/tool.test.ts +++ b/packages/nodes/src/roof/tool.test.ts @@ -2,21 +2,31 @@ import { afterEach, expect, test } from 'bun:test' import { type AnyNode, type AnyNodeId, + BuildingNode, + clearSceneHistory, + createSceneApi, + emitter, + type GridEvent, LevelNode, - type RoofNode, + RoofNode, + type RoofSegmentNode, resolveRoomRoofFootprint, type SceneApi, + useScene, WallNode, ZoneNode, } from '@pascal-app/core' -import { useEditor } from '@pascal-app/editor' -import { commitRoofFootprint, commitRoofPlacement } from './tool' +import { RegistryToolProvider, sceneLayout, useEditor } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { createElement } from 'react' +import { commitRoofFootprint, commitRoofPlacement, RoofTool } from './tool' const originalDefaults = useEditor.getState().toolDefaults afterEach(() => useEditor.setState({ toolDefaults: originalDefaults })) -function setup() { +function setup(thickness?: number) { const level = LevelNode.parse({ level: 0, height: 3 }) const upper = LevelNode.parse({ level: 1, height: 3 }) const polygon: Array<[number, number]> = [ @@ -31,6 +41,7 @@ function setup() { start, end: polygon[(index + 1) % polygon.length], height: 2.5, + thickness, }), ) level.children = walls.map((wall) => wall.id) @@ -40,6 +51,7 @@ function setup() { const sceneApi = { nodes: () => nodes, createMany: (ops) => created.push(...ops), + upsert: (node, parentId) => created.push({ node, parentId }), update: (id, data) => updated.push({ id, data }), } as SceneApi return { level, upper, nodes, created, updated, sceneApi } @@ -121,3 +133,141 @@ test('a porch drawn against the house over posts stays on its storey', () => { expect(roof.node).toMatchObject({ support: { kind: 'level' } }) expect(updated).toEqual([]) }) + +// Standard trusses bear on the top plate: a raised heel is an explicit Wall Height, never a default. +test('a drawn roof bears on the top plate, and a raised heel stays a setting', () => { + const { level, created, sceneApi } = setup() + commitRoofPlacement(sceneApi, level.id, [0, 0, 0], [4, 0, 3], [], false, 'ground') + const segment = created.find(({ node }) => node.type === 'roof-segment')?.node + expect(segment).toMatchObject({ type: 'roof-segment', wallHeight: 0 }) + + const raised = setup() + useEditor.getState().setToolDefaults('roof', { roofType: 'gable', wallHeight: 0.45 }) + commitRoofPlacement(raised.sceneApi, raised.level.id, [0, 0, 0], [4, 0, 3], [], false, 'ground') + const heeled = raised.created.find(({ node }) => node.type === 'roof-segment')?.node + expect(heeled).toMatchObject({ type: 'roof-segment', wallHeight: 0.45 }) +}) + +// A segment's ridge runs along its width (local x): a long-side ridge over a deep rectangle turns a quarter. +test('a drawn gable runs its ridge along the long side by default, and across it on request', () => { + const ridgeOf = (corner2: [number, number, number], ridge: 'long' | 'short') => { + const { level, created, sceneApi } = setup() + commitRoofPlacement(sceneApi, level.id, [0, 0, 0], corner2, [], ridge, 'ground') + return created.find(({ node }) => node.type === 'roof-segment')?.node + } + expect(ridgeOf([2, 0, 6], 'long')).toMatchObject({ width: 6, depth: 2, rotation: Math.PI / 2 }) + expect(ridgeOf([6, 0, 2], 'long')).toMatchObject({ width: 6, depth: 2, rotation: 0 }) + expect(ridgeOf([2, 0, 6], 'short')).toMatchObject({ width: 2, depth: 6, rotation: 0 }) +}) + +const segmentOf = (created: Array<{ node: AnyNode }>) => + created.find(({ node }) => node.type === 'roof-segment')?.node as RoofSegmentNode | undefined + +// Gable and heel walls 0.1 m thick on 0.18 m walls stood a ledge proud of the walls below. +test('a roof drawn or made over walls gives its gable and heel walls their thickness', () => { + const drawn = setup(0.18) + commitRoofPlacement(drawn.sceneApi, drawn.level.id, [0, 0, 0], [4, 0, 3], [], false, 'ground') + expect(segmentOf(drawn.created)?.wallThickness).toBeCloseTo(0.18) + + const room = setup(0.18) + const target = resolveRoomRoofFootprint(room.level.id, room.nodes, [2, 1])! + commitRoofFootprint(room.sceneApi, room.level.id, target, false) + expect(segmentOf(room.created)?.wallThickness).toBeCloseTo(0.18) + + // Off the walls, a plane keeps the default. + const porch = setup(0.18) + commitRoofPlacement(porch.sceneApi, porch.level.id, [5, 0, 0.5], [7, 0, 2.5], [], false, 'ground') + expect(segmentOf(porch.created)?.wallThickness).toBeCloseTo(0.1) +}) + +test('a plane added to a selected roof over the walls takes their thickness', () => { + const { upper, nodes, created, sceneApi } = setup(0.18) + const roof = RoofNode.parse({ parentId: upper.id, position: [2, 0, 1.5], children: [] }) + const scene = nodes as Record + scene[roof.id] = roof + scene[upper.id] = { ...upper, children: [roof.id] } + commitRoofPlacement(sceneApi, upper.id, [0, 0, 0], [4, 0, 3], [roof.id], false, 'ground') + const segment = segmentOf(created) + expect(segment?.position).toEqual([0, 0, 0]) + expect(segment?.wallThickness).toBeCloseTo(0.18) +}) + +type RafFn = (cb: (time: number) => void) => number + +// Hidden behind the canvas in 2D-only view, the 3D tool kept a first corner from before the view +// changed: back in split view, the next click made a roof from that corner. +test('a corner opened before the view went 2D-only does not end the next roof in split view', async () => { + const globals = globalThis as unknown as { + IS_REACT_ACT_ENVIRONMENT?: boolean + requestAnimationFrame?: RafFn + cancelAnimationFrame?: (id: number) => void + } + globals.IS_REACT_ACT_ENVIRONMENT = true + globals.requestAnimationFrame ??= (cb) => { + cb(0) + return 0 + } + globals.cancelAnimationFrame ??= () => {} + const previous = { editor: useEditor.getState(), viewer: useViewer.getState() } + const windowDescriptor = Object.getOwnPropertyDescriptor(globalThis, 'window') + globalThis.window = new EventTarget() as Window & typeof globalThis + + const { level, upper, nodes } = setup() + delete nodes[upper.id] + const building = BuildingNode.parse({ children: [level.id] }) + useScene.getState().setScene( + { + ...nodes, + [building.id]: building, + [level.id]: { ...level, parentId: building.id }, + } as Record, + [building.id as AnyNodeId], + { installedPlugins: [], hasExplicitPluginInstallState: false }, + ) + clearSceneHistory() + useViewer.getState().setSelection({ buildingId: building.id, levelId: level.id }) + // Over the plan, the cursor's DOM tooltip stays out of the canvas. + useEditor.setState({ mode: 'build', tool: 'roof', isFloorplanHovered: true }) + const showViews = (scene: '2d' | 'split') => + act(async () => useEditor.setState({ viewLayouts: { edit: sceneLayout(scene) } })) + const click = (x: number, z: number) => + act(async () => { + const event = { + position: [x, 0, z], + localPosition: [x, 0, z], + nativeEvent: {} as GridEvent['nativeEvent'], + } satisfies GridEvent + emitter.emit('grid:move', event) + emitter.emit('grid:click', event) + }) + const roofs = () => Object.values(useScene.getState().nodes).filter((n) => n.type === 'roof') + + await showViews('split') + const renderer = await create( + createElement(RegistryToolProvider, { + value: { + activeLevelId: level.id, + isCameraDragging: () => false, + sceneApi: createSceneApi(useScene), + selectNode: (id) => useViewer.getState().setSelection({ selectedIds: [id] }), + unit: 'metric', + }, + children: createElement(RoofTool), + }), + ) + try { + await click(0, 0) + await showViews('2d') + await showViews('split') + await click(4, 3) + expect(roofs()).toHaveLength(0) + await click(8, 6) + expect(roofs()).toHaveLength(1) + } finally { + await act(async () => renderer.unmount()) + useEditor.setState(previous.editor, true) + useViewer.setState(previous.viewer, true) + if (windowDescriptor) Object.defineProperty(globalThis, 'window', windowDescriptor) + else Reflect.deleteProperty(globalThis, 'window') + } +}) diff --git a/packages/nodes/src/roof/tool.tsx b/packages/nodes/src/roof/tool.tsx index 8c8a047c08..032bfc99dd 100644 --- a/packages/nodes/src/roof/tool.tsx +++ b/packages/nodes/src/roof/tool.tsx @@ -9,6 +9,7 @@ import { getWallArcData, getWallBaseElevationForNodes, getWallEffectiveHeightForNodes, + getWallThickness, isCurvedWall, type LevelNode, type RoofFootprintTarget, @@ -18,9 +19,7 @@ import { RoofType as RoofTypeSchema, resolveBuildingForLevel, resolveLevelId, - resolveRoomRoofFootprint, type SceneApi, - sceneRegistry, type WallEvent, type WallNode, wallSegmentAnchors, @@ -39,6 +38,7 @@ import { useFloorplanDraftPreview, useInteractionScope, useRegistryToolContext, + visibleScene, } from '@pascal-app/editor' import { generateRoofSegmentGeometry, useViewer } from '@pascal-app/viewer' import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react' @@ -56,9 +56,13 @@ import { resolveConicalRoofPlacement } from './conical-roof-placement' import { isStandardRoofWallEligible, parseRoofFootprintSource, + pickRoofRoom, resolveRoofFootprintElevation, resolveRoofFootprintWorldElevation, resolveRoofWallTopWorldElevation, + roofEdgeAnchors, + roofFootprintPreviewIds, + roofSnapEdges, subscribeToConicalRoofWallClicks, } from './roof-footprint' import useRoofFootprintSource from './roof-footprint-source' @@ -72,8 +76,19 @@ import { writeSeatedRoofEdit, } from './roof-level-target' import useRoofPlacementMode, { type RoofPlacementMode } from './roof-placement-mode' - -const DEFAULT_WALL_HEIGHT = 0.5 +import useRoofRidgeMode, { type RoofRidgeMode, ridgeQuarterTurn } from './roof-ridge-mode' +import { + bearingWallThickness, + type RoofCatchLine, + roofCatchWalls, + segmentWallLine, + settleSegmentOnEdges, +} from './wall-catch' + +// Rafters bear on the top plate, as standard trusses do; a raised heel is the segment's Wall Height. +const DEFAULT_HEEL_HEIGHT = 0 +// A cone's curb: it stands on the ground or on another roof, not on a plate. +const DEFAULT_CONICAL_CURB_HEIGHT = 0.5 const DEFAULT_PITCH_DEG = 40 const GRID_OFFSET = 0.02 @@ -84,10 +99,24 @@ function placementOptions(mode: RoofPlacementMode) { } } +/** + * A placed segment's quarter turn: given outright, or from a ridge mode, which + * runs the ridge along the footprint's long side ('long') or across it. + */ +function resolveQuarterTurn( + ridge: boolean | RoofRidgeMode, + footprintWidth: number, + footprintDepth: number, +): boolean { + return typeof ridge === 'boolean' + ? ridge + : ridgeQuarterTurn(footprintWidth, footprintDepth, ridge) +} + function resolveRoofDraftPlacement( footprintWidth: number, footprintDepth: number, - quarterTurn: boolean, + ridge: boolean | RoofRidgeMode, parentRotation = 0, roofType: RoofType = 'gable', ) { @@ -95,6 +124,7 @@ function resolveRoofDraftPlacement( const diameter = Math.max(footprintWidth, footprintDepth) return { width: diameter, depth: diameter, rotation: -parentRotation } } + const quarterTurn = resolveQuarterTurn(ridge, footprintWidth, footprintDepth) return { width: quarterTurn ? footprintDepth : footprintWidth, depth: quarterTurn ? footprintWidth : footprintDepth, @@ -115,33 +145,51 @@ function getLevelWalls( .filter((node): node is WallNode => node?.type === 'wall') } -// Walls on the level directly beneath the active one. Levels share the same -// local XZ origin (they only differ in world Y), so these walls live in the -// identical coordinate frame and feed straight into both the alignment pool -// and the magnetic wall-snap pipeline — letting a roof drawn on the upper -// floor snap onto the wall corners of the floor below. -function getBelowLevelWalls( +// The level directly beneath the active one. Levels share the same local XZ +// origin (they only differ in world Y), so its walls live in the identical +// coordinate frame and feed straight into both the alignment pool and the +// magnetic wall-snap pipeline — letting a roof drawn on the upper floor snap +// onto the wall corners of the floor below. +function getBelowLevel( currentLevelId: string | null, nodes: Readonly>, -): WallNode[] { - if (!currentLevelId) return [] +): LevelNode | undefined { + if (!currentLevelId) return undefined const currentLevel = nodes[currentLevelId] - if (currentLevel?.type !== 'level') return [] + if (currentLevel?.type !== 'level') return undefined const buildingId = resolveBuildingForLevel(currentLevel.id, nodes) - if (!buildingId) return [] + if (!buildingId) return undefined const building = nodes[buildingId] - if (building?.type !== 'building') return [] + if (building?.type !== 'building') return undefined const currentIndex = (currentLevel as LevelNode).level - const belowLevel = (building.children ?? []) + return (building.children ?? []) .map((childId) => nodes[childId]) .filter((node): node is LevelNode => node?.type === 'level' && node.level < currentIndex) .sort((a, b) => b.level - a.level)[0] - return getLevelWalls(belowLevel?.id ?? null, nodes) +} + +// Walls on the level directly beneath the active one. +function getBelowLevelWalls( + currentLevelId: string | null, + nodes: Readonly>, +): WallNode[] { + return getLevelWalls(getBelowLevel(currentLevelId, nodes)?.id ?? null, nodes) +} + +// The active level and the one beneath it, whose rooms' edges a roof draws to. +function roofEdgeLevelIds( + currentLevelId: string | null, + nodes: Readonly>, +): string[] { + const below = getBelowLevel(currentLevelId, nodes) + return [currentLevelId, below?.id].filter((id): id is string => !!id) } // Current-level + floor-below walls — the magnetic snap targets the roof draft // locks onto (corners, midpoints, crossings, wall bodies), matching the wall -// tool. Same coordinate frame, so no transform is needed. +// tool. Same coordinate frame, so no transform is needed. A porch, deck or +// patio is drawn to as the house is: its invisible walls, and its railings at +// their outside faces in place of their centre lines (`roofSnapEdges`). function getRoofSnapWalls( currentLevelId: string | null, nodes: Readonly>, @@ -151,13 +199,16 @@ function getRoofSnapWalls( ...getLevelWalls(currentLevelId, nodes), ...getBelowLevelWalls(currentLevelId, nodes), ] - return roofType === 'conical' - ? walls.filter((wall) => isCurvedWall(wall)) - : walls.filter(isStandardRoofWallEligible) + if (roofType === 'conical') return walls.filter((wall) => isCurvedWall(wall)) + const edges = roofSnapEdges(nodes, roofEdgeLevelIds(currentLevelId, nodes)) + return [...walls.filter((wall) => !edges.faced.has(wall.id)), ...edges.references].filter( + isStandardRoofWallEligible, + ) } -// Current-level alignment anchors plus the floor-below wall corners. -function collectRoofAlignmentAnchors( +// Current-level alignment anchors plus the floor-below wall corners, and both +// levels' invisible walls and porch, deck and patio corners. +export function collectRoofAlignmentAnchors( nodes: Readonly>, currentLevelId: string | null, roofType: RoofType, @@ -165,8 +216,11 @@ function collectRoofAlignmentAnchors( const anchors = [ ...collectAlignmentAnchors(nodes, '', currentLevelId), ...getBelowLevelWalls(currentLevelId, nodes).flatMap((wall) => - wallSegmentAnchors(wall.id, wall.start, wall.end, wall.thickness), + wallSegmentAnchors(wall.id, wall.start, wall.end, wall.thickness, wall.justification), ), + ...(roofType === 'conical' + ? [] + : roofEdgeAnchors(nodes, roofEdgeLevelIds(currentLevelId, nodes))), ] if (roofType === 'conical') { return anchors.filter((anchor) => { @@ -181,7 +235,69 @@ function collectRoofAlignmentAnchors( } /** - * Creates a roof group with one default gable segment + * A grid event's point as the roof tools draft and commit it, the 3D tool and the plan's alike: + * through the shared surface snap pipeline, magnetic lock onto wall corners / midpoints / crossings + * / bodies on the active level and the floor below (raising the green beacon), falling back to + * alignment guides, then to the building-grid snap. The pipeline reads the active snapping mode + * (grid / lines / angles / off). `levelId` is intentionally omitted so the explicit floor-below + * `walls` aren't filtered back out. + */ +export function resolveRoofDraftPoint( + event: GridEvent, + levelId: string, + nodes: Readonly>, + roofType: RoofType, + candidates: readonly AlignmentAnchor[], +): [number, number] { + const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]] + const gridFallback: [number, number] = isGridSnapActive() + ? snapWorldXZForActiveBuilding( + event.position[0], + event.position[2], + useEditor.getState().gridSnapStep, + ).local + : rawPoint + return resolveSurfacePlanPointSnap({ + rawPoint, + fallbackPoint: gridFallback, + walls: getRoofSnapWalls(levelId, nodes, roofType), + candidates, + movingId: '__roof-draft__', + highlightWalls: true, + }).point +} + +/** + * A drawn segment whose gable and heel walls stand flush on the walls it is drawn over, and whose + * walls' outer faces stand on the rooms' edges it is drawn out to: a porch's invisible walls, a + * deck's railings at their outside faces (`settleSegmentOnEdges`). + */ +function onBearingWalls( + segment: RoofSegmentNode, + roof: Pick, + walls: readonly RoofCatchLine[], +): RoofSegmentNode { + const thickness = bearingWallThickness(segmentWallLine(roof, segment), walls) + const bearing = thickness === null ? segment : { ...segment, wallThickness: thickness } + return { ...bearing, ...settleSegmentOnEdges(roof, bearing, walls) } +} + +/** + * A new roof's place, and its one segment centred in it: the segment settled onto a room's edge + * (`onBearingWalls`) moves off the roof's centre, so the roof moves with it. + */ +function centredRoof( + roof: Pick, + segment: RoofSegmentNode, +): { center: [number, number]; segment: RoofSegmentNode } { + const [x, z] = segmentWallLine(roof, segment).center + return { center: [x, z], segment: { ...segment, position: [0, segment.position[1], 0] } } +} + +/** + * Creates a roof group with one default gable segment. `ridge` is a quarter + * turn outright, or a ridge mode: 'long' runs the ridge along the drawn + * rectangle's long side, 'short' across it. */ export const commitRoofPlacement = ( sceneApi: SceneApi, @@ -189,7 +305,7 @@ export const commitRoofPlacement = ( corner1: [number, number, number], corner2: [number, number, number], selectedIds: string[], - quarterTurn: boolean, + ridge: boolean | RoofRidgeMode, placementMode: RoofPlacementMode, ): AnyNode['id'] | null => { const nodes = sceneApi.nodes() @@ -212,7 +328,7 @@ export const commitRoofPlacement = ( if (roofType === 'conical') { const diameter = Math.max(footprintWidth, footprintDepth) const curbHeight = - typeof defaults.wallHeight === 'number' ? defaults.wallHeight : DEFAULT_WALL_HEIGHT + typeof defaults.wallHeight === 'number' ? defaults.wallHeight : DEFAULT_CONICAL_CURB_HEIGHT const cone = RoofSegmentNode.parse({ pitch: DEFAULT_PITCH_DEG, ...defaults, @@ -277,29 +393,18 @@ export const commitRoofPlacement = ( if (targetRoofId) { const targetRoof = nodes[targetRoofId] as RoofNode - let localX = centerX - let localZ = centerZ - - // Convert world coordinates to the local space of the parent roof - const targetObj = sceneRegistry.nodes.get(targetRoofId) - if (targetObj) { - const worldVec = new THREE.Vector3(centerX, 0, centerZ) - targetObj.worldToLocal(worldVec) - localX = worldVec.x - localZ = worldVec.z - } else { - // Math fallback if mesh isn't ready - const dx = centerX - targetRoof.position[0] - const dz = centerZ - targetRoof.position[2] - const angle = -targetRoof.rotation - localX = dx * Math.cos(angle) - dz * Math.sin(angle) - localZ = dx * Math.sin(angle) + dz * Math.cos(angle) - } + // The corners are in the level's plan, the roof's frame: its own place and turn in it. (Its + // mesh's world matrix also carries the building's, which would throw the plane off.) + const dx = centerX - targetRoof.position[0] + const dz = centerZ - targetRoof.position[2] + const angle = -targetRoof.rotation + const localX = dx * Math.cos(angle) - dz * Math.sin(angle) + const localZ = dx * Math.sin(angle) + dz * Math.cos(angle) const placement = resolveRoofDraftPlacement( footprintWidth, footprintDepth, - quarterTurn, + ridge, targetRoof.rotation, roofType, ) @@ -307,12 +412,13 @@ export const commitRoofPlacement = ( const sibling = targetRoof.children .map((id) => nodes[id]) .find((node) => node?.type === 'roof-segment') - const segment = RoofSegmentNode.parse({ + const drawn = RoofSegmentNode.parse({ roofSystem: sibling?.type === 'roof-segment' ? sibling.roofSystem : undefined, glazing: sibling?.type === 'roof-segment' ? sibling.glazing : undefined, barrelRise: sibling?.type === 'roof-segment' ? sibling.barrelRise : undefined, barrelSegments: sibling?.type === 'roof-segment' ? sibling.barrelSegments : undefined, - wallHeight: DEFAULT_WALL_HEIGHT, + // A wing bears where the roof it joins does: on the plate, or on the same raised heel. + wallHeight: sibling?.type === 'roof-segment' ? sibling.wallHeight : DEFAULT_HEEL_HEIGHT, pitch: DEFAULT_PITCH_DEG, roofType: 'gable', ...defaults, @@ -321,6 +427,7 @@ export const commitRoofPlacement = ( position: [localX, 0, localZ], rotation: placement.rotation, }) + const segment = onBearingWalls(drawn, targetRoof, roofCatchWalls(nodes, targetRoof.parentId)) writeSeatedRoofEdit(sceneApi, { create: [{ node: segment, parentId: targetRoofId as AnyNode['id'] }], @@ -336,22 +443,36 @@ export const commitRoofPlacement = ( const placement = resolveRoofDraftPlacement( footprintWidth, footprintDepth, - quarterTurn, + ridge, roofRotation, roofType, ) // Create the segment first (centered in its new parent) - const segment = RoofSegmentNode.parse({ - wallHeight: DEFAULT_WALL_HEIGHT, - pitch: DEFAULT_PITCH_DEG, - roofType: 'gable', - ...defaults, - width: placement.width, - depth: placement.depth, - position: [0, 0, 0], - rotation: placement.rotation, - }) + const drawnRoof = { + position: [centerX, 0, centerZ] as [number, number, number], + rotation: roofRotation, + } + const { + center: [roofX, roofZ], + segment, + } = centredRoof( + drawnRoof, + onBearingWalls( + RoofSegmentNode.parse({ + wallHeight: DEFAULT_HEEL_HEIGHT, + pitch: DEFAULT_PITCH_DEG, + roofType: 'gable', + ...defaults, + width: placement.width, + depth: placement.depth, + position: [0, 0, 0], + rotation: placement.rotation, + }), + drawnRoof, + roofCatchWalls(nodes, levelId), + ), + ) // Drawn over a storey's walls, the roof goes on the level above them. const target = drawnRoofLevelTarget( @@ -366,7 +487,7 @@ export const commitRoofPlacement = ( const placed = RoofNode.parse({ ...defaults, name, - position: [centerX, 0, centerZ], + position: [roofX, 0, roofZ], support, children: [segment.id], }) @@ -374,7 +495,7 @@ export const commitRoofPlacement = ( support.kind === 'walls' ? RoofNode.parse({ ...placed, - position: [centerX, seatedRoofElevation(target, placed, [segment]), centerZ], + position: [roofX, seatedRoofElevation(target, placed, [segment]), roofZ], }) : placed // Create roof first (so segment can be parented to it), then segment @@ -391,19 +512,25 @@ export const commitRoofFootprint = ( sceneApi: SceneApi, levelId: LevelNode['id'], target: RoofFootprintTarget, - quarterTurn: boolean, + ridge: boolean | RoofRidgeMode, ): AnyNode['id'] | null => { if (!target.rectangular) return null + const quarterTurn = resolveQuarterTurn(ridge, target.width, target.depth) const nodes = sceneApi.nodes() const defaults = useEditor.getState().toolDefaults.roof ?? {} const parsedRoofType = RoofTypeSchema.safeParse(defaults.roofType) const roofType = parsedRoofType.success ? parsedRoofType.data : 'gable' if (roofType === 'conical') return null const roofCount = Object.values(nodes).filter((node) => node.type === 'roof').length - const segment = RoofSegmentNode.parse({ + const walls = target.wallIds + .map((id) => nodes[id]) + .filter((node): node is WallNode => node?.type === 'wall') + const picked = RoofSegmentNode.parse({ pitch: DEFAULT_PITCH_DEG, roofType: 'gable', ...defaults, + // Its gable and heel walls stand flush on the room's walls. + ...(walls.length > 0 && { wallThickness: Math.max(...walls.map(getWallThickness)) }), wallHeight: 0, width: quarterTurn ? target.depth : target.width, depth: quarterTurn ? target.width : target.depth, @@ -412,16 +539,27 @@ export const commitRoofFootprint = ( }) // A roof belongs to the level above the walls it covers, whichever level the tool was armed // on: over the top floor, the building's roof level. - const firstWall = target.wallIds.map((id) => nodes[id]).find((node) => node?.type === 'wall') - const wallsLevelId = (firstWall ? resolveLevelId(firstWall, nodes) : levelId) as LevelNode['id'] + const firstWall = walls[0] + const wallsLevelId = (target.levelId ?? + (firstWall ? resolveLevelId(firstWall, nodes) : levelId)) as LevelNode['id'] + // Over a porch, deck or patio, the roof's walls stand their outer faces on the room's outside + // edge, as a house roof's stand on the walls' outside faces. + const pickedRoof = { + position: [target.center[0], 0, target.center[1]] as [number, number, number], + rotation: target.rotation, + } + const { center, segment } = centredRoof(pickedRoof, { + ...picked, + ...settleSegmentOnEdges(pickedRoof, picked, roofCatchWalls(nodes, wallsLevelId)), + }) const level = roofLevelTarget(nodes, wallsLevelId, getActiveRoofHeight(segment)) const roof = RoofNode.parse({ ...defaults, name: `Roof ${roofCount + 1}`, position: [ - target.center[0], + center[0], resolveRoofFootprintElevation(level.levelId, target, level.nodes), - target.center[1], + center[1], ], rotation: target.rotation, support: { kind: 'walls' }, @@ -638,7 +776,11 @@ export const RoofTool: React.FC = () => { const footprintSourceChoice = useRoofFootprintSource((state) => state.source) const footprintSource = parseRoofFootprintSource(footprintSourceChoice, roofType) const previewWallHeight = - typeof roofDefaults?.wallHeight === 'number' ? roofDefaults.wallHeight : DEFAULT_WALL_HEIGHT + typeof roofDefaults?.wallHeight === 'number' + ? roofDefaults.wallHeight + : roofType === 'conical' + ? DEFAULT_CONICAL_CURB_HEIGHT + : DEFAULT_HEEL_HEIGHT const previewPitch = typeof roofDefaults?.pitch === 'number' ? roofDefaults.pitch : DEFAULT_PITCH_DEG @@ -647,11 +789,6 @@ export const RoofTool: React.FC = () => { selectedIdsRef.current = selectedIds }, [selectedIds]) - useEffect(() => { - useRoofPlacementMode.getState().setConical(roofType === 'conical') - return () => useRoofPlacementMode.getState().setConical(false) - }, [roofType]) - useEffect(() => { if (!currentLevelId) return const draft = RoofNode.parse({ @@ -678,11 +815,12 @@ export const RoofTool: React.FC = () => { // Clear preset-seeded defaults on deactivation so a later manual roof draw // isn't built with a stale preset's parameters. Unmount-only. useEffect(() => () => useEditor.getState().setToolDefaults('roof', null), []) + // Each arming starts with the ridge along the long side. Unmount-only. + useEffect(() => () => useRoofRidgeMode.getState().reset(), []) const corner1Ref = useRef<[number, number, number] | null>(null) const previousGridPosRef = useRef<[number, number] | null>(null) - const quarterTurnRef = useRef(false) - const [quarterTurn, setQuarterTurn] = useState(false) + const ridgeMode = useRoofRidgeMode((state) => state.mode) const [footprintTarget, setFootprintTarget] = useState(null) const previewTargetIdRef = useRef(null) const [previewedConicalWallId, setPreviewedConicalWallId] = useState(null) @@ -703,7 +841,18 @@ export const RoofTool: React.FC = () => { if (!currentLevelId) return outlineRef.current.geometry = new BufferGeometry() - useFloorplanDraftPreview.getState().setRoofDraftQuarterTurn(quarterTurnRef.current) + // The 2D draft's ridge line follows the drawn rectangle and the ridge mode. + const syncDraftRidge = (corner1: [number, number, number], corner2: [number, number, number]) => + useFloorplanDraftPreview + .getState() + .setRoofDraftQuarterTurn( + ridgeQuarterTurn( + Math.abs(corner2[0] - corner1[0]), + Math.abs(corner2[2] - corner1[2]), + useRoofRidgeMode.getState().mode, + ), + ) + let lastCursor: [number, number, number] | null = null // Alignment candidates — anchors of every alignable object on the active // level plus the wall corners of the floor directly below, so a roof drawn @@ -715,39 +864,14 @@ export const RoofTool: React.FC = () => { roofType, ) - // Resolve a grid:move/click into the drafted corner via the shared surface - // snap pipeline: magnetic lock onto wall corners / midpoints / crossings / - // bodies on the active level + floor below (raising the green beacon), - // falling back to alignment guides, then to the world-grid snap. The same - // path the slab/ceiling tools use, so the beacon and coloring match. The - // pipeline reads the active snapping mode (grid / lines / angles / off), - // so this tool never inspects the flags. `levelId` is intentionally omitted - // so the explicit floor-below `walls` aren't filtered back out. - const resolveDraftPoint = (event: GridEvent): [number, number] => { - const rawPoint: [number, number] = [event.localPosition[0], event.localPosition[2]] - const gridFallback: [number, number] = isGridSnapActive() - ? snapWorldXZForActiveBuilding( - event.position[0], - event.position[2], - useEditor.getState().gridSnapStep, - ).local - : rawPoint - const nodes = sceneApi.nodes() - return resolveSurfacePlanPointSnap({ - rawPoint, - fallbackPoint: gridFallback, - walls: getRoofSnapWalls(currentLevelId, nodes, roofType), - candidates: alignmentCandidates, - movingId: '__roof-draft__', - highlightWalls: true, - }).point - } + const resolveDraftPoint = (event: GridEvent) => + resolveRoofDraftPoint(event, currentLevelId, sceneApi.nodes(), roofType, alignmentCandidates) const updateFootprintPreview = (target: RoofFootprintTarget | null) => { setFootprintTarget((previous) => (previous?.id === target?.id ? previous : target)) if (previewTargetIdRef.current === (target?.id ?? null)) return previewTargetIdRef.current = target?.id ?? null - setPreviewSelectedIds(target?.wallIds ?? []) + setPreviewSelectedIds(roofFootprintPreviewIds(target)) } const updateOutline = ( @@ -765,7 +889,9 @@ export const RoofTool: React.FC = () => { ) const defaults = useEditor.getState().toolDefaults.roof const curbHeight = - typeof defaults?.wallHeight === 'number' ? defaults.wallHeight : DEFAULT_WALL_HEIGHT + typeof defaults?.wallHeight === 'number' + ? defaults.wallHeight + : DEFAULT_CONICAL_CURB_HEIGHT const target = drawnRoofLevelTarget(sceneApi.nodes(), currentLevelId, 0, { center: [centerX, centerZ], width: diameter, @@ -821,21 +947,21 @@ export const RoofTool: React.FC = () => { outlineRef.current.visible = true } + // In 2D-only view the plan's own roof tool owns the input (`floorplan-tool.tsx`); this tool, + // mounted behind the hidden canvas from an earlier visit, still hears the plan's grid events, + // so it must not also draft or commit (every roof would be made twice). + const planOwnsInput = () => visibleScene(useEditor.getState()) === '2d' + const onGridMove = (event: GridEvent) => { - if (!cursorRef.current) return + if (!cursorRef.current || planOwnsInput()) return if (footprintSource !== 'draw') { const [snappedX, snappedZ] = resolveDraftPoint(event) let target: RoofFootprintTarget | null = null if (footprintSource === 'room') { - target = resolveRoomRoofFootprint( - currentLevelId, - sceneApi.nodes(), - [snappedX, snappedZ], - { - rectangularOnly: true, - }, - ) + const room = pickRoofRoom(currentLevelId, sceneApi.nodes(), [snappedX, snappedZ]) + target = room.target + useRoofFootprintSource.getState().setRoomPick(room.pick) updateFootprintPreview(target) } cursorRef.current.position.set( @@ -878,24 +1004,30 @@ export const RoofTool: React.FC = () => { const draftPreview = useFloorplanDraftPreview.getState() draftPreview.setRoofDraftStart([corner1Ref.current[0], corner1Ref.current[2]]) draftPreview.setRoofDraftEnd([gridX, gridZ]) + syncDraftRidge(corner1Ref.current, cursorPosition) updateOutline(corner1Ref.current, cursorPosition) } + lastCursor = cursorPosition } const onGridClick = (event: GridEvent) => { - if (!currentLevelId) return + if (!currentLevelId || planOwnsInput()) return if (footprintSource !== 'draw') { if (footprintSource !== 'room') return const [snappedX, snappedZ] = resolveDraftPoint(event) - const target = resolveRoomRoofFootprint( + const { target, pick } = pickRoofRoom(currentLevelId, sceneApi.nodes(), [ + snappedX, + snappedZ, + ]) + useRoofFootprintSource.getState().setRoomPick(pick) + if (!target) return + const roofId = commitRoofFootprint( + sceneApi, currentLevelId, - sceneApi.nodes(), - [snappedX, snappedZ], - { rectangularOnly: true }, + target, + useRoofRidgeMode.getState().mode, ) - if (!target) return - const roofId = commitRoofFootprint(sceneApi, currentLevelId, target, quarterTurnRef.current) if (roofId) selectNode(roofId) return } @@ -910,7 +1042,7 @@ export const RoofTool: React.FC = () => { corner1Ref.current, [gridX, y, gridZ], selectedIdsRef.current, - quarterTurnRef.current, + useRoofRidgeMode.getState().mode, useRoofPlacementMode.getState().mode, ) @@ -942,15 +1074,25 @@ export const RoofTool: React.FC = () => { } } + const dropCorner = () => { + corner1Ref.current = null + outlineRef.current.visible = false + setPreview((prev) => ({ ...prev, corner1: null })) + } + + // A corner opened before the view went 2D-only would end the next rectangle drawn back in + // split view. The plan's tool clears the shared draft as it takes over. + const unsubscribeView = useEditor.subscribe((state) => { + if (corner1Ref.current && visibleScene(state) === '2d') dropCorner() + }) + const onCancel = () => { if (corner1Ref.current) { markToolCancelConsumed() - corner1Ref.current = null + dropCorner() const draftPreview = useFloorplanDraftPreview.getState() draftPreview.setRoofDraftStart(null) draftPreview.setRoofDraftEnd(null) - outlineRef.current.visible = false - setPreview((prev) => ({ ...prev, corner1: null })) } clearSurfacePlanSnapFeedback() previewTargetIdRef.current = null @@ -958,6 +1100,7 @@ export const RoofTool: React.FC = () => { } const onKeyDown = (event: KeyboardEvent) => { + if (planOwnsInput()) return if ( event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement || @@ -990,12 +1133,13 @@ export const RoofTool: React.FC = () => { } event.preventDefault() - const nextQuarterTurn = !quarterTurnRef.current - quarterTurnRef.current = nextQuarterTurn - setQuarterTurn(nextQuarterTurn) - useFloorplanDraftPreview.getState().setRoofDraftQuarterTurn(nextQuarterTurn) + useRoofRidgeMode.getState().toggle() triggerSFX('sfx:item-rotate') } + // The HUD chip flips the mode too; either way the 2D draft follows. + const unsubscribeRidgeMode = useRoofRidgeMode.subscribe(() => { + if (corner1Ref.current && lastCursor) syncDraftRidge(corner1Ref.current, lastCursor) + }) emitter.on('grid:move', onGridMove) emitter.on('grid:click', onGridClick) @@ -1043,11 +1187,14 @@ export const RoofTool: React.FC = () => { emitter.off('wall:leave', onWallLeave) unsubscribeConicalRoofWallClicks() window.removeEventListener('keydown', onKeyDown) + unsubscribeRidgeMode() + unsubscribeView() clearSurfacePlanSnapFeedback() previewTargetIdRef.current = null setPreviewedConicalWallId(null) setInvalidStandardWallHover(false) setPreviewSelectedIds([]) + useRoofFootprintSource.getState().setRoomPick(null) corner1Ref.current = null const draftPreview = useFloorplanDraftPreview.getState() @@ -1173,7 +1320,7 @@ export const RoofTool: React.FC = () => { const placement = resolveRoofDraftPlacement( resolvedPreviewDimensions.length, resolvedPreviewDimensions.width, - quarterTurn, + ridgeMode, 0, roofType, ) @@ -1189,7 +1336,7 @@ export const RoofTool: React.FC = () => { ghostWallHeight, invalidStandardWallHover, previewPitch, - quarterTurn, + ridgeMode, resolvedPreviewDimensions, roofType, ]) @@ -1204,7 +1351,7 @@ export const RoofTool: React.FC = () => { const placement = resolveRoofDraftPlacement( resolvedPreviewDimensions.length, resolvedPreviewDimensions.width, - quarterTurn, + ridgeMode, 0, roofType, ) @@ -1220,7 +1367,7 @@ export const RoofTool: React.FC = () => { ghostWallHeight, invalidStandardWallHover, previewPitch, - quarterTurn, + ridgeMode, resolvedPreviewDimensions, roofType, ]) @@ -1322,7 +1469,14 @@ export const RoofTool: React.FC = () => { rotation={[ 0, (footprintSource !== 'draw' ? (footprintTarget?.rotation ?? 0) : 0) + - (roofType === 'conical' ? 0 : quarterTurn ? Math.PI / 2 : 0), + (roofType !== 'conical' && + ridgeQuarterTurn( + resolvedPreviewDimensions.length, + resolvedPreviewDimensions.width, + ridgeMode, + ) + ? Math.PI / 2 + : 0), 0, ]} > diff --git a/packages/nodes/src/roof/wall-catch.test.ts b/packages/nodes/src/roof/wall-catch.test.ts new file mode 100644 index 0000000000..4353e044d0 --- /dev/null +++ b/packages/nodes/src/roof/wall-catch.test.ts @@ -0,0 +1,345 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + LevelNode, + RoofNode, + RoofSegmentNode, + SeparatorNode, + WallNode, +} from '@pascal-app/core' +import { + bearingWallThickness, + catchRoofOnWalls, + catchRoofSideOnWalls, + placeRoofOnWalls, + type RoofCatchLine, + type RoofWallLine, + roofCatchWalls, + segmentWallLine, + settleRoofOnEdges, + settleSegmentOnEdges, +} from './wall-catch' + +const room: [number, number][] = [ + [0, 0], + [10, 0], + [10, 8], + [0, 8], +] +const roomWalls = room.map((start, i) => + WallNode.parse({ id: `wall_${i}`, parentId: 'level_g', start, end: room[(i + 1) % 4] }), +) + +const near = (value: readonly number[], expected: number[]) => { + for (const [i, v] of value.entries()) expect(v).toBeCloseTo(expected[i]!, 6) +} + +describe('a roof catching on the walls under it', () => { + test('wall lines a little off the walls lay onto their centre lines', () => { + const line: RoofWallLine = { center: [5.15, 4.1], angle: 0, hw: 5, hd: 4 } + const caught = catchRoofOnWalls({ lines: [line], walls: roomWalls, angle: 0 }) + expect(caught).toMatchObject({ u: true, v: true }) + near(caught.shift, [-0.15, -0.1]) + }) + + test('a segment turned a quarter catches the same walls on its own axes', () => { + const line: RoofWallLine = { center: [5.15, 4.1], angle: -Math.PI / 2, hw: 4, hd: 5 } + const caught = catchRoofOnWalls({ lines: [line], walls: roomWalls, angle: -Math.PI / 2 }) + expect(caught).toMatchObject({ u: true, v: true }) + near(caught.shift, [-0.15, -0.1]) + }) + + test('too far off, or past the end of a wall, it does not catch', () => { + const far: RoofWallLine = { center: [5.4, 4], angle: 0, hw: 5, hd: 4 } + expect(catchRoofOnWalls({ lines: [far], walls: roomWalls, angle: 0 })).toMatchObject({ + u: false, + v: true, + }) + const beyond = WallNode.parse({ parentId: 'level_g', start: [20, 0.1], end: [30, 0.1] }) + const line: RoofWallLine = { center: [5, 4], angle: 0, hw: 5, hd: 4 } + expect(catchRoofOnWalls({ lines: [line], walls: [beyond], angle: 0 })).toMatchObject({ + u: false, + v: false, + }) + }) + + test('a wall drawn by its face catches on its body centre, half its thickness in', () => { + // Drawn along its outer face at z 0, a 0.2 m wall's body centre runs at z 0.1. + const faced = WallNode.parse({ + parentId: 'level_g', + start: [0, 0], + end: [10, 0], + thickness: 0.2, + justification: 'a', + }) + const line: RoofWallLine = { center: [5, 4.05], angle: 0, hw: 5, hd: 4 } + const caught = catchRoofOnWalls({ lines: [line], walls: [faced], angle: 0 }) + expect(caught).toMatchObject({ u: false, v: true }) + near(caught.shift, [0, 0.05]) + }) + + test('a caught axis beats the grid, the other keeps the grid', () => { + const placed = placeRoofOnWalls( + [5.12, 4.3], + [5.5, 4.5], + { shift: [-0.12, 0], u: true, v: false }, + 0, + ) + near(placed, [5, 4.5]) + }) + + test('a side pulled near a wall sizes the segment onto its centre line, the other side held', () => { + // Wall line x 1..9 by z 1..7: the +x side pulled to 9.85, the -x side to 0.2. + const line: RoofWallLine = { center: [5, 4], angle: 0, hw: 4, hd: 3 } + expect( + catchRoofSideOnWalls({ line, axis: 'x', side: 1, value: 8.85, walls: roomWalls }), + ).toBeCloseTo(9, 6) + expect( + catchRoofSideOnWalls({ line, axis: 'x', side: -1, value: 8.8, walls: roomWalls }), + ).toBeCloseTo(9, 6) + expect( + catchRoofSideOnWalls({ line, axis: 'z', side: 1, value: 6.9, walls: roomWalls }), + ).toBeCloseTo(7, 6) + expect( + catchRoofSideOnWalls({ line, axis: 'x', side: 1, value: 8.5, walls: roomWalls }), + ).toBeNull() + }) + + test('a quarter-turned segment pulls its own x side onto the wall across it', () => { + // Its x axis runs along plan -z, so its +x side faces the wall at z 0. + const line: RoofWallLine = { center: [5, 4], angle: -Math.PI / 2, hw: 3, hd: 4 } + expect( + catchRoofSideOnWalls({ line, axis: 'x', side: 1, value: 6.9, walls: roomWalls }), + ).toBeCloseTo(7, 6) + }) + + test('a wall line on the walls takes the thickest of the walls it stands on', () => { + const thick = (thickness: number, i: number) => WallNode.parse({ ...roomWalls[i], thickness }) + const walls = [thick(0.18, 0), thick(0.18, 1), thick(0.24, 2), thick(0.18, 3)] + const on: RoofWallLine = { center: [5.1, 4], angle: 0, hw: 5, hd: 4 } + expect(bearingWallThickness(on, walls)).toBeCloseTo(0.24, 6) + // Only its -z edge on the wall at z 0: that wall's thickness. + const half: RoofWallLine = { center: [5, 2], angle: 0, hw: 3, hd: 2 } + expect(bearingWallThickness(half, walls)).toBeCloseTo(0.18, 6) + const off: RoofWallLine = { center: [5, 4], angle: 0, hw: 3, hd: 2 } + expect(bearingWallThickness(off, walls)).toBeNull() + }) + + test("a segment's wall line goes through its roof's place and turn", () => { + const line = segmentWallLine( + { position: [2, 0, 3], rotation: Math.PI / 2 }, + { position: [1, 0, 0], rotation: 0, width: 6, depth: 4 }, + ) + near(line.center, [2, 2]) + expect(line.angle).toBeCloseTo(-Math.PI / 2, 6) + expect([line.hw, line.hd]).toEqual([3, 2]) + }) + + test("the walls are the roof level's and the storey's below, never curved ones", () => { + const curved = WallNode.parse({ + parentId: 'level_g', + start: [0, 0], + end: [0, -4], + curveOffset: 1, + }) + const upstairs = WallNode.parse({ parentId: 'level_roof', start: [2, 2], end: [6, 2] }) + const ground = LevelNode.parse({ + id: 'level_g', + parentId: 'building_h', + level: 0, + children: [...roomWalls.map((wall) => wall.id), curved.id], + }) + const roofLevel = LevelNode.parse({ + id: 'level_roof', + parentId: 'building_h', + level: 1, + children: [upstairs.id, 'roof_r'], + }) + const segment = RoofSegmentNode.parse({ id: 'rseg_r', parentId: 'roof_r' }) + const roof = RoofNode.parse({ id: 'roof_r', parentId: roofLevel.id, children: [segment.id] }) + const building = BuildingNode.parse({ id: 'building_h', children: [ground.id, roofLevel.id] }) + const nodes = Object.fromEntries( + [building, ground, roofLevel, roof, segment, curved, upstairs, ...roomWalls].map((node) => [ + node.id, + node as AnyNode, + ]), + ) as Record + const ids = roofCatchWalls(nodes, roofLevel.id).map((wall) => wall.id) + expect(ids.sort()).toEqual([upstairs.id, ...roomWalls.map((wall) => wall.id)].sort()) + }) +}) + +// A porch 6 m along a 0.2 m house wall at z 0, 2.5 m deep, closed by invisible walls or fenced by +// railings: its edges at x 2 and 8 and z -2.5. +const RAIL = 3.5 * 0.0254 +const H = RAIL / 2 +const houseWall = WallNode.parse({ + id: 'wall_house', + parentId: 'level_g', + start: [0, 0], + end: [10, 0], + thickness: 0.2, +}) +const porchSides: [[number, number], [number, number]][] = [ + [ + [2, 0], + [2, -2.5], + ], + [ + [2, -2.5], + [8, -2.5], + ], + [ + [8, -2.5], + [8, 0], + ], +] +function porchLines(open: 'separators' | 'railings'): RoofCatchLine[] { + const ground = LevelNode.parse({ id: 'level_g', level: 0 }) + const sides = porchSides.map(([start, end], i) => + open === 'separators' + ? SeparatorNode.parse({ id: `separator_${i}`, parentId: ground.id, start, end }) + : WallNode.parse({ + id: `wall_rail${i}`, + parentId: ground.id, + start, + end, + wallType: 'railing', + thickness: RAIL, + }), + ) + ground.children = [houseWall.id, ...sides.map((side) => side.id)] as LevelNode['children'] + const nodes = Object.fromEntries( + [ground, houseWall, ...sides].map((node) => [node.id, node as AnyNode]), + ) as Record + return roofCatchWalls(nodes, ground.id) +} + +describe("a roof catching on a porch's invisible walls and a deck's railings", () => { + test('invisible walls and railings are edges, the house wall a wall it stands on', () => { + const porch = porchLines('separators') + expect(porch.map((line) => line.type).sort()).toEqual([ + 'roof-edge', + 'roof-edge', + 'roof-edge', + 'wall', + ]) + const deck = porchLines('railings').filter((line) => line.type === 'roof-edge') + expect(deck.map((line) => line.type === 'roof-edge' && line.thickness)).toEqual([ + RAIL, + RAIL, + RAIL, + ]) + }) + + test("drawn out to a porch's invisible walls, its walls' outer faces stand on them", () => { + // Drawn from the house wall's centre line out to the porch's corners, with walls 0.2 m thick. + const drawn: RoofWallLine = { center: [5, -1.25], angle: 0, hw: 3, hd: 1.25 } + const settled = settleRoofOnEdges(drawn, porchLines('separators'), 0.2) + near(settled.center, [5, -1.2]) + near([settled.hw, settled.hd], [2.9, 1.2]) + }) + + test("drawn out to a deck's railings, its outer faces stand on the railings' outside faces", () => { + const drawn: RoofWallLine = { + center: [5, -(2.5 + H) / 2], + angle: 0, + hw: 3 + H, + hd: (2.5 + H) / 2, + } + const settled = settleRoofOnEdges(drawn, porchLines('railings'), 0.2) + near(settled.center, [5, -(2.4 + H) / 2]) + near([settled.hw, settled.hd], [2.9 + H, (2.4 + H) / 2]) + // Drawn on the grid, on the railings' centre lines, it takes their outside faces all the same. + const gridded: RoofWallLine = { center: [5, -1.25], angle: 0, hw: 3, hd: 1.25 } + const onGrid = settleRoofOnEdges(gridded, porchLines('railings'), 0.2) + near(onGrid.center, [5, -(2.4 + H) / 2]) + near([onGrid.hw, onGrid.hd], [2.9 + H, (2.4 + H) / 2]) + }) + + test('a segment settles in its roof, a quarter-turned one on its own axes', () => { + const roof = { position: [5, 0, -1.25] as [number, number, number], rotation: 0 } + const lines = porchLines('separators') + const flat = { position: [0, 0, 0] as [number, number, number], rotation: 0 } + const settled = settleSegmentOnEdges( + roof, + { ...flat, width: 6, depth: 2.5, wallThickness: 0.2 }, + lines, + ) + near(settled.position, [0, 0, 0.05]) + near([settled.width, settled.depth], [5.8, 2.4]) + const turned = settleSegmentOnEdges( + roof, + { ...flat, rotation: Math.PI / 2, width: 2.5, depth: 6, wallThickness: 0.2 }, + lines, + ) + near(turned.position, [0, 0, 0.05]) + near([turned.width, turned.depth], [2.4, 5.8]) + }) + + test('moved a little off, it catches with its outer faces on the edges', () => { + // On the house wall's centre line at z 0, its outer faces on x 2 and 8 and z -2.5. + const off: RoofWallLine = { center: [5.1, -1.15], angle: 0, hw: 2.9, hd: 1.2 } + const porch = catchRoofOnWalls({ lines: [off], walls: porchLines('separators'), angle: 0 }) + expect(porch).toMatchObject({ u: true, v: true }) + near(porch.shift, [-0.1, -0.05]) + // A deck: the outer faces on the railings' outside faces, x 2 - H and 8 + H and z -2.5 - H. + const deckOff: RoofWallLine = { + center: [5.08, -(2.4 + H) / 2 - 0.04], + angle: 0, + hw: 2.9 + H, + hd: (2.4 + H) / 2, + } + const deck = catchRoofOnWalls({ lines: [deckOff], walls: porchLines('railings'), angle: 0 }) + expect(deck).toMatchObject({ u: true, v: true }) + near(deck.shift, [-0.08, 0.04]) + }) + + test("a side arrow pulled near an invisible wall stops with the roof's outer face on it", () => { + const line: RoofWallLine = { + center: [5, -1.2], + angle: 0, + hw: 2.9, + hd: 1.2, + wallThickness: 0.2, + } + const walls = porchLines('separators') + expect(catchRoofSideOnWalls({ line, axis: 'x', side: 1, value: 5.7, walls })).toBeCloseTo( + 5.8, + 6, + ) + expect(catchRoofSideOnWalls({ line, axis: 'z', side: -1, value: 2.3, walls })).toBeCloseTo( + 2.4, + 6, + ) + }) + + test('with no wall under it, a roof keeps its own walls, their outer faces on the edges', () => { + const lines = porchLines('separators').filter((line) => line.type === 'roof-edge') + const free: RoofWallLine = { + center: [5.1, -1.3], + angle: 0, + hw: 2.95, + hd: 1.2, + wallThickness: 0.1, + } + expect(bearingWallThickness(free, lines)).toBeNull() + // Its 0.1 m walls' outer faces on x 2 and 8 and z -2.5: its line 0.05 m in from them. + const caught = catchRoofOnWalls({ lines: [free], walls: lines, angle: 0 }) + expect(caught).toMatchObject({ u: true, v: true }) + near(caught.shift, [-0.1, 0.05]) + }) + + test('neither an invisible wall nor a railing gives the roof its wall thickness', () => { + const along: RoofWallLine = { center: [5, -1.25], angle: 0, hw: 3, hd: 1.25 } + expect(bearingWallThickness(along, porchLines('separators'))).toBeCloseTo(0.2, 6) + expect( + bearingWallThickness( + along, + porchLines('railings').filter((line) => line.type === 'roof-edge'), + ), + ).toBeNull() + }) +}) diff --git a/packages/nodes/src/roof/wall-catch.ts b/packages/nodes/src/roof/wall-catch.ts new file mode 100644 index 0000000000..65c74a82a5 --- /dev/null +++ b/packages/nodes/src/roof/wall-catch.ts @@ -0,0 +1,372 @@ +import { + type AnyNode, + type AnyNodeId, + getLevelBelow, + getWallBodyLine, + getWallThickness, + isCurvedWall, + isRailingWall, + type RoofNode, + type RoofSegmentNode, + type WallNode, +} from '@pascal-app/core' + +type Pt = readonly [number, number] + +/** How near a roof's wall line comes to a wall's centre line before it catches on it (metres). */ +export const ROOF_WALL_CATCH_M = 0.25 + +/** + * A side drawn or picked this near a room's edge stands on it (metres): as near as a drawn corner + * sticks to a wall in grid (the wall snap's connect radius), so one drawn on a railing's centre + * line, a grid point, still takes its outside face. + */ +export const ROOF_EDGE_ON_M = 0.05 + +/** A wall within this of parallel (the sine of 1°) runs along a roof edge. */ +const PARALLEL = Math.sin(Math.PI / 180) + +/** + * A segment's footprint at its wall centre lines, in its level's plan: its centre, the plan angle + * of its own x axis (the floor plan's `-(roof.rotation + segment.rotation)`), and its own walls' + * thickness, their outer faces half of it outside the line (absent, none). + */ +export type RoofWallLine = { + center: Pt + angle: number + hw: number + hd: number + wallThickness?: number +} + +/** + * A room's edge a roof lines up with but does not stand on: an invisible wall (its line, no + * thickness), or a railing (a guard round a deck or a porch, its faces half its thickness either + * side of its line). A wall takes a roof's wall line on its centre line, the roof's walls as thick + * as it, so their outer faces meet its outside face; an edge takes the roof's outer face itself, + * on the edge's face on the roof's outer side, so the roof reaches the room's edge as it reaches + * the house's walls' outside faces. + */ +export type RoofEdgeLine = { type: 'roof-edge'; id: string; start: Pt; end: Pt; thickness: number } + +/** A line a roof catches on: a wall's centre line, or a room's edge (`RoofEdgeLine`). */ +export type RoofCatchLine = WallNode | RoofEdgeLine + +/** Which axes of the dragged frame caught on a wall, and the plan shift that lays them there. */ +export type RoofWallCatch = { shift: [number, number]; u: boolean; v: boolean } + +const NO_CATCH: RoofWallCatch = { shift: [0, 0], u: false, v: false } + +const dot = (a: Pt, b: Pt) => a[0] * b[0] + a[1] * b[1] + +/** + * A catch line's ends: a wall's body centre line (a wall drawn by a face has it half its thickness + * off the middle), an edge's own. + */ +function lineEnds(line: RoofCatchLine): [Pt, Pt] { + if (line.type === 'roof-edge') return [line.start, line.end] + const body = getWallBodyLine(line) + return [ + [body.start.x, body.start.y], + [body.end.x, body.end.y], + ] +} + +/** A railing as a room's edge: its body's centre line, its faces half its thickness off it. */ +function railingEdge(wall: WallNode): RoofEdgeLine { + const [start, end] = lineEnds(wall) + return { type: 'roof-edge', id: wall.id, start, end, thickness: getWallThickness(wall) } +} + +/** + * The line nearest an edge through `mid`, and how far along `n` the edge moves onto it: lines + * parallel to the edge, within `threshold` of it, and overlapping its `half` length either side of + * `mid` along `t`. Null when none does. A wall takes the edge on its centre line; a room's edge + * takes the outer face of the roof's walls, `thickness` thick, on its face on the outer side. + */ +function nearestLine( + mid: Pt, + n: Pt, + t: Pt, + half: number, + lines: readonly RoofCatchLine[], + threshold: number, + thickness: number, +): { gap: number; line: RoofCatchLine } | null { + let best: { gap: number; line: RoofCatchLine } | null = null + for (const line of lines) { + const [from, to] = lineEnds(line) + const run: Pt = [to[0] - from[0], to[1] - from[1]] + const length = Math.hypot(run[0], run[1]) + if (length < 1e-6 || Math.abs(dot(run, n)) / length > PARALLEL) continue + const start: Pt = [from[0] - mid[0], from[1] - mid[1]] + const end: Pt = [to[0] - mid[0], to[1] - mid[1]] + const gap = dot(start, n) + (line.type === 'roof-edge' ? (line.thickness - thickness) / 2 : 0) + if (Math.abs(gap) > threshold) continue + const a = dot(start, t) + const b = dot(end, t) + if (Math.max(a, b) <= -half || Math.min(a, b) >= half) continue + if (best === null || Math.abs(gap) < Math.abs(best.gap)) best = { gap, line } + } + return best +} + +/** A wall line's four edges: outward normal, run, distance from the centre, half length. */ +function lineEdges(line: RoofWallLine) { + const ex: Pt = [Math.cos(line.angle), Math.sin(line.angle)] + const ez: Pt = [-ex[1], ex[0]] + return [ + { n: ex, t: ez, offset: line.hw, half: line.hd }, + { n: [-ex[0], -ex[1]] as Pt, t: ez, offset: line.hw, half: line.hd }, + { n: ez, t: ex, offset: line.hd, half: line.hw }, + { n: [-ez[0], -ez[1]] as Pt, t: ex, offset: line.hd, half: line.hw }, + ] +} + +/** A segment's wall line with its roof at `roof`'s place, which a drag may be proposing. */ +export function segmentWallLine( + roof: Pick, + segment: Pick & + Partial>, +): RoofWallLine { + const cos = Math.cos(-roof.rotation) + const sin = Math.sin(-roof.rotation) + const [lx, , lz] = segment.position + return { + center: [roof.position[0] + lx * cos - lz * sin, roof.position[2] + lx * sin + lz * cos], + angle: -(roof.rotation + segment.rotation), + hw: segment.width / 2, + hd: segment.depth / 2, + wallThickness: segment.wallThickness, + } +} + +/** + * What a roof on `levelId` catches on, on its own level and the storey below, which the roof plan + * draws dashed: the walls it sits on, and the rooms' edges there, their invisible walls and + * railings (`RoofEdgeLine`): a porch's, a deck's, a patio's. Curved walls have no straight line to + * catch on. + */ +export function roofCatchWalls( + nodes: Readonly>, + levelId: string | null | undefined, +): RoofCatchLine[] { + if (!levelId) return [] + const below = getLevelBelow(levelId, nodes as Record) + const lines: RoofCatchLine[] = [] + for (const id of [levelId, below?.id]) { + const level = id ? nodes[id as AnyNodeId] : undefined + if (level?.type !== 'level') continue + for (const childId of level.children) { + const node = nodes[childId as AnyNodeId] + if (node?.type === 'separator') + lines.push({ + type: 'roof-edge', + id: node.id, + start: node.start, + end: node.end, + thickness: 0, + }) + else if (node?.type === 'wall' && !isCurvedWall(node)) + lines.push(isRailingWall(node) ? railingEdge(node) : node) + } + } + return lines +} + +/** + * Where a dragged roof's wall lines catch on the walls under it. Along each axis of the frame at + * `angle` (the dragged segment's or roof's x and z), the edge nearest the centre line of a wall + * that runs alongside it, parallel and within `threshold`, sets the shift onto that wall; one + * nearest a room's edge (`RoofEdgeLine`) stands the outer face of the walls the segment takes + * there on it. + */ +export function catchRoofOnWalls(args: { + lines: readonly RoofWallLine[] + walls: readonly RoofCatchLine[] + angle: number + threshold?: number +}): RoofWallCatch { + const threshold = args.threshold ?? ROOF_WALL_CATCH_M + const u: Pt = [Math.cos(args.angle), Math.sin(args.angle)] + const v: Pt = [-u[1], u[0]] + let bestU: number | null = null + let bestV: number | null = null + + for (const line of args.lines) { + const thickness = landedWallThickness(line, args.walls, threshold) + for (const { n, t, offset, half } of lineEdges(line)) { + // An edge of a segment turned off the dragged frame moves along neither axis alone. + const alongU = dot(n, u) + const alongV = dot(n, v) + const axis = Math.abs(alongV) <= PARALLEL ? 'u' : Math.abs(alongU) <= PARALLEL ? 'v' : null + if (!axis) continue + const mid: Pt = [line.center[0] + n[0] * offset, line.center[1] + n[1] * offset] + const gap = nearestLine(mid, n, t, half, args.walls, threshold, thickness)?.gap + if (gap === undefined) continue + const amount = gap * (axis === 'u' ? alongU : alongV) + if (axis === 'u') { + if (bestU === null || Math.abs(amount) < Math.abs(bestU)) bestU = amount + } else if (bestV === null || Math.abs(amount) < Math.abs(bestV)) bestV = amount + } + } + + if (bestU === null && bestV === null) return NO_CATCH + const du = bestU ?? 0 + const dv = bestV ?? 0 + return { + shift: [u[0] * du + v[0] * dv, u[1] * du + v[1] * dv], + u: bestU !== null, + v: bestV !== null, + } +} + +/** + * A dragged roof's place: along an axis its wall line caught on, the raw place shifted onto the + * wall; along the other, the snapped place (the grid's, or the raw cursor's with no grid). + */ +export function placeRoofOnWalls( + raw: Pt, + snapped: Pt, + caught: RoofWallCatch, + angle: number, +): [number, number] { + const out: [number, number] = [snapped[0], snapped[1]] + const target: Pt = [raw[0] + caught.shift[0], raw[1] + caught.shift[1]] + const u: Pt = [Math.cos(angle), Math.sin(angle)] + const v: Pt = [-u[1], u[0]] + for (const [axis, on] of [ + [u, caught.u], + [v, caught.v], + ] as const) { + if (!on) continue + const k = (target[0] - out[0]) * axis[0] + (target[1] - out[1]) * axis[1] + out[0] += axis[0] * k + out[1] += axis[1] * k + } + return out +} + +/** + * A side dragged out from `line` (the segment's wall line before the drag), its opposite side held: + * the size along `axis` that lays the dragged side on the centre line of a wall alongside it, or + * stands its outer face on a room's edge there (`RoofEdgeLine`); null when neither is within + * `threshold` of `value`. + */ +export function catchRoofSideOnWalls(args: { + line: RoofWallLine + axis: 'x' | 'z' + side: 1 | -1 + value: number + walls: readonly RoofCatchLine[] + threshold?: number +}): number | null { + const { line, axis, side, value } = args + const threshold = args.threshold ?? ROOF_WALL_CATCH_M + const ex: Pt = [Math.cos(line.angle), Math.sin(line.angle)] + const ez: Pt = [-ex[1], ex[0]] + const along = axis === 'x' ? ex : ez + const n: Pt = [along[0] * side, along[1] * side] + const half = axis === 'x' ? line.hw : line.hd + const reach = value - half + const mid: Pt = [line.center[0] + n[0] * reach, line.center[1] + n[1] * reach] + // Sized to `value`, the opposite side held: the thickness its walls take there. + const shift = value / 2 - half + const sized: RoofWallLine = { + ...line, + center: [line.center[0] + n[0] * shift, line.center[1] + n[1] * shift], + hw: axis === 'x' ? value / 2 : line.hw, + hd: axis === 'z' ? value / 2 : line.hd, + } + const caught = nearestLine( + mid, + n, + axis === 'x' ? ez : ex, + axis === 'x' ? line.hd : line.hw, + args.walls, + threshold, + landedWallThickness(sized, args.walls, threshold), + ) + return caught ? value + caught.gap : null +} + +/** + * The thickness a segment's walls take to stand flush on the walls under it: the thickest wall + * whose centre line runs along an edge of its wall line, within `threshold`. A room's edge (an + * invisible wall, a railing) carries no roof and gives none. Null when no wall does. + */ +export function bearingWallThickness( + line: RoofWallLine, + walls: readonly RoofCatchLine[], + threshold = ROOF_WALL_CATCH_M, +): number | null { + const bearing = walls.filter((wall): wall is WallNode => wall.type === 'wall') + let thickest: number | null = null + for (const { n, t, offset, half } of lineEdges(line)) { + const mid: Pt = [line.center[0] + n[0] * offset, line.center[1] + n[1] * offset] + const wall = nearestLine(mid, n, t, half, bearing, threshold, 0)?.line + if (wall?.type === 'wall' && (thickest === null || getWallThickness(wall) > thickest)) + thickest = getWallThickness(wall) + } + return thickest +} + +/** The thickness a wall line's walls take where it stands: the walls' under it, else its own. */ +function landedWallThickness( + line: RoofWallLine, + walls: readonly RoofCatchLine[], + threshold: number, +): number { + return bearingWallThickness(line, walls, threshold) ?? line.wallThickness ?? 0 +} + +/** + * A wall line drawn or picked out to a room's edges (`RoofEdgeLine`): each side lying on one, + * within `tolerance`, moved in so the outer face of its walls, `thickness` thick, stands there, as + * the house's roof stands its walls' outer faces on the walls' outside faces. A side on a wall's + * centre line, or on nothing, stays. + */ +export function settleRoofOnEdges( + line: RoofWallLine, + walls: readonly RoofCatchLine[], + thickness: number, + tolerance = ROOF_EDGE_ON_M, +): RoofWallLine { + const ex: Pt = [Math.cos(line.angle), Math.sin(line.angle)] + const ez: Pt = [-ex[1], ex[0]] + // How far each side (+x, -x, +z, -z) moves out along its own normal. + const [px = 0, nx = 0, pz = 0, nz = 0] = lineEdges(line).map(({ n, t, offset, half }) => { + const mid: Pt = [line.center[0] + n[0] * offset, line.center[1] + n[1] * offset] + const on = nearestLine(mid, n, t, half, walls, tolerance, 0) + return on?.line.type === 'roof-edge' ? on.gap - thickness / 2 : 0 + }) + const du = (px - nx) / 2 + const dv = (pz - nz) / 2 + return { + ...line, + center: [line.center[0] + ex[0] * du + ez[0] * dv, line.center[1] + ex[1] * du + ez[1] * dv], + hw: Math.max(1e-3, line.hw + (px + nx) / 2), + hd: Math.max(1e-3, line.hd + (pz + nz) / 2), + } +} + +/** + * A segment drawn or picked out to a room's edges, in its roof at `roof`'s place: its size and + * place in its roof with its walls' outer faces on them (`settleRoofOnEdges`). + */ +export function settleSegmentOnEdges( + roof: Pick, + segment: Pick, + walls: readonly RoofCatchLine[], +): Pick { + const settled = settleRoofOnEdges(segmentWallLine(roof, segment), walls, segment.wallThickness) + const dx = settled.center[0] - roof.position[0] + const dz = settled.center[1] - roof.position[2] + const cos = Math.cos(-roof.rotation) + const sin = Math.sin(-roof.rotation) + return { + position: [dx * cos + dz * sin, segment.position[1], -dx * sin + dz * cos], + width: settled.hw * 2, + depth: settled.hd * 2, + } +} diff --git a/packages/nodes/src/separator/definition.test.ts b/packages/nodes/src/separator/definition.test.ts index 9d204b32c5..198967780d 100644 --- a/packages/nodes/src/separator/definition.test.ts +++ b/packages/nodes/src/separator/definition.test.ts @@ -34,3 +34,31 @@ test('separator registers selectable dashed plan and 3D renderers, without movem restore() } }) + +test('a selected separator shows a wall’s handles on the plan: its two ends and a push arrow off each side', () => { + const node = separatorDefinition.schema.parse({ + ...separatorDefinition.defaults(), + start: [0, 0], + end: [4, 0], + }) + const geometry = separatorDefinition.floorplan!(node, { viewState: { selected: true } } as never) + const children = geometry?.kind === 'group' ? geometry.children : [] + expect(children[0]).toMatchObject({ kind: 'line', strokeDasharray: '6 4' }) + expect( + children.flatMap((child) => + child.kind === 'endpoint-handle' ? [[child.affordance, child.payload]] : [], + ), + ).toEqual([ + ['move-endpoint', { separatorId: node.id, endpoint: 'start' }], + ['move-endpoint', { separatorId: node.id, endpoint: 'end' }], + ]) + const arrows = children.flatMap((child) => (child.kind === 'move-arrow' ? [child] : [])) + expect(arrows.map((arrow) => arrow.payload)).toEqual([ + { wallId: node.id, side: 'a' }, + { wallId: node.id, side: 'b' }, + ]) + // One off each side of the line. + expect(arrows.map((arrow) => Math.sign(arrow.point[1]))).toEqual([1, -1]) + expect(separatorDefinition.floorplanAffordances?.['move-endpoint']).toBeDefined() + expect(separatorDefinition.snapProfile).toBe('structural') +}) diff --git a/packages/nodes/src/separator/definition.ts b/packages/nodes/src/separator/definition.ts index db9fd058a6..5aef4d1783 100644 --- a/packages/nodes/src/separator/definition.ts +++ b/packages/nodes/src/separator/definition.ts @@ -1,7 +1,11 @@ import { type NodeDefinition, SeparatorNode } from '@pascal-app/core' +import { buildSeparatorFloorplan } from './floorplan' +import { separatorMoveEndpointAffordance } from './floorplan-affordances' export const separatorDefinition: NodeDefinition = { kind: 'separator', + // Its pushes and end drags snap as a wall's do. + snapProfile: 'structural', bake: 'strip', schemaVersion: 1, schema: SeparatorNode, @@ -19,18 +23,12 @@ export const separatorDefinition: NodeDefinition = { dirtyTracking: false, rendersChildren: false, renderer: { kind: 'parametric', module: () => import('./renderer') }, - floorplan: (node) => ({ - kind: 'line', - x1: node.start[0], - y1: node.start[1], - x2: node.end[0], - y2: node.end[1], - stroke: '#818cf8', - strokeWidth: 2, - strokeDasharray: '6 4', - vectorEffect: 'non-scaling-stroke', - pointerEvents: 'stroke', - }), + floorplan: buildSeparatorFloorplan, + // An invisible wall moves as a wall does, taking the ones joined to it along: + // its side arrows push it (the plan's `move-arrow`s, the 3D selection + // handles) and its ends drag, on the plan and in 3D. + floorplanAffordances: { 'move-endpoint': separatorMoveEndpointAffordance }, + affordanceTools: { selection: () => import('./selection') }, presentation: { icon: { kind: 'iconify', name: 'lucide:split' }, label: 'Separator', diff --git a/packages/nodes/src/separator/floorplan-affordances.ts b/packages/nodes/src/separator/floorplan-affordances.ts new file mode 100644 index 0000000000..8cc4cdaecd --- /dev/null +++ b/packages/nodes/src/separator/floorplan-affordances.ts @@ -0,0 +1,32 @@ +import type { + AnyNodeId, + FloorplanAffordance, + FloorplanAffordanceSession, + SeparatorNode, +} from '@pascal-app/core' +import { beginSeparatorEndDrag } from '@pascal-app/editor' + +type SeparatorEndpointPayload = { separatorId: AnyNodeId; endpoint: 'start' | 'end' } + +/** + * An invisible wall's end handle on the plan, the 2D twin of its 3D grip: the + * same snapping, preview and commit (`beginSeparatorEndDrag`), the pointer read + * by the floor plan. The invisible walls joined at that end follow it; Alt + * leaves them where they are. + */ +export const separatorMoveEndpointAffordance: FloorplanAffordance = { + start({ node, payload }): FloorplanAffordanceSession { + const { endpoint } = payload as SeparatorEndpointPayload + const drag = beginSeparatorEndDrag(node.id, endpoint) + return { + affectedIds: drag?.affectedIds ?? [node.id], + apply({ planPoint, modifiers }) { + drag?.move([planPoint[0], planPoint[1]], modifiers.altKey) + }, + canCommit: () => drag?.canCommit() ?? false, + commit() { + drag?.commit() + }, + } + }, +} diff --git a/packages/nodes/src/separator/floorplan.ts b/packages/nodes/src/separator/floorplan.ts new file mode 100644 index 0000000000..c1e7f5ea2f --- /dev/null +++ b/packages/nodes/src/separator/floorplan.ts @@ -0,0 +1,59 @@ +import type { FloorplanGeometry, GeometryContext, SeparatorNode } from '@pascal-app/core' +import { WALL_PUSH_AFFORDANCE, type WallPushArrowPayload } from '@pascal-app/editor' + +/** How far off the line (m) a selected invisible wall's side arrows sit. */ +const SIDE_ARROW_OFFSET = 0.12 + +/** + * An invisible wall on the plan: a dashed line. Selected, it shows a wall's own + * handles — an end handle at each end (drag it; the invisible walls joined + * there follow) and a push arrow off each side (the wall push, the ones joined + * to it following). + */ +export function buildSeparatorFloorplan( + node: SeparatorNode, + ctx: GeometryContext, +): FloorplanGeometry { + const line: FloorplanGeometry = { + kind: 'line', + x1: node.start[0], + y1: node.start[1], + x2: node.end[0], + y2: node.end[1], + stroke: '#818cf8', + strokeWidth: 2, + strokeDasharray: '6 4', + vectorEffect: 'non-scaling-stroke', + pointerEvents: 'stroke', + } + if (!ctx.viewState?.selected) return line + const children: FloorplanGeometry[] = [line] + for (const endpoint of ['start', 'end'] as const) + children.push({ + kind: 'endpoint-handle', + point: [node[endpoint][0], node[endpoint][1]], + state: 'idle', + affordance: 'move-endpoint', + payload: { separatorId: node.id, endpoint }, + }) + const dx = node.end[0] - node.start[0] + const dz = node.end[1] - node.start[1] + const length = Math.hypot(dx, dz) + if (length > 1e-6) { + const middle = [(node.start[0] + node.end[0]) / 2, (node.start[1] + node.end[1]) / 2] as const + for (const side of ['a', 'b'] as const) { + // Side `a` is the +normal one, as a wall's front. + const sign = side === 'a' ? 1 : -1 + const nx = (-dz / length) * sign + const nz = (dx / length) * sign + children.push({ + kind: 'move-arrow', + point: [middle[0] + nx * SIDE_ARROW_OFFSET, middle[1] + nz * SIDE_ARROW_OFFSET], + angle: Math.atan2(nz, nx), + affordance: WALL_PUSH_AFFORDANCE, + payload: { wallId: node.id, side } satisfies WallPushArrowPayload, + }) + } + } + return { kind: 'group', children } +} diff --git a/packages/nodes/src/separator/renderer.tsx b/packages/nodes/src/separator/renderer.tsx index 06cc1a0c8b..d26377b6ae 100644 --- a/packages/nodes/src/separator/renderer.tsx +++ b/packages/nodes/src/separator/renderer.tsx @@ -1,7 +1,7 @@ 'use client' -import { type SeparatorNode, useRegistry, useScene } from '@pascal-app/core' -import { useEditor } from '@pascal-app/editor' +import { type SeparatorNode, useLiveNodeOverrides, useRegistry, useScene } from '@pascal-app/core' +import { separatorFloorElevation, useEditor } from '@pascal-app/editor' import { useNodeEvents, useViewer } from '@pascal-app/viewer' import { useEffect, useMemo, useRef } from 'react' import { BufferGeometry, Float32BufferAttribute, type Group } from 'three' @@ -10,20 +10,40 @@ import { LineBasicNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu' const separatorMaterial = new LineBasicNodeMaterial({ color: '#818cf8', depthWrite: false }) const hitMaterial = new MeshBasicNodeMaterial({ transparent: true, opacity: 0, depthWrite: false }) +/** + * An invisible wall in 3D: a dashed line on the floor, shown while its room or + * it is selected. A push or end drag moves it live (`useLiveNodeOverrides`). + */ export default function SeparatorRenderer({ node }: { node: SeparatorNode }) { const ref = useRef(null!) useRegistry(node.id, 'separator', ref) const roomId = useEditor((s) => s.room?.zoneId) const viewerRoomId = useViewer((s) => s.selection.zoneId) + const selected = useViewer((s) => s.selection.selectedIds.includes(node.id)) const zone = useScene((s) => { const id = roomId ?? viewerRoomId return id ? s.nodes[id as keyof typeof s.nodes] : undefined }) - const visible = zone?.type === 'zone' && zone.boundarySeparatorIds.includes(node.id) + const inRoom = zone?.type === 'zone' && zone.boundarySeparatorIds.includes(node.id) + const visible = inRoom || selected + // Selected on its own, it lies on the floor of a room it closes. + const ownFloor = useScene((s) => + selected && !inRoom ? separatorFloorElevation(s.nodes, node.id) : null, + ) + const live = useLiveNodeOverrides((s) => s.overrides.get(node.id)) as + | Partial + | undefined + const start = live?.start ?? node.start + const end = live?.end ?? node.end const events = useNodeEvents(node, 'separator') - const y = zone?.type === 'zone' ? (zone.floor?.elevation ?? 0.05) + 0.02 : 0.07 - const dx = node.end[0] - node.start[0], - dz = node.end[1] - node.start[1] + const y = + zone?.type === 'zone' && inRoom + ? (zone.floor?.elevation ?? 0.05) + 0.02 + : ownFloor !== null + ? ownFloor + 0.02 + : 0.07 + const dx = end[0] - start[0], + dz = end[1] - start[1] const length = Math.hypot(dx, dz) const geometry = useMemo(() => { const positions: number[] = [] @@ -42,14 +62,14 @@ export default function SeparatorRenderer({ node }: { node: SeparatorNode }) { {}} /> diff --git a/packages/nodes/src/separator/selection.tsx b/packages/nodes/src/separator/selection.tsx new file mode 100644 index 0000000000..dd3d8dd25e --- /dev/null +++ b/packages/nodes/src/separator/selection.tsx @@ -0,0 +1,59 @@ +'use client' + +import { type AnyNodeId, type SeparatorNode, sceneRegistry, useScene } from '@pascal-app/core' +import { + type SelectionAffordanceProps, + SeparatorEndGrip, + separatorFloorElevation, + separatorPushHandles, + useEditor, + useInteractionScope, + WallPushArrow, +} from '@pascal-app/editor' +import { createPortal } from '@react-three/fiber' +import { useEffect, useState } from 'react' +import type { Object3D } from 'three' + +/** + * A selected invisible wall's 3D handles, the twins of its plan ones: a push + * arrow off each side (the wall push: the invisible walls joined to it follow + * and those T'd into it stretch) and a grip at each end (drag it across the + * floor; the ones joined there follow). They lie on the floor of the room it + * closes, where its dashed line is drawn, and hide while a drag runs. + */ +export default function SeparatorSelectionAffordance({ node, readOnly }: SelectionAffordanceProps) { + const editing = useEditor((s) => s.mode === 'select' && !s.isFloorplanHovered) + const idle = useInteractionScope((s) => s.scope.kind === 'idle') + const floor = useScene((s) => separatorFloorElevation(s.nodes, node.id)) + const levelId = node.parentId + const [level, setLevel] = useState(null) + useEffect(() => { + let frame = 0 + const resolve = () => { + const next = levelId ? (sceneRegistry.nodes.get(levelId as AnyNodeId) ?? null) : null + setLevel((current) => (current === next ? current : next)) + if (!next && levelId) frame = window.requestAnimationFrame(resolve) + } + resolve() + return () => window.cancelAnimationFrame(frame) + }, [levelId]) + if (node.type !== 'separator' || readOnly || !editing || !idle || !level || !levelId) return null + const separator = node as SeparatorNode + return createPortal( + <> + {separatorPushHandles(separator, floor).map((handle) => ( + + ))} + {(['start', 'end'] as const).map((endpoint) => ( + + ))} + , + level, + ) +} diff --git a/packages/nodes/src/shared/build-up-controls.tsx b/packages/nodes/src/shared/build-up-controls.tsx new file mode 100644 index 0000000000..713101f528 --- /dev/null +++ b/packages/nodes/src/shared/build-up-controls.tsx @@ -0,0 +1,297 @@ +'use client' + +import { + DRYWALL_THICKNESSES, + FRAMING_SPACINGS, + type InsulationKind, + type InteriorFinishKind, + LUMBER_DEPTH, + type NominalLumber, + PANEL_THICKNESSES, + type PanelKind, + PLASTER_THICKNESS, + WOOD_PLANK_THICKNESS, +} from '@pascal-app/core' +import { SelectControl } from '@pascal-app/editor' +import { useState } from 'react' +import { formatLayerThickness, parseLayerThickness } from './layer-thickness' + +type LinearUnit = 'metric' | 'imperial' + +const key = (value: number) => value.toFixed(6) + +/** + * A select over standard sizes (panel thicknesses, spacings). A stored value + * off the list stays selectable, so a scene written elsewhere reads back as + * it is. + */ +export function SizeSelect({ + label, + value, + sizes, + format, + onChange, +}: { + label: string + value: number + sizes: readonly number[] + format: (value: number) => string + onChange: (value: number) => void +}) { + const listed = sizes.some((size) => Math.abs(size - value) < 1e-6) + const choices = listed ? sizes : [value, ...sizes] + const current = choices.find((size) => Math.abs(size - value) < 1e-6) ?? value + return ( + { + const picked = choices.find((size) => key(size) === next) + if (picked !== undefined) onChange(picked) + }} + options={choices.map((size) => ({ label: format(size), value: key(size) }))} + value={key(current)} + /> + ) +} + +/** A free thickness, typed as `5/8`, `3 1/2"` or `90mm`; a bare number is inches or millimetres. */ +export function ThicknessField({ + label, + metres, + onCommit, + unit, +}: { + label: string + metres: number + onCommit: (metres: number) => void + unit: LinearUnit +}) { + const [draft, setDraft] = useState(null) + return ( + + ) +} + +export function BuildUpReadout({ label, value }: { label: string; value: string }) { + return ( +
+ + {label} + + {value} +
+ ) +} + +/** A spacing on centre, as the viewer's unit names it: `16" o.c.` or `406 mm o.c.`. */ +export function formatSpacing(metres: number, unit: LinearUnit): string { + if (unit === 'imperial') return `${formatLayerThickness(metres, unit)} o.c.` + return `${Math.round(metres * 1000)} mm o.c.` +} + +export const NONE = 'none' +const INCH = 0.0254 + +const PANEL_OPTIONS: { label: string; value: PanelKind | typeof NONE }[] = [ + { label: 'OSB', value: 'osb' }, + { label: 'Plywood', value: 'plywood' }, +] +const OPTIONAL_PANEL_OPTIONS: typeof PANEL_OPTIONS = [ + { label: 'None', value: NONE }, + ...PANEL_OPTIONS, +] + +const LUMBER_OPTIONS: { label: string; value: NominalLumber }[] = ( + Object.keys(LUMBER_DEPTH) as NominalLumber[] +).map((size) => ({ label: size, value: size })) + +const INSULATION_OPTIONS: { label: string; value: InsulationKind | typeof NONE }[] = [ + { label: 'None', value: NONE }, + { label: 'Batt', value: 'batt' }, + { label: 'Blown', value: 'blown' }, + { label: 'Rigid board', value: 'rigid' }, +] + +const FINISH_OPTIONS: { label: string; value: InteriorFinishKind | typeof NONE }[] = [ + { label: 'None', value: NONE }, + { label: 'Drywall', value: 'drywall' }, + { label: 'Plaster', value: 'plaster' }, + { label: 'Wood plank', value: 'wood-plank' }, +] + +const FINISH_THICKNESS: Record = { + drywall: DRYWALL_THICKNESSES[0]!, + plaster: PLASTER_THICKNESS, + 'wood-plank': WOOD_PLANK_THICKNESS, +} + +/** `base` with `change` applied and no longer a preset; a layer set to `undefined` goes. */ +export function editedBuildUp(base: T, change: Partial): T { + const next: Record = { ...base, ...change } + delete next.presetId + for (const field of Object.keys(next)) if (next[field] === undefined) delete next[field] + return next as T +} + +export function FramingSizeFields({ + framing, + onChange, + sizeLabel, + unit, +}: { + framing: { size: NominalLumber; spacing: number } + onChange: (change: { size?: NominalLumber; spacing?: number }) => void + sizeLabel: string + unit: LinearUnit +}) { + return ( + <> + onChange({ size })} + options={LUMBER_OPTIONS} + value={framing.size} + /> + formatSpacing(value, unit)} + label="Spacing" + onChange={(spacing) => onChange({ spacing })} + sizes={FRAMING_SPACINGS} + value={framing.spacing} + /> + + ) +} + +/** A structural panel; with `optional`, None takes it out and a new one is `added`. */ +export function PanelFields({ + added = PANEL_THICKNESSES[0]!, + label, + layer, + onChange, + optional = false, + unit, +}: { + added?: number + label: string + layer: { kind: PanelKind; thickness: number } | undefined + onChange: (layer: { kind: PanelKind; thickness: number } | undefined) => void + optional?: boolean + unit: LinearUnit +}) { + return ( + <> + + onChange(kind === NONE ? undefined : { thickness: layer?.thickness ?? added, kind }) + } + options={optional ? OPTIONAL_PANEL_OPTIONS : PANEL_OPTIONS} + value={layer?.kind ?? NONE} + /> + {layer && ( + formatLayerThickness(value, unit)} + label={`${label} thickness`} + onChange={(thickness) => onChange({ ...layer, thickness })} + sizes={PANEL_THICKNESSES} + value={layer.thickness} + /> + )} + + ) +} + +/** A new insulation layer is cavity fill as deep as the framing, or 2 in of rigid board. */ +export function InsulationFields({ + framingDepth, + layer, + onChange, + unit, +}: { + framingDepth: number + layer: { kind: InsulationKind; thickness: number } | undefined + onChange: (layer: { kind: InsulationKind; thickness: number } | undefined) => void + unit: LinearUnit +}) { + return ( + <> + + onChange( + kind === NONE + ? undefined + : { kind, thickness: kind === 'rigid' ? 2 * INCH : framingDepth }, + ) + } + options={INSULATION_OPTIONS} + value={layer?.kind ?? NONE} + /> + {layer && ( + onChange({ ...layer, thickness })} + unit={unit} + /> + )} + + ) +} + +export function FinishFields({ + label, + layer, + onChange, + unit, +}: { + label: string + layer: { kind: InteriorFinishKind; thickness: number } | undefined + onChange: (layer: { kind: InteriorFinishKind; thickness: number } | undefined) => void + unit: LinearUnit +}) { + return ( + <> + + onChange(kind === NONE ? undefined : { kind, thickness: FINISH_THICKNESS[kind] }) + } + options={FINISH_OPTIONS} + value={layer?.kind ?? NONE} + /> + {layer?.kind === 'drywall' && ( + formatLayerThickness(value, unit)} + label="Drywall thickness" + onChange={(thickness) => onChange({ kind: 'drywall', thickness })} + sizes={DRYWALL_THICKNESSES} + value={layer.thickness} + /> + )} + + ) +} diff --git a/packages/nodes/src/shared/contextual-floorplan-dimensions.test.ts b/packages/nodes/src/shared/contextual-floorplan-dimensions.test.ts index efdee5dff3..6ab2edbb9d 100644 --- a/packages/nodes/src/shared/contextual-floorplan-dimensions.test.ts +++ b/packages/nodes/src/shared/contextual-floorplan-dimensions.test.ts @@ -7,7 +7,10 @@ import { WindowNode, ZoneNode, } from '@pascal-app/core' -import { createFloorplanContextExtensions } from '@pascal-app/editor' +import { + createFloorplanContextExtensions, + type FloorplanWallDimensionFaces, +} from '@pascal-app/editor' import { buildDoorContextualDimensions } from '../door/contextual-dimensions' import { buildItemContextualDimensions } from '../item/floorplan' import { buildWallContextualDimensions } from '../wall/contextual-dimensions' @@ -18,6 +21,7 @@ function context( parent: GeometryContext['parent'] = null, siblings: GeometryContext['siblings'] = [], moving = false, + wallDimensionFaces: FloorplanWallDimensionFaces = 'both', ): GeometryContext { return { resolve: () => undefined, @@ -28,6 +32,7 @@ function context( metricNotation: 'meters', purpose: 'edit', wallDimensionReference: 'centerline', + wallDimensionFaces, }), viewState: moving ? { @@ -42,7 +47,7 @@ function context( } describe('contextual floor-plan dimensions', () => { - test('uses modeled centerline length for a straight wall', () => { + test('a free-standing wall reads its full length along its face', () => { const wall = WallNode.parse({ id: 'wall_primary', start: [0, 0], @@ -51,8 +56,8 @@ describe('contextual floor-plan dimensions', () => { expect(buildWallContextualDimensions(wall, context())).toMatchObject({ kind: 'dimension', - start: [0, 0], - end: [4, 0], + start: [0, 0.05], + end: [4, 0.05], text: '4m', }) }) @@ -84,12 +89,31 @@ describe('contextual floor-plan dimensions', () => { WallNode.parse({ id: 'wall_left', start: [0, 6], end: [0, 0] }), ] + // ID inside the room, between the corner walls' inner faces; OD outside, to their outer faces. expect(buildWallContextualDimensions(wall, context(null, siblings))).toMatchObject({ - kind: 'dimension', - offsetNormal: [1, 0], + kind: 'group', + children: [ + { kind: 'dimension', offsetNormal: [-1, 0], start: [3.95, 0.05], text: '5.9m' }, + { kind: 'dimension', offsetNormal: [1, 0], start: [4.05, -0.05], text: '6.1m' }, + ], }) }) + test('the dimension-faces preference keeps only the interior or the exterior length', () => { + const wall = WallNode.parse({ id: 'wall_right', start: [4, 0], end: [4, 6] }) + const siblings = [ + WallNode.parse({ id: 'wall_top', start: [0, 0], end: [4, 0] }), + WallNode.parse({ id: 'wall_bottom', start: [4, 6], end: [0, 6] }), + WallNode.parse({ id: 'wall_left', start: [0, 6], end: [0, 0] }), + ] + expect( + buildWallContextualDimensions(wall, context(null, siblings, false, 'interior')), + ).toMatchObject({ kind: 'dimension', offsetNormal: [-1, 0], text: '5.9m' }) + expect( + buildWallContextualDimensions(wall, context(null, siblings, false, 'exterior')), + ).toMatchObject({ kind: 'dimension', offsetNormal: [1, 0], text: '6.1m' }) + }) + test('shows one internal-wall dimension between the connected stud faces', () => { const wall = WallNode.parse({ id: 'wall_internal', @@ -123,7 +147,7 @@ describe('contextual floor-plan dimensions', () => { ) }) - test('measures a selected perimeter wall between connected stud centerlines', () => { + test('measures a perimeter wall that abuts its neighbours between their near faces', () => { const wall = WallNode.parse({ id: 'wall_perimeter', start: [0, 0], @@ -146,10 +170,10 @@ describe('contextual floor-plan dimensions', () => { expect(buildWallContextualDimensions(wall, context(null, [startWall, endWall]))).toMatchObject({ kind: 'dimension', - start: [0, 0], - end: [0, 4], + start: [0.05, 0.1], + end: [0.05, 3.9], offsetNormal: [1, 0], - text: '4m', + text: '3.8m', }) }) diff --git a/packages/nodes/src/shared/eyedropper.test.ts b/packages/nodes/src/shared/eyedropper.test.ts index 035abf2708..72e3f3bbe1 100644 --- a/packages/nodes/src/shared/eyedropper.test.ts +++ b/packages/nodes/src/shared/eyedropper.test.ts @@ -89,7 +89,7 @@ test('walls: a face region, a room override, the room finish, then the face defa useScene.setState({ nodes: { ...nodes, [zone.id]: plain as AnyNode } }) expect(preset(wall, wallRoomFaceRole(zone.id, 'a'))).toBe('library:brick') useScene.setState({ nodes }) - expect(preset(wall, 'b')).toBe('library:concrete-drywall') + expect(preset(wall, 'b')).toBe('library:preset-seattlegrey') }) test('floors: a region, the room floor, and an unpainted room shows the plate top', () => { diff --git a/packages/nodes/src/shared/layer-thickness.ts b/packages/nodes/src/shared/layer-thickness.ts new file mode 100644 index 0000000000..bfcddca748 --- /dev/null +++ b/packages/nodes/src/shared/layer-thickness.ts @@ -0,0 +1,3 @@ +// The layer thickness reader and writer live in the editor, which the floor panels share with +// the roof, ceiling and wall layers here. +export { formatLayerDepth, formatLayerThickness, parseLayerThickness } from '@pascal-app/editor' diff --git a/packages/nodes/src/shared/move-roof-tool.tsx b/packages/nodes/src/shared/move-roof-tool.tsx index 08c1212b3f..da05699c85 100644 --- a/packages/nodes/src/shared/move-roof-tool.tsx +++ b/packages/nodes/src/shared/move-roof-tool.tsx @@ -28,6 +28,7 @@ import { consumePlacementDragRelease, DragBoundingBox, getFloorStackPreviewPosition, + isGridSnapActive, isMagneticSnapActive, isTypingTarget, resolvePlanarCursorPosition, @@ -43,6 +44,13 @@ import { useViewer } from '@pascal-app/viewer' import { useCallback, useEffect, useRef, useState } from 'react' import * as THREE from 'three' import { snapFenceDraftPoint } from '../fence/drafting' +import { + bearingWallThickness, + catchRoofOnWalls, + placeRoofOnWalls, + roofCatchWalls, + segmentWallLine, +} from '../roof/wall-catch' import { clearStairMovePreview, publishStairMovePreview } from '../stair/surface-move-preview' /** Figma-style alignment-snap threshold (meters), matching the other tools. */ @@ -276,6 +284,88 @@ export const MoveRoofTool: React.FC<{ return ar.snap && isMagneticSnapActive() ? [lx + ar.snap.dx, lz + ar.snap.dz] : [lx, lz] } + // A roof's wall line lands on the walls under it, as in the plan's segment move + // (`catchRoofOnWalls`); that catch replaces the cursor's own snap onto a wall. + const catchesWalls = movingNode.type === 'roof' || movingNode.type === 'roof-segment' + const catchWalls = catchesWalls ? roofCatchWalls(useScene.getState().nodes, levelId) : [] + let rawAnchor: [number, number] | null = null + const catchOnWalls = ( + raw: [number, number], + snapped: [number, number], + ): [number, number] | null => { + const nodes = useScene.getState().nodes + const roof = + movingNode.type === 'roof' + ? movingNode + : (nodes[movingNode.parentId as AnyNodeId] as RoofNode | undefined) + if (roof?.type !== 'roof' || catchWalls.length === 0) return null + // A segment's place is in its roof's frame; the walls are in the level's plan. + const cos = Math.cos(-roof.rotation) + const sin = Math.sin(-roof.rotation) + const toPlan = ([x, z]: [number, number]): [number, number] => + movingNode.type === 'roof' + ? [x, z] + : [roof.position[0] + x * cos - z * sin, roof.position[2] + x * sin + z * cos] + const fromPlan = ([x, z]: [number, number]): [number, number] => { + if (movingNode.type === 'roof') return [x, z] + const dx = x - roof.position[0] + const dz = z - roof.position[2] + return [dx * cos + dz * sin, -dx * sin + dz * cos] + } + const lines = + movingNode.type === 'roof' + ? roof.children + .map((id) => nodes[id as AnyNodeId]) + .filter((child): child is RoofSegmentNode => child?.type === 'roof-segment') + .map((segment) => + segmentWallLine( + { position: [raw[0], 0, raw[1]], rotation: pendingRotation }, + segment, + ), + ) + : movingNode.type === 'roof-segment' + ? [ + segmentWallLine(roof, { + ...movingNode, + position: [raw[0], 0, raw[1]], + rotation: pendingRotation, + }), + ] + : [] + const angle = + movingNode.type === 'roof' ? -pendingRotation : -(roof.rotation + pendingRotation) + const caught = catchRoofOnWalls({ lines, walls: catchWalls, angle }) + if (!(caught.u || caught.v)) return null + return fromPlan(placeRoofOnWalls(toPlan(raw), toPlan(snapped), caught, angle)) + } + // Dropped onto the walls under it, a roof's segments take their thickness for their gable + // and heel walls. + const bearingThicknesses = ( + position: [number, number, number], + rotation: number, + ): { id: AnyNodeId; data: Partial }[] => { + const nodes = useScene.getState().nodes + const roof = + movingNode.type === 'roof' + ? { ...movingNode, position, rotation } + : (nodes[movingNode.parentId as AnyNodeId] as RoofNode | undefined) + if (roof?.type !== 'roof' || catchWalls.length === 0) return [] + const segments = + movingNode.type === 'roof' + ? roof.children + .map((id) => nodes[id as AnyNodeId]) + .filter((child): child is RoofSegmentNode => child?.type === 'roof-segment') + : movingNode.type === 'roof-segment' + ? [{ ...movingNode, position, rotation }] + : [] + return segments.flatMap((segment) => { + const thickness = bearingWallThickness(segmentWallLine(roof, segment), catchWalls) + return thickness === null || thickness === segment.wallThickness + ? [] + : [{ id: segment.id as AnyNodeId, data: { wallThickness: thickness } }] + }) + } + const localToWorldPoint = (localPoint: WallPlanPoint, y: number): [number, number, number] => { if (buildingObj) { const worldPoint = buildingObj.localToWorld( @@ -348,9 +438,10 @@ export const MoveRoofTool: React.FC<{ const y = event.position[1] const roofBypassSnap = event.nativeEvent?.altKey === true + const cursor: WallPlanPoint = [event.localPosition[0], event.localPosition[2]] const snappedLocal = snapFenceDraftPoint({ - point: [event.localPosition[0], event.localPosition[2]], - walls: levelWalls, + point: cursor, + walls: catchesWalls ? [] : levelWalls, fences: levelFences, bypassSnap: roofBypassSnap, magnetic: !roofBypassSnap && isMagneticSnapActive(), @@ -372,7 +463,24 @@ export const MoveRoofTool: React.FC<{ dragAnchorRef.current = resolved.anchor let [localX, localZ] = resolved.point - if (alignTopLevel) { + // The catch reads the unsnapped place, so the walls win over the grid near them. + let caught: [number, number] | null = null + if (catchesWalls) { + const [cursorX, , cursorZ] = localToWorldPoint(cursor, y) + const raw = resolvePlanarCursorPosition({ + cursor: computeLocal(cursorX, cursorZ, y, cursor[0], cursor[1]), + original: [movingNode.position[0], movingNode.position[2]], + anchor: rawAnchor, + mode: useAbsoluteCursorPlacement ? 'absolute' : 'relative', + }) + rawAnchor = raw.anchor + if (!roofBypassSnap && (isGridSnapActive() || isMagneticSnapActive())) + caught = catchOnWalls(raw.point, [localX, localZ]) + } + if (caught) { + ;[localX, localZ] = caught + useAlignmentGuides.getState().clear() + } else if (alignTopLevel) { const aligned = alignLocalPoint(localX, localZ, event.nativeEvent?.altKey === true) localX = aligned[0] localZ = aligned[1] @@ -525,9 +633,13 @@ export const MoveRoofTool: React.FC<{ if (movingNode.type === 'roof' || movingNode.type === 'roof-segment') { // Moved or turned onto the walls that carry it, a roof goes to the level above them. const scene = useScene.getState() - scene.applyNodeChanges( - seatRoofs(scene.nodes, { update: [{ id: movingNode.id, data: patch }] }).changes, - ) + const thicknesses = bearingThicknesses(position, pendingRotation) + const own = thicknesses.find(({ id }) => id === movingNode.id)?.data + const update = [ + { id: movingNode.id, data: { ...patch, ...own } }, + ...thicknesses.filter(({ id }) => id !== movingNode.id), + ] + scene.applyNodeChanges(seatRoofs(scene.nodes, { update }).changes) } else { useScene.getState().updateNodes([ { diff --git a/packages/nodes/src/shared/node-batch/candidates.ts b/packages/nodes/src/shared/node-batch/candidates.ts index a6d8ce4ed4..0686c26a10 100644 --- a/packages/nodes/src/shared/node-batch/candidates.ts +++ b/packages/nodes/src/shared/node-batch/candidates.ts @@ -61,7 +61,9 @@ const hiddenMeshesByNode = new Map() * variant, a cutout group) — and at any HOSTED child node's registered group: * an item can host other items (a shelf's books), whose groups mount inside * the host's. Packing those would freeze the child at the host's join pose - * with no release of its own (it is not a store member). + * with no release of its own (it is not a store member). A ceiling's cap draws + * itself: it shows and hides with a viewer flag that rebuilds nothing, so a + * batched copy would stay drawn after it hides. */ function collectMeshes(object: Object3D, out: Mesh[], hostedRoots: ReadonlySet): void { if (object.visible === false || hostedRoots.has(object)) return @@ -70,6 +72,7 @@ function collectMeshes(object: Object3D, out: Mesh[], hostedRoots: ReadonlySet +let savedEditor: ReturnType +let savedViewer: ReturnType + +/** Walls A and B, both drawn left to right, with what each face (`a`, `b`) looks onto. */ +function walls(sides: { a: WallNode['frontSide']; b: WallNode['backSide'] }[]) { + return [ + { id: WALL_A_ID, start: [0, 0], end: [6, 0] }, + { id: WALL_B_ID, start: [0, 4], end: [6, 4] }, + ].map((wall, index) => + WallNode.parse({ + ...wall, + parentId: LEVEL_ID, + thickness: 0.2, + height: 3, + frontSide: sides[index]!.a, + backSide: sides[index]!.b, + children: [], + }), + ) +} + +const OUTSIDE_WALLS = walls([ + { a: 'interior', b: 'exterior' }, + { a: 'exterior', b: 'interior' }, +]) + +function opening(kind: 'door' | 'window'): DoorNode | WindowNode { + const shared = { id: OPENING_ID[kind], parentId: WALL_A_ID, wallId: WALL_A_ID, width: 1.2 } + return kind === 'door' + ? DoorNode.parse({ ...shared, ...ON_A, position: [2, 1.05, 0], height: 2.1 }) + : WindowNode.parse({ ...shared, ...ON_A, position: [2, 1.5, 0], height: 1.2 }) +} + +function seedScene(kind: 'door' | 'window'): DoorNode | WindowNode { + const node = opening(kind) + const [wallA, wallB] = OUTSIDE_WALLS as [WallNode, WallNode] + const level = LevelNode.parse({ id: LEVEL_ID, children: [WALL_A_ID, WALL_B_ID], level: 0 }) + const building = BuildingNode.parse({ id: 'building_casing-move', children: [LEVEL_ID] }) + useScene.setState({ + nodes: { + [building.id]: building, + [LEVEL_ID]: { ...level, parentId: building.id }, + [WALL_A_ID]: { ...wallA, children: [node.id] }, + [WALL_B_ID]: wallB, + [node.id]: node, + }, + rootNodeIds: [building.id], + dirtyNodes: new Set(), + readOnly: false, + } as never) + return node +} + +function current(id: AnyNodeId): AnyNode { + const found = useScene.getState().nodes[id] + if (!found) throw new Error(`missing ${id}`) + return found +} + +beforeEach(() => { + savedScene = useScene.getState() + savedEditor = useEditor.getState() + savedViewer = useViewer.getState() + savedWindow = Object.getOwnPropertyDescriptor(globalThis, 'window') + savedDocument = globalThis.document + savedRaf = globalThis.requestAnimationFrame + savedCancelRaf = globalThis.cancelAnimationFrame + globalThis.window = new EventTarget() as Window & typeof globalThis + globalThis.document = { body: { style: { cursor: '' } } } as Document + globalThis.requestAnimationFrame = (cb: FrameRequestCallback) => { + cb(0) + return 0 + } + globalThis.cancelAnimationFrame = () => {} + spatialGridManager.clear() + useLiveNodeOverrides.getState().clearAll() + useLiveTransforms.getState().clearAll() + useEditor.setState({ mode: 'build', placementDragMode: false }) + useEditor.getState().setSnappingMode('item', 'off') + useViewer.setState({ + selection: { buildingId: null, levelId: LEVEL_ID, zoneId: null, selectedIds: [] }, + }) + sceneRegistry.nodes.set(LEVEL_ID, new Group()) +}) + +afterEach(() => { + sceneRegistry.nodes.clear() + spatialGridManager.clear() + useLiveNodeOverrides.getState().clearAll() + useLiveTransforms.getState().clearAll() + useScene.setState(savedScene) + useEditor.setState(savedEditor) + useViewer.setState(savedViewer) + if (savedWindow) Object.defineProperty(globalThis, 'window', savedWindow) + else Reflect.deleteProperty(globalThis, 'window') + globalThis.document = savedDocument + globalThis.requestAnimationFrame = savedRaf + globalThis.cancelAnimationFrame = savedCancelRaf +}) + +function wallEvent(wallId: AnyNodeId, localX: number, localY: number) { + return { + node: current(wallId), + normal: [0, 0, 1], + localPosition: [localX, localY, 0.1], + position: [localX, localY, 0.1], + nativeEvent: { timeStamp: localX }, + stopPropagation() {}, + } as never +} + +async function drag3d(kind: 'door' | 'window', wallId: AnyNodeId, toLocalX: number) { + const node = seedScene(kind) + const y = node.position[1] + const Tool = kind === 'door' ? MoveDoorTool : MoveWindowTool + const renderer = await create( + false, + sceneApi: undefined as never, + selectNode: () => {}, + unit: 'metric', + }} + > + + , + ) + await act(async () => { + emitter.emit('wall:enter', wallEvent(WALL_A_ID, node.position[0], y)) + emitter.emit('wall:move', wallEvent(wallId, toLocalX, y)) + emitter.emit('wall:click', wallEvent(wallId, toLocalX, y)) + }) + await act(async () => renderer.unmount()) + return current(node.id) as DoorNode | WindowNode +} + +function drag2d(kind: 'door' | 'window', planPoint: [number, number]) { + const node = seedScene(kind) + const nodes = useScene.getState().nodes + const session = + kind === 'door' + ? doorFloorplanMoveTarget({ node: node as DoorNode, nodes }) + : windowFloorplanMoveTarget({ node: node as WindowNode, nodes }) + session.apply({ + planPoint, + modifiers: { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false }, + }) + expect(session.canCommit()).toBe(true) + session.commit() + return current(node.id) as DoorNode | WindowNode +} + +const casingOf = (node: Pick) => ({ + casingFaceA: node.casingFaceA, + casingFaceB: node.casingFaceB, +}) + +describe('movedOpeningCasingFaces', () => { + const sceneOf = (hosts: AnyNode[]) => + Object.fromEntries(hosts.map((host) => [host.id, host])) as Record + const onA = (rotation = 0) => ({ + parentId: WALL_A_ID, + rotation: [0, rotation, 0] as [number, number, number], + ...ON_A, + }) + + test('outside walls: each face goes to the face on its side, outside or in', () => { + const nodes = sceneOf(OUTSIDE_WALLS) + expect(movedOpeningCasingFaces(onA(), WALL_B_ID, [0, 0, 0], nodes)).toEqual({ + casingFaceA: true, + casingFaceB: false, + }) + // Whichever way the unit is turned on its new wall. + expect(movedOpeningCasingFaces(onA(), WALL_B_ID, [0, Math.PI, 0], nodes)).toEqual({ + casingFaceA: true, + casingFaceB: false, + }) + const alike = sceneOf( + walls([ + { a: 'interior', b: 'exterior' }, + { a: 'interior', b: 'exterior' }, + ]), + ) + expect(movedOpeningCasingFaces(onA(), WALL_B_ID, [0, 0, 0], alike)).toEqual(ON_A) + }) + + test('walls that do not know their outside: the faces follow the way the unit faces', () => { + const nodes = sceneOf( + walls([ + { a: 'interior', b: 'interior' }, + { a: 'unknown', b: 'unknown' }, + ]), + ) + expect(movedOpeningCasingFaces(onA(), WALL_B_ID, [0, 0, 0], nodes)).toEqual(ON_A) + expect(movedOpeningCasingFaces(onA(), WALL_B_ID, [0, Math.PI, 0], nodes)).toEqual({ + casingFaceA: true, + casingFaceB: false, + }) + expect(movedOpeningCasingFaces(onA(Math.PI), WALL_B_ID, [0, Math.PI, 0], nodes)).toEqual(ON_A) + }) + + test("a roof face's `a` is its outside", () => { + const roofFace = { id: 'rseg_casing-move', type: 'roof-segment' } as unknown as AnyNode + const nodes = sceneOf([...OUTSIDE_WALLS, roofFace]) + // Wall A's outside is its `b`: on the gable it is the `a`. + expect(movedOpeningCasingFaces(onA(), roofFace.id, [0, 0, 0], nodes)).toEqual({ + casingFaceA: true, + casingFaceB: false, + }) + }) + + test('a slide along its own wall, or a host without cased faces, keeps them as they are', () => { + const nodes = sceneOf(OUTSIDE_WALLS) + expect(movedOpeningCasingFaces(onA(), WALL_A_ID, [0, Math.PI, 0], nodes)).toEqual(ON_A) + expect(movedOpeningCasingFaces(onA(), LEVEL_ID, [0, Math.PI, 0], nodes)).toEqual(ON_A) + const fromLevel = { ...onA(), parentId: LEVEL_ID } + expect(movedOpeningCasingFaces(fromLevel, WALL_B_ID, [0, Math.PI, 0], nodes)).toEqual(ON_A) + }) +}) + +describe('a moved opening keeps its casing on its side of the wall', () => { + for (const kind of ['door', 'window'] as const) { + test(`3D ${kind} moved to a wall drawn the other way swaps its casing faces`, async () => { + const moved = await drag3d(kind, WALL_B_ID, 3.5) + expect(moved.wallId).toBe(WALL_B_ID) + expect(casingOf(moved)).toEqual({ casingFaceA: true, casingFaceB: false }) + }) + + test(`3D ${kind} slid along its own wall keeps its casing faces`, async () => { + const moved = await drag3d(kind, WALL_A_ID, 3.5) + expect(moved.wallId).toBe(WALL_A_ID) + expect(casingOf(moved)).toEqual(ON_A) + }) + + test(`2D ${kind} moved to a wall drawn the other way swaps its casing faces`, () => { + const moved = drag2d(kind, [4, 3.95]) + expect(moved.wallId).toBe(WALL_B_ID) + expect(casingOf(moved)).toEqual({ casingFaceA: true, casingFaceB: false }) + }) + + test(`2D ${kind} slid along its own wall keeps its casing faces`, () => { + const moved = drag2d(kind, [4, 0.05]) + expect(moved.wallId).toBe(WALL_A_ID) + expect(casingOf(moved)).toEqual(ON_A) + }) + } +}) diff --git a/packages/nodes/src/shared/opening-casing-move.ts b/packages/nodes/src/shared/opening-casing-move.ts new file mode 100644 index 0000000000..aa733111b2 --- /dev/null +++ b/packages/nodes/src/shared/opening-casing-move.ts @@ -0,0 +1,50 @@ +import { + type AnyNode, + type DoorNode, + openingHostFacing, + resolveWallExteriorSide, + type WindowNode, +} from '@pascal-app/core' + +type MovedOpening = Pick< + DoorNode | WindowNode, + 'parentId' | 'roofSegmentId' | 'rotation' | 'casingFaceA' | 'casingFaceB' +> +type CasingFaces = Pick + +/** The face of a host that looks outdoors: +1 for `a`, -1 for `b`, null when it cannot tell. */ +function hostOutside(host: AnyNode): 1 | -1 | null { + if (host.type === 'wall') return resolveWallExteriorSide(host) + // A roof face's `a` is its outside, as its casing has it. + return host.type === 'roof-segment' ? 1 : null +} + +/** + * The casing faces a door or window keeps when a move takes it from its host to `hostId`, the + * wall or roof face it lands on turned to `rotation`. Casing is kept per host face, and two hosts + * need not name their faces alike: a wall drawn the other way round has its `a` where the first + * has its `b`. So each face's casing goes to the matching face of the new host: outside to + * outside where both hosts know their outside (an outside wall, as its rooms give it, or a roof + * face), else the face the unit's front looks at to the face it looks at there. A slide along its + * own host, or a host with no cased faces on either end (a dormer, open floor), keeps them as they + * are. + */ +export function movedOpeningCasingFaces( + opening: MovedOpening, + hostId: string, + rotation: DoorNode['rotation'], + nodes: Readonly>, +): CasingFaces { + const kept = { casingFaceA: opening.casingFaceA, casingFaceB: opening.casingFaceB } + const fromId = opening.roofSegmentId ?? opening.parentId + const [from, to] = [nodes[fromId ?? ''], nodes[hostId]] + if (fromId === hostId || !from || !to) return kept + const cased = (host: AnyNode) => host.type === 'wall' || host.type === 'roof-segment' + if (!(cased(from) && cased(to))) return kept + const [fromOutside, toOutside] = [hostOutside(from), hostOutside(to)] + const swapped = + fromOutside !== null && toOutside !== null + ? fromOutside !== toOutside + : openingHostFacing(opening) !== openingHostFacing({ rotation }) + return swapped ? { casingFaceA: kept.casingFaceB, casingFaceB: kept.casingFaceA } : kept +} diff --git a/packages/nodes/src/shared/opening-casing-section.tsx b/packages/nodes/src/shared/opening-casing-section.tsx new file mode 100644 index 0000000000..f9d3a3f161 --- /dev/null +++ b/packages/nodes/src/shared/opening-casing-section.tsx @@ -0,0 +1,81 @@ +'use client' + +import { + type DoorNode, + openingCasingWidth, + resolveOpeningCasingHost, + useScene, + type WindowNode, +} from '@pascal-app/core' +import { PanelSection, SliderControl, ToggleControl } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { useMemo } from 'react' +import { openingCasingFaceLabels } from './opening-casing' + +const INCH = 0.0254 +const MIN_CASING_WIDTH = 0.5 * INCH +const MAX_CASING_WIDTH = 8 * INCH + +type CasingPatch = Partial> + +/** + * The door and window panels' Casing section: casing on or off for each face + * of the host wall (Exterior and Interior on an outside wall), and the boards' + * face width, in inches on an imperial plan (feet would show 2 in as 0.17). + * Nothing where the opening takes no casing (a curtain wall, a dormer face, a + * unit built from a script). + */ +export function OpeningCasingSection({ + node, + onUpdate, +}: { + node: DoorNode | WindowNode + onUpdate: (patch: CasingPatch) => void +}) { + // Its own component, so the whole scene it reads for the face names + // re-renders this section alone, not the panel around it. + const nodes = useScene((state) => state.nodes) + const cased = useMemo(() => resolveOpeningCasingHost(node, nodes) !== null, [node, nodes]) + const { parentId, roofSegmentId } = node + const labels = useMemo( + () => openingCasingFaceLabels(nodes, { parentId, roofSegmentId }), + [nodes, parentId, roofSegmentId], + ) + const imperial = useViewer((state) => state.unit) === 'imperial' + if (!cased) return null + const faceA = node.casingFaceA !== false + const faceB = node.casingFaceB !== false + const toShown = (meters: number) => (imperial ? meters / INCH : meters) + + return ( + + onUpdate({ casingFaceA: checked })} + /> + onUpdate({ casingFaceB: checked })} + /> + {(faceA || faceB) && ( + { + const meters = imperial ? value * INCH : value + onUpdate({ + casingWidth: Math.min(Math.max(meters, MIN_CASING_WIDTH), MAX_CASING_WIDTH), + }) + }} + precision={imperial ? 2 : 3} + step={imperial ? 0.25 : 0.005} + unit={imperial ? 'in' : 'm'} + value={toShown(openingCasingWidth(node))} + /> + )} + + ) +} diff --git a/packages/nodes/src/shared/opening-casing.test.ts b/packages/nodes/src/shared/opening-casing.test.ts new file mode 100644 index 0000000000..bee3a79f4e --- /dev/null +++ b/packages/nodes/src/shared/opening-casing.test.ts @@ -0,0 +1,136 @@ +import { describe, expect, test } from 'bun:test' +import { + type AnyNode, + DoorNode, + type FloorplanGeometry, + type GeometryContext, + LevelNode, + OPENING_CASING_PROUD, + WallNode, + WindowNode, +} from '@pascal-app/core' +import { buildDoorFloorplan } from '../door/floorplan' +import { buildWindowFloorplan } from '../window/floorplan' +import { openingCasingFaceLabels } from './opening-casing' + +const INCH = 0.0254 +const CASING = 2 * INCH +const wall = WallNode.parse({ id: 'wall_casing-plan', start: [0, 0], end: [4, 0], thickness: 0.2 }) +const context = { children: [], parent: wall, resolve: () => undefined, siblings: [] } + +/** + * The casing legs a plan draws, as along / across spans: the polygons of its nested group of + * polygons (the opening's mark is a group too, of a line, a tag and its text). + */ +function casingLegs(geometry: FloorplanGeometry | null) { + const children = geometry?.kind === 'group' ? geometry.children : [] + const casing = children.find( + (child) => child.kind === 'group' && child.children.every((leg) => leg.kind === 'polygon'), + ) + const legs = casing?.kind === 'group' ? casing.children : [] + return legs.flatMap((leg) => { + if (leg.kind !== 'polygon') return [] + const xs = leg.points.map((point) => point[0]) + const zs = leg.points.map((point) => point[1]) + return [ + { along: [Math.min(...xs), Math.max(...xs)], across: [Math.min(...zs), Math.max(...zs)] }, + ] + }) +} + +const windowPlan = (values: Partial = {}) => + buildWindowFloorplan( + WindowNode.parse({ + id: 'window_casing-plan', + parentId: wall.id, + wallId: wall.id, + position: [2, 1.2, 0], + width: 1.2, + height: 1, + ...values, + }), + context as GeometryContext, + ) + +const doorPlan = (values: Partial = {}) => + buildDoorFloorplan( + DoorNode.parse({ + id: 'door_casing-plan', + parentId: wall.id, + wallId: wall.id, + position: [2, 1.05, 0], + width: 0.9, + ...values, + }), + context as GeometryContext, + ) + +describe('opening casing in plan', () => { + test('a window shows its legs beside the cutout, standing proud of both faces', () => { + const legs = casingLegs(windowPlan()) + expect(legs).toHaveLength(4) + for (const leg of legs) { + const besideStart = Math.abs(leg.along[1]! - 1.4) < 1e-9 + const besideEnd = Math.abs(leg.along[0]! - 2.6) < 1e-9 + expect(besideStart || besideEnd).toBe(true) + expect(leg.along[1]! - leg.along[0]!).toBeCloseTo(CASING, 9) + const onA = Math.abs(leg.across[0]! - 0.1) < 1e-9 + const onB = Math.abs(leg.across[1]! + 0.1) < 1e-9 + expect(onA || onB).toBe(true) + expect(leg.across[1]! - leg.across[0]!).toBeCloseTo(OPENING_CASING_PROUD, 9) + } + }) + + test('a door shows its legs too, and a face turned off shows none', () => { + expect(casingLegs(doorPlan())).toHaveLength(4) + const legs = casingLegs(doorPlan({ casingFaceA: false })) + expect(legs).toHaveLength(2) + for (const leg of legs) expect(leg.across[1]).toBeCloseTo(-0.1, 9) + expect(casingLegs(doorPlan({ casingFaceA: false, casingFaceB: false }))).toHaveLength(0) + }) + + test('a wider casing draws wider legs', () => { + for (const leg of casingLegs(windowPlan({ casingWidth: 3.5 * INCH }))) + expect(leg.along[1]! - leg.along[0]!).toBeCloseTo(3.5 * INCH, 9) + }) +}) + +describe('opening casing face names', () => { + const grid: [number, number][] = [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], + ] + const level = LevelNode.parse({ id: 'level_casing' }) + const walls = grid.map((start, i) => + WallNode.parse({ + id: `wall_casing${i}`, + parentId: level.id, + thickness: 0.2, + start, + end: grid[(i + 1) % 4]!, + }), + ) + const nodes: Record = Object.fromEntries( + [level, ...walls].map((node) => [node.id, node]), + ) + + test('an outside wall names its faces Exterior and Interior', () => { + // Drawn anticlockwise, the room is on each wall's face a. + expect(openingCasingFaceLabels(nodes, { parentId: walls[0]!.id })).toEqual({ + a: 'Interior', + b: 'Exterior', + }) + }) + + test('a gable end is outside on face a; a lone wall keeps Side A and Side B', () => { + expect(openingCasingFaceLabels(nodes, { parentId: 'rseg_x', roofSegmentId: 'rseg_x' })).toEqual( + { a: 'Exterior', b: 'Interior' }, + ) + expect(openingCasingFaceLabels({ [wall.id]: wall }, { parentId: wall.id })).toEqual({ + a: 'Side A', + b: 'Side B', + }) + }) +}) diff --git a/packages/nodes/src/shared/opening-casing.ts b/packages/nodes/src/shared/opening-casing.ts new file mode 100644 index 0000000000..76b781e9d1 --- /dev/null +++ b/packages/nodes/src/shared/opening-casing.ts @@ -0,0 +1,102 @@ +import { + type AnyNode, + type DoorNode, + type FloorplanGeometry, + type FloorplanPoint, + getWallBodyCenterOffset, + type OpeningCasingHost, + openingCasingBoards, + roomSideFaces, + type WallFace, + type WallNode, + type WindowNode, +} from '@pascal-app/core' + +type CasingPlanStyle = { + fill: string + stroke: string + strokeWidth: number + /** Screen-pixel strokes in the editor; plan-metre ink on a drafted sheet. */ + vectorEffect?: 'non-scaling-stroke' +} + +/** + * The casing a door's or window's plan shows: the legs beside the cutout on + * each face of the wall it is cased on, each a short rectangle standing proud + * of the finished face, as plans draw trim. Null when there is none to draw (a + * curtain wall, a railing, a script-built unit, both faces turned off). + * `cornerEnd` is the wall end a corner window runs to, where it has no leg. + */ +export function buildOpeningCasingPlan( + opening: DoorNode | WindowNode, + wall: WallNode, + style: CasingPlanStyle, + cornerEnd?: OpeningCasingHost['cornerEnd'], +): FloorplanGeometry | null { + if (opening.source || wall.wallType === 'curtain' || wall.wallType === 'railing') return null + const [x1, z1] = wall.start + const dx = wall.end[0] - x1 + const dz = wall.end[1] - z1 + const length = Math.hypot(dx, dz) + if (length < 1e-9) return null + const dir = [dx / length, dz / length] as const + const perp = [-dir[1], dir[0]] as const + const plan = (along: number, across: number): FloorplanPoint => [ + x1 + dir[0] * along + perp[0] * across, + z1 + dir[1] * along + perp[1] * across, + ] + // Boards are measured from the opening's centre plane, which a justified wall + // sets off its reference line and a plane offset sets off the wall's body. + const centreAcross = getWallBodyCenterOffset(wall) + (opening.position[2] ?? 0) + const host: OpeningCasingHost = { + thickness: wall.thickness ?? 0.1, + ...(cornerEnd ? { cornerEnd } : {}), + } + const children: FloorplanGeometry[] = openingCasingBoards(opening, host) + .filter((board) => board.part === 'leg') + .map(({ center, size }): FloorplanGeometry => { + const along = opening.position[0] + center[0] + const across = centreAcross + center[2] + const [halfAlong, halfAcross] = [size[0] / 2, size[2] / 2] + return { + kind: 'polygon', + points: [ + plan(along - halfAlong, across + halfAcross), + plan(along + halfAlong, across + halfAcross), + plan(along + halfAlong, across - halfAcross), + plan(along - halfAlong, across - halfAcross), + ], + ...style, + strokeLinejoin: 'miter', + } + }) + return children.length ? { kind: 'group', children } : null +} + +/** + * What the door and window panels call the faces an opening can be cased on. A + * wall with rooms on one side only (an outside wall, as the wall panel's paint + * regions tell it) names them Exterior and Interior, as does a roof face, whose + * `a` is its outside; any other wall keeps the wall panel's Side A and Side B. + */ +export function openingCasingFaceLabels( + nodes: Readonly>, + opening: Pick, +): Record { + if (opening.roofSegmentId) return { a: 'Exterior', b: 'Interior' } + const wallId = opening.parentId + if (wallId && nodes[wallId]?.type === 'wall') { + try { + const { inside, outside } = roomSideFaces( + nodes as Parameters[0], + wallId, + ) + if (inside && outside) { + return { [outside]: 'Exterior', [inside]: 'Interior' } as Record + } + } catch { + // Not a wall (any more): fall through to the neutral labels. + } + } + return { a: 'Side A', b: 'Side B' } +} diff --git a/packages/nodes/src/shared/polygon-vertex-affordance.ts b/packages/nodes/src/shared/polygon-vertex-affordance.ts index 34fca1ca10..406f086c35 100644 --- a/packages/nodes/src/shared/polygon-vertex-affordance.ts +++ b/packages/nodes/src/shared/polygon-vertex-affordance.ts @@ -60,6 +60,12 @@ export type PolygonAffordanceSnapContext + vertexIndex?: number } export type PolygonEdgeSnapContext = { @@ -84,6 +90,13 @@ export type PolygonAffordanceOptions * translated edge, or `null` to keep the candidate. */ snapEdge?: (context: PolygonEdgeSnapContext) => [[number, number], [number, number]] | null + /** + * `move-edge`: the sides beside the dragged one keep their own angle, running on to meet it + * where it goes; a side in line with it (a side split in two) gets a square jog instead, so + * pulling out part of a side makes a bump-out. Off, both ends of the side translate square to + * it and its neighbours pivot on their far ends. + */ + keepNeighbourAngles?: boolean } type PolygonShape = { @@ -177,6 +190,8 @@ export function createPolygonVertexAffordance i === vertexIndex ? [snapped[0], snapped[1]] : p, @@ -261,6 +276,8 @@ export function createPolygonAddVertexAffordance i === newVertexIndex ? [snapped[0], snapped[1]] : p, @@ -325,6 +342,61 @@ export function createPolygonDeleteVertexAffordance, + edgeIndex: number, + normal: readonly [number, number], + distance: number, +): [number, number][] { + const copy = ring.map(([x, y]) => [x, y] as [number, number]) + const count = ring.length + // Under a millimetre the side has not moved: no jog a hair long. + if (count < 3 || Math.abs(distance) < 1e-3) return copy + const startIndex = edgeIndex + const endIndex = (edgeIndex + 1) % count + const start = ring[startIndex]! + const end = ring[endIndex]! + const moved = (p: readonly [number, number]): [number, number] => [ + p[0] + normal[0] * distance, + p[1] + normal[1] * distance, + ] + /** Where the side from `neighbour` to `vertex` meets the moved edge; `null` in line with it. */ + const meet = (vertex: readonly [number, number], neighbour: readonly [number, number]) => { + const dx = vertex[0] - neighbour[0] + const dy = vertex[1] - neighbour[1] + const length = Math.hypot(dx, dy) + if (length < 1e-9) return null + const along = (dx * normal[0] + dy * normal[1]) / length + if (Math.abs(along) < IN_LINE_SINE) return null + const target = moved(start) + const t = + ((target[0] - neighbour[0]) * normal[0] + (target[1] - neighbour[1]) * normal[1]) / + (along * length) + return [neighbour[0] + dx * t, neighbour[1] + dy * t] as [number, number] + } + const startMeet = meet(start, ring[(startIndex - 1 + count) % count]!) + const endMeet = meet(end, ring[(endIndex + 1) % count]!) + const startPoints: [number, number][] = startMeet + ? [startMeet] + : [[start[0], start[1]], moved(start)] + const endPoints: [number, number][] = endMeet ? [endMeet] : [moved(end), [end[0], end[1]]] + const out: [number, number][] = [] + for (let i = 0; i < count; i++) { + if (i === startIndex) out.push(...startPoints) + else if (i === endIndex) out.push(...endPoints) + else out.push(copy[i]!) + } + return out +} + /** * Edge-drag: move a whole edge perpendicular to itself. Both endpoints * translate by `edgeNormal * projectedDelta`. The other vertices of @@ -437,12 +509,19 @@ export function createPolygonMoveEdgeAffordance { - if (i === edgeStartIndex || i === edgeEndIndex) { - return [p[0] + normalX * normalDistance, p[1] + normalY * normalDistance] - } - return [p[0], p[1]] as [number, number] - }) + const nextRing: [number, number][] = options?.keepNeighbourAngles + ? movedEdgeKeepingNeighbours( + originalRing, + edgeIndex, + [normalX, normalY], + normalDistance, + ) + : originalRing.map((p, i) => { + if (i === edgeStartIndex || i === edgeEndIndex) { + return [p[0] + normalX * normalDistance, p[1] + normalY * normalDistance] + } + return [p[0], p[1]] as [number, number] + }) const patch = buildRingPatch(node, holeIndex, nextRing, options?.boundaryCommitData) commitRingPatch(node.id, holeIndex, patch) }, diff --git a/packages/nodes/src/site/foundation-view-shell.test.tsx b/packages/nodes/src/site/foundation-view-shell.test.tsx new file mode 100644 index 0000000000..470f0c5814 --- /dev/null +++ b/packages/nodes/src/site/foundation-view-shell.test.tsx @@ -0,0 +1,90 @@ +import { afterEach, beforeEach, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + FootingNode, + LevelNode, + sceneRegistry, + useScene, + WallNode, +} from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' +import { act, create } from '@react-three/test-renderer' +import { Group } from 'three' +import { FoundationViewShell } from './foundation-view-shell' + +const wall = WallNode.parse({ id: 'wall_a', parentId: 'level_g', start: [0, 0], end: [4, 0] }) +const hiddenWall = WallNode.parse({ + id: 'wall_b', + parentId: 'level_g', + start: [4, 0], + end: [4, 3], + visible: false, +}) +const footing = FootingNode.parse({ + id: 'footing_a', + parentId: 'level_g', + start: [0, 1], + end: [4, 1], +}) +const nodes: AnyNode[] = [ + BuildingNode.parse({ id: 'building_a', children: ['level_g'] }), + LevelNode.parse({ + id: 'level_g', + parentId: 'building_a', + level: 0, + children: ['wall_a', 'wall_b', 'footing_a'], + }), + wall, + hiddenWall, + footing, +] +const objects = new Map(['level_g', 'wall_a', 'wall_b', 'footing_a'].map((id) => [id, new Group()])) + +let previous: { + viewer: ReturnType + scene: ReturnType +} +beforeEach(() => { + previous = { viewer: useViewer.getState(), scene: useScene.getState() } + useScene.setState({ + nodes: Object.fromEntries(nodes.map((node) => [node.id, node])) as Record, + }) + for (const [id, object] of objects) { + object.visible = id !== 'wall_b' + sceneRegistry.nodes.set(id, object) + } + useViewer + .getState() + .setSelection({ buildingId: 'building_a' as never, levelId: 'level_g' as never }) +}) +afterEach(() => { + for (const id of objects.keys()) sceneRegistry.nodes.delete(id) + useViewer.setState(previous.viewer, true) + useScene.setState(previous.scene, true) +}) + +const shown = () => Object.fromEntries([...objects].map(([id, object]) => [id, object.visible])) + +test('the foundation view hides the house, keeps the level and its footings, and gives each object back its own visibility', async () => { + const renderer = await create() + // The state lands (and the hider mounts) before the frames run. + const settle = async (state: Partial>) => { + await act(async () => useViewer.setState(state)) + await act(async () => renderer.advanceFrames(2, 0.05)) + } + + await settle({ foundationView: true, walkthroughMode: false }) + expect(shown()).toEqual({ level_g: true, wall_a: false, wall_b: false, footing_a: true }) + + // The walkthrough walks through the finished house. + await settle({ walkthroughMode: true }) + expect(shown()).toEqual({ level_g: true, wall_a: true, wall_b: false, footing_a: true }) + + await settle({ walkthroughMode: false }) + expect(shown().wall_a).toBe(false) + await settle({ foundationView: false }) + expect(shown()).toEqual({ level_g: true, wall_a: true, wall_b: false, footing_a: true }) + await renderer.unmount() +}) diff --git a/packages/nodes/src/site/foundation-view-shell.tsx b/packages/nodes/src/site/foundation-view-shell.tsx new file mode 100644 index 0000000000..fcb5c2f717 --- /dev/null +++ b/packages/nodes/src/site/foundation-view-shell.tsx @@ -0,0 +1,64 @@ +'use client' + +import { + type AnyNodeId, + foundationViewHiddenIds, + isCaptureSessionOn, + sceneRegistry, + subscribeCaptureSessions, + useScene, +} from '@pascal-app/core' +import { foundationViewBuildingId, useViewer } from '@pascal-app/viewer' +import { useFrame } from '@react-three/fiber' +import { useEffect, useRef, useSyncExternalStore } from 'react' + +/** Gives each object of `hidden` but those in `keep` back its own `visible`. */ +function restore(hidden: Set, keep: ReadonlySet = new Set()) { + const nodes = useScene.getState().nodes + for (const id of hidden) { + if (keep.has(id)) continue + const object = sceneRegistry.nodes.get(id as AnyNodeId) + if (object) object.visible = nodes[id as AnyNodeId]?.visible !== false + hidden.delete(id) + } +} + +/** + * Hides the house over its foundation every frame (the host redraws visibility as it likes) and + * gives each object back its own `visible` after: what `foundationViewHiddenIds` names, so the + * concrete the engine draws, the footings and a framing plugin's foundation stay. What it hides + * takes no pointer either (`hiddenByFoundationView`, in `useNodeEvents`). + */ +function HiddenHouse() { + const hidden = useRef(new Set()) + useEffect(() => { + const ids = hidden.current + return () => restore(ids) + }, []) + useFrame(() => { + const buildingId = foundationViewBuildingId() + const ids = new Set( + buildingId ? foundationViewHiddenIds(useScene.getState().nodes, buildingId) : [], + ) + restore(hidden.current, ids) + for (const id of ids) { + const object = sceneRegistry.nodes.get(id as AnyNodeId) + if (!object) continue + if (object.visible) object.visible = false + hidden.current.add(id) + } + }) + return null +} + +/** + * The 3D side of the foundation view (`useViewer.foundationView`): the house steps aside so its + * foundation and footings show, the ground thinned to its veil (`groundSheer`). Bones need not be + * installed. With Framing only on too, the frame stays over the foundation. A picture and the + * walkthrough show the finished house. + */ +export function FoundationViewShell() { + const on = useViewer((state) => state.foundationView && !state.walkthroughMode) + const capturing = useSyncExternalStore(subscribeCaptureSessions, isCaptureSessionOn, () => false) + return on && !capturing ? : null +} diff --git a/packages/nodes/src/site/generic-foundation-geometry.test.ts b/packages/nodes/src/site/generic-foundation-geometry.test.ts index 3b2148d20d..cdb828f283 100644 --- a/packages/nodes/src/site/generic-foundation-geometry.test.ts +++ b/packages/nodes/src/site/generic-foundation-geometry.test.ts @@ -1,6 +1,6 @@ import { expect, test } from 'bun:test' import type { FoundationMember } from '@pascal-app/core' -import { buildFoundationMesh } from './generic-foundation-geometry' +import { buildDesignedFoundationMesh, buildFoundationMesh } from './generic-foundation-geometry' const run = (over: Partial = {}): FoundationMember => ({ kind: 'stem', @@ -66,3 +66,83 @@ test('runs of the same kind join into one mesh', () => { const mesh = buildFoundationMesh([run(), run({ from: [4, 0], to: [4, 3] })], 'stem') expect(mesh?.indices.length).toBe(2 * 12 * 3) }) + +/** Each triangle of a convex solid faces away from its centre. */ +function facesOut(mesh: NonNullable>) { + const count = mesh.positions.length / 3 + const centre = [0, 1, 2].map((k) => { + let sum = 0 + for (let i = 0; i < count; i++) sum += mesh.positions[i * 3 + k] as number + return sum / count + }) + const point = (i: number) => [0, 1, 2].map((k) => mesh.positions[i * 3 + k] as number) + for (let t = 0; t < mesh.indices.length; t += 3) { + const [a, b, c] = [0, 1, 2].map((k) => point(mesh.indices[t + k] as number)) as number[][] + const ab = [b[0]! - a[0]!, b[1]! - a[1]!, b[2]! - a[2]!] + const ac = [c[0]! - a[0]!, c[1]! - a[1]!, c[2]! - a[2]!] + const winding = [ + ab[1]! * ac[2]! - ab[2]! * ac[1]!, + ab[2]! * ac[0]! - ab[0]! * ac[2]!, + ab[0]! * ac[1]! - ab[1]! * ac[0]!, + ] + const middle = [0, 1, 2].map((k) => (a[k]! + b[k]! + c[k]!) / 3) + const away = middle.map((value, k) => value - centre[k]!) + expect( + winding[0]! * away[0]! + winding[1]! * away[1]! + winding[2]! * away[2]!, + ).toBeGreaterThan(0) + } +} + +test('a pile is a closed cylinder standing on its point, facing out', () => { + const mesh = buildDesignedFoundationMesh([ + run({ kind: 'pile', from: [2, 3], to: [2, 3], width: 0.3, bottomY: -3, topY: -0.5 }), + ]) + if (!mesh) throw new Error('no mesh') + facesOut(mesh) + const xs = Array.from(mesh.positions).filter((_, index) => index % 3 === 0) + expect(Math.max(...xs) - Math.min(...xs)).toBeCloseTo(0.3, 6) +}) + +test('a slab is its outline, top and bottom capped, facing out either way it winds', () => { + const square: Array<[number, number]> = [ + [0, 0], + [4, 0], + [4, 3], + [0, 3], + ] + for (const outline of [square, [...square].reverse()]) { + const mesh = buildDesignedFoundationMesh([ + run({ kind: 'slab', outline, bottomY: -0.1, topY: 0 }), + ]) + if (!mesh) throw new Error('no mesh') + facesOut(mesh) + } +}) + +test('a turned-down edge is swept along its run, its outer face on the run line facing out of the building', () => { + const section: Array<[number, number]> = [ + [0, -0.4], + [0.3, -0.4], + [0.6, -0.1], + [0.6, 0], + [0.15, 0], + [0.15, 0.05], + [0, 0.05], + ] + const mesh = buildDesignedFoundationMesh([ + run({ kind: 'turndown', from: [0, 0], to: [5, 0], section }), + ]) + if (!mesh) throw new Error('no mesh') + // The building is to the run's left (+z here); its outer face, on z = 0, faces -z. + const zs = Array.from(mesh.positions).filter((_, index) => index % 3 === 2) + expect(Math.min(...zs)).toBeCloseTo(0, 9) + expect(Math.max(...zs)).toBeCloseTo(0.6, 6) + let outer = 0 + for (let i = 0; i < mesh.positions.length / 3; i++) + if ( + Math.abs(mesh.positions[i * 3 + 2] as number) < 1e-9 && + (mesh.normals[i * 3 + 2] as number) < -0.99 + ) + outer += 1 + expect(outer).toBe(4) +}) diff --git a/packages/nodes/src/site/generic-foundation-geometry.ts b/packages/nodes/src/site/generic-foundation-geometry.ts index 9b4c32ac9a..50b3cc7515 100644 --- a/packages/nodes/src/site/generic-foundation-geometry.ts +++ b/packages/nodes/src/site/generic-foundation-geometry.ts @@ -1,4 +1,5 @@ import type { FoundationMember } from '@pascal-app/core' +import { BufferAttribute, BufferGeometry, ShapeUtils, Vector2 } from 'three' /** The foundation members of one kind as one indexed mesh, in world coordinates. */ export type FoundationMesh = { @@ -9,6 +10,17 @@ export type FoundationMesh = { indices: Uint32Array } +/** A built mesh as a three geometry; `null` for none. */ +export function geometryOf(mesh: FoundationMesh | null): BufferGeometry | null { + if (!mesh) return null + const geometry = new BufferGeometry() + geometry.setAttribute('position', new BufferAttribute(mesh.positions, 3)) + geometry.setAttribute('normal', new BufferAttribute(mesh.normals, 3)) + geometry.setAttribute('along', new BufferAttribute(mesh.along, 1)) + geometry.setIndex(new BufferAttribute(mesh.indices, 1)) + return geometry +} + type Vec3 = readonly [number, number, number] const sub = (a: Vec3, b: Vec3): Vec3 => [a[0] - b[0], a[1] - b[1], a[2] - b[2]] @@ -19,73 +31,275 @@ const cross = (a: Vec3, b: Vec3): Vec3 => [ ] const dot = (a: Vec3, b: Vec3) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2] +/** Faces collected into one mesh, each with its own flat normal (a cylinder's sides share theirs). */ +class MeshSink { + readonly positions: number[] = [] + readonly normals: number[] = [] + readonly along: number[] = [] + readonly indices: number[] = [] + + /** A planar polygon facing `outward`, fanned from its first corner; `normals` per corner where it is curved. */ + face(corners: readonly Vec3[], outward: Vec3, alongAt: readonly number[], normals?: Vec3[]) { + if (corners.length < 3) return + const [a, b, c] = corners as [Vec3, Vec3, Vec3] + const facing = dot(cross(sub(b, a), sub(c, a)), outward) >= 0 + const order = corners.map((_, index) => + facing ? index : (corners.length - index) % corners.length, + ) + const base = this.positions.length / 3 + for (const index of order) { + this.positions.push(...(corners[index] as Vec3)) + this.normals.push(...(normals?.[index] ?? outward)) + this.along.push(alongAt[index] ?? 0) + } + for (let k = 1; k + 1 < corners.length; k++) this.indices.push(base, base + k, base + k + 1) + } + + /** + * A flat polygon (a cap) of any shape, triangulated, facing `outward`; `corners` are the plan's + * and then each hole's, in order. + */ + cap( + corners: readonly Vec3[], + plan: readonly Vector2[], + outward: Vec3, + alongAt: number, + holes: readonly (readonly Vector2[])[] = [], + ) { + const triangles = ShapeUtils.triangulateShape(plan as Vector2[], holes as Vector2[][]) + for (const [i = 0, j = 0, k = 0] of triangles) { + this.face([corners[i] as Vec3, corners[j] as Vec3, corners[k] as Vec3], outward, [ + alongAt, + alongAt, + alongAt, + ]) + } + } + + mesh(): FoundationMesh | null { + if (this.indices.length === 0) return null + return { + positions: new Float32Array(this.positions), + normals: new Float32Array(this.normals), + along: new Float32Array(this.along), + indices: new Uint32Array(this.indices), + } + } +} + +/** A run's plan frame: its length, the direction along it and the one to its left. */ +function frameOf({ from, to }: Pick) { + const dx = to[0] - from[0] + const dz = to[1] - from[1] + const length = Math.hypot(dx, dz) + if (length < 1e-9) return null + const [ux, uz] = [dx / length, dz / length] + return { length, ux, uz, nx: -uz, nz: ux } +} + /** - * The runs of `kind` as closed boxes, six faces each with its own flat normal. A run is a prism on - * the plan line `from` to `to`, `width` across, from `bottomY` to `topY`. Corners are closed by the - * runs themselves (they overlap), so the boxes carry no joins. + * A run as a closed box, six faces each with its own flat normal: the plan line `from` to `to`, + * `width` across, from `bottomY` to `topY`. Corners are closed by the runs themselves (they + * overlap), so the boxes carry no joins. */ +function writeBox(sink: MeshSink, member: FoundationMember) { + const { from, to, width, bottomY, topY } = member + const frame = frameOf(member) + if (!frame || topY - bottomY < 1e-9) return + const { length, ux, uz, nx, nz } = frame + const half = width / 2 + const at = (end: [number, number], side: number, y: number): Vec3 => [ + end[0] + nx * half * side, + y, + end[1] + nz * half * side, + ] + const [a0, a1, a2, a3] = [ + at(from, 1, bottomY), + at(to, 1, bottomY), + at(to, -1, bottomY), + at(from, -1, bottomY), + ] + const [b0, b1, b2, b3] = [ + at(from, 1, topY), + at(to, 1, topY), + at(to, -1, topY), + at(from, -1, topY), + ] + const run = [0, length, length, 0] + sink.face([b0, b1, b2, b3], [0, 1, 0], run) + sink.face([a0, a1, a2, a3], [0, -1, 0], run) + sink.face([a0, a1, b1, b0], [nx, 0, nz], run) + sink.face([a3, a2, b2, b3], [-nx, 0, -nz], run) + sink.face([a0, a3, b3, b0], [-ux, 0, -uz], [0, 0, 0, 0]) + sink.face([a1, a2, b2, b1], [ux, 0, uz], [length, length, length, length]) +} + +/** A run drawn as its `section` (`[across, y]`, across to the run's left), swept from `from` to `to`. */ +function writeSection(sink: MeshSink, member: FoundationMember) { + const frame = frameOf(member) + const raw = member.section ?? [] + // Corners repeated in a row (a step that closed up) are one. + const section = raw.filter((point, index) => { + const next = raw[(index + 1) % raw.length]! + return Math.hypot(point[0] - next[0], point[1] - next[1]) > 1e-9 + }) + if (!frame || section.length < 3) return + const { length, ux, uz, nx, nz } = frame + const at = (end: [number, number], [across, y]: [number, number]): Vec3 => [ + end[0] + nx * across, + y, + end[1] + nz * across, + ] + let area = 0 + for (let i = 0; i < section.length; i++) { + const [ax, ay] = section[i]! + const [bx, by] = section[(i + 1) % section.length]! + area += ax * by - bx * ay + } + const turn = area >= 0 ? 1 : -1 + for (let i = 0; i < section.length; i++) { + const p = section[i]! + const q = section[(i + 1) % section.length]! + // Out of a counter-clockwise outline is to the right of each side. + const [dc, dy] = [q[0] - p[0], q[1] - p[1]] + const size = Math.hypot(dc, dy) + const [oc, oy] = [(dy / size) * turn, (-dc / size) * turn] + sink.face( + [at(member.from, p), at(member.to, p), at(member.to, q), at(member.from, q)], + [nx * oc, oy, nz * oc], + [0, length, length, 0], + ) + } + const plan = section.map(([across, y]) => new Vector2(across, y)) + sink.cap( + section.map((point) => at(member.from, point)), + plan, + [-ux, 0, -uz], + 0, + ) + sink.cap( + section.map((point) => at(member.to, point)), + plan, + [ux, 0, uz], + length, + ) +} + +const PILE_SIDES = 16 + +/** A pile: a cylinder `width` across, standing on `from`. */ +function writePile(sink: MeshSink, { from, width, bottomY, topY }: FoundationMember) { + if (topY - bottomY < 1e-9 || width <= 0) return + const radius = width / 2 + const ring = (y: number): Vec3[] => + Array.from({ length: PILE_SIDES }, (_, k) => { + const angle = (k / PILE_SIDES) * Math.PI * 2 + return [from[0] + Math.cos(angle) * radius, y, from[1] + Math.sin(angle) * radius] as Vec3 + }) + const [low, high] = [ring(bottomY), ring(topY)] + for (let k = 0; k < PILE_SIDES; k++) { + const next = (k + 1) % PILE_SIDES + const radial = (index: number): Vec3 => { + const angle = (index / PILE_SIDES) * Math.PI * 2 + return [Math.cos(angle), 0, Math.sin(angle)] + } + const middle = ((k + 0.5) / PILE_SIDES) * Math.PI * 2 + sink.face( + [low[k]!, low[next]!, high[next]!, high[k]!], + [Math.cos(middle), 0, Math.sin(middle)], + [0, 0, 0, 0], + [radial(k), radial(next), radial(next), radial(k)], + ) + } + sink.face(high, [0, 1, 0], []) + sink.face(low, [0, -1, 0], []) +} + +/** The slab: its plan `outline` (less its `holes`) from `bottomY` to `topY`. */ +function writeSlab(sink: MeshSink, { outline, holes = [], bottomY, topY }: FoundationMember) { + if (!outline || outline.length < 3 || topY - bottomY < 1e-9) return + const inner = holes.filter((hole) => hole.length >= 3) + const corners = [...outline, ...inner.flat()] + const plan = outline.map(([x, z]) => new Vector2(x, z)) + const holePlans = inner.map((hole) => hole.map(([x, z]) => new Vector2(x, z))) + sink.cap( + corners.map(([x, z]) => [x, topY, z] as Vec3), + plan, + [0, 1, 0], + 0, + holePlans, + ) + sink.cap( + corners.map(([x, z]) => [x, bottomY, z] as Vec3), + plan, + [0, -1, 0], + 0, + holePlans, + ) + for (const [ring, hole] of [ + [outline, false], + ...inner.map((ring) => [ring, true] as const), + ] as const) { + const clockwise = ShapeUtils.isClockWise(ring.map(([x, z]) => new Vector2(x, z))) + for (let i = 0; i < ring.length; i++) { + const [ax, az] = ring[i]! + const [bx, bz] = ring[(i + 1) % ring.length]! + const size = Math.hypot(bx - ax, bz - az) + if (size < 1e-9) continue + // Out of the slab: to the right of each side as the outline winds in the plan (x, z), into + // a hole round its own. + const side = (clockwise ? -1 : 1) * (hole ? -1 : 1) + sink.face( + [ + [ax, bottomY, az], + [bx, bottomY, bz], + [bx, topY, bz], + [ax, topY, az], + ], + [((bz - az) / size) * side, 0, (-(bx - ax) / size) * side], + [], + ) + } + } +} + +/** The runs of `kind` as closed boxes: footings, stems and grade beams. */ export function buildFoundationMesh( members: readonly FoundationMember[], kind: FoundationMember['kind'], ): FoundationMesh | null { - const runs = members.filter((member) => member.kind === kind) - if (runs.length === 0) return null - const positions: number[] = [] - const normals: number[] = [] - const along: number[] = [] - const indices: number[] = [] - - const quad = (corners: [Vec3, Vec3, Vec3, Vec3], outward: Vec3, alongAt: number[]) => { - const [a, b, c] = corners - const facing = dot(cross(sub(b, a), sub(c, a)), outward) >= 0 - const order = facing ? [0, 1, 2, 3] : [0, 3, 2, 1] - const base = positions.length / 3 - for (const index of order) { - positions.push(...(corners[index] as Vec3)) - normals.push(...outward) - along.push(alongAt[index] as number) - } - indices.push(base, base + 1, base + 2, base, base + 2, base + 3) - } + const sink = new MeshSink() + for (const member of members) if (member.kind === kind && !member.section) writeBox(sink, member) + return sink.mesh() +} - for (const { from, to, width, bottomY, topY } of runs) { - const dx = to[0] - from[0] - const dz = to[1] - from[1] - const length = Math.hypot(dx, dz) - if (length < 1e-9 || topY - bottomY < 1e-9) continue - const [ux, uz] = [dx / length, dz / length] - const [nx, nz] = [-uz, ux] - const half = width / 2 - const at = (end: [number, number], side: number, y: number): Vec3 => [ - end[0] + nx * half * side, - y, - end[1] + nz * half * side, - ] - const [a0, a1, a2, a3] = [ - at(from, 1, bottomY), - at(to, 1, bottomY), - at(to, -1, bottomY), - at(from, -1, bottomY), - ] - const [b0, b1, b2, b3] = [ - at(from, 1, topY), - at(to, 1, topY), - at(to, -1, topY), - at(from, -1, topY), - ] - const run = [0, length, length, 0] - quad([b0, b1, b2, b3], [0, 1, 0], run) - quad([a0, a1, a2, a3], [0, -1, 0], run) - quad([a0, a1, b1, b0], [nx, 0, nz], [0, length, length, 0]) - quad([a3, a2, b2, b3], [-nx, 0, -nz], [0, length, length, 0]) - quad([a0, a3, b3, b0], [-ux, 0, -uz], [0, 0, 0, 0]) - quad([a1, a2, b2, b1], [ux, 0, uz], [length, length, length, length]) +/** + * What a designed foundation pours besides footings and stems, as one concrete mesh: a slab's + * turned-down edge (its section), grade beams, piles and the slab itself. + */ +export function buildDesignedFoundationMesh( + members: readonly FoundationMember[], +): FoundationMesh | null { + const sink = new MeshSink() + for (const member of members) { + if (member.section) writeSection(sink, member) + else if (member.kind === 'grade-beam') writeBox(sink, member) + else if (member.kind === 'pile') writePile(sink, member) + else if (member.kind === 'slab') writeSlab(sink, member) } - if (indices.length === 0) return null - return { - positions: new Float32Array(positions), - normals: new Float32Array(normals), - along: new Float32Array(along), - indices: new Uint32Array(indices), + return sink.mesh() +} + +/** Every member as one mesh, each drawn as its kind is: boxes, sections, piles, the slab. */ +export function buildFoundationMembersMesh( + members: readonly FoundationMember[], +): FoundationMesh | null { + const sink = new MeshSink() + for (const member of members) { + if (member.section) writeSection(sink, member) + else if (member.kind === 'pile') writePile(sink, member) + else if (member.kind === 'slab') writeSlab(sink, member) + else writeBox(sink, member) } + return sink.mesh() } diff --git a/packages/nodes/src/site/generic-foundation.test.tsx b/packages/nodes/src/site/generic-foundation.test.tsx index f455caf37b..d0e712e77c 100644 --- a/packages/nodes/src/site/generic-foundation.test.tsx +++ b/packages/nodes/src/site/generic-foundation.test.tsx @@ -70,11 +70,18 @@ function ringOf(levelId: string) { ) } -/** A house on the flat site, over a basement when `basement` is set (the villa's numbers). */ +/** + * A house on the flat site, over a basement when `basement` is set (the villa's numbers), its designed + * foundation switched off: it stands on the generic one under test. + */ function setHouse(basement: boolean, extra: AnyNode[] = []) { const levels = basement ? ['level_b', 'level_g'] : ['level_g'] const nodes: AnyNode[] = [ - BuildingNode.parse({ id: 'building_a', children: levels }), + BuildingNode.parse({ + id: 'building_a', + children: levels, + metadata: { foundation: { enabled: false } }, + }), ...levels.flatMap((levelId) => { const walls = ringOf(levelId) return [ @@ -115,7 +122,7 @@ const parts = (root: Object3D) => { const names = (root: Object3D) => parts(root).map((part) => part.userData.genericFoundation) -test('a house stands on a footing and a block wall, marked, found by captures and stripped from exports; over a basement only the footing; the walkthrough has none', async () => { +test('a house stands on a footing and a poured wall, marked, found by captures and stripped from exports; over a basement only the footing; the walkthrough has none', async () => { setHouse(false) const renderer = await create() try { @@ -152,7 +159,7 @@ const paris = () => }, }) -test('the stem is drawn in what the building is built of: block, concrete in Europe', async () => { +test('the stem is poured, in the US as in Europe: one concrete with its footing', async () => { const materialOf = async (extra: AnyNode[]) => { setHouse(false, extra) const renderer = await create() @@ -163,7 +170,7 @@ test('the stem is drawn in what the building is built of: block, concrete in Eur await renderer.unmount() } } - expect(await materialOf([])).toBe('masonry') + expect(await materialOf([])).toBe('concrete') expect(await materialOf([paris()])).toBe('concrete') }) diff --git a/packages/nodes/src/site/generic-foundation.tsx b/packages/nodes/src/site/generic-foundation.tsx index ab92e077f1..ce3b44ae8b 100644 --- a/packages/nodes/src/site/generic-foundation.tsx +++ b/packages/nodes/src/site/generic-foundation.tsx @@ -1,29 +1,44 @@ 'use client' import { + type AnyNode, + type AnyNodeId, type FoundationMember, genericFoundation, isCaptureSessionOn, + type LiveNodeOverrides, subscribeCaptureSessions, + useLiveNodeOverrides, useScene, } from '@pascal-app/core' import { BLOCK_MASONRY, concreteMaterial, + framingMaterial, masonryMaterial, SITE_FOUNDATION, useViewer, } from '@pascal-app/viewer' import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react' -import { BufferAttribute, BufferGeometry } from 'three' +import type { BufferGeometry } from 'three' import type { MeshLambertNodeMaterial } from 'three/webgpu' -import { buildFoundationMesh, type FoundationMesh } from './generic-foundation-geometry' +import { + buildDesignedFoundationMesh, + buildFoundationMesh, + geometryOf, +} from './generic-foundation-geometry' import { noopRaycast } from './ground-veil' /** - * The generic foundation under the buildings (`genericFoundation` in core): a concrete footing and - * a foundation wall, drawn from the walls and never stored, of the building's foundation - * construction: block by default, cast concrete in Europe (each stem run carries its `material`). It is always drawn: the ground hides the part below it from above + * The foundation under the buildings (`genericFoundation` in core). A building stands on its + * designed foundation unless its record is switched off: the slab, its turned-down edge, stem + * walls, footings, grade beams and piles in one concrete mesh (`designed`), a raised floor's + * cripple walls in framing over their stems (in the foundation view: elsewhere the walls carry + * their siding down over them), under its own outline + * where it has one, following that outline live while it is dragged. Switched off, it stands on + * the generic one: a concrete footing and a poured foundation wall drawn from the walls and never + * stored (each stem run carries its `material`). It is + * always drawn: the ground hides the part below it from above * (the eye under the ground, or looking through the hole over a level below grade, is what sees * it), and the course the building's seat leaves above grade reads as what it is built of in the * normal view. The walkthrough draws none of it. @@ -35,16 +50,6 @@ import { noopRaycast } from './ground-veil' * them. */ -function geometryOf(mesh: FoundationMesh | null): BufferGeometry | null { - if (!mesh) return null - const geometry = new BufferGeometry() - geometry.setAttribute('position', new BufferAttribute(mesh.positions, 3)) - geometry.setAttribute('normal', new BufferAttribute(mesh.normals, 3)) - geometry.setAttribute('along', new BufferAttribute(mesh.along, 1)) - geometry.setIndex(new BufferAttribute(mesh.indices, 1)) - return geometry -} - /** The stem runs built of `material` as one geometry. */ function stemGeometry( members: readonly FoundationMember[], @@ -74,24 +79,60 @@ export function sameFoundation(a: readonly FoundationMember[], b: readonly Found member.from[0] === other.from[0] && member.from[1] === other.from[1] && member.to[0] === other.to[0] && - member.to[1] === other.to[1] + member.to[1] === other.to[1] && + member.designed === other.designed && + samePoints(member.section, other.section) && + samePoints(member.outline, other.outline) && + samePoints(member.holes?.flat(), other.holes?.flat()) && + member.holes?.length === other.holes?.length ) }) ) } +function samePoints( + a: ReadonlyArray | undefined, + b: ReadonlyArray | undefined, +) { + if (a === b) return true + if (!(a && b) || a.length !== b.length) return false + return a.every((point, index) => point[0] === b[index]![0] && point[1] === b[index]![1]) +} + +/** + * The scene with each foundation outline being dragged at its live shape (the outline editors + * preview through the live overrides), so the concrete follows the drag; the scene itself + * otherwise, which keeps the foundation's own memo. + */ +export function withLiveOutlines( + nodes: Record, + overrides: ReadonlyMap, +): Record { + let merged: Record | null = null + for (const [id, patch] of overrides) { + const node = nodes[id as AnyNodeId] + if (node?.type !== 'foundation') continue + merged ??= { ...nodes } + merged[id as AnyNodeId] = { ...node, ...patch } as AnyNode + } + return merged ?? nodes +} + function useFoundationMembers(): readonly FoundationMember[] { // Stable across store writes that leave the foundation as it was. const kept = useRef([]) // A capture session starting or ending changes what a plugin claims, though no node changes: // the picture shows the finished house, with the generic course a framing plugin gives back. - useSyncExternalStore(subscribeCaptureSessions, isCaptureSessionOn, () => false) - return useScene((state) => { - const next = genericFoundation(state.nodes) + const capturing = useSyncExternalStore(subscribeCaptureSessions, isCaptureSessionOn, () => false) + const nodes = useScene((state) => state.nodes) + const overrides = useLiveNodeOverrides((state) => state.overrides) + return useMemo(() => { + void capturing + const next = genericFoundation(withLiveOutlines(nodes, overrides)) if (sameFoundation(next, kept.current)) return kept.current kept.current = next return next - }) + }, [nodes, overrides, capturing]) } function FoundationPart({ @@ -103,7 +144,7 @@ function FoundationPart({ geometry: BufferGeometry | null material: MeshLambertNodeMaterial visible: boolean - part: FoundationMember['kind'] + part: 'footing' | 'stem' | 'cripple' | 'designed' }) { useEffect(() => () => geometry?.dispose(), [geometry]) if (!geometry) return null @@ -125,27 +166,41 @@ export function GenericFoundation() { () => takenOver.length === 0 ? derived - : derived.filter((member) => !takenOver.includes(member.levelId)), + : derived.filter((member) => member.designed || !takenOver.includes(member.levelId)), [derived, takenOver], ) const shown = useViewer((state) => !state.walkthroughMode) const concrete = useMemo(concreteMaterial, []) const block = useMemo(() => masonryMaterial(BLOCK_MASONRY), []) + const framing = useMemo(() => framingMaterial(), []) useEffect( () => () => { concrete.dispose() block.dispose() + framing.dispose() }, - [concrete, block], + [concrete, block, framing], ) const footing = useMemo(() => geometryOf(buildFoundationMesh(members, 'footing')), [members]) const blockStem = useMemo(() => stemGeometry(members, 'cmu'), [members]) const concreteStem = useMemo(() => stemGeometry(members, 'concrete'), [members]) + const designed = useMemo(() => geometryOf(buildDesignedFoundationMesh(members)), [members]) + const cripple = useMemo(() => geometryOf(buildFoundationMesh(members, 'cripple')), [members]) + // The house's walls carry their siding down over a cripple wall; the foundation view, the + // house hidden, shows its framing. + const framed = useViewer((state) => state.foundationView) return ( <> + + ) } diff --git a/packages/nodes/src/site/ground-veil.test.tsx b/packages/nodes/src/site/ground-veil.test.tsx index 15740a6e2c..8a3c890326 100644 --- a/packages/nodes/src/site/ground-veil.test.tsx +++ b/packages/nodes/src/site/ground-veil.test.tsx @@ -621,3 +621,60 @@ test('a scene with no level below grade has no hole and no pit', async () => { expect(pitParts(plain.scene.instance as Object3D)).toHaveLength(0) await plain.unmount() }) + +// Framing only took the whole ground away: the stepped footings and the stem stood in the sky with +// no grade to read them against. +test.each([false, true])( + 'in framing only the solid ground steps aside and its veil shows where grade is; the walkthrough keeps it solid (terrain: %p)', + async (withTerrain) => { + const site = lot(withTerrain) + setNodes([site]) + const renderer = await create() + const scene = renderer.scene.instance as Object3D + const settle = async (state: Partial>) => + act(async () => { + useViewer.setState(state) + await renderer.advanceFrames(3, 0.05) + }) + await settle({ framingOnly: true, walkthroughMode: false, belowGrade: false }) + expect(ground(scene, 'solid').filter(drawn)).toHaveLength(0) + const sheets = veilsOf(scene).sheets.filter(drawn) + // The lot's ground and the horizon past it. + expect(sheets.length).toBeGreaterThanOrEqual(2) + expect(useViewer.getState().groundWindowVeiled).toBe(false) + // The veil is never picked: a click reaches the footing under it. + expect(groundPickedAt(scene)).toHaveLength(0) + + await settle({ walkthroughMode: true }) + expect(ground(scene, 'solid').length).toBeGreaterThan(0) + expect(ground(scene, 'solid').every(drawn)).toBe(true) + expect(ground(scene, 'veil').some(drawn)).toBe(false) + await renderer.unmount() + }, +) + +// The level list's Foundation row: the concrete under grade is what it shows, without Bones. +test.each([false, true])( + 'the foundation view veils the ground as framing only does, and gives it back after (terrain: %p)', + async (withTerrain) => { + const site = lot(withTerrain) + setNodes([site]) + const renderer = await create() + const scene = renderer.scene.instance as Object3D + const settle = async (state: Partial>) => + act(async () => { + useViewer.setState(state) + await renderer.advanceFrames(3, 0.05) + }) + await settle({ foundationView: true, framingOnly: false, walkthroughMode: false }) + expect(ground(scene, 'solid').filter(drawn)).toHaveLength(0) + expect(veilsOf(scene).sheets.filter(drawn).length).toBeGreaterThanOrEqual(2) + expect(groundPickedAt(scene)).toHaveLength(0) + + await settle({ foundationView: false }) + expect(ground(scene, 'solid').length).toBeGreaterThan(0) + expect(ground(scene, 'solid').every(drawn)).toBe(true) + expect(veilsOf(scene).sheets.some(drawn)).toBe(false) + await renderer.unmount() + }, +) diff --git a/packages/nodes/src/site/ground-veil.tsx b/packages/nodes/src/site/ground-veil.tsx index 81afdec636..dd2efcc413 100644 --- a/packages/nodes/src/site/ground-veil.tsx +++ b/packages/nodes/src/site/ground-veil.tsx @@ -116,7 +116,22 @@ export function gradeRingMaterial() { } /** - * Drawn only while the ground is veiled: the veil of one solid ground surface (the same geometry + * Whether the site's ground is its veil alone: in Framing only and in the foundation view the solid + * ground steps aside, so the footings and stem walls under grade show, and its faint veil stays to + * show where grade is. The walkthrough walks on solid ground. + */ +export function groundSheer(state: { + framingOnly?: boolean + foundationView?: boolean + walkthroughMode?: boolean +}) { + return ( + (state.framingOnly === true || state.foundationView === true) && state.walkthroughMode !== true + ) +} + +/** + * Drawn only while the ground is veiled, or sheer: the veil of one solid ground surface (the same geometry * in the same place), or the grade ring. A direct child of the site, as the surfaces are, so the * captures find it (`siteGround`) and hide it with the site's other surfaces. * @@ -137,10 +152,12 @@ export function GroundVeil({ rotation?: [number, number, number] shows?: 'window' | 'veiled' }) { - const veiled = useViewer((state) => - shows === 'veiled' - ? anyGroundVeiled(state) - : state.groundMode === 'auto' && state.groundWindowVeiled, + const veiled = useViewer( + (state) => + groundSheer(state) || + (shows === 'veiled' + ? anyGroundVeiled(state) + : state.groundMode === 'auto' && state.groundWindowVeiled), ) return ( { state.hideLevelsAboveSelection ? state.selection.levelId : null, ) const belowGrade = useViewer((state) => state.belowGrade) + const framingOnly = useViewer((state) => state.framingOnly) + const foundationView = useViewer((state) => state.foundationView) + const walkthroughMode = useViewer((state) => state.walkthroughMode) const groundMode = useViewer((state) => state.groundMode) const buriedCache = useRef<{ all: [number, number][][] @@ -423,15 +429,22 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { // the level shown does the rest as it does. The one case it closes: the level display has the // storeys away over a level below grade and the person keeps the ground Solid, nothing covers // it then and Solid asks for solid ground. Clipped to the lot, as a hole past its line breaks - // the triangulation. + // the triangulation. A basement's dig (its walls' outside faces, `designedBasement`) is open the + // same way, so the ground meets its walls as it lies and never shows inside them. const coverOpen = !(belowGrade && groundMode === 'solid') + const pads = useGradingPads() + const dug = useMemo( + () => pads.filter(isExcavation).map((pad) => pad.outline as [number, number][]), + [pads], + ) const holes = useMemo(() => { - if (!coverOpen || buried.covered.length === 0 || !polygonPoints || polygonPoints.length < 3) + const open = [...buried.covered, ...dug] + if (!coverOpen || open.length === 0 || !polygonPoints || polygonPoints.length < 3) return slabPolygons - const inLot = intersection(union(buried.covered), polygonPoints as [number, number][]) + const inLot = intersection(union(open), polygonPoints as [number, number][]) const covered = inLot.filter((piece) => piece.holes.length === 0).map((piece) => piece.outer) return covered.length > 0 ? [...slabPolygons, ...covered] : slabPolygons - }, [coverOpen, buried.covered, polygonPoints, slabPolygons]) + }, [coverOpen, buried.covered, dug, polygonPoints, slabPolygons]) const gradeRings = useMemo( () => buried.shown.map((outline) => gradeRingGeometry(outline, persistedField)), @@ -688,8 +701,11 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { // backdrop rather than part of the parcel, so `visible` does not hide it. // `showGround` hides both ground and horizon when authored geometry supplies them, unless a level // below grade is shown: the level display hides that geometry then, and the ground stands in. + // Framing only and the foundation view keep only their veils, so the foundation shows under grade + // against it. const showSiteSurfaces = node.visible !== false const showGround = siteGroundShown(node, { belowGrade }) + const sheer = groundSheer({ framingOnly, foundationView, walkthroughMode }) if (!(node && lineGeometry)) { return null @@ -699,6 +715,7 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { + { holes={holes} material={groundMaterial} polygon={polygonPoints} + sheer={sheer} site={node} veil={groundVeil} /> @@ -727,14 +745,16 @@ export const SiteRenderer = ({ node }: { node: SiteNode }) => { {/* Ground fill: site polygon with slab holes, occludes below-grade geometry */} {showSiteSurfaces && showGround && groundGeometry && !showTerrain && ( <> - + {!sheer && ( + + )} { )} {/* Infinite-ground presentation disc fading into the sky at the horizon */} - {showGround && !immersiveXR && horizonGeometry && horizonMaterial && fadeBounds && ( + {showGround && !sheer && !immersiveXR && horizonGeometry && horizonMaterial && fadeBounds && ( ({ key: '[]', pads: [] }) + return useScene((state) => { + const pads = foundationGradingPads(state.nodes) + const key = JSON.stringify(pads) + if (key !== kept.current.key) kept.current = { key, pads } + return kept.current.pads + }) +} + +/** + * Renders the site's sculpted ground, graded to the slabs' building pads (`gradeTerrainField`): the + * ground filled or cut so every slab stands its exposure over it all round. Round a basement it + * lies as it was, open over the dig (`holes`). * * Mounted by `SiteRenderer` only when the site has terrain (or a stroke is in * flight), so a scene that never touched terrain mounts nothing at all and pays @@ -37,6 +63,7 @@ import { export const TerrainRenderer = ({ material, veil, + sheer = false, site, holes, polygon, @@ -45,6 +72,8 @@ export const TerrainRenderer = ({ material: Material /** The veil drawn in the ground's place over a level below grade, owned there too. */ veil: Material + /** The veil alone, the solid ground stepped aside (`groundSheer`). */ + sheer?: boolean site: SiteNode holes: Ring[] /** @@ -63,12 +92,17 @@ export const TerrainRenderer = ({ [site.id, site.terrain], ) + // The ground as built: filled and cut to the slabs' building pads round the houses. A basement's + // dig is not graded in: the ground stands open over it (one of `holes`), cut plumb at its walls. + const pads = useGradingPads() + const graded = useMemo(() => pads.filter((pad) => !isExcavation(pad)), [pads]) const target = useMemo(() => { - const field = useLiveTerrain.getState().fieldOf(site.id) ?? persistedField - if (!field) return null + const natural = useLiveTerrain.getState().fieldOf(site.id) ?? persistedField + if (!natural) return null + const field = gradeTerrainField(natural, graded) const ring = polygon && polygon.length >= 3 ? skirtRing(field, polygon) : null return createTerrainGeometry(field, holes, ring && ring.length >= 6 ? ring : null) - }, [persistedField, site.id, holes, polygon]) + }, [persistedField, site.id, holes, polygon, graded]) // The surface (and the cut edges round recessed slabs) stop at the property // line; the skirt keeps the plain material — it *is* the line. @@ -128,23 +162,27 @@ export const TerrainRenderer = ({ return ( <> - + {!sheer && ( + + )} {target.holeBoundary && ( <> - + {!sheer && ( + + )} )} @@ -155,12 +193,14 @@ export const TerrainRenderer = ({ round the lot. `receiveShadow` for the same reason the surface has it — a raised edge is lit ground that a building next to it should darken. */} - + {!sheer && ( + + )} ) diff --git a/packages/nodes/src/slab/__tests__/framing-cavity.test.ts b/packages/nodes/src/slab/__tests__/framing-cavity.test.ts new file mode 100644 index 0000000000..6315300ee9 --- /dev/null +++ b/packages/nodes/src/slab/__tests__/framing-cavity.test.ts @@ -0,0 +1,243 @@ +import { beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearPlateSurfaceCaches, + DEFAULT_FLOOR_BUILD_UP, + FLOOR_BUILD_UP_PRESETS, + type FloorBuildUp, + floorBuildUpPatch, + floorLayerDepths, + type GeometryContext, + LevelNode, + SiteNode, + SlabNode, + ZoneNode, +} from '@pascal-app/core' +import { type Group, Mesh } from 'three' +import { buildSlabGeometry, slabFramingCavity } from '../geometry' + +const SQUARE: Array<[number, number]> = [ + [0, 0], + [4, 0], + [4, 4], + [0, 4], +] + +/** + * A building of two storeys with a 4 m square room on `levelId` and its floor plate framed with + * `buildUp` (the thickness its layers sum to, unless `thickness` says otherwise). `manual` draws + * the floor as a hand-drawn slab instead of the room's plate. + */ +function scene( + buildUp: FloorBuildUp, + options: { levelId?: 'level_0' | 'level_1'; thickness?: number; manual?: boolean } = {}, +) { + const levelId = options.levelId ?? 'level_1' + const site = SiteNode.parse({ id: 'site_1', children: ['building_1'] }) + const building = BuildingNode.parse({ + id: 'building_1', + parentId: site.id, + children: ['level_0', 'level_1'], + }) + const zone = ZoneNode.parse({ + id: 'zone_room', + parentId: levelId, + name: 'Room', + polygon: SQUARE, + spaceRole: 'room', + }) + const patch = floorBuildUpPatch(buildUp) + const plate = SlabNode.parse({ + id: 'slab_floor', + parentId: levelId, + ...(options.manual + ? { autoFromWalls: false } + : { boundary: 'auto', autoFromWalls: true, zoneIds: [zone.id] }), + elevation: 0.05, + ...patch, + thickness: options.thickness ?? patch.thickness, + polygon: SQUARE, + }) + const levels = (['level_0', 'level_1'] as const).map((id, level) => + LevelNode.parse({ + id, + parentId: building.id, + level, + height: 2.7, + children: id === levelId ? [zone.id, plate.id] : [], + }), + ) + const nodes: Record = { [site.id]: site, [building.id]: building } + for (const node of [...levels, zone, plate]) nodes[node.id] = node + const ctx: GeometryContext = { + resolve: (id: AnyNodeId) => nodes[id] as N | undefined, + children: [], + siblings: [], + parent: nodes[levelId] as AnyNode, + } + return { plate, ctx } +} + +type Face = { + slotId: string + minY: number + maxY: number + normal: [number, number, number] + /** The triangle's centre in plan. */ + centre: [number, number] +} + +function faces(group: Group): Face[] { + const out: Face[] = [] + for (const mesh of group.children) { + if (!(mesh instanceof Mesh)) continue + const position = mesh.geometry.getAttribute('position') + const index = mesh.geometry.getIndex() + const count = (index ? index.count : position.count) / 3 + for (let triangle = 0; triangle < count; triangle += 1) { + const [a, b, c] = [0, 1, 2].map((corner) => { + const i = index ? index.getX(triangle * 3 + corner) : triangle * 3 + corner + return [position.getX(i), position.getY(i), position.getZ(i)] as const + }) as [readonly number[], readonly number[], readonly number[]] + const ab = [b[0]! - a[0]!, b[1]! - a[1]!, b[2]! - a[2]!] + const ac = [c[0]! - a[0]!, c[1]! - a[1]!, c[2]! - a[2]!] + const n: [number, number, number] = [ + ab[1]! * ac[2]! - ab[2]! * ac[1]!, + ab[2]! * ac[0]! - ab[0]! * ac[2]!, + ab[0]! * ac[1]! - ab[1]! * ac[0]!, + ] + const length = Math.hypot(...n) + if (length < 1e-12) continue + out.push({ + slotId: mesh.userData.slotId as string, + minY: Math.min(a[1]!, b[1]!, c[1]!), + maxY: Math.max(a[1]!, b[1]!, c[1]!), + normal: n.map((value) => value / length) as [number, number, number], + centre: [(a[0]! + b[0]! + c[0]!) / 3, (a[2]! + b[2]! + c[2]!) / 3], + }) + } + } + return out +} + +/** The horizontal faces' planes, `up@y` or `down@y`, sorted from the top. */ +function caps(list: readonly Face[]): string[] { + const planes = new Map() + for (const face of list) { + if (Math.abs(face.normal[1]) < 0.5) continue + const key = `${face.normal[1] > 0 ? 'up' : 'down'}@${face.maxY.toFixed(4)}` + planes.set(key, face.maxY) + } + return [...planes].sort((a, b) => b[1] - a[1]).map(([key]) => key) +} + +const at = (direction: 'up' | 'down', y: number) => `${direction}@${y.toFixed(4)}` + +/** The lowest and highest point of `list`. */ +function span(list: readonly Face[]) { + return { + bottom: Math.min(...list.map((face) => face.minY)), + top: Math.max(...list.map((face) => face.maxY)), + } +} + +describe('a floor framed inside its plate, the frame shown', () => { + beforeEach(() => clearPlateSurfaceCaches()) + + test('opens its joist band between the subfloor and the ceiling, closed all round', () => { + const { plate, ctx } = scene(DEFAULT_FLOOR_BUILD_UP) + const { flooring, sheathing, ceiling } = floorLayerDepths(DEFAULT_FLOOR_BUILD_UP) + const top = plate.elevation + const soffit = plate.elevation - plate.thickness + const band = { top: top - flooring - sheathing, bottom: soffit + ceiling } + const cavity = slabFramingCavity(plate, ctx) + expect(cavity?.top).toBeCloseTo(band.top, 9) + expect(cavity?.bottom).toBeCloseTo(band.bottom, 9) + expect(cavity?.ceiling).toBe(true) + + const closed = faces(buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, false)) + expect(caps(closed)).toEqual([at('up', top), at('down', soffit)]) + + const open = faces(buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, true)) + // the floor, the subfloor's underside, the ceiling's top, the ceiling + expect(caps(open)).toEqual([ + at('up', top), + at('down', band.top), + at('up', band.bottom), + at('down', soffit), + ]) + // the band's faces draw in the plate's underside slot + expect( + new Set( + open + .filter((face) => Math.abs(face.normal[1]) > 0.5 && face.maxY < band.top + 1e-6) + .filter((face) => face.maxY > band.bottom - 1e-6) + .map((face) => face.slotId), + ), + ).toEqual(new Set(['underside'])) + // outside, the rim closes the whole thickness; inside, the band's faces turn in, toward the + // middle of the square + const vertical = open.filter((face) => Math.abs(face.normal[1]) < 1e-3) + const outward = vertical.filter((face) => face.slotId !== 'underside') + const inward = vertical.filter((face) => face.slotId === 'underside') + expect(outward.length).toBeGreaterThan(0) + expect(inward.length).toBeGreaterThan(0) + expect(span(outward).bottom).toBeCloseTo(soffit, 6) + expect(span(outward).top).toBeCloseTo(top, 6) + expect(span(inward).bottom).toBeCloseTo(band.bottom, 6) + expect(span(inward).top).toBeCloseTo(band.top, 6) + const towardMiddle = (face: Face) => + face.normal[0] * (2 - face.centre[0]) + face.normal[2] * (2 - face.centre[1]) > 0 + expect(inward.every(towardMiddle)).toBe(true) + expect(outward.some(towardMiddle)).toBe(false) + // all four sides of the square + expect( + new Set(inward.map((face) => `${Math.round(face.normal[0])},${Math.round(face.normal[2])}`)), + ).toEqual(new Set(['1,0', '-1,0', '0,1', '0,-1'])) + }) + + test('with no ceiling under the joists the band opens to the room below', () => { + const exposed = FLOOR_BUILD_UP_PRESETS.find((preset) => preset.id === 'exposed-2x10-16')! + const { plate, ctx } = scene(exposed.buildUp) + const { flooring, sheathing } = floorLayerDepths(exposed.buildUp) + const top = plate.elevation + const soffit = plate.elevation - plate.thickness + expect(slabFramingCavity(plate, ctx)?.ceiling).toBe(false) + const open = faces(buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, true)) + // no underside: the joists show from below, the subfloor over them + expect(caps(open)).toEqual([at('up', top), at('down', top - flooring - sheathing)]) + // the rim still closes the band from outside, down to the joists' bottom + const outward = open.filter( + (face) => Math.abs(face.normal[1]) < 1e-3 && face.slotId !== 'underside', + ) + expect(span(outward).bottom).toBeCloseTo(soffit, 6) + }) + + test('a hand-drawn slab framed the same opens too, its band in the side slot', () => { + const { plate, ctx } = scene(DEFAULT_FLOOR_BUILD_UP, { manual: true }) + const open = faces(buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, true)) + const { flooring, sheathing } = floorLayerDepths(DEFAULT_FLOOR_BUILD_UP) + expect(caps(open)).toContain(at('down', plate.elevation - flooring - sheathing)) + expect(new Set(open.map((face) => face.slotId))).toEqual(new Set(['surface', 'side'])) + }) + + test('a lowest storey, a plate not its layers and a sunken plate stay solid', () => { + const cases = [ + scene(DEFAULT_FLOOR_BUILD_UP, { levelId: 'level_0' }), + scene(DEFAULT_FLOOR_BUILD_UP, { thickness: 0.2 }), + ] + const { plate: sunkenPlate, ctx: sunkenCtx } = scene(DEFAULT_FLOOR_BUILD_UP) + cases.push({ plate: { ...sunkenPlate, plateRole: 'sunken' } as SlabNode, ctx: sunkenCtx }) + for (const { plate, ctx } of cases) { + expect(slabFramingCavity(plate, ctx)).toBeNull() + const open = faces(buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, true)) + const closed = faces( + buildSlabGeometry(plate, ctx, 'rendered', true, 'clay', undefined, false), + ) + expect(caps(open)).toEqual(caps(closed)) + expect(open.length).toBe(closed.length) + } + }) +}) diff --git a/packages/nodes/src/slab/__tests__/geometry.test.ts b/packages/nodes/src/slab/__tests__/geometry.test.ts index 826fc112b2..a7fe787029 100644 --- a/packages/nodes/src/slab/__tests__/geometry.test.ts +++ b/packages/nodes/src/slab/__tests__/geometry.test.ts @@ -240,6 +240,37 @@ describe('buildSlabGeometry', () => { expect(fill.geometry.boundingBox?.min.y).toBeCloseTo(0) expect(fill.geometry.boundingBox?.max.y).toBeCloseTo(0.3) }) + + test('a porch or patio slab outside the house runs its concrete edge down to the ground', () => { + const site = SiteNode.parse({ id: 'site_test', children: ['building_test'] }) + const porch = SlabNode.parse({ + plateRole: 'sunken', + boundary: 'auto', + elevation: 0.4, + thickness: 0.1, + metadata: { floor: 'porch-slab' }, + polygon: [ + [0, 0], + [2, 0], + [2, 1], + [0, 1], + ], + }) + const skirt = meshesOf(buildSlabGeometry(porch, geometryContext(site), 'solid', false)).find( + (mesh) => mesh.userData.slotId === 'foundation', + )! + expect(skirt).toBeDefined() + skirt.geometry.computeBoundingBox() + expect(skirt.geometry.boundingBox?.min.y).toBeCloseTo(0) + expect(skirt.geometry.boundingBox?.max.y).toBeCloseTo(0.3) + // A plate a step down inside the house stays as it is. + const sunken = { ...porch, metadata: {} } + expect( + meshesOf(buildSlabGeometry(sunken, geometryContext(site), 'solid', false)).some( + (mesh) => mesh.userData.slotId === 'foundation', + ), + ).toBe(false) + }) }) describe('buildSlabGeometry on empty and collapsed polygons', () => { diff --git a/packages/nodes/src/slab/__tests__/step-geometry.test.ts b/packages/nodes/src/slab/__tests__/step-geometry.test.ts index 5dbd6085a7..9da81eba75 100644 --- a/packages/nodes/src/slab/__tests__/step-geometry.test.ts +++ b/packages/nodes/src/slab/__tests__/step-geometry.test.ts @@ -332,7 +332,12 @@ test('terrain skirt clips only the partial step run and keeps the adjacent exter const field = createTerrainField({ cols: 11, rows: 7, spacing: 1, origin: [-1, -1] }) field.heights.fill(-150) const site = SiteNode.parse({ terrain: encodeTerrainField(field) }) - const building = BuildingNode.parse({ parentId: site.id, children: [level.id] }) + // The plate's own skirt is the foundation only with the building's designed one switched off. + const building = BuildingNode.parse({ + parentId: site.id, + children: [level.id], + metadata: { foundation: { enabled: false } }, + }) site.children = [building.id] level.parentId = building.id host.parentId = level.id @@ -721,7 +726,12 @@ test('a raised terrace keeps the base terrain skirt and never fills the platform const field = createTerrainField({ cols: 11, rows: 11, spacing: 1, origin: [-2, -2] }) field.heights.fill(-150) const site = SiteNode.parse({ terrain: encodeTerrainField(field) }) - const building = BuildingNode.parse({ parentId: site.id, children: [level.id] }) + // The plate's own skirt is the foundation only with the building's designed one switched off. + const building = BuildingNode.parse({ + parentId: site.id, + children: [level.id], + metadata: { foundation: { enabled: false } }, + }) level.parentId = building.id site.children = [building.id] const { group, slabs } = derivedScene( @@ -943,7 +953,12 @@ test.each([0, 0.7])('raised house band has a foundation only at site datum %s', for (let z = 0; z < field.rows; z++) for (let x = 0; x < field.cols; x++) field.heights[z * field.cols + x] = -100 + x * 3 const site = SiteNode.parse({ terrain: encodeTerrainField(field) }) - const building = BuildingNode.parse({ parentId: site.id, children: [level.id] }) + // The plate's own skirt is the foundation only with the building's designed one switched off. + const building = BuildingNode.parse({ + parentId: site.id, + children: [level.id], + metadata: { foundation: { enabled: false } }, + }) site.children = [building.id] level.parentId = building.id const outside = source.walls[0]! diff --git a/packages/nodes/src/slab/deck-geometry.test.ts b/packages/nodes/src/slab/deck-geometry.test.ts new file mode 100644 index 0000000000..8d7c734ccc --- /dev/null +++ b/packages/nodes/src/slab/deck-geometry.test.ts @@ -0,0 +1,204 @@ +import { beforeEach, describe, expect, test } from 'bun:test' +import { + type AnyNode, + type AnyNodeId, + BuildingNode, + clearPlateSurfaceCaches, + DECK_BOARD_STOCK, + type GeometryContext, + LevelNode, + SiteNode, + SlabNode, + WallNode, + ZoneNode, +} from '@pascal-app/core' +import { Box3, type Group, Mesh, type MeshStandardMaterial } from 'three' +import { buildSlabGeometry } from './geometry' + +const IN = 0.0254 +const FT = 12 * IN +const TOP = 0.6 +const WOOD = 'library:wood-woodplank48' + +/** A 16 × 12 ft deck room off the house's rear wall (z = 0, 0.2 thick), its plate 1 in down. */ +function deckScene(decking?: ZoneNode['decking'], buildingY = 0, painted?: string) { + const wall = WallNode.parse({ + id: 'wall_rear', + parentId: 'level_1', + start: [-2, 0], + end: [8, 0], + thickness: 0.2, + height: 2.7, + }) + const outline: Array<[number, number]> = [ + [0, 0.1], + [16 * FT, 0.1], + [16 * FT, 0.1 + 12 * FT], + [0, 0.1 + 12 * FT], + ] + const zone = ZoneNode.parse({ + id: 'zone_deck', + parentId: 'level_1', + name: 'Deck', + polygon: outline, + spaceRole: 'room', + roomType: 'deck', + floor: { + finish: WOOD, + drop: IN, + ...(painted + ? { + regions: [ + { + id: 'region_1', + finish: painted, + polygon: [ + [1, 1], + [3, 1], + [3, 2.5], + [1, 2.5], + ], + }, + ], + } + : {}), + }, + ...(decking ? { decking } : {}), + }) + const plate = SlabNode.parse({ + id: 'slab_deck', + parentId: 'level_1', + boundary: 'auto', + autoFromWalls: true, + plateRole: 'sunken', + zoneIds: [zone.id], + elevation: TOP, + thickness: 0.15, + polygon: outline, + metadata: { floor: 'deck', decking: DECK_BOARD_STOCK[decking?.boardSize ?? '5/4x6'].thickness }, + }) + const site = SiteNode.parse({ id: 'site_1', children: ['building_1'] }) + const building = BuildingNode.parse({ + id: 'building_1', + parentId: site.id, + children: ['level_1'], + position: [0, buildingY, 0], + metadata: { foundation: { enabled: false } }, + }) + const level = LevelNode.parse({ + id: 'level_1', + parentId: building.id, + level: 0, + height: 2.7, + children: [wall.id, zone.id, plate.id], + }) + const nodes: Record = { + [site.id]: site, + [building.id]: building, + [level.id]: level, + [wall.id]: wall, + [zone.id]: zone, + [plate.id]: plate, + } + const ctx: GeometryContext = { + resolve: (id: AnyNodeId) => nodes[id] as N | undefined, + children: [], + siblings: [], + parent: level, + } + return { plate, ctx } +} + +const meshes = (group: Group) => + group.children.filter((child): child is Mesh => child instanceof Mesh) +const boxOf = (mesh: Mesh) => + new Box3().setFromBufferAttribute(mesh.geometry.getAttribute('position') as never) +/** The plan area the upward faces at `y` cover. */ +function topArea(mesh: Mesh, y: number): number { + const position = mesh.geometry.getAttribute('position') + const index = mesh.geometry.getIndex()! + let total = 0 + for (let i = 0; i < index.count; i += 3) { + const [a, b, c] = [index.getX(i), index.getX(i + 1), index.getX(i + 2)] + if ([a, b, c].some((v) => Math.abs(position.getY(v) - y) > 1e-6)) continue + const twice = + (position.getX(b) - position.getX(a)) * (position.getZ(c) - position.getZ(a)) - + (position.getX(c) - position.getX(a)) * (position.getZ(b) - position.getZ(a)) + if (twice < 0) total += -twice / 2 + } + return total +} + +describe('a deck room draws boards on a frame, not a slab', () => { + beforeEach(() => clearPlateSurfaceCaches()) + + test('its boards: the room finish and paint role, their top the floor top, gaps between them', () => { + const { plate, ctx } = deckScene() + const group = buildSlabGeometry(plate, ctx, 'rendered', true) + const boards = meshes(group).filter((mesh) => typeof mesh.userData.paintRole === 'string') + expect(boards).toHaveLength(1) + const [deck] = boards as [Mesh] + expect(deck.userData.paintRole).toBe('room:zone_deck') + const box = boxOf(deck) + expect(box.max.y).toBeCloseTo(TOP, 6) + expect(box.min.y).toBeCloseTo(TOP - IN, 6) + const full = 16 * FT * 12 * FT + const covered = topArea(deck, TOP) / full + expect(covered).toBeGreaterThan(0.94) + expect(covered).toBeLessThan(0.97) + }) + + test('its frame: treated wood under the boards down to the ground on posts, a concrete pier under each, never the framing plugin’s green', () => { + const { plate, ctx } = deckScene() + const group = buildSlabGeometry(plate, ctx, 'rendered', true) + const frame = meshes(group).filter((mesh) => mesh.userData.paintRole === undefined) + expect(frame).toHaveLength(2) + const [lumber, piers] = frame as [Mesh, Mesh] + const wood = boxOf(lumber) + expect(wood.max.y).toBeCloseTo(TOP - IN, 6) + expect(wood.min.y).toBeCloseTo(0, 6) + // the boards overhang the rims an inch on the free edges, none past the deck + expect(wood.min.x).toBeCloseTo(IN, 6) + expect(wood.max.x).toBeCloseTo(16 * FT - IN, 6) + expect(wood.min.z).toBeCloseTo(0.1, 6) + const concrete = boxOf(piers) + expect(concrete.min.y).toBeLessThan(0) + expect(concrete.max.y).toBeGreaterThan(0) + for (const mesh of frame) { + const color = (mesh.material as MeshStandardMaterial).color?.getHexString() + expect(color).not.toBe('7d9d6a') + } + }) + + test('its posts reach the ground wherever the house stands', () => { + const { plate, ctx } = deckScene(undefined, 0.3) + const group = buildSlabGeometry(plate, ctx, 'rendered', true) + const lumber = meshes(group).find((mesh) => mesh.userData.paintRole === undefined)! + expect(boxOf(lumber).min.y).toBeCloseTo(-0.3, 6) + }) + + test('the room’s boards: 2x6 stock 1 1/2 in thick, run out from the house', () => { + const { plate, ctx } = deckScene({ boardSize: '2x6', direction: 'perpendicular' }) + const group = buildSlabGeometry(plate, ctx, 'rendered', true) + const deck = meshes(group).find((mesh) => mesh.userData.paintRole === 'room:zone_deck')! + expect(boxOf(deck).min.y).toBeCloseTo(TOP - 1.5 * IN, 6) + const lumber = meshes(group).find((mesh) => mesh.userData.paintRole === undefined)! + expect(boxOf(lumber).max.y).toBeCloseTo(TOP - 1.5 * IN, 6) + }) + + test('a painted part of the floor is its own boards, cut from the same rows', () => { + const plain = buildSlabGeometry(deckScene().plate, deckScene().ctx, 'rendered', true) + const { plate, ctx } = deckScene(undefined, 0, 'library:wood-woodfine2') + const group = buildSlabGeometry(plate, ctx, 'rendered', true) + const boards = meshes(group).filter((mesh) => typeof mesh.userData.paintRole === 'string') + expect(boards.map((mesh) => mesh.userData.paintRole).sort()).toEqual([ + 'room:zone_deck', + 'room:zone_deck/region_1', + ]) + const total = boards.reduce((sum, mesh) => sum + topArea(mesh, TOP), 0) + const before = meshes(plain) + .filter((mesh) => typeof mesh.userData.paintRole === 'string') + .reduce((sum, mesh) => sum + topArea(mesh, TOP), 0) + expect(total).toBeCloseTo(before, 3) + }) +}) diff --git a/packages/nodes/src/slab/deck-geometry.ts b/packages/nodes/src/slab/deck-geometry.ts new file mode 100644 index 0000000000..46705eb690 --- /dev/null +++ b/packages/nodes/src/slab/deck-geometry.ts @@ -0,0 +1,412 @@ +import { + type AnyNode, + type AnyNodeId, + area, + BEAM_MATERIAL_LIBRARY_REFS, + type BuildingNode, + type DeckFramingPlan, + type DeckPiece, + type DeckWall, + deckBoardRun, + deckBoardThickness, + deckSlabBoards, + type GeometryContext, + getLevelElevations, + gradedTerrainFieldOf, + intersection, + type LevelNode, + layoutDeckBoards, + type MultiPolygon, + type PlateSurfacePartition, + type PlateTopCell, + type Polygon, + planDeckFraming, + plateLevelContext, + SITE_DATUM_Y, + type SiteNode, + type SlabNode, + surfaceHeightAt, + useScene, + type ZoneNode, +} from '@pascal-app/core' +import { + type ColorPreset, + createSurfaceRoleMaterial, + type RenderShading, + resolveSlotDefaultMaterial, +} from '@pascal-app/viewer' +import { + BufferGeometry, + Float32BufferAttribute, + FrontSide, + Group, + type Material, + Mesh, + ShapeUtils, + Vector2, +} from 'three' + +/** + * A deck's floor (`isDeckSlab`: a deck room's floor plate, a generated deck) drawn as a deck is + * built, never as a solid slab: its boards (`layoutDeckBoards`), the room's finish on them, each + * painted part of the floor its own boards with its own paint role, over its frame + * (`planDeckFraming`), the same plan the framing plugin books: ledger, joists, rims, beams and + * posts in treated wood, a concrete pier under each post at the ground. The boards' top is the + * slab's top, so items, the room label and clicks find the floor where they did. Each material is + * one merged mesh, so the deck batches like any slab. + */ + +const IN = 0.0254 +/** A post's pier: 12 in square, standing 2 in out of the ground, 10 in into it. */ +const PIER_SIDE = 12 * IN +const PIER_ABOVE = 2 * IN +const PIER_BELOW = 10 * IN + +export type DeckLook = { + shading: RenderShading + textures: boolean + colorPreset: ColorPreset + sceneTheme?: string +} + +export type DeckGeometryOptions = { + /** The deck's outline: the slab's renderable polygon. */ + polygon: Array<[number, number]> + /** The plate's top partition: one set of boards per cell. Null for a drawn slab. */ + partition: PlateSurfacePartition | null + /** A cell's finish as a material; `null` asks for the slab's own surface finish. */ + boardMaterial: (cell: PlateTopCell | null) => Material + look: DeckLook +} + +/** The level-local ground under a plan point: the site's graded terrain, or the datum, wherever the storey stands. */ +function groundUnder(ctx: GeometryContext | undefined): (x: number, z: number) => number { + const level = ctx?.parent + if (!ctx || level?.type !== 'level' || !level.parentId) return () => 0 + const building = ctx.resolve(level.parentId as AnyNodeId) + if (building?.type !== 'building') return () => 0 + const site = building.parentId ? ctx.resolve(building.parentId as AnyNodeId) : undefined + const nodes: Record = { [building.id]: building, [level.id]: level as LevelNode } + if (site?.type === 'site') nodes[site.id] = site + for (const childId of building.children) { + const child = ctx.resolve(childId as AnyNodeId) + if (child) nodes[child.id] = child + } + const elevation = getLevelElevations(nodes).get(level.id) + if (!elevation) return () => 0 + const baseWorldY = (building.position?.[1] ?? 0) + elevation.baseY + const field = site?.type === 'site' ? gradedTerrainFieldOf(useScene.getState().nodes, site) : null + if (!field) return () => SITE_DATUM_Y - baseWorldY + const angle = building.rotation?.[1] ?? 0 + const cos = Math.cos(angle) + const sin = Math.sin(angle) + const [px, , pz] = building.position ?? [0, 0, 0] + return (x, z) => + surfaceHeightAt(field, cos * x + sin * z + px, -sin * x + cos * z + pz) - baseWorldY +} + +/** The house walls a deck edge may hang its ledger on: no railing, no hidden wall. */ +function ledgerWalls(ctx: GeometryContext | undefined): DeckWall[] { + if (!ctx) return [] + return plateLevelContext(ctx.parent, ctx.resolve) + .walls.filter( + (wall) => wall.visible !== false && (wall as { wallType?: string }).wallType !== 'railing', + ) + .map((wall) => ({ + start: wall.start, + end: wall.end, + thickness: wall.thickness ?? 0.1, + curved: Math.abs(wall.curveOffset ?? 0) > 1e-6, + })) +} + +/** + * Posts already standing on the ground by the deck (a generated porch's, hosted on the ground and + * passing through the deck): a beam two of them stand on is theirs to carry. + */ +function carriersBy(node: SlabNode, ctx: GeometryContext | undefined): Array<[number, number]> { + const level = ctx?.parent + if (!ctx || !level || !('children' in level)) return [] + const out: Array<[number, number]> = [] + for (const id of level.children) { + const column = ctx.resolve(id as AnyNodeId) + if (column?.type !== 'column' || column.visible === false || column.supportSlabId === node.id) + continue + const support = column.supportSlabId + ? ctx.resolve(column.supportSlabId as AnyNodeId) + : undefined + const base = column.position[1] + (support?.type === 'slab' ? support.elevation : 0) + if (base < node.elevation - 0.3) out.push([column.position[0], column.position[2]]) + } + return out +} + +/** A material's surface: the treated wood of the frame, the piers' concrete. */ +function frameMaterial(ref: string, role: 'wall' | 'floor', look: DeckLook): Material { + return look.textures + ? resolveSlotDefaultMaterial(ref, look.shading) + : createSurfaceRoleMaterial(role, look.colorPreset, FrontSide, look.sceneTheme) +} + +type Buffers = { positions: number[]; normals: number[]; uvs: number[]; indices: number[] } +const buffers = (): Buffers => ({ positions: [], normals: [], uvs: [], indices: [] }) + +function toGeometry({ positions, normals, uvs, indices }: Buffers): BufferGeometry { + const geometry = new BufferGeometry() + geometry.setAttribute('position', new Float32BufferAttribute(positions, 3)) + geometry.setAttribute('normal', new Float32BufferAttribute(normals, 3)) + geometry.setAttribute('uv', new Float32BufferAttribute(uvs, 2)) + geometry.setIndex(indices) + return geometry +} + +/** A box (centre, [run, height, across], yaw), flat-shaded with metre UVs on every face. */ +function pushBox(out: Buffers, piece: Pick) { + const [cx, cy, cz] = piece.position + const [hx, hy, hz] = piece.dims.map((value) => value / 2) as [number, number, number] + const cos = Math.cos(piece.yaw) + const sin = Math.sin(piece.yaw) + // three.js Y rotation: local +x → (cos, 0, −sin), local +z → (sin, 0, cos) + const world = (x: number, y: number, z: number): [number, number, number] => [ + cx + x * cos + z * sin, + cy + y, + cz - x * sin + z * cos, + ] + const turn = (x: number, z: number): [number, number, number] => [ + x * cos + z * sin, + 0, + -x * sin + z * cos, + ] + // each face: its normal (local), and two edge axes (local) with their half lengths + const faces: Array<{ + n: [number, number, number] + a: [number, number, number] + b: [number, number, number] + }> = [ + { n: [1, 0, 0], a: [0, 0, -1], b: [0, 1, 0] }, + { n: [-1, 0, 0], a: [0, 0, 1], b: [0, 1, 0] }, + { n: [0, 1, 0], a: [1, 0, 0], b: [0, 0, -1] }, + { n: [0, -1, 0], a: [1, 0, 0], b: [0, 0, 1] }, + { n: [0, 0, 1], a: [1, 0, 0], b: [0, 1, 0] }, + { n: [0, 0, -1], a: [-1, 0, 0], b: [0, 1, 0] }, + ] + const half = [hx, hy, hz] + for (const { n, a, b } of faces) { + const base = out.positions.length / 3 + const span = (axis: [number, number, number]) => + Math.abs(axis[0]) * half[0]! + Math.abs(axis[1]) * half[1]! + Math.abs(axis[2]) * half[2]! + const ha = span(a) + const hb = span(b) + const normal = n[1] !== 0 ? ([0, n[1], 0] as [number, number, number]) : turn(n[0], n[2]) + for (const [sa, sb] of [ + [-1, -1], + [1, -1], + [1, 1], + [-1, 1], + ] as const) { + const x = n[0] * half[0]! + a[0] * ha * sa + b[0] * hb * sb + const y = n[1] * half[1]! + a[1] * ha * sa + b[1] * hb * sb + const z = n[2] * half[2]! + a[2] * ha * sa + b[2] * hb * sb + out.positions.push(...world(x, y, z)) + out.normals.push(...normal) + out.uvs.push(ha * (sa + 1), hb * (sb + 1)) + } + out.indices.push(base, base + 1, base + 2, base, base + 2, base + 3) + } +} + +function signedArea2(ring: ReadonlyArray): number { + let twice = 0 + for (let i = 0; i < ring.length; i++) { + const [ax, az] = ring[i]! + const [bx, bz] = ring[(i + 1) % ring.length]! + twice += ax * bz - bx * az + } + return twice +} + +/** + * One board: its top at `top`, its bottom `thickness` under, its sides; the top's grain along + * the board (`along`), shifted by `shift` so neighbouring boards read as boards. + */ +function pushBoard( + out: Buffers, + board: Polygon, + top: number, + thickness: number, + along: readonly [number, number], + shift: number, +) { + const outer = signedArea2(board.outer) < 0 ? [...board.outer].reverse() : board.outer + const holes = board.holes.filter((hole) => hole.length >= 3) + const contour = outer.map(([x, z]) => new Vector2(x, z)) + const holeRings = holes.map((hole) => hole.map(([x, z]) => new Vector2(x, z))) + const bottom = top - thickness + const grain = (x: number, z: number): [number, number] => [ + x * along[0] + z * along[1] + shift, + -x * along[1] + z * along[0], + ] + const cap = [...contour, ...holeRings.flat()] + const topBase = out.positions.length / 3 + for (const p of cap) { + out.positions.push(p.x, top, p.y) + out.normals.push(0, 1, 0) + out.uvs.push(...grain(p.x, p.y)) + } + const bottomBase = out.positions.length / 3 + for (const p of cap) { + out.positions.push(p.x, bottom, p.y) + out.normals.push(0, -1, 0) + out.uvs.push(...grain(p.x, p.y)) + } + for (const [a, b, c] of ShapeUtils.triangulateShape(contour, holeRings)) { + out.indices.push(topBase + a!, topBase + c!, topBase + b!) + out.indices.push(bottomBase + a!, bottomBase + b!, bottomBase + c!) + } + // the board's edges, outward on the outline; a hole's both ways + const side = (a: Vector2, b: Vector2, flipped: boolean) => { + const length = Math.hypot(b.x - a.x, b.y - a.y) + if (length < 1e-9) return + const nx = (b.y - a.y) / length + const nz = -(b.x - a.x) / length + const base = out.positions.length / 3 + for (const [p, y, u, v] of [ + [a, bottom, 0, 0], + [b, bottom, length, 0], + [b, top, length, thickness], + [a, top, 0, thickness], + ] as const) { + out.positions.push(p.x, y, p.y) + out.normals.push(flipped ? -nx : nx, 0, flipped ? -nz : nz) + out.uvs.push(u, v) + } + if (flipped) out.indices.push(base, base + 1, base + 2, base, base + 2, base + 3) + else out.indices.push(base, base + 2, base + 1, base, base + 3, base + 2) + } + for (let i = 0; i < contour.length; i++) + side(contour[i]!, contour[(i + 1) % contour.length]!, false) + for (const ring of holeRings) + for (let i = 0; i < ring.length; i++) { + side(ring[i]!, ring[(i + 1) % ring.length]!, false) + side(ring[i]!, ring[(i + 1) % ring.length]!, true) + } +} + +/** A board's own shift along its grain, the same every build. */ +const shiftOf = (index: number) => { + const x = Math.sin(index * 12.9898 + 78.233) * 43758.5453 + return (x - Math.floor(x)) * 3 +} + +/** The deck's frame as the engine draws it: a joist a side rim stands in for is the rim. */ +export function deckFramePieces(plan: DeckFramingPlan): DeckPiece[] { + return [ + ...(plan.ledger ? [plan.ledger] : []), + ...plan.rims, + ...plan.joists.filter((joist) => !joist.atRim), + ...plan.beams, + ...plan.posts, + ] +} + +/** The deck's frame plan and boards, as drawn for `node`. */ +export function planDeck( + node: SlabNode, + ctx: GeometryContext | undefined, + polygon: Array<[number, number]>, +) { + const boards = deckSlabBoards(node, (id) => { + const zone = ctx?.resolve(id as AnyNodeId) + return zone?.type === 'zone' ? (zone as ZoneNode) : undefined + }) + const thickness = deckBoardThickness(boards) + const ground = groundUnder(ctx) + let cx = 0 + let cz = 0 + for (const [x, z] of polygon) { + cx += x + cz += z + } + const n = Math.max(polygon.length, 1) + const plan = planDeckFraming({ + polygon, + top: node.elevation, + decking: thickness, + walls: ledgerWalls(ctx), + boards, + gradeY: ground(cx / n, cz / n), + gradeAt: ground, + carriers: carriersBy(node, ctx), + }) + return { boards, thickness, plan } +} + +export function buildDeckGeometry( + node: SlabNode, + ctx: GeometryContext | undefined, + options: DeckGeometryOptions, +): Group { + const group = new Group() + const { polygon, partition, look } = options + if (polygon.length < 3) return group + const { boards, thickness, plan } = planDeck(node, ctx, polygon) + const addMesh = (geometry: BufferGeometry, material: Material, role?: string) => { + if (geometry.getAttribute('position').count === 0) { + geometry.dispose() + return + } + const mesh = new Mesh(geometry, material) + mesh.castShadow = true + mesh.receiveShadow = true + if (role) { + mesh.userData.slotId = role + mesh.userData.paintRole = role + } + group.add(mesh) + } + + // ---- the boards: a set for each painted part of the floor, cut along it ---- + const axes = plan.axes ?? { + origin: polygon[0]!, + u: [1, 0] as [number, number], + v: [0, 1] as [number, number], + } + const joists = + plan.stationAxis && plan.axes + ? { origin: plan.axes.origin, axis: plan.stationAxis, stations: plan.stations } + : undefined + // One set of rows over the whole deck, so a painted part's boards line up with the rest. + const top: MultiPolygon = [{ outer: polygon, holes: node.holes ?? [] }] + const laid = layoutDeckBoards(top, axes, boards, joists) + const cells: Array<{ cell: PlateTopCell | null; role: string; outline: MultiPolygon }> = partition + ? partition.cells.map((cell) => ({ cell, role: cell.role, outline: cell.polygons })) + : [{ cell: null, role: 'surface', outline: top }] + const whole = area(top) + const { along } = deckBoardRun(axes, boards.direction) + for (const { cell, role, outline } of cells) { + const out = buffers() + const all = cells.length === 1 && Math.abs(area(outline) - whole) < 1e-4 + laid.forEach((board, index) => { + for (const part of all ? [board] : intersection(board, outline)) + pushBoard(out, part, node.elevation, thickness, along, shiftOf(index)) + }) + addMesh(toGeometry(out), options.boardMaterial(cell), role) + } + + // ---- the frame, and a pier under each post ---- + const frame = buffers() + for (const piece of deckFramePieces(plan)) pushBox(frame, piece) + addMesh( + toGeometry(frame), + frameMaterial(BEAM_MATERIAL_LIBRARY_REFS['pressure-treated'], 'wall', look), + ) + const piers = buffers() + for (const post of plan.posts) + pushBox(piers, { + position: [post.position[0], post.bottom + (PIER_ABOVE - PIER_BELOW) / 2, post.position[2]], + dims: [PIER_SIDE, PIER_ABOVE + PIER_BELOW, PIER_SIDE], + yaw: post.yaw, + }) + addMesh(toGeometry(piers), frameMaterial('library:concrete-raw', 'floor', look)) + return group +} diff --git a/packages/nodes/src/slab/dependency-tracker.test.ts b/packages/nodes/src/slab/dependency-tracker.test.ts index 741b18aa4a..3a7236333f 100644 --- a/packages/nodes/src/slab/dependency-tracker.test.ts +++ b/packages/nodes/src/slab/dependency-tracker.test.ts @@ -28,6 +28,7 @@ for (const [field, value] of [ ['floorStepFinish', 'library:preset-white'], ['floorEdgeFinish', 'library:preset-white'], ['floorStepOverrides', [{ key: 'door_1', finish: 'library:preset-white' }]], + ['decking', { direction: 'diagonal' }], ] as const) test(`${field} changes invalidate the room plate without changing floor intent`, async () => { const { LevelNode, ZoneNode } = await import('@pascal-app/core') diff --git a/packages/nodes/src/slab/dependency-tracker.ts b/packages/nodes/src/slab/dependency-tracker.ts index 096d09e159..fbeae6cf85 100644 --- a/packages/nodes/src/slab/dependency-tracker.ts +++ b/packages/nodes/src/slab/dependency-tracker.ts @@ -3,6 +3,7 @@ import { type AnyNodeId, floorConstructionLift, getRenderableSlabPolygon, + isBuildingFoundationOn, isFloorPlate, plateLevelContext, prepareSlabPolygonContext, @@ -83,6 +84,9 @@ export function createSlabDependencyTracker(initialNodes: Record(left: T[], right: T[]) => left.length === right.length && diff --git a/packages/nodes/src/slab/geometry.ts b/packages/nodes/src/slab/geometry.ts index 612067398b..4f6252b2fb 100644 --- a/packages/nodes/src/slab/geometry.ts +++ b/packages/nodes/src/slab/geometry.ts @@ -7,16 +7,23 @@ import { cutterContextNodes, FenceNode, FOUNDATION_SLOT_DEFAULT, + floorBuildUpMatches, + floorBuildUpOf, + floorLayerDepths, type GeometryContext, getLevelElevations, getMaterialPresetByRef, getRenderableSlabPolygon, + gradedTerrainFieldOf, intersection, + isBuildingFoundationOn, + isDeckSlab, isFloorPlate, type LevelNode, levelBaseElevationAt, liftedManualSlab, type MaterialSchema, + OUTDOOR_ROOM_DEFAULTS, type PlateSurfacePartition, type PlateTopCell, parseFloorStepRole, @@ -46,11 +53,19 @@ import { registerMaterialCacheCleanup, resolveMaterialRef, resolveSlotDefaultMaterial, + useViewer, } from '@pascal-app/viewer' import { type BufferGeometry, FrontSide, Group, type Material, Mesh, type Texture } from 'three' import { generateFenceSlotGeometries } from '../fence/geometry-parts' import { creaseCrossings } from '../site/terrain-drape' -import { clipPlateTerrainFill, splitPlateFaces, splitSlabFacesByFacing } from './surface-split' +import { buildDeckGeometry } from './deck-geometry' +import { + clipPlateTerrainFill, + hollowSlabFaces, + type SlabCavity, + splitPlateFaces, + splitSlabFacesByFacing, +} from './surface-split' /** * Stage B builder for slab. Reuses `generateSlabGeometry` (pure @@ -171,7 +186,38 @@ function getLegacySlabMaterial(node: SlabNode, shading: RenderShading): Material return material } -function terrainFillContext(ctx: GeometryContext | undefined, foundation: boolean) { +function designedFoundationUnder(ctx: GeometryContext | undefined): boolean { + const level = ctx?.parent + if (level?.type !== 'level' || !level.parentId) return false + return isBuildingFoundationOn(ctx?.resolve(level.parentId as AnyNodeId)) +} + +/** + * The plank flooring decks were given before they had boards (the old deck room default, and the + * porch generator's decks): laid on each board it reads as flooring, so boards still carrying it + * draw the deck default's plain grain. + */ +const LEGACY_DECK_FINISH = 'library:wood-floorplank1' + +/** + * A porch or patio slab outside the house (a plate the structure kernel tags `metadata.floor: + * 'porch-slab'`): it stands down in the ground, its edge drawn down to the ground the house's + * foundation grades round it (`foundationGradingPads`), never floating over it. + */ +function isOutdoorGroundSlab(node: SlabNode): boolean { + return ( + isFloorPlate(node) && + node.plateRole !== 'base' && + node.support !== 'open' && + (node.metadata as Record | undefined)?.floor === 'porch-slab' + ) +} + +/** + * The ground a skirt runs down to: the site's terrain, or (`graded`) the terrain as drawn, graded to + * the buildings' foundations, for a slab that stands in that graded ground. + */ +function terrainFillContext(ctx: GeometryContext | undefined, foundation: boolean, graded = false) { if (!ctx) return null const level = ctx.parent if (level?.type !== 'level' || !level.parentId) return null @@ -179,7 +225,9 @@ function terrainFillContext(ctx: GeometryContext | undefined, foundation: boolea if (building?.type !== 'building' || !building.parentId) return null const site = ctx.resolve(building.parentId as AnyNodeId) if (site?.type !== 'site') return null - const field = terrainFieldOf(site) + const field = graded + ? gradedTerrainFieldOf(useScene.getState().nodes, site) + : terrainFieldOf(site) const nodes: Record = { [site.id]: site, @@ -207,8 +255,9 @@ function buildSlabTerrainFillGeometry( polygon: Array<[number, number]>, ctx: GeometryContext | undefined, partition: PlateSurfacePartition | null, + outdoor = false, ): BufferGeometry | null { - const terrain = terrainFillContext(ctx, node.plateRole === 'base') + const terrain = terrainFillContext(ctx, node.plateRole === 'base' || outdoor, outdoor) if (!terrain || polygon.length < 3) return null let area2 = 0 @@ -304,6 +353,39 @@ function platePartitionOf( return computePlateSurfacePartition(node, { ...context, platform }) } +/** + * The joist band of a floor framed inside its slab, as Bones frames it: a slab whose thickness is + * its layers' (`floorBuildUpMatches`), neither a pool, a platform nor a sunken plate, on a storey + * with one under it (a lowest storey's floor stands on the ground or its foundation, with no joists + * in it). Null for every other slab, and for a band with no depth. + */ +export function slabFramingCavity( + node: SlabNode, + ctx: Pick | undefined, +): SlabCavity | null { + if (node.recessed || (node.plateRole !== undefined && node.plateRole !== 'base')) return null + if (!floorBuildUpMatches(node)) return null + const level = ctx?.parent + if (!ctx || level?.type !== 'level' || !level.parentId) return null + const building = ctx.resolve(level.parentId as AnyNodeId) + if (building?.type !== 'building') return null + const overStorey = building.children.some((id) => { + const other = ctx.resolve(id as AnyNodeId) + return other?.type === 'level' && other.level < level.level + }) + if (!overStorey) return null + const { flooring, sheathing, ceiling } = floorLayerDepths(floorBuildUpOf(node)) + const elevation = node.elevation ?? 0.05 + const top = elevation - flooring - sheathing + const bottom = elevation - node.thickness + ceiling + return top - bottom > 1e-4 ? { top, bottom, ceiling: ceiling > 1e-6 } : null +} + +/** + * `framingShown`: the viewer's "Framing" (`useViewer.framingShown`, read when the generic geometry + * system builds; the slab system rebuilds the framed floors when it flips). A floor framed inside + * its slab (`slabFramingCavity`) then opens its joist band. + */ export function buildSlabGeometry( node: SlabNode, ctx?: GeometryContext, @@ -311,6 +393,7 @@ export function buildSlabGeometry( textures = true, colorPreset: ColorPreset = 'clay', sceneTheme?: string, + framingShown = useViewer.getState().framingShown, ): Group { if (ctx) node = withHostedCutterHoles(node, cutterContextNodes(node, ctx)) if (ctx && !node.plateRole && !node.autoFromWalls) @@ -356,7 +439,53 @@ export function buildSlabGeometry( return false }), ) - const merged = generateSlabGeometry(node, polygonContext, platform ? levelBaseAt : undefined) + // A deck is its boards on its frame (`buildDeckGeometry`), never a solid slab. + if (isDeckSlab(node)) + return buildDeckGeometry(node, ctx, { + polygon, + partition: platePartitionOf(node, ctx, node.plateRole === 'platform'), + boardMaterial: (cell) => + (cell ? cell.finish : (node.slots?.surface ?? node.materialPreset)) === LEGACY_DECK_FINISH + ? getPlateCellMaterial( + node, + { + role: cell?.role ?? 'surface', + finish: OUTDOOR_ROOM_DEFAULTS.deck.finish, + materialKey: plateFinishKey(OUTDOOR_ROOM_DEFAULTS.deck.finish), + polygons: cell?.polygons ?? [], + }, + shading, + textures, + colorPreset, + sceneTheme, + ctx?.materials, + ) + : cell + ? getPlateCellMaterial( + node, + cell, + shading, + textures, + colorPreset, + sceneTheme, + ctx?.materials, + ) + : getSlabSlotMaterial( + node, + 'surface', + shading, + textures, + colorPreset, + sceneTheme, + ctx?.materials, + ), + look: { shading, textures, colorPreset, sceneTheme }, + }) + const solid = generateSlabGeometry(node, polygonContext, platform ? levelBaseAt : undefined) + // With the frame shown, a floor framed inside its slab opens its joist band. + const cavity = framingShown && !platform ? slabFramingCavity(node, ctx) : null + const hollow = cavity ? hollowSlabFaces(solid, cavity) : null + const merged = hollow?.body ?? solid const elevation = node.elevation ?? 0.05 const partition = platePartitionOf(node, ctx, platform || node.plateRole === 'platform') @@ -471,11 +600,25 @@ export function buildSlabGeometry( ) } } + if (hollow) { + // the open band's faces: the subfloor's underside, the ceiling's top, the rim turned in + const slotId: SlabSlotId = partition ? 'underside' : 'side' + addMesh( + hollow.cavity, + slotId, + getSlabSlotMaterial(node, slotId, shading, textures, colorPreset, sceneTheme, ctx?.materials), + ) + } merged.dispose() + if (merged !== solid) solid.dispose() - const foundation = node.plateRole === 'base' && node.foundation?.type === 'solid' + // A building standing on its designed foundation draws that under the plate, not this skirt. + const foundation = + node.plateRole === 'base' && node.foundation?.type === 'solid' && !designedFoundationUnder(ctx) + // A porch or patio slab runs down into the ground beside the house, whatever stands under it. + const outdoor = isOutdoorGroundSlab(node) if ( - (foundation || (!node.plateRole && node.fillToTerrain)) && + (foundation || outdoor || (!node.plateRole && node.fillToTerrain)) && !node.recessed && !platform && group.children.length > 0 @@ -485,10 +628,11 @@ export function buildSlabGeometry( getRenderableSlabPolygon(node, polygonContext), ctx, partition, + outdoor, ) if (terrainFill) { - // The skirt hangs below the plate perimeter and always faces out. - const slotId: SlabSlotId = foundation ? 'foundation' : partition ? 'edge' : 'side' + // The skirt hangs below the plate perimeter and always faces out: the slab's concrete. + const slotId: SlabSlotId = foundation || outdoor ? 'foundation' : partition ? 'edge' : 'side' addMesh( terrainFill, slotId, diff --git a/packages/nodes/src/slab/panel.tsx b/packages/nodes/src/slab/panel.tsx index 5d8174d218..607145dd0a 100644 --- a/packages/nodes/src/slab/panel.tsx +++ b/packages/nodes/src/slab/panel.tsx @@ -14,6 +14,8 @@ import { ActionButton, ActionGroup, FloorFoundationPanel, + formatAreaLabel, + getAreaUnitLabel, holeEditScope, PanelSection, PanelWrapper, @@ -21,6 +23,7 @@ import { SegmentedControl, ShapeChoice, SliderControl, + squareMetersToAreaUnit, startOpeningDraft, triggerSFX, useEditingHole, @@ -433,7 +436,9 @@ export function SlabPanel() {
{i18n._({ id: 'node.slab.panel.area', message: 'Area' })} - {area.toFixed(2)} m² + + {formatAreaLabel(area, unit, 2)} +
@@ -486,9 +491,14 @@ export function SlabPanel() { {i18n._({ id: 'node.holeSummary', message: - '{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}', + '{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}', values: { - area: formatFixed(holeArea, 2, i18n.getLocale()), + area: formatFixed( + squareMetersToAreaUnit(holeArea, unit), + 2, + i18n.getLocale(), + ), + unit: getAreaUnitLabel(unit), count: hole.length, points: String(hole.length), source: isAutoHole diff --git a/packages/nodes/src/slab/surface-split.ts b/packages/nodes/src/slab/surface-split.ts index f284254ba7..d88597bb4a 100644 --- a/packages/nodes/src/slab/surface-split.ts +++ b/packages/nodes/src/slab/surface-split.ts @@ -185,6 +185,21 @@ function clipTriangleBySlab( dirZ: number, from: number, to: number, +): Triangle[] { + return clipTriangleBetween( + triangle, + (vertex) => (vertex.x - origin[0]) * dirX + (vertex.z - origin[1]) * dirZ, + from, + to, + ) +} + +/** Clip a triangle to `from <= measure(vertex) <= to`, `measure` linear over the triangle. */ +function clipTriangleBetween( + triangle: Triangle, + measure: (vertex: Vertex) => number, + from: number, + to: number, ): Triangle[] { let polygon: Vertex[] = [...triangle] for (const [limit, keepGreater] of [ @@ -194,8 +209,8 @@ function clipTriangleBySlab( const next: Vertex[] = [] for (const [index, current] of polygon.entries()) { const previous = polygon[(index + polygon.length - 1) % polygon.length]! - const sPrevious = (previous.x - origin[0]) * dirX + (previous.z - origin[1]) * dirZ - limit - const sCurrent = (current.x - origin[0]) * dirX + (current.z - origin[1]) * dirZ - limit + const sPrevious = measure(previous) - limit + const sCurrent = measure(current) - limit const insidePrevious = keepGreater ? sPrevious >= 0 : sPrevious <= 0 const insideCurrent = keepGreater ? sCurrent >= 0 : sCurrent <= 0 if (insidePrevious !== insideCurrent) { @@ -257,6 +272,73 @@ export function splitSlabFacesByFacing(geometry: BufferGeometry): { return { top: buildGeometry(top, withUv), side: buildGeometry(side, withUv) } } +/** + * The joist band of a floor framed inside its slab, open while the frame is shown: `top` is the + * subfloor's underside, `bottom` the top of the ceiling hung under the joists, or the slab's + * underside when it has none (`ceiling` false). + */ +export type SlabCavity = { top: number; bottom: number; ceiling: boolean } + +/** Whether the point lies on a triangle's plan (inclusive). */ +function planContains(triangle: Triangle, x: number, z: number): boolean { + const [a, b, c] = triangle + const d1 = (x - b.x) * (a.z - b.z) - (a.x - b.x) * (z - b.z) + const d2 = (x - c.x) * (b.z - c.z) - (b.x - c.x) * (z - c.z) + const d3 = (x - a.x) * (c.z - a.z) - (c.x - a.x) * (z - a.z) + const negative = d1 < 0 || d2 < 0 || d3 < 0 + const positive = d1 > 0 || d2 > 0 || d3 > 0 + return !(negative && positive) +} + +/** Whether a vertical face looks out of the slab: the slab's top covers the plan just behind it. */ +function facesOut(triangle: Triangle, tops: readonly Triangle[]): boolean { + const [a, b, c] = triangle + const nx = (b.y - a.y) * (c.z - a.z) - (b.z - a.z) * (c.y - a.y) + const nz = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x) + const length = Math.hypot(nx, nz) + if (length < 1e-12) return false + const x = (a.x + b.x + c.x) / 3 - (nx / length) * 0.002 + const z = (a.z + b.z + c.z) / 3 - (nz / length) * 0.002 + return tops.some((top) => planContains(top, x, z)) +} + +/** + * Open a solid slab buffer's joist band. `body` is the buffer as drawn, less its underside when no + * ceiling hangs under the joists (they show from below); its outer and hole faces keep their whole + * height, the rim closing the band from outside. `cavity` is what the open band shows: the + * subfloor's underside, the ceiling's top, and the band's height of every outward face turned in, + * so the floor reads closed from inside it too. + */ +export function hollowSlabFaces( + geometry: BufferGeometry, + cavity: SlabCavity, +): { body: BufferGeometry; cavity: BufferGeometry } { + const withUv = geometry.getAttribute('uv') !== undefined + const triangles = readTriangles(geometry) + const tops = triangles.filter((triangle) => faceNormalY(triangle) > TOP_NORMAL_THRESHOLD) + const body: Triangle[] = [] + const inside: Triangle[] = [] + // a cap moved to `y`, turned to face the other way + const capAt = (triangle: Triangle, y: number) => + reverse(triangle.map((vertex) => ({ ...vertex, y })) as Triangle) + for (const triangle of triangles) { + const normalY = faceNormalY(triangle) + if (normalY < -TOP_NORMAL_THRESHOLD) { + if (!cavity.ceiling) continue + body.push(triangle) + inside.push(capAt(triangle, cavity.bottom)) + continue + } + body.push(triangle) + if (normalY > TOP_NORMAL_THRESHOLD) inside.push(capAt(triangle, cavity.top)) + else if (Math.abs(normalY) < 1e-3 && facesOut(triangle, tops)) + inside.push( + ...clipTriangleBetween(reverse(triangle), (vertex) => vertex.y, cavity.bottom, cavity.top), + ) + } + return { body: buildGeometry(body, withUv), cavity: buildGeometry(inside, withUv) } +} + /** * Split a plate buffer into one bucket per rendered role: a top bucket per * partition cell, `edge` / `riser` per exposure run, and `underside`. Buckets diff --git a/packages/nodes/src/slab/system.tsx b/packages/nodes/src/slab/system.tsx index d2aec6ac1e..e29dfad638 100644 --- a/packages/nodes/src/slab/system.tsx +++ b/packages/nodes/src/slab/system.tsx @@ -1,6 +1,7 @@ 'use client' -import { useScene } from '@pascal-app/core' +import { floorBuildUpMatches, isDeckSlab, useScene } from '@pascal-app/core' +import { useViewer } from '@pascal-app/viewer' import { useEffect } from 'react' import { createSlabDependencyTracker } from './dependency-tracker' @@ -13,6 +14,32 @@ const SlabSystems = () => { }) }, []) + // "Framing" opens a framed floor's joist band (`slabFramingCavity`): its slabs rebuild when it flips. + useEffect( + () => + useViewer.subscribe((state, previous) => { + if (state.framingShown === previous.framingShown) return + const scene = useScene.getState() + for (const node of Object.values(scene.nodes)) + if (node.type === 'slab' && !node.recessed && floorBuildUpMatches(node)) + scene.markDirty(node.id) + }), + [], + ) + + // "Framing only" hides the shell, a deck's boards and frame with it, and draws the frame on its + // own: a deck rebuilds when it flips, so no batched copy of it stays drawn over the frame. + useEffect( + () => + useViewer.subscribe((state, previous) => { + if (state.framingOnly === previous.framingOnly) return + const scene = useScene.getState() + for (const node of Object.values(scene.nodes)) + if (node.type === 'slab' && isDeckSlab(node)) scene.markDirty(node.id) + }), + [], + ) + return null } diff --git a/packages/nodes/src/stair/construction-controls.tsx b/packages/nodes/src/stair/construction-controls.tsx index b359b5667e..19db246658 100644 --- a/packages/nodes/src/stair/construction-controls.tsx +++ b/packages/nodes/src/stair/construction-controls.tsx @@ -46,10 +46,13 @@ const OPTIONS = [ export function StairConstructionControls({ node, inherited, + structured = false, onChange, }: { node: StairNode | StairSegmentNode inherited?: StairConstruction + /** The stair has a structure: without a body of its own it draws the structure's. */ + structured?: boolean onChange: (construction: StairConstruction | undefined) => void }) { const i18n = usePascalI18n() @@ -62,13 +65,18 @@ export function StairConstructionControls({ label={i18n._({ id: 'node.stair.constructionControls.body', message: 'Body' })} value={node.construction?.mode ?? 'original'} options={OPTIONS.map((option) => - option.value === 'original' && inherited + option.value === 'original' && (inherited || structured) ? { ...option, - label: i18n._({ - id: 'node.stair.constructionControls.inherit', - message: 'Inherit', - }), + label: inherited + ? i18n._({ + id: 'node.stair.constructionControls.inherit', + message: 'Inherit', + }) + : i18n._({ + id: 'node.stair.constructionControls.fromStructure', + message: 'From structure', + }), } : { ...option, label: i18n._(option.label) }, )} @@ -197,6 +205,7 @@ export function StairConstructionField({ return ( onUpdate({ construction })} /> ) @@ -213,10 +222,15 @@ export function StairSegmentConstructionField({ const parent = node.parentId ? state.nodes[node.parentId as AnyNodeId] : undefined return parent?.type === 'stair' ? parent.construction : undefined }) + const structured = useScene((state) => { + const parent = node.parentId ? state.nodes[node.parentId as AnyNodeId] : undefined + return parent?.type === 'stair' && Boolean(parent.structure) + }) return ( onUpdate({ construction })} /> ) diff --git a/packages/nodes/src/stair/panel.tsx b/packages/nodes/src/stair/panel.tsx index b378c5c649..790b3b91f9 100644 --- a/packages/nodes/src/stair/panel.tsx +++ b/packages/nodes/src/stair/panel.tsx @@ -56,6 +56,7 @@ import { StairConstructionControls } from './construction-controls' import { getStairDestinationUpdates } from './destination' import { StairRailingControls } from './railing-controls' import { getStairTypeChange } from './stair-type' +import { StairStructureControls } from './structure-controls' /** Core's stair diagnostics in the UI language; codes without fixed text show core's message. */ const STAIR_DIAGNOSTIC_MESSAGES: Partial> = { @@ -1073,8 +1074,12 @@ export default function StairPanel() { + {node.stairType === 'straight' ? ( + + ) : null} handleUpdate({ construction })} /> diff --git a/packages/nodes/src/stair/parametrics.ts b/packages/nodes/src/stair/parametrics.ts index e01d18be43..e41353ce29 100644 --- a/packages/nodes/src/stair/parametrics.ts +++ b/packages/nodes/src/stair/parametrics.ts @@ -1,12 +1,19 @@ import { type ParametricDescriptor, planStairRiseEdit, type StairNode } from '@pascal-app/core' import { StairConstructionField } from './construction-controls' import { StairRailingField } from './railing-controls' +import { StairStructureField } from './structure-controls' export const stairParametrics: ParametricDescriptor = { groups: [ { label: 'Construction', fields: [ + { + key: 'structure', + kind: 'custom', + component: StairStructureField, + visibleIf: (node) => node.stairType === 'straight', + }, { key: 'construction', kind: 'custom', diff --git a/packages/nodes/src/stair/placement-defaults.test.ts b/packages/nodes/src/stair/placement-defaults.test.ts new file mode 100644 index 0000000000..529a04b9d7 --- /dev/null +++ b/packages/nodes/src/stair/placement-defaults.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, test } from 'bun:test' +import { resolveStairStringerLayout, resolveStairStructure, StairNode } from '@pascal-app/core' +import { + newStairStructure, + pickedStairRailingMode, + surfaceFlightRailingMode, +} from './placement-defaults' + +const IN = 0.0254 + +describe('a new stair', () => { + test('is built on standard wood stringers unless the tool asks for another structure', () => { + const structure = newStairStructure() + expect(structure).toMatchObject({ + type: 'wood-stringers', + stringerStock: '2x12', + stringerType: 'cut', + }) + expect(structure.stringerSpacing).toBeCloseTo(16 * IN, 9) + // a 1 m stair on it carries four stringers, under 16 in apart + const layout = resolveStairStringerLayout( + 1, + resolveStairStructure(StairNode.parse({ structure }))!, + ) + expect(layout.count).toBe(4) + expect(layout.spacing).toBeLessThan(16 * IN) + expect(newStairStructure({ type: 'concrete' }).type).toBe('concrete') + expect(newStairStructure({ type: 'bamboo' }).type).toBe('wood-stringers') + }) + + test('fitted to a deck or porch, takes railings from four risers or a 30 in drop, unless the tool picks one', () => { + expect(surfaceFlightRailingMode({ stepCount: 3, totalRise: 21 * IN })).toBe('none') + expect(surfaceFlightRailingMode({ stepCount: 4, totalRise: 28 * IN })).toBe('both') + expect(surfaceFlightRailingMode({ stepCount: 3, totalRise: 31 * IN })).toBe('both') + expect(surfaceFlightRailingMode({ stepCount: 6, totalRise: 42 * IN }, 'left')).toBe('left') + expect(surfaceFlightRailingMode({ stepCount: 2, totalRise: 14 * IN }, 'both')).toBe('both') + expect(pickedStairRailingMode('right')).toBe('right') + expect(pickedStairRailingMode('sideways')).toBeNull() + expect(pickedStairRailingMode(undefined)).toBeNull() + }) +}) diff --git a/packages/nodes/src/stair/placement-defaults.ts b/packages/nodes/src/stair/placement-defaults.ts new file mode 100644 index 0000000000..930de5e2a0 --- /dev/null +++ b/packages/nodes/src/stair/placement-defaults.ts @@ -0,0 +1,32 @@ +import { STAIR_CODE, type StairNode, StairStructure } from '@pascal-app/core' + +/** A railing mode picked for the stair tool, or null when none (or an unknown one) is. */ +export function pickedStairRailingMode(value: unknown): StairNode['railingMode'] | null { + return value === 'none' || value === 'left' || value === 'right' || value === 'both' + ? value + : null +} + +/** + * The railing a flight fitted to a deck, porch or patio edge takes: both sides once it has four + * risers (the IRC R311.7.8 handrail) or its open side drops more than 30 in (the R312.1.1 guard), + * none on a shorter step. A railing mode picked for the tool still wins. + */ +export function surfaceFlightRailingMode( + flight: { stepCount: number; totalRise: number }, + picked: StairNode['railingMode'] | null = null, +): StairNode['railingMode'] { + if (picked) return picked + return flight.stepCount >= STAIR_CODE.handrailRisers || flight.totalRise > STAIR_CODE.guardDrop + ? 'both' + : 'none' +} + +/** + * The structure a new stair is built with: the tool's when one is set, else standard wood + * stringers — 2x12 cut stringers at 16 in o.c. at most (IRC R311.7, DCA 6). + */ +export function newStairStructure(picked?: unknown): StairStructure { + const parsed = picked === undefined ? null : StairStructure.safeParse(picked) + return parsed?.success ? parsed.data : StairStructure.parse({}) +} diff --git a/packages/nodes/src/stair/renderer.tsx b/packages/nodes/src/stair/renderer.tsx index 88f38fd4c3..0cde5f90a2 100644 --- a/packages/nodes/src/stair/renderer.tsx +++ b/packages/nodes/src/stair/renderer.tsx @@ -14,6 +14,7 @@ import { type StairSegmentNode, type StairSlotId, stairArcSliceCount, + stairStructureConstructionOf, useLiveNodeOverrides, useRegistry, useScene, @@ -115,7 +116,7 @@ export const StairRenderer = ({ node: rawNode }: { node: StairNode }) => { const { segments, totalRise } = renderData useLayoutEffect(() => { for (const id of node.children) useScene.getState().markDirty(id) - }, [node.children, node.construction]) + }, [node.children, node.construction, node.structure]) const detail = useMemo(() => measureStairDetail(node, segments), [node, segments]) const mergedMaterials = useMemo( () => @@ -262,16 +263,20 @@ function StairSurfaceTransition({ (flight as StairSegmentNode | undefined)?.length ?? 0 const segment = flight as StairSegmentNode | undefined - const fillToFloor = - (flightOverride?.fillToFloor as boolean | undefined) ?? - segment?.fillToFloor ?? - stair.fillToFloor ?? - true - const thickness = - (flightOverride?.thickness as number | undefined) ?? - segment?.thickness ?? - stair.thickness ?? - 0.25 + // a structure fills to the floor when it is poured, and is a tread thick when it is framed + const structureBody = stair.construction ? null : stairStructureConstructionOf(stair) + const fillToFloor = structureBody + ? structureBody.mode === 'solid' + : ((flightOverride?.fillToFloor as boolean | undefined) ?? + segment?.fillToFloor ?? + stair.fillToFloor ?? + true) + const thickness = structureBody + ? structureBody.treadThickness + structureBody.finishThickness + : ((flightOverride?.thickness as number | undefined) ?? + segment?.thickness ?? + stair.thickness ?? + 0.25) const segmentHeight = (flightOverride?.height as number | undefined) ?? segment?.height ?? 0 const profile = useMemo( () => diff --git a/packages/nodes/src/stair/structure-controls.tsx b/packages/nodes/src/stair/structure-controls.tsx new file mode 100644 index 0000000000..ae1dbb0e4a --- /dev/null +++ b/packages/nodes/src/stair/structure-controls.tsx @@ -0,0 +1,253 @@ +'use client' + +import { + type AnyNodeId, + resolveStairStringerLayout, + resolveStairStructure, + STAIR_STRINGER_SPACINGS_IN, + type StairNode, + type StairSegmentNode, + StairStructure, + type StairStructureType, + type StairTreadStock, + useScene, +} from '@pascal-app/core' +import { formatLinearMeasurement, MetricControl, SelectControl } from '@pascal-app/editor' +import { useViewer } from '@pascal-app/viewer' +import { useShallow } from 'zustand/react/shallow' + +const IN = 0.0254 +/** The concrete a poured flight is painted when it carries no paint of its own. */ +const CONCRETE_PRESET = 'library:concrete-raw' + +const STRUCTURE_OPTIONS: { label: string; value: StairStructureType | 'none' }[] = [ + { label: 'Original body', value: 'none' }, + { label: 'Wood stringers', value: 'wood-stringers' }, + { label: 'Concrete', value: 'concrete' }, + { label: 'Steel stringers', value: 'steel-stringers' }, +] + +const TREAD_LABELS: Record = { + '2x6-pair': 'Two 2x6', + '5/4x6-pair': 'Two 5/4x6', + '2x12': 'One 2x12', + 'plywood-finish': 'Plywood + finish', +} + +type Choice = 'auto' | 'yes' | 'no' +const choiceOf = (value: boolean | undefined): Choice => + value === undefined ? 'auto' : value ? 'yes' : 'no' +const choiceValue = (choice: Choice): boolean | undefined => + choice === 'auto' ? undefined : choice === 'yes' + +/** + * How the stair is built (its stringers, with the code's numbers behind them): + * wood stringers — stock, cut or housed, a maximum spacing that sets the count (or a count by + * hand), treads, risers and treated lumber — a poured concrete flight, or steel channels. The + * count it lands on shows under the controls. + */ +export function StairStructureControls({ + node, + segments, + onUpdate, +}: { + node: StairNode + segments: readonly StairSegmentNode[] + onUpdate: (patch: Partial) => void +}) { + const unit = useViewer((state) => state.unit) + const metricNotation = useViewer((state) => state.metricNotation) + const resolved = resolveStairStructure(node) + const stored = node.structure ? StairStructure.safeParse(node.structure) : null + const structure = stored?.success ? stored.data : null + const update = (patch: Partial) => + onUpdate({ structure: StairStructure.parse({ ...structure, ...patch }) }) + + const setType = (value: StairStructureType | 'none') => { + if (value === 'none') { + onUpdate({ + structure: undefined, + ...(node.materialPreset === CONCRETE_PRESET ? { materialPreset: undefined } : {}), + }) + return + } + const painted = node.material !== undefined || typeof node.materialPreset === 'string' + onUpdate({ + structure: StairStructure.parse({ ...structure, type: value }), + // a poured flight reads as concrete until it is painted otherwise; leaving concrete takes + // that paint back off + ...(value === 'concrete' && !painted ? { materialPreset: CONCRETE_PRESET } : {}), + ...(value !== 'concrete' && node.materialPreset === CONCRETE_PRESET + ? { materialPreset: undefined } + : {}), + }) + } + + const flights = segments.filter((segment) => segment.segmentType === 'stair') + const widths = flights.length ? flights.map((flight) => flight.width) : [node.width] + const layouts = + resolved && resolved.type !== 'concrete' + ? widths.map((width) => ({ width, layout: resolveStairStringerLayout(width, resolved) })) + : [] + const first = layouts[0]?.layout + const autoCount = + resolved && first + ? resolveStairStringerLayout(widths[0]!, { ...resolved, stringerCount: undefined }).count + : 0 + const wood = resolved?.type === 'wood-stringers' + const spacingIn = structure ? Math.round((structure.stringerSpacing / IN) * 100) / 100 : 16 + const spacings: number[] = STAIR_STRINGER_SPACINGS_IN.includes( + spacingIn as (typeof STAIR_STRINGER_SPACINGS_IN)[number], + ) + ? [...STAIR_STRINGER_SPACINGS_IN] + : [...STAIR_STRINGER_SPACINGS_IN, spacingIn].sort((a, b) => a - b) + + return ( +
+ + {resolved && structure ? ( + <> + {wood ? ( + <> + update({ stringerStock })} + /> + update({ stringerType })} + /> + ({ + label: `${inches} in o.c.`, + value: String(inches), + }))} + onChange={(value) => update({ stringerSpacing: Number(value) * IN })} + /> + + ) : null} + {resolved.type !== 'concrete' && first ? ( + <> + ({ + label: String(count), + value: String(count), + })), + ]} + onChange={(value) => + update({ stringerCount: value === 'auto' ? undefined : Number(value) }) + } + /> + ({ + label: TREAD_LABELS[stock], + value: stock, + })), + ]} + onChange={(value) => + update({ treadStock: value === 'auto' ? undefined : (value as StairTreadStock) }) + } + /> + update({ closedRisers: choiceValue(value as Choice) })} + /> + {wood ? ( + update({ treated: choiceValue(value as Choice) })} + /> + ) : null} + + ) : null} + update({ nosing })} + unit="m" + step={0.005} + precision={3} + /> +
+ {resolved.type === 'concrete' ? ( +
A solid poured flight on a footing; risers closed, no stringers.
+ ) : ( + layouts.map(({ width, layout }, index) => ( +
+ {layouts.length > 1 ? `Flight ${index + 1}: ` : ''} + {layout.count} {layout.steel ? 'steel channels' : 'stringers'} + {layout.count > 1 + ? ` at ${formatLinearMeasurement(layout.spacing, unit, metricNotation)} o.c.` + : ''} + {layout.auto ? ' (auto)' : ''} +
+ )) + )} +
+ + ) : null} +
+ ) +} + +/** The structure controls in the parametric inspector, over the stair's own flights. */ +export function StairStructureField({ + node, + onUpdate, +}: { + node: StairNode + onUpdate: (patch: Partial) => void +}) { + const segments = useScene( + useShallow((state) => + (node.children ?? []) + .map((id) => state.nodes[id as AnyNodeId]) + .filter((child): child is StairSegmentNode => child?.type === 'stair-segment'), + ), + ) + return +} diff --git a/packages/nodes/src/stair/tool.tsx b/packages/nodes/src/stair/tool.tsx index 3225ac9462..955a741666 100644 --- a/packages/nodes/src/stair/tool.tsx +++ b/packages/nodes/src/stair/tool.tsx @@ -61,6 +61,11 @@ import { useViewer } from '@pascal-app/viewer' import { useThree } from '@react-three/fiber' import { useEffect, useMemo, useRef } from 'react' import * as THREE from 'three' +import { + newStairStructure, + pickedStairRailingMode, + surfaceFlightRailingMode, +} from './placement-defaults' import { createStairCommitGate, swallowFollowUpBrowserClick } from './stair-click-guard' const GRID_OFFSET = 0.02 @@ -144,6 +149,16 @@ function createSeedStairSegment(rise: number) { }) } +/** The railing mode picked for the stair tool, when one is. */ +function pickedRailingMode(): StairNode['railingMode'] | null { + return pickedStairRailingMode(useEditor.getState().toolDefaults.stair?.railingMode) +} + +/** A deck, porch or patio flight's railing (`surfaceFlightRailingMode`), the tool's pick first. */ +function surfaceSnapRailingMode(snap: StairSurfaceSnap): StairNode['railingMode'] { + return surfaceFlightRailingMode(snap, pickedRailingMode()) +} + function createDefaultStairNode({ name, levelId, @@ -159,7 +174,6 @@ function createDefaultStairNode({ rotation: number segmentId: StairSegmentNode['id'] }) { - const railingMode = useEditor.getState().toolDefaults.stair?.railingMode return StairNode.parse({ name, parentId: levelId, @@ -181,13 +195,8 @@ function createDefaultStairNode({ showCenterColumn: DEFAULT_SPIRAL_SHOW_CENTER_COLUMN, showStepSupports: DEFAULT_SPIRAL_SHOW_STEP_SUPPORTS, railingHeight: DEFAULT_STAIR_RAILING_HEIGHT, - railingMode: - railingMode === 'none' || - railingMode === 'left' || - railingMode === 'right' || - railingMode === 'both' - ? railingMode - : DEFAULT_STAIR_RAILING_MODE, + railingMode: pickedRailingMode() ?? DEFAULT_STAIR_RAILING_MODE, + structure: newStairStructure(useEditor.getState().toolDefaults.stair?.structure), children: [segmentId], }) } @@ -241,7 +250,7 @@ function commitStairPlacement( stepCount: surfaceSnap.stepCount, slabOpeningMode: 'none' as const, destinationSurfaceId: surfaceSnap.surfaceId, - railingMode: 'none' as const, + railingMode: surfaceSnapRailingMode(surfaceSnap), } : {}), }) @@ -419,7 +428,7 @@ export default function StairTool() { stair.totalRise = surfaceSnap.totalRise stair.destinationSurfaceId = surfaceSnap.surfaceId stair.slabOpeningMode = 'none' - stair.railingMode = 'none' + stair.railingMode = surfaceSnapRailingMode(surfaceSnap) segment = { ...segment, height: surfaceSnap.totalRise, diff --git a/packages/nodes/src/wall/actions.tsx b/packages/nodes/src/wall/actions.tsx index c2ae39cae7..16922ed4cf 100644 --- a/packages/nodes/src/wall/actions.tsx +++ b/packages/nodes/src/wall/actions.tsx @@ -9,10 +9,12 @@ import { import { captureElementActionOrigin, completeElementAction, + type FloorplanWallDimensionFaces, Tooltip, TooltipContent, TooltipTrigger, triggerSFX, + useFloorplanAnnotationVisibility, } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { FoldHorizontal, Scissors } from 'lucide-react' @@ -22,16 +24,70 @@ import { openWallSplit } from './split-session' const BUTTON = 'tooltip-trigger rounded-md p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-50' -/** The wall's action-menu contributions: Split (one wall) and Merge (several). */ +/** The wall's action-menu contributions: Split and dimension faces (one wall), Merge (several). */ export default function WallActions() { return ( <> + ) } +const DIMENSION_FACES: Record< + FloorplanWallDimensionFaces, + { label: string; title: string; next: FloorplanWallDimensionFaces } +> = { + both: { + label: 'ID+OD', + title: 'Dimensions to the interior and exterior faces. Click for interior only', + next: 'interior', + }, + interior: { + label: 'ID', + title: 'Dimensions to the interior faces (room size). Click for exterior only', + next: 'exterior', + }, + exterior: { + label: 'OD', + title: 'Dimensions to the exterior faces (outside size). Click for both', + next: 'both', + }, +} + +/** + * What a selected wall's dimensions measure to — interior faces, exterior + * faces or both — for its length and its temporary room and outside sizes, in + * 2D and 3D. A display preference, kept per browser; it changes no geometry. + */ +function DimensionFacesAction() { + const selected = useViewer((s) => s.selection.selectedIds) + const wall = useScene((s) => + selected.length === 1 ? s.nodes[selected[0] as AnyNodeId] : undefined, + ) + const faces = useFloorplanAnnotationVisibility((s) => s.wallDimensionFaces) + const setFaces = useFloorplanAnnotationVisibility((s) => s.setWallDimensionFaces) + if (wall?.type !== 'wall') return null + // A partition between two rooms has no outside: its dimensions read the same either way. + if (wall.frontSide === 'interior' && wall.backSide === 'interior') return null + const option = DIMENSION_FACES[faces] + return ( + + ) +} + function SplitWallAction() { const i18n = usePascalI18n() const selected = useViewer((s) => s.selection.selectedIds) diff --git a/packages/nodes/src/wall/contextual-dimensions.ts b/packages/nodes/src/wall/contextual-dimensions.ts index 12faaa993a..3d253a2671 100644 --- a/packages/nodes/src/wall/contextual-dimensions.ts +++ b/packages/nodes/src/wall/contextual-dimensions.ts @@ -8,7 +8,11 @@ import { isCurvedWall, type WallNode, } from '@pascal-app/core' -import { formatLinearMeasurement, readFloorplanMetricNotationOverride } from '@pascal-app/editor' +import { + formatLinearMeasurement, + readFloorplanContext, + readFloorplanMetricNotationOverride, +} from '@pascal-app/editor' type WallHostedOpening = { position: readonly [number, number, number] @@ -90,6 +94,7 @@ function pointSegmentDistance( } function structuralFaceProjections( + /** A point on the measured line (the centerline or one face) at the wall's start. */ wallStart: FloorplanPoint, wallTangent: FloorplanPoint, connectedWall: WallNode, @@ -161,6 +166,92 @@ function wallStudFaceSpan( return { start, end, length: Math.max(0, endProjection - startProjection) } } +type FaceSpan = { end: FloorplanPoint; length: number; start: FloorplanPoint } + +function nearPlanPoint(left: FloorplanPoint, right: FloorplanPoint): boolean { + return Math.hypot(left[0] - right[0], left[1] - right[1]) <= WALL_CONNECTION_TOLERANCE +} + +/** + * One face of a straight wall, from end to end. `inner` stops at the near + * face of each connected wall (an interior face, the room size); `outer` + * runs on to the far face of a wall that turns the corner with it (an + * exterior face, the outside size). A wall that only abuts another (a T) + * stops at its near face either way, and a free end at the end cap. + */ +function wallFaceSpan( + wall: WallNode, + side: 'a' | 'b', + siblings: GeometryContext['siblings'], + reach: 'inner' | 'outer', +): FaceSpan | null { + const dx = wall.end[0] - wall.start[0] + const dy = wall.end[1] - wall.start[1] + const length = Math.hypot(dx, dy) + if (length <= 1e-6) return null + const tangent: FloorplanPoint = [dx / length, dy / length] + const offset = getWallFaceOffsets(wall)[side] + const origin: FloorplanPoint = [ + wall.start[0] - tangent[1] * offset, + wall.start[1] + tangent[0] * offset, + ] + + const resolveEnd = (endpoint: FloorplanPoint, atStart: boolean): number => { + const rest = atStart ? 0 : length + let near: number | null = null + let far: number | null = null + for (const sibling of siblings) { + if (sibling.type !== 'wall' || sibling.id === wall.id) continue + if (pointSegmentDistance(endpoint, sibling.start, sibling.end) > WALL_CONNECTION_TOLERANCE) { + continue + } + const corner = nearPlanPoint(endpoint, sibling.start) || nearPlanPoint(endpoint, sibling.end) + for (const projection of structuralFaceProjections(origin, tangent, sibling)) { + const inward = atStart ? projection : length - projection + if (inward > 0 && inward < length) { + if (near === null || inward > (atStart ? near : length - near)) near = projection + } else if (corner && inward < 0 && inward > -length) { + if (far === null || inward < (atStart ? far : length - far)) far = projection + } + } + } + if (reach === 'outer' && far !== null) return far + return near ?? rest + } + + const startProjection = resolveEnd(wall.start, true) + const endProjection = resolveEnd(wall.end, false) + if (endProjection - startProjection <= 1e-6) return null + return { + start: [origin[0] + tangent[0] * startProjection, origin[1] + tangent[1] * startProjection], + end: [origin[0] + tangent[0] * endProjection, origin[1] + tangent[1] * endProjection], + length: endProjection - startProjection, + } +} + +function faceDimension( + span: FaceSpan, + offsetNormal: FloorplanPoint, + ctx: GeometryContext, +): FloorplanGeometry { + return { + kind: 'dimension', + start: span.start, + end: span.end, + offsetNormal, + offsetDistance: 0.34, + extensionOvershoot: 0.08, + text: formatLength(span.length, ctx), + stroke: selectedStroke(ctx), + } +} + +/** + * A selected wall's length, to its faces rather than its centerline: the + * interior face (ID) on the room side and the exterior face (OD) outside, as + * the dimension-faces preference asks. An interior partition has no outside, + * so it shows its clear span between the walls it runs to. + */ export function buildWallContextualDimensions( node: WallNode, ctx: GeometryContext, @@ -189,27 +280,30 @@ export function buildWallContextualDimensions( const dy = node.end[1] - node.start[1] const chordLength = Math.hypot(dx, dy) if (chordLength <= 1e-6) return null - const offsetNormal = contextualWallDimensionNormal( - node, - [-dy / chordLength, dx / chordLength], - ctx.siblings, - ) - const span = - node.frontSide === 'interior' && node.backSide === 'interior' - ? wallStudFaceSpan(node, ctx.siblings) - : { start: node.start, end: node.end, length: chordLength } - if (span.length <= 1e-6) return null + const front: FloorplanPoint = [-dy / chordLength, dx / chordLength] + const offsetNormal = contextualWallDimensionNormal(node, front, ctx.siblings) + if (node.frontSide === 'interior' && node.backSide === 'interior') { + const span = wallStudFaceSpan(node, ctx.siblings) + return span.length <= 1e-6 ? null : faceDimension(span, offsetNormal, ctx) + } - return { - kind: 'dimension', - start: span.start, - end: span.end, - offsetNormal, - offsetDistance: 0.34, - extensionOvershoot: 0.08, - text: formatLength(span.length, ctx), - stroke: selectedStroke(ctx), + // `offsetNormal` points out of the wall's exterior (or its best guess at it). + const exteriorSide = offsetNormal[0] * front[0] + offsetNormal[1] * front[1] > 0 ? 'a' : 'b' + const interiorNormal: FloorplanPoint = [cleanZero(-offsetNormal[0]), cleanZero(-offsetNormal[1])] + const faces = readFloorplanContext(ctx).wallDimensionFaces + const inner = wallFaceSpan(node, exteriorSide === 'a' ? 'b' : 'a', ctx.siblings, 'inner') + const outer = wallFaceSpan(node, exteriorSide, ctx.siblings, 'outer') + const interior = faces === 'exterior' ? null : inner + const exterior = faces === 'interior' ? null : outer + if (interior && exterior && Math.abs(interior.length - exterior.length) < 0.001) { + return faceDimension(exterior, offsetNormal, ctx) } + const children = [ + ...(interior ? [faceDimension(interior, interiorNormal, ctx)] : []), + ...(exterior ? [faceDimension(exterior, offsetNormal, ctx)] : []), + ] + if (children.length === 0) return null + return children.length === 1 ? children[0]! : { kind: 'group', children } } export function buildWallHostedOpeningContextualDimensions( diff --git a/packages/nodes/src/wall/curtain-wall-materials.ts b/packages/nodes/src/wall/curtain-wall-materials.ts index 9d7473e773..b89e874d81 100644 --- a/packages/nodes/src/wall/curtain-wall-materials.ts +++ b/packages/nodes/src/wall/curtain-wall-materials.ts @@ -14,6 +14,7 @@ import { } from '@pascal-app/viewer' import type { Material } from 'three' import { MeshLambertNodeMaterial, MeshStandardNodeMaterial } from 'three/webgpu' +import { createRailingWallMaterials } from './railing-wall-materials' export function createCurtainWallMaterials( wall: WallNode, @@ -76,10 +77,13 @@ export function getCurtainAwareWallMaterials( materials?: Record, finishRefs: readonly string[] = [], ): WallMaterials { - const override = - textures && wall.wallType === 'curtain' + const override = !textures + ? undefined + : wall.wallType === 'curtain' ? createCurtainWallMaterials(wall, shading, materials) - : undefined + : wall.wallType === 'railing' + ? createRailingWallMaterials(wall, shading, materials) + : undefined return getMaterialsForWall( wall, shading, diff --git a/packages/nodes/src/wall/curve-tool.tsx b/packages/nodes/src/wall/curve-tool.tsx index efc3a7c2e1..e185b9c0b6 100644 --- a/packages/nodes/src/wall/curve-tool.tsx +++ b/packages/nodes/src/wall/curve-tool.tsx @@ -93,9 +93,8 @@ export const CurveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { // nothing clears any more. if (wasFinalized) return const snapStep = getSegmentGridStep() - // Snap the cursor on the WORLD XZ grid (still in building-local - // coords for the rest of the math) so a rotated building doesn't - // pull the curve handle off the visible grid lines. + // Snap the cursor on the building's own grid lattice, the one the + // wall tool drafts on (it turns with a turned building). const [snappedLocalX, snappedLocalZ] = snapBuildingLocalToWorldGrid( [event.localPosition[0], event.localPosition[2]], snapStep, diff --git a/packages/nodes/src/wall/definition.test.ts b/packages/nodes/src/wall/definition.test.ts index ccc8eb3c95..4b32094da7 100644 --- a/packages/nodes/src/wall/definition.test.ts +++ b/packages/nodes/src/wall/definition.test.ts @@ -91,6 +91,19 @@ describe('wallDefinition floor-plan extension', () => { const canCurve = getFloorplanNodeExtension(wallDefinition)?.actionMenu?.canCurve expect(canCurve?.({ node: wall, nodes })).toBe(false) }) + test('takes its own plan hits and shows typeable dimensions only between rooms', () => { + const extension = getFloorplanNodeExtension(wallDefinition) + expect(extension?.planHitSelectsNode).toBe(true) + const wall = wallDefinition.schema.parse({ + id: 'wall_lone', + parentId: 'level_lone', + start: [0, 0], + end: [4, 0], + }) + const nodes = { [wall.id]: wall } as Record + // A wall bounding no room has no room widths to show. + expect(extension?.selectionDimensions?.({ node: wall, nodes, faces: 'both' })).toEqual([]) + }) }) test('wall top surface follows the effective level-bound height', () => { @@ -357,7 +370,7 @@ describe('wall tool hints', () => { selectWallDrawVariant('polygon') expect(visibleClickHints()).toEqual(['Add a corner · click the first to close']) selectWallDrawVariant('walls') - expect(visibleClickHints()).toEqual(['Set wall start / end']) + expect(visibleClickHints()).toEqual(['Add a wall · each one starts where the last ended']) } finally { selectWallDrawVariant('polygon') } diff --git a/packages/nodes/src/wall/definition.ts b/packages/nodes/src/wall/definition.ts index 00a3333302..435a29da8d 100644 --- a/packages/nodes/src/wall/definition.ts +++ b/packages/nodes/src/wall/definition.ts @@ -14,6 +14,7 @@ import { getWallDrawVariant, type NodePanelModel, PANEL_MODEL_EXTENSION, + selectedWallDimensions, useEditor, type WallDrawVariant, } from '@pascal-app/editor' @@ -34,7 +35,12 @@ import { wallMeasurementFeatures, } from './measurement' import { wallPaint } from './paint' -import { type WallReferenceValue, wallReferenceModel, wallSettings } from './panel-model' +import { + type WallReferenceValue, + wallAssemblyMultiControls, + wallReferenceModel, + wallSettings, +} from './panel-model' import { wallParametrics } from './parametrics' import { wallQuickMeasurement } from './quick-measurement' import { WallNode } from './schema' @@ -89,6 +95,7 @@ export const wallDefinition: NodeDefinition = { value: reference.value, onChange: (value) => reference.apply(value as WallReferenceValue), }, + ...wallAssemblyMultiControls(selection), ] }, } satisfies NodePanelModel, @@ -112,6 +119,8 @@ export const wallDefinition: NodeDefinition = { tool: () => import('./floorplan-tool'), reshapeLayers: { split: () => import('./split-floorplan-layer') }, contextualDimensions: buildWallContextualDimensions, + planHitSelectsNode: true, + selectionDimensions: ({ node, nodes, faces }) => selectedWallDimensions(nodes, node, faces), actionMenu: { actions: () => import('./actions'), canCurve: ({ node, nodes }) => @@ -141,7 +150,16 @@ export const wallDefinition: NodeDefinition = { reveal: { phase: 'structure', style: 'rise' }, exterior: wallSeenFromOutside, revealLayout: { line: (node) => (node.type === 'wall' ? [node.start, node.end] : null) }, - revealOnEdit: ['start', 'end', 'curveOffset', 'height', 'thickness', 'wallType', 'curtainWall'], + revealOnEdit: [ + 'start', + 'end', + 'curveOffset', + 'height', + 'thickness', + 'wallType', + 'curtainWall', + 'railing', + ], // Wall move is bespoke (endpoint drag, linked-wall corner cascade, // ALT-detach). Omitting `movable` keeps the legacy MoveWallTool via // capability-driven dispatch. @@ -252,7 +270,21 @@ export const wallDefinition: NodeDefinition = { label: 'Add a corner · click the first to close', visible: whenWallVariant('polygon'), }, - { key: 'Left click', label: 'Set wall start / end', visible: whenWallVariant('walls') }, + { + key: 'Left click', + label: 'Add a wall · each one starts where the last ended', + visible: whenWallVariant('walls'), + }, + { key: 'Double click', label: 'Finish the run', visible: whenWallVariant('walls') }, + // A started Walls or Polygon run holds square; Alt lets it go. + { + key: 'Alt', + label: 'Free angle', + visible: { + subscribe: (onChange: () => void) => useEditor.subscribe(onChange), + value: () => getWallDrawVariant() !== 'rectangle', + }, + }, { key: 'Esc', label: 'Cancel' }, ], // The split session (`split-session.ts`) is the wall's own reshape; the HUD @@ -304,6 +336,6 @@ export const wallDefinition: NodeDefinition = { mcp: { description: - 'A wall defined by endpoints and optional curve sagitta. wallType selects standard or curtain. curtainWall configures construction, framing, grids, glazing, spandrels, and zero-based panel overrides. Thickness is frame depth for curtain walls.', + 'A wall defined by endpoints and optional curve sagitta. wallType selects standard, curtain or railing. curtainWall configures construction, framing, grids, glazing, spandrels, and zero-based panel overrides. Thickness is frame depth for curtain walls. A railing is an open guard (railing: style, postSpacing, postSize, infillSize, infillGap, bottomClearance, cap) that closes rooms like a wall; its height is its own (36 in by default), its thickness its posts, and a door on it is a gap in the guard.', }, } diff --git a/packages/nodes/src/wall/floorplan-affordances.test.ts b/packages/nodes/src/wall/floorplan-affordances.test.ts index 0d5bb94576..ef3199a256 100644 --- a/packages/nodes/src/wall/floorplan-affordances.test.ts +++ b/packages/nodes/src/wall/floorplan-affordances.test.ts @@ -127,6 +127,44 @@ describe('wall endpoint floorplan affordance', () => { expect((useScene.getState().nodes[lower.id] as typeof lower).start).toEqual([0, 1]) expect((useScene.getState().nodes[upper.id] as typeof upper).start).toEqual([0, 0]) }) + + test('a wall T-joined on another does not fold onto its line when dragged into its body', () => { + // `wall_stem` stands on `wall_host`'s line at [2, 0]; its free end is + // dragged down into the host's body, 8 cm off the line. + const host = WallNode.parse({ + id: 'wall_host', + parentId: 'level_main', + start: [0, 0], + end: [4, 0], + thickness: 0.2, + }) + const stem = WallNode.parse({ + id: 'wall_stem', + parentId: 'level_main', + start: [2, 0], + end: [2, 3], + }) + useScene.setState({ nodes: { [host.id]: host, [stem.id]: stem } as never }) + + const session = wallMoveEndpointAffordance.start({ + node: stem, + payload: { wallId: stem.id, endpoint: 'end' }, + nodes: useScene.getState().nodes, + initialPlanPoint: [2, 3], + gridSnapStep: 0.1, + }) + session.apply({ planPoint: [2.5, 0.08], modifiers }) + + // As in 3D, the fixed corner is the segment's start: a segment that runs + // alongside the host isn't captured into its body. + expect(useLiveNodeOverrides.getState().get(stem.id as AnyNodeId)?.end).toEqual([2.5, 0.08]) + expect(session.canCommit()).toBe(true) + session.commit?.() + const committed = useScene.getState().nodes[stem.id] as typeof stem + expect(committed.start).toEqual([2, 0]) + expect(committed.end).toEqual([2.5, 0.08]) + expect(Object.keys(useScene.getState().nodes).sort()).toEqual(['wall_host', 'wall_stem']) + }) }) describe('wall thickness floorplan affordance', () => { diff --git a/packages/nodes/src/wall/floorplan-affordances.ts b/packages/nodes/src/wall/floorplan-affordances.ts index a3cfb5d8dd..fe1ea047fa 100644 --- a/packages/nodes/src/wall/floorplan-affordances.ts +++ b/packages/nodes/src/wall/floorplan-affordances.ts @@ -131,8 +131,8 @@ export const wallCurveAffordance: FloorplanAffordance = { affectedIds: [node.id], apply({ planPoint }) { const snapStep = getSegmentGridStep() - // World-grid snap so a rotated building doesn't drag the curve - // handle off the visible grid. + // Building-grid snap, the lattice the plan draws (it turns with the + // building). const [x, y] = snapBuildingLocalToWorldGrid([planPoint[0], planPoint[1]], snapStep) // Signed projection of (snappedPoint - chord midpoint) onto the @@ -263,16 +263,19 @@ export const wallMoveEndpointAffordance: FloorplanAffordance = { // is 0 outside grid mode), so `'lines' / 'angles' / 'off'` no longer // force a grid snap the mode chip says is inactive. In `'angles'` mode // the endpoint angle-locks off the fixed corner (free length), matching - // the draft tool — the angle path ignores the `gridSnap` override. - const angleLocked = isAngleSnapActive() + // the draft tool — the angle path ignores the `gridSnap` override. The + // fixed corner is the segment's start in every mode, as in the 3D tool: + // the body capture slides the end along it, so a wall whose fixed end + // is T-joined on another wall can't fold onto that wall's line. const snapped = snapWallDraftPoint({ point: planPoint as WallPlanPoint, walls, ignoreWallIds: staleWallIds, - start: angleLocked ? fixedPoint : undefined, - angleSnap: angleLocked, + start: fixedPoint, + angleSnap: isAngleSnapActive(), magnetic: isMagneticSnapActive(), gridSnap: (p) => snapBuildingLocalToWorldGrid(p, getSegmentGridStep()), + captureWallBodies: true, }) // Figma-style alignment on the dragged corner — snaps it onto another // object's edge / wall face and publishes a guide. The guide is diff --git a/packages/nodes/src/wall/floorplan-move.test.ts b/packages/nodes/src/wall/floorplan-move.test.ts index 70e9ff9d59..d2dc9cf748 100644 --- a/packages/nodes/src/wall/floorplan-move.test.ts +++ b/packages/nodes/src/wall/floorplan-move.test.ts @@ -3,6 +3,8 @@ import { CeilingNode, initSpaceDetectionSync, LevelNode, + nodeRegistry, + registerNode, SlabNode, useLiveNodeOverrides, useScene, @@ -10,6 +12,8 @@ import { type ZoneNode, } from '@pascal-app/core' import { migrateCeilingRoomLinks, migrateRoomZones } from '@pascal-app/core/scene-migrations' +import { getActiveSnappingMode, useAlignmentGuides, useInteractionScope } from '@pascal-app/editor' +import { wallDefinition } from './definition' import { wallFloorplanMoveTarget } from './floorplan-move' const square = [ @@ -25,7 +29,12 @@ beforeEach(() => { globalThis.requestAnimationFrame = () => 0 globalThis.cancelAnimationFrame = () => {} }) +// The parallel-wall tests register the wall kind; put the registry back after. +let restoreRegistry: (() => void) | null = null afterEach(() => { + restoreRegistry?.() + restoreRegistry = null + useInteractionScope.getState().end() useLiveNodeOverrides.getState().clearAll() globalThis.requestAnimationFrame = originalRaf globalThis.cancelAnimationFrame = originalCancelRaf @@ -125,3 +134,77 @@ test('whole-wall floorplan move previews and commits automatic slab and ceiling stop() } }) + +// Two outside walls in line but apart, as when partitions break the outside up. +function outsideStep() { + const level = LevelNode.parse({ id: 'level_parallel', level: 0, height: 3, children: [] }) + const outside = { frontSide: 'exterior', backSide: 'interior', thickness: 0.15 } as const + const top = WallNode.parse({ + id: 'wall_parallel-top', + parentId: level.id, + start: [0, 0.07], + end: [4, 0.07], + ...outside, + }) + const step = WallNode.parse({ + id: 'wall_parallel-step', + parentId: level.id, + start: [5, 0.6], + end: [8, 0.6], + ...outside, + }) + useScene.setState({ + nodes: { + [level.id]: { ...level, children: [top.id, step.id] }, + [top.id]: top, + [step.id]: step, + }, + } as never) + // Snapping follows the move's own context, as in the editor. + restoreRegistry = nodeRegistry._snapshot() + if (!nodeRegistry.has(wallDefinition.kind)) registerNode(wallDefinition) + useInteractionScope.getState().begin({ + kind: 'moving', + node: step, + nodeId: step.id, + nodeType: step.type, + view: '2d', + }) + const session = wallFloorplanMoveTarget({ + node: step, + nodes: useScene.getState().nodes, + sceneApi: {} as never, + }) + return { step, session } +} + +const plain = { shiftKey: false, altKey: false, ctrlKey: false, metaKey: false } + +test('a wall moved near a parallel outside wall lines up with it, off the grid', () => { + const { step, session } = outsideStep() + expect(getActiveSnappingMode()).not.toBe('off') + session.apply({ planPoint: [6.5, 0.6], modifiers: plain }) + session.apply({ planPoint: [6.5, 0.12], modifiers: plain }) + + const preview = useLiveNodeOverrides.getState().get(step.id) as Pick + expect(preview.start[0]).toBe(5) + expect(preview.start[1]).toBeCloseTo(0.07, 10) + expect(preview.end[1]).toBeCloseTo(0.07, 10) + expect(useAlignmentGuides.getState().draft?.lines).toHaveLength(1) + + session.commit?.() + const moved = useScene.getState().nodes[step.id] as WallNode + expect(moved.start[1]).toBeCloseTo(0.07, 10) + expect(useAlignmentGuides.getState().draft).toBeNull() +}) + +test('Alt moves a wall exactly with the pointer', () => { + const { step, session } = outsideStep() + const free = { ...plain, altKey: true } + session.apply({ planPoint: [6.5, 0.6], modifiers: free }) + session.apply({ planPoint: [6.5, 0.6 - 0.0254], modifiers: free }) + session.commit?.() + const moved = useScene.getState().nodes[step.id] as WallNode + expect(moved.start[1]).toBeCloseTo(0.6 - 0.0254, 10) + expect(moved.end[1]).toBeCloseTo(0.6 - 0.0254, 10) +}) diff --git a/packages/nodes/src/wall/floorplan-move.ts b/packages/nodes/src/wall/floorplan-move.ts index 3e927dd364..d570d905aa 100644 --- a/packages/nodes/src/wall/floorplan-move.ts +++ b/packages/nodes/src/wall/floorplan-move.ts @@ -4,8 +4,12 @@ import { createLevelStructurePreview, type FloorplanMoveTarget, type FloorplanMoveTargetSession, + finishWallEdit, + generateId, getPerpendicularWallMoveAxis, getPlannedLinkedWallUpdates, + planAttachedWallChanges, + planAttachedWallUpdates, planWallEndRejoins, planWallMoveJunctions, useLiveNodeOverrides, @@ -17,10 +21,15 @@ import { getFloorplanWallThickness, getSegmentGridStep, isSegmentLongEnough, + isWallRunFree, + nearestWallParallelTarget, + publishWallParallelGuide, snapScalarToGrid, useWallMoveGhosts, WALL_CONNECT_SNAP_RADIUS, type WallMoveGhostBridge, + type WallParallelTarget, + wallParallelTargets, } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { @@ -112,7 +121,35 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) .filter((node) => node.parentId === levelId) .map((node) => [node.id, node]), ) - const affectedIds: AnyNodeId[] = [wallId, ...linkedOriginals.map((wall) => wall.id as AnyNodeId)] + // Lines up with the parallel walls on the level: an outside wall's faces + // with the rest of the outside, any other wall's with every wall. + const parallelTargets = moveAxis + ? wallParallelTargets( + node, + moveAxis, + Object.values(dragNodes).filter((entry): entry is WallNode => entry.type === 'wall'), + ) + : [] + let lastLineUp: WallParallelTarget | null = null + // Walls T'd into this one stay on it, stretched along their own line. + const linkedIds = new Set(linkedOriginals.map((wall) => wall.id)) + const attachedWalls = isNew + ? [] + : planAttachedWallUpdates( + Object.values(dragNodes).filter( + (entry): entry is WallNode => + entry.type === 'wall' && entry.id !== wallId && !linkedIds.has(entry.id), + ), + originalStart, + originalEnd, + originalStart, + originalEnd, + ).map(({ id }) => dragNodes[id] as WallNode) + const affectedIds: AnyNodeId[] = [ + wallId, + ...linkedOriginals.map((wall) => wall.id as AnyNodeId), + ...attachedWalls.map((wall) => wall.id as AnyNodeId), + ] const affectedIdSet = new Set(affectedIds) const touchedSurfaceIds = new Set() @@ -160,7 +197,7 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) const session: FloorplanMoveTargetSession = { affectedIds, - apply({ planPoint }) { + apply({ planPoint, modifiers }) { if (!rawAnchor) { rawAnchor = [planPoint[0], planPoint[1]] return @@ -168,19 +205,24 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) const rawDx = planPoint[0] - rawAnchor[0] const rawDz = planPoint[1] - rawAnchor[1] - const step = getSegmentGridStep() + // Alt (or snapping off) moves the wall exactly with the pointer. + const free = isWallRunFree(modifiers) + const step = free ? 0 : getSegmentGridStep() // Axis-lock the move to the wall's perpendicular (normal). Project // the original centre + raw cursor delta onto the axis, snap the - // absolute projection to a grid multiple, then translate the wall - // by `axis * perpDelta`. Matches `MoveWallTool` so 2D and 3D drag - // produce identical wall topology. + // absolute projection to a parallel wall it lines up with, else to a + // grid multiple, then translate the wall by `axis * perpDelta`. + // Matches `MoveWallTool` so 2D and 3D drag produce identical wall + // topology. let dx: number let dz: number + let lineUp: WallParallelTarget | null = null if (moveAxis) { const originalProj = originalCenter[0] * moveAxis[0] + originalCenter[1] * moveAxis[1] const rawProj = originalProj + rawDx * moveAxis[0] + rawDz * moveAxis[1] - const snappedProj = snapScalarToGrid(rawProj, step) + lineUp = free ? null : nearestWallParallelTarget(parallelTargets, rawProj) + const snappedProj = lineUp ? lineUp.position : snapScalarToGrid(rawProj, step) const perpDelta = snappedProj - originalProj dx = moveAxis[0] * perpDelta dz = moveAxis[1] * perpDelta @@ -188,6 +230,10 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) dx = snapScalarToGrid(rawDx, step) dz = snapScalarToGrid(rawDz, step) } + if (lineUp !== lastLineUp) { + lastLineUp = lineUp + publishWallParallelGuide(lineUp) + } if (dx === lastDelta[0] && dz === lastDelta[1]) return lastDelta = [dx, dz] @@ -242,11 +288,20 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) // until the user commits. Batched into a single zustand // notification — otherwise each per-wall `.set` would re-render // every override subscriber once per linked wall per tick. + const attachedById = new Map( + planAttachedWallUpdates(attachedWalls, originalStart, originalEnd, nextStart, nextEnd).map( + (update) => [update.id, update], + ), + ) + // A wall the move carries off this one's ends lets go: back where it was. + const attachedUpdates = attachedWalls.map( + (wall) => attachedById.get(wall.id) ?? { id: wall.id, start: wall.start, end: wall.end }, + ) useLiveNodeOverrides .getState() .setMany([ [wallId, { start: nextStart, end: nextEnd }], - ...linkedUpdates.map( + ...[...linkedUpdates, ...attachedUpdates].map( (upd) => [upd.id, { start: upd.start, end: upd.end }] as [string, Record], ), @@ -260,7 +315,10 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) // overrides we just published (zustand is still on baseline). const previewSceneWalls = getWallsAfterUpdates(dragNodes, [ { id: wallId, data: { start: nextStart, end: nextEnd } }, - ...linkedUpdates.map((u) => ({ id: u.id, data: { start: u.start, end: u.end } })), + ...[...linkedUpdates, ...attachedUpdates].map((u) => ({ + id: u.id, + data: { start: u.start, end: u.end }, + })), ]).filter((wall) => !collapsedIds.has(wall.id)) const bridgePreviews = buildBridgeWallPreviews({ bridgePlans: plan.bridgePlans, @@ -285,8 +343,8 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) // (override-merged) so the mesh follows the cursor. const sceneState = useScene.getState() sceneState.markDirty(wallId) - for (const upd of linkedUpdates) { - sceneState.markDirty(upd.id) + for (const upd of [...linkedUpdates, ...attachedUpdates]) { + sceneState.markDirty(upd.id as AnyNodeId) } }, @@ -298,13 +356,16 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) }, commit() { + publishWallParallelGuide(null) const sceneState = useScene.getState() const liveWall = sceneState.nodes[wallId] as WallNode | undefined if (liveWall?.type !== 'wall') { // Bail without leaving stale overrides behind. const overrides = useLiveNodeOverrides.getState() overrides.clear(wallId) - for (const wall of linkedOriginals) overrides.clear(wall.id as AnyNodeId) + for (const wall of [...linkedOriginals, ...attachedWalls]) { + overrides.clear(wall.id as AnyNodeId) + } clearSurfacePreviews() return } @@ -362,15 +423,46 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) wallCount: Object.values(sceneState.nodes).filter((entry) => entry?.type === 'wall').length, }) + // Walls T'd into this one follow it, what hangs on them staying put. + const attachedChanges = planAttachedWallChanges( + sceneState.nodes, + wallId, + originalStart, + originalEnd, + lastNextStart, + lastNextEnd, + new Set([...linkedIds, ...collapsedLinkedWallIds]), + ) + const attachedWallIds = attachedChanges.flatMap(({ id }) => + sceneState.nodes[id]?.type === 'wall' ? [id] : [], + ) + // The moved and stretched walls and the jogs take over any stretch of a + // parallel wall they now run along; jogs on the outline are outside walls. sceneState.applyNodeChanges( - planWallEndRejoins( + finishWallEdit( sceneState.nodes, - commitUpdates.map(({ id }) => id), - WALL_CONNECT_SNAP_RADIUS, + planWallEndRejoins( + sceneState.nodes, + commitUpdates.map(({ id }) => id), + WALL_CONNECT_SNAP_RADIUS, + { + update: [ + ...(commitUpdates as Array<{ id: AnyNodeId; data: Partial }>), + ...attachedChanges, + ], + create: bridgeCreates, + delete: Array.from(collapsedLinkedWallIds), + }, + ), { - update: commitUpdates as Array<{ id: AnyNodeId; data: Partial }>, - create: bridgeCreates, - delete: Array.from(collapsedLinkedWallIds), + winnerIds: [ + ...commitUpdates.map(({ id }) => id), + ...attachedWallIds, + ...bridgeCreates.map(({ node }) => node.id), + ], + movedWallId: wallId, + bridgeIds: bridgeCreates.map(({ node }) => node.id), + mintId: () => generateId('wall'), }, ), ) @@ -382,7 +474,9 @@ export const wallFloorplanMoveTarget: FloorplanMoveTarget = ({ node }) // next frame reads the new scene state directly. const overrides = useLiveNodeOverrides.getState() overrides.clear(wallId) - for (const wall of linkedOriginals) overrides.clear(wall.id as AnyNodeId) + for (const wall of [...linkedOriginals, ...attachedWalls]) { + overrides.clear(wall.id as AnyNodeId) + } clearSurfacePreviews() // Swap ghosts → real walls: the bridges we just created render diff --git a/packages/nodes/src/wall/floorplan-tool.tsx b/packages/nodes/src/wall/floorplan-tool.tsx index ded96fbfb6..afd7525719 100644 --- a/packages/nodes/src/wall/floorplan-tool.tsx +++ b/packages/nodes/src/wall/floorplan-tool.tsx @@ -7,6 +7,7 @@ import { } from '@pascal-app/core' import { type FloorplanToolContext, + getDrawnWallTypes, markToolCancelConsumed, triggerSFX, useEditor, @@ -86,6 +87,7 @@ function RectangleFloorplanTool({ activeLevelId }: FloorplanToolContext) { point, useEditor.getState().toolDefaults.wall ?? {}, construction, + getDrawnWallTypes(), ) cancel() triggerSFX('sfx:structure-build') diff --git a/packages/nodes/src/wall/floorplan.test.ts b/packages/nodes/src/wall/floorplan.test.ts index 88471444ed..6e78ba5642 100644 --- a/packages/nodes/src/wall/floorplan.test.ts +++ b/packages/nodes/src/wall/floorplan.test.ts @@ -10,6 +10,7 @@ import { import { createFloorplanContextExtensions, readFloorplanGeometryMetadata, + WALL_PULL_AFFORDANCE, WALL_PUSH_AFFORDANCE, } from '@pascal-app/editor' import { buildWallFloorplan } from './floorplan' @@ -221,7 +222,11 @@ describe('buildWallFloorplan render purpose', () => { test('places selected push arrows on the curved wall midpoint', () => { const curved = WallNode.parse({ ...wall, curveOffset: 1 }) const geometry = buildWallFloorplan(curved, context('edit', true)) - const arrows = geometry ? flatten(geometry).filter((entry) => entry.kind === 'move-arrow') : [] + const arrows = geometry + ? flatten(geometry).filter( + (entry) => entry.kind === 'move-arrow' && entry.affordance === WALL_PUSH_AFFORDANCE, + ) + : [] expect(arrows).toHaveLength(2) // Each arrow is the 3D side arrow's push for its face, not a free move. @@ -244,6 +249,39 @@ describe('buildWallFloorplan render purpose', () => { expect(arrows[1].point[1]).toBeCloseTo(-1.115) }) + test('a selected wall carries a pull grip at each end, pointing out along the wall', () => { + const unselected = buildWallFloorplan(wall, context('edit', false)) + const pullsOf = (geometry: FloorplanGeometry | null) => + geometry + ? flatten(geometry).filter( + (entry) => entry.kind === 'move-arrow' && entry.affordance === WALL_PULL_AFFORDANCE, + ) + : [] + expect(pullsOf(unselected)).toHaveLength(0) + + const grips = pullsOf(buildWallFloorplan(wall, context('edit', true))) + expect(grips).toHaveLength(2) + expect(grips[0]).toMatchObject({ + kind: 'move-arrow', + variant: 'pull', + point: wall.start, + affordance: WALL_PULL_AFFORDANCE, + payload: { wallId: wall.id, endpoint: 'start' }, + title: 'Drag to draw the next wall', + }) + expect(grips[1]).toMatchObject({ + kind: 'move-arrow', + variant: 'pull', + point: wall.end, + payload: { wallId: wall.id, endpoint: 'end' }, + }) + if (grips[0]?.kind !== 'move-arrow' || grips[1]?.kind !== 'move-arrow') return + // Out of the wall: back along it from the start, on along it from the end. + const along = Math.atan2(wall.end[1] - wall.start[1], wall.end[0] - wall.start[0]) + expect(Math.cos(grips[1].angle - along)).toBeCloseTo(1) + expect(Math.cos(grips[0].angle - along)).toBeCloseTo(-1) + }) + test('places thickness handles on both visible faces of a curved wall', () => { const curved = WallNode.parse({ ...wall, curveOffset: 1 }) const geometry = buildWallFloorplan(curved, context('edit', true)) diff --git a/packages/nodes/src/wall/floorplan.ts b/packages/nodes/src/wall/floorplan.ts index 1be8ec9d0a..84a52482c9 100644 --- a/packages/nodes/src/wall/floorplan.ts +++ b/packages/nodes/src/wall/floorplan.ts @@ -16,6 +16,7 @@ import { getWallPlanFootprint, getWallThickness, isCurvedWall, + isRailingWall, type WallLayerMiterData, type WallLayerPolyline, type WallMiterData, @@ -25,8 +26,12 @@ import { import { floorplanGeometryMetadata, readFloorplanContext, + WALL_PULL_AFFORDANCE, + WALL_PULL_GRIP_TITLE, WALL_PUSH_AFFORDANCE, + type WallPullGripPayload, type WallPushArrowPayload, + wallPullSeed, } from '@pascal-app/editor' import { constructionDimensionStandard } from '../shared/construction-dimension-standards' import { @@ -38,6 +43,7 @@ import { } from './construction-dimensions' import { buildCurtainWallFloorplan } from './curtain-wall-floorplan' import { hasWallCurveBlockingChildren } from './curve-eligibility' +import { buildRailingWallFloorplan } from './railing-wall-floorplan' // Same constants the legacy `getFloorplanWall` uses (editor/lib/floorplan/walls.ts). // Slightly exaggerates thin walls so the 2D plan stays legible without @@ -47,6 +53,7 @@ const FLOORPLAN_MIN_VISIBLE_WALL_THICKNESS = 0.13 const FLOORPLAN_MAX_EXTRA_THICKNESS = 0.035 const FLOORPLAN_SELECTION_HATCH_SPACING = 0.12 const FLOORPLAN_SELECTED_WALL_STROKE_WIDTH = 0.03 +const FLOORPLAN_RAILING_STROKE_WIDTH = 0.01 const FLOORPLAN_SELECTION_HATCH_STROKE_WIDTH = 0.02 const WALL_DIMENSION_REFERENCES = ['finished-faces', 'centerline', 'stud-faces'] as const @@ -54,6 +61,8 @@ type WallDimensionReference = (typeof WALL_DIMENSION_REFERENCES)[number] function floorplanWallThickness(wall: WallNode): number { const baseThickness = getWallThickness(wall) + // A railing draws as thin as its posts. + if (isRailingWall(wall)) return baseThickness const scaledThickness = baseThickness * FLOORPLAN_WALL_THICKNESS_SCALE return Math.min( baseThickness + FLOORPLAN_MAX_EXTRA_THICKNESS, @@ -286,7 +295,8 @@ export function computeWallFloorplanLevelData({ * 3. A transparent hit-line on the centerline so the user can grab the * wall body easily. * 4. Two endpoint handles (start + end) when selected — the registry - * layer hosts the 5-circle stack + hover transitions + 2D drag. + * layer hosts the 5-circle stack + hover transitions + 2D drag — and, + * just past each, a "pull to continue" grip that draws the next wall. * 5. Exterior facade strings plus interior wall spans and hosted-opening widths. * * `ctx.levelData` provides the shared level miter graph when the floor-plan @@ -342,8 +352,14 @@ export function buildWallFloorplan(node: WallNode, ctx: GeometryContext): Floorp : isHovered && palette ? palette.wallHoverStroke : '#1f2937' + // A railing is an outline round its posts, with no poché. + const railing = isRailingWall(node) const fill = - node.wallType === 'curtain' ? 'transparent' : showSelectedChrome ? '#ffffff' : '#374151' + node.wallType === 'curtain' || railing + ? 'transparent' + : showSelectedChrome + ? '#ffffff' + : '#374151' const children: FloorplanGeometry[] = [ { @@ -351,7 +367,11 @@ export function buildWallFloorplan(node: WallNode, ctx: GeometryContext): Floorp points, fill, stroke, - strokeWidth: showSelectedChrome ? FLOORPLAN_SELECTED_WALL_STROKE_WIDTH : 0.02, + strokeWidth: showSelectedChrome + ? FLOORPLAN_SELECTED_WALL_STROKE_WIDTH + : railing + ? FLOORPLAN_RAILING_STROKE_WIDTH + : 0.02, opacity: 0.92, metadata: floorplanGeometryMetadata({ annotationObstacle: 'outline' }), // Once the wall is selected, the body keeps catching the pointer @@ -372,6 +392,15 @@ export function buildWallFloorplan(node: WallNode, ctx: GeometryContext): Floorp ctx.children, ), ) + } else if (railing) { + children.push( + ...buildRailingWallFloorplan( + node, + ctx.children, + ctx.siblings.filter((sibling): sibling is AnyNode & WallNode => sibling.type === 'wall'), + stroke, + ), + ) } else if (node.assembly) { // Assembly layer lines + framing poché, drawn inside the footprint. children.push( @@ -467,9 +496,25 @@ export function buildWallFloorplan(node: WallNode, ctx: GeometryContext): Floorp affordance: 'move-endpoint', payload: { wallId: node.id, endpoint: 'end' as const }, }) + // Pull grips — an arrow just past each end, pointing out along the wall: + // drag one to draw the next wall from that end (`startWallPull`). + for (const endpoint of ['start', 'end'] as const) { + const seed = wallPullSeed(node, endpoint) + if (!seed) continue + children.push({ + kind: 'move-arrow', + variant: 'pull', + point: seed.start, + angle: Math.atan2(seed.reference[1], seed.reference[0]), + affordance: WALL_PULL_AFFORDANCE, + payload: { wallId: node.id, endpoint } satisfies WallPullGripPayload, + title: WALL_PULL_GRIP_TITLE, + }) + } const thicknessFrame = getWallCurveFrameAt(self, 0.5) - for (const side of [1, -1] as const) { + // A railing's posts set its thickness (the panel's Post size): it has no thickness handles. + for (const side of railing ? [] : ([1, -1] as const)) { const halfVisibleThickness = getWallLocalFaceZ(self, side > 0 ? 'a' : 'b') * side children.push({ kind: 'endpoint-handle', diff --git a/packages/nodes/src/wall/justification.test.ts b/packages/nodes/src/wall/justification.test.ts index d41b0b5833..33b4890455 100644 --- a/packages/nodes/src/wall/justification.test.ts +++ b/packages/nodes/src/wall/justification.test.ts @@ -87,7 +87,9 @@ test('floorplan wall body, hover polygon, face dots and side arrows follow justi (child) => child.kind === 'endpoint-handle' && child.affordance === 'thickness', ) expect(dots.map((dot) => dot.kind === 'endpoint-handle' && dot.point[1])).toEqual([0.2, 0]) - const arrows = geometry.children.filter((child) => child.kind === 'move-arrow') + const arrows = geometry.children.filter( + (child) => child.kind === 'move-arrow' && child.variant !== 'pull', + ) expect(arrows).toHaveLength(2) expect(arrows[0]).toMatchObject({ point: [2, 0.28500000000000003] }) expect(arrows[1]).toMatchObject({ point: [2, -0.05] }) diff --git a/packages/nodes/src/wall/move-endpoint-tool.tsx b/packages/nodes/src/wall/move-endpoint-tool.tsx index d8c96a761e..c6e00ccaf9 100644 --- a/packages/nodes/src/wall/move-endpoint-tool.tsx +++ b/packages/nodes/src/wall/move-endpoint-tool.tsx @@ -405,6 +405,7 @@ export const MoveWallEndpointTool: React.FC<{ target: MovingWallEndpoint }> = ({ start: fixedPoint, angleSnap: isAngleSnapActive(), magnetic: isMagneticSnapActive(), + captureWallBodies: true, }) const snappedPoint = snapResult.point diff --git a/packages/nodes/src/wall/move-tool.test.tsx b/packages/nodes/src/wall/move-tool.test.tsx index 6cc27bebd6..59f359be7e 100644 --- a/packages/nodes/src/wall/move-tool.test.tsx +++ b/packages/nodes/src/wall/move-tool.test.tsx @@ -300,10 +300,12 @@ describe('3D wall move', () => { }) test('drop rejoins a moved endpoint within the connect radius in one reversible step', async () => { + // Slanted, so the move's line-up with parallel walls leaves the drop at 2.5 + // and only the rejoin carries the endpoint onto it. const target = WallNode.parse({ id: 'wall_drop_target', parentId: LEVEL_ID, - start: [2.53, -2], + start: [3.53, -2], end: [2.53, 0.04], }) useScene.getState().applyNodeChanges({ diff --git a/packages/nodes/src/wall/move-tool.tsx b/packages/nodes/src/wall/move-tool.tsx index 87be189b3f..9548913457 100644 --- a/packages/nodes/src/wall/move-tool.tsx +++ b/packages/nodes/src/wall/move-tool.tsx @@ -6,9 +6,13 @@ import { beginSceneHistoryPauseSession, createLevelStructurePreview, emitter, + finishWallEdit, type GridEvent, + generateId, getPerpendicularWallMoveAxis, getPlannedLinkedWallUpdates, + planAttachedWallChanges, + planAttachedWallUpdates, planWallEndRejoins, planWallMoveJunctions, resolveMovedWallSupportSlabPatch, @@ -24,12 +28,17 @@ import { getSegmentGridStep, isSegmentLongEnough, isTypingTarget, + isWallRunFree, markToolCancelConsumed, + nearestWallParallelTarget, + publishWallParallelGuide, snapBuildingLocalToWorldGrid, snapScalarToGrid, triggerSFX, useEditor, WALL_CONNECT_SNAP_RADIUS, + type WallParallelTarget, + wallParallelTargets, } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -328,6 +337,33 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { return { start: nextStart, end: nextEnd } } + // The level's other walls, which the moving wall lines up with when parallel + // (`wallParallelTargets`). Recomputed when R / T turns the wall. + const levelWalls = Object.values(useScene.getState().nodes).filter( + (entry): entry is WallNode => entry?.type === 'wall' && (entry.parentId ?? null) === levelId, + ) + const movingWall = levelWalls.find((entry) => entry.id === nodeId) + let parallelTargets: { rotation: number; targets: WallParallelTarget[] } | null = null + const getParallelTargets = (axis: WallMoveAxis) => { + const rotation = pendingRotationRef.current + if (parallelTargets?.rotation !== rotation) { + const turned = buildWallFromCenter(originalCenter) + parallelTargets = { + rotation, + targets: movingWall + ? wallParallelTargets({ ...movingWall, ...turned }, axis, levelWalls) + : [], + } + } + return parallelTargets.targets + } + let lastLineUp: WallParallelTarget | null = null + // Alt moves the wall exactly with the pointer, as in the floor plan. + let altHeld = false + const onPointerMove = (event: PointerEvent) => { + altHeld = event.altKey + } + const getMovePlan = (nextStart: [number, number], nextEnd: [number, number]) => planWallMoveJunctions( linkedOriginalsRef.current, @@ -350,10 +386,27 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { nextEnd, ) const movedById = new Map(movedUpdates.map((entry) => [entry.id, entry])) - - return linkedOriginalsRef.current.map( - (wall) => movedById.get(wall.id) ?? { id: wall.id, start: wall.start, end: wall.end }, - ) + const linkedIds = new Set(linkedOriginalsRef.current.map((wall) => wall.id)) + // Walls T'd into this one stay on it, stretched along their own line. + const attached = isNew + ? [] + : planAttachedWallUpdates( + Object.values(dragNodes).filter( + (entry): entry is WallNode => + entry.type === 'wall' && entry.id !== nodeId && !linkedIds.has(entry.id), + ), + originalStart, + originalEnd, + nextStart, + nextEnd, + ) + + return [ + ...linkedOriginalsRef.current.map( + (wall) => movedById.get(wall.id) ?? { id: wall.id, start: wall.start, end: wall.end }, + ), + ...attached, + ] } const applyPreview = (nextStart: [number, number], nextEnd: [number, number]) => { @@ -432,6 +485,7 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { const rawDeltaX = rawX - anchor[0] const rawDeltaZ = rawZ - anchor[1] if (!hasDraggedRef.current && Math.hypot(rawDeltaX, rawDeltaZ) < 1e-6) return + const free = isWallRunFree({ altKey: altHeld }) // When the move is axis-locked (side-handle drag), snap the wall // center's absolute perpendicular offset to a multiple of @@ -444,30 +498,40 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { // the displacement alone preserves any pre-existing off-grid // offset, so the wall could only ever sit at `originalCentre ± // k·snapStep` — never on actual grid lines. Snapping the wall's - // own perpendicular coordinate fixes that. + // own perpendicular coordinate fixes that. A parallel wall the wall + // lines up with wins over the grid. const axis = moveAxisRef.current let deltaX: number let deltaZ: number + let lineUp: WallParallelTarget | null = null if (axis) { const originalProj = originalCenter[0] * axis[0] + originalCenter[1] * axis[1] const rawProj = originalProj + rawDeltaX * axis[0] + rawDeltaZ * axis[1] - const snappedProj = snapScalarToGrid(rawProj, snapStep) + lineUp = free ? null : nearestWallParallelTarget(getParallelTargets(axis), rawProj) + const snappedProj = lineUp + ? lineUp.position + : snapScalarToGrid(rawProj, free ? 0 : snapStep) const perpDelta = snappedProj - originalProj deltaX = axis[0] * perpDelta deltaZ = axis[1] * perpDelta } else { - // Snap the resulting wall center to the WORLD XZ grid (projected - // back into building-local), then express the result as a delta - // from the original centre. Without this, a rotated building - // dragged the wall along its local axes instead of world ones. + // Snap the resulting wall center to the building's own grid lattice + // (the one the wall tool drafts on, square to a turned house), then + // express the result as a delta from the original centre. const targetLocal: [number, number] = [ originalCenter[0] + rawDeltaX, originalCenter[1] + rawDeltaZ, ] - const snappedLocal = snapBuildingLocalToWorldGrid(targetLocal, snapStep) + const snappedLocal = free + ? targetLocal + : snapBuildingLocalToWorldGrid(targetLocal, snapStep) deltaX = snappedLocal[0] - originalCenter[0] deltaZ = snappedLocal[1] - originalCenter[1] } + if (lineUp !== lastLineUp) { + lastLineUp = lineUp + publishWallParallelGuide(lineUp) + } const constrainedGridPos: [number, number] = [anchor[0] + deltaX, anchor[1] + deltaZ] @@ -564,14 +628,46 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { // The drag writes nothing to the store (the preview is live overrides), so history is // paused only for this drop. Keyed by the moving wall: in split view the 2D move overlay // co-owns the gesture, and commitStep lifts its pause too. - const changes = planWallEndRejoins( + // The moved and stretched walls and the jogs take over any stretch of a + // parallel wall they now run along; jogs on the outline are outside walls. + // Walls T'd into this one follow it, what hangs on them staying put. + const attachedChanges = isNew + ? [] + : planAttachedWallChanges( + sceneState.nodes, + nodeId, + originalStart, + originalEnd, + preview.start, + preview.end, + new Set([ + ...linkedOriginalsRef.current.map((wall) => wall.id), + ...collapsedLinkedWallIds, + ]), + ) + const changes = finishWallEdit( sceneState.nodes, - commitUpdates.map(({ id }) => id), - WALL_CONNECT_SNAP_RADIUS, + planWallEndRejoins( + sceneState.nodes, + commitUpdates.map(({ id }) => id), + WALL_CONNECT_SNAP_RADIUS, + { + update: [...wallUpdates, ...attachedChanges], + create: wallCreates, + delete: Array.from(collapsedLinkedWallIds), + }, + ), { - update: wallUpdates, - create: wallCreates, - delete: Array.from(collapsedLinkedWallIds), + winnerIds: [ + ...commitUpdates.map(({ id }) => id), + ...attachedChanges.flatMap(({ id }) => + sceneState.nodes[id]?.type === 'wall' ? [id] : [], + ), + ...wallCreates.map(({ node }) => node.id), + ], + movedWallId: nodeId, + bridgeIds: wallCreates.map(({ node }) => node.id), + mintId: () => generateId('wall'), }, ) const drop = beginSceneHistoryPauseSession(useScene, { gesture: nodeId }) @@ -650,10 +746,12 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { emitter.on('grid:move', onGridMove) emitter.on('tool:cancel', onCancel) + window.addEventListener('pointermove', onPointerMove) window.addEventListener('pointerup', onPointerUp) window.addEventListener('keydown', onKeyDown) return () => { + if (lastLineUp) publishWallParallelGuide(null) if (shouldRestoreOnCleanup) { // `shouldRestoreOnCleanup` is only true if neither `onPointerUp` // (commit branch) nor `onCancel` ran — i.e., the unmount came @@ -668,6 +766,7 @@ export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => { } emitter.off('grid:move', onGridMove) emitter.off('tool:cancel', onCancel) + window.removeEventListener('pointermove', onPointerMove) window.removeEventListener('pointerup', onPointerUp) window.removeEventListener('keydown', onKeyDown) } diff --git a/packages/nodes/src/wall/multi-reference.test.ts b/packages/nodes/src/wall/multi-reference.test.ts index 01a71745ef..740deded02 100644 --- a/packages/nodes/src/wall/multi-reference.test.ts +++ b/packages/nodes/src/wall/multi-reference.test.ts @@ -1,6 +1,18 @@ import { afterEach, expect, test } from 'bun:test' -import { type AnyNode, type AnyNodeId, useScene, WallNode } from '@pascal-app/core' -import { getNodePanelModel } from '@pascal-app/editor' +import { + type AnyNode, + type AnyNodeId, + GYPSUM_HALF, + getWallAssemblyPreset, + SiteNode, + STUCCO_3_COAT, + STUD_2X8, + useScene, + WallNode, + wallAssemblyPatch, + wallTypeAssembly, +} from '@pascal-app/core' +import { getNodePanelModel, type MultiPanelControl } from '@pascal-app/editor' import { wallDefinition } from './definition' globalThis.requestAnimationFrame ??= () => 0 @@ -21,18 +33,113 @@ const room = () => { ) } +type Choice = Extract +const controls = (selection: WallNode[]) => + getNodePanelModel(wallDefinition)!.multiControls!({ + selection, + nodes: useScene.getState().nodes, + }) +const choices = (selection: WallNode[]) => + controls(selection).filter((control): control is Choice => control.kind !== 'readout') +const control = (selection: WallNode[], id: string) => + controls(selection).find((entry) => entry.id === id) + +const load = (walls: WallNode[]) => { + const nodes = Object.fromEntries(walls.map((w) => [w.id, w])) as Record + useScene.setState({ nodes, rootNodeIds: [] }) + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() +} +const current = (walls: WallNode[]) => + walls.map((w) => useScene.getState().nodes[w.id as AnyNodeId] as WallNode) + +test('multi-select Preset and Exterior apply to every wall in one step, mixed reads as mixed', () => { + const walls = room() + load(walls) + const preset = control(walls, 'wall-assembly-preset') as Choice + expect(preset).toMatchObject({ kind: 'select', section: 'Assembly', value: 'none' }) + preset.onChange('exterior-2x6-siding') + expect(useScene.temporal.getState().pastStates).toHaveLength(1) + const sided = current(walls) + const total = wallAssemblyPatch(getWallAssemblyPreset('exterior-2x6-siding')!.assembly).thickness + for (const wall of sided) { + expect(wall.assembly?.presetId).toBe('exterior-2x6-siding') + expect(wall.thickness).toBeCloseTo(total, 9) + } + // The assembly owns the thickness: a readout, not a field to type over. + expect(control(sided, 'wall-thickness-assembly')).toMatchObject({ + kind: 'readout', + detail: 'from assembly', + }) + const exterior = control(sided, 'wall-assembly-exterior') as Choice + expect(exterior.value).toBe('siding') + exterior.onChange('stucco') + expect(useScene.temporal.getState().pastStates).toHaveLength(2) + const stucco = current(walls) + expect((control(stucco, 'wall-assembly-exterior') as Choice).value).toBe('stucco') + for (const wall of stucco) + expect(wall.assembly?.layers.find((layer) => layer.role === 'finish')?.material).toBe('stucco') + expect(stucco[0]!.assembly?.presetId).toBeUndefined() + // One wall back to a plain single layer: the selection's preset is mixed. + const mixed = [{ ...stucco[0]!, assembly: undefined }, ...stucco.slice(1)] as WallNode[] + expect((control(mixed, 'wall-assembly-preset') as Choice).value).toBeNull() + expect(control(mixed, 'wall-assembly-exterior')).toBeUndefined() + // The readout covers only the assembly walls; the plain one keeps its Thickness field. + expect(control(mixed, 'wall-thickness-assembly')).toMatchObject({ + label: 'Assembly walls', + detail: '3 of 4, from assembly', + }) + const thickness = wallDefinition.parametrics!.groups[0]!.fields.find( + (field) => field.key === 'thickness', + ) as { appliesTo?: (wall: WallNode) => boolean } + expect(mixed.map((wall) => thickness.appliesTo!(wall))).toEqual([true, false, false, false]) + // A curtain wall is neither in the readout nor given a preset. + const curtain = [{ ...stucco[0]!, wallType: 'curtain' }, ...stucco.slice(1)] as WallNode[] + expect(control(curtain, 'wall-thickness-assembly')).toMatchObject({ + detail: '3 of 4, from assembly', + }) +}) + +test("the Preset lists the project's own wall types by name and applies one like a preset", () => { + const walls = room() + const type = { + id: 'custom-deep', + label: '2x8 stucco', + assembly: wallTypeAssembly('custom-deep', [ + { role: 'finish', material: 'stucco', thickness: STUCCO_3_COAT }, + { role: 'structure', core: true, material: 'wood', thickness: STUD_2X8 }, + { role: 'lining', material: 'drywall', thickness: GYPSUM_HALF }, + ]), + } + const site = SiteNode.parse({ id: 'site_lot', wallTypes: [type] }) + const nodes = Object.fromEntries([site, ...walls].map((n) => [n.id, n])) as Record< + AnyNodeId, + AnyNode + > + useScene.setState({ nodes, rootNodeIds: [site.id as AnyNodeId] }) + useScene.temporal.getState().clear() + useScene.temporal.getState().resume() + const preset = control(walls, 'wall-assembly-preset') as Choice + expect(preset.options.at(-1)).toEqual({ value: 'custom-deep', label: '2x8 stucco' }) + preset.onChange('custom-deep') + const applied = current(walls) + for (const wall of applied) { + expect(wall.assembly?.presetId).toBe('custom-deep') + expect(wall.thickness).toBeCloseTo(STUCCO_3_COAT + STUD_2X8 + GYPSUM_HALF, 9) + } + expect((control(applied, 'wall-assembly-preset') as Choice).value).toBe('custom-deep') + // A type this project does not have reads as a custom stack. + useScene.setState({ rootNodeIds: [] }) + expect((control(applied, 'wall-assembly-preset') as Choice).value).toBe('custom') +}) + test('multi-select Reference: room labels, mixed shows no option, one step for every wall', () => { const walls = room() const nodes = Object.fromEntries(walls.map((w) => [w.id, w])) as Record useScene.setState({ nodes, rootNodeIds: [] }) useScene.temporal.getState().clear() useScene.temporal.getState().resume() - const controls = (selection: WallNode[]) => - getNodePanelModel(wallDefinition)!.multiControls!({ - selection, - nodes: useScene.getState().nodes, - }) - const [reference] = controls(walls) + const [reference] = choices(walls) expect(reference).toMatchObject({ id: 'wall-reference', section: 'Dimensions', @@ -47,8 +154,8 @@ test('multi-select Reference: room labels, mixed shows no option, one step for e reference!.onChange('outside') expect(useScene.temporal.getState().pastStates).toHaveLength(1) const after = walls.map((w) => useScene.getState().nodes[w.id as AnyNodeId] as WallNode) - expect(controls(after)[0]!.value).toBe('outside') + expect(choices(after)[0]!.value).toBe('outside') // Mixed: one wall back to centre. const mixed = [{ ...after[0]!, justification: undefined }, ...after.slice(1)] as WallNode[] - expect(controls(mixed)[0]!.value).toBeNull() + expect(choices(mixed)[0]!.value).toBeNull() }) diff --git a/packages/nodes/src/wall/paint-plan.test.ts b/packages/nodes/src/wall/paint-plan.test.ts index d9a948821a..e9300b029c 100644 --- a/packages/nodes/src/wall/paint-plan.test.ts +++ b/packages/nodes/src/wall/paint-plan.test.ts @@ -135,7 +135,7 @@ describe('wall paint plan', () => { 'stripe_b', ]) expect(after[room.id].wallOverrides).toEqual([ - { wallId: WALL_ID, face: 'a', finish: 'library:concrete-drywall' }, + { wallId: WALL_ID, face: 'a', finish: 'library:preset-seattlegrey' }, ]) expect(after[room.id].wallMaterial).toBe('library:room') expect(after[other.id]).toEqual(other) diff --git a/packages/nodes/src/wall/paint.test.ts b/packages/nodes/src/wall/paint.test.ts index 41f87987cf..9d938b6e78 100644 --- a/packages/nodes/src/wall/paint.test.ts +++ b/packages/nodes/src/wall/paint.test.ts @@ -287,7 +287,7 @@ describe('wall paint roles', () => { expect(nodes()[wall.id].slots?.a).toBeUndefined() expect(nodes()[wall.id].faceRegions).toBeUndefined() expect(nodes().zone_room.wallOverrides).toEqual([ - { wallId: wall.id, face: 'a', finish: 'library:concrete-drywall' }, + { wallId: wall.id, face: 'a', finish: 'library:preset-seattlegrey' }, ]) expect(nodes().zone_room.wallMaterial).toBe('library:new-room') expect( diff --git a/packages/nodes/src/wall/paint.ts b/packages/nodes/src/wall/paint.ts index f2773c39b8..82b7d54bf3 100644 --- a/packages/nodes/src/wall/paint.ts +++ b/packages/nodes/src/wall/paint.ts @@ -15,6 +15,7 @@ import { parseMaterialRef, plannedWallIds, planWallPaint, + type RailingWallSlotId, resolveWallFaceChain, resolveWallFinish, type SceneMaterialId, @@ -55,6 +56,7 @@ import { resolveSlotPaintMaterialRef, } from '../shared/slot-paint' import { swapPreviewMaterial } from '../shared/swap-preview-material' +import { RAILING_WALL_SLOTS } from './railing-wall-materials' // Roles (see `parseWallPaintRole` in core): face / trim slots write // `node.slots`, `region:` a paint region, `room:/` this face @@ -63,7 +65,7 @@ import { swapPreviewMaterial } from '../shared/swap-preview-material' // shows there: a region, else the room's finish on that face, else the face slot. type ResolvedWallPaintRole = - | { kind: 'slot'; slotId: WallSurfaceSlotId | CurtainWallRole } + | { kind: 'slot'; slotId: WallSurfaceSlotId | CurtainWallRole | RailingWallSlotId } | Exclude const WALL_SLOT_IDS = new Set(Object.keys(WALL_SURFACE_SLOT_DEFAULTS)) @@ -72,6 +74,9 @@ const CURTAIN_WALL_ROLES = ['curtain-frame', 'curtain-glass', 'curtain-solid'] a type CurtainWallRole = (typeof CURTAIN_WALL_ROLES)[number] const isCurtainWallRole = (role: string): role is CurtainWallRole => (CURTAIN_WALL_ROLES as readonly string[]).includes(role) +// A railing paints its posts and rails, and its infill (palette 0..1). +const isRailingWallRole = (role: string): role is RailingWallSlotId => + (RAILING_WALL_SLOTS as readonly string[]).includes(role) const FACE_MATERIAL_INDEX: Record = { a: 1, b: 2 } @@ -80,7 +85,7 @@ export { wallRegionRole, wallRoomFaceRole, wallRoomFinishRole } export function parseWallPaintRole(role: string): ResolvedWallPaintRole | null { return parseCoreWallPaintRole( role, - (slotId) => WALL_SLOT_IDS.has(slotId) || isCurtainWallRole(slotId), + (slotId) => WALL_SLOT_IDS.has(slotId) || isCurtainWallRole(slotId) || isRailingWallRole(slotId), ) as ResolvedWallPaintRole | null } @@ -126,12 +131,13 @@ export function resolveWallRole(args: { nodes?: Readonly> }): string | null { const { node, hitObject, localPosition, ray } = args - if (node.wallType === 'curtain') { + if (node.wallType === 'curtain' || node.wallType === 'railing') { const root = sceneRegistry.nodes.get(node.id) if (!(root && ray && hasMaterialsForGroups(root))) return null wallSlotRaycaster.ray.copy(ray) const hit = wallSlotRaycaster.intersectObject(root, false)[0] - return CURTAIN_WALL_ROLES[hit?.face?.materialIndex ?? -1] ?? null + const roles = node.wallType === 'curtain' ? CURTAIN_WALL_ROLES : RAILING_WALL_SLOTS + return roles[hit?.face?.materialIndex ?? -1] ?? null } const directSlotId = hitObject?.userData?.slotId if (typeof directSlotId === 'string' && WALL_SLOT_IDS.has(directSlotId)) { @@ -205,10 +211,12 @@ function refToEffective(ref: string | undefined) { /** * The wall mesh's palette entry a non-face slot draws with, when it is part of * the wall body rather than its own mesh: a curtain wall's frame / glass / - * solid panels (0..2), or the underpinning's stemwall (a finish ref, 3..). + * solid panels (0..2), a railing's frame / infill (0..1), or the underpinning's + * stemwall (a finish ref, 3..). */ function wallPaletteSlotIndex(wall: WallNode, slotId: string, mesh: Mesh): number | null { if (isCurtainWallRole(slotId)) return CURTAIN_WALL_ROLES.indexOf(slotId) + if (isRailingWallRole(slotId)) return RAILING_WALL_SLOTS.indexOf(slotId) if (slotId !== 'foundation' || !wall.underpinning) return null const ref = wall.slots?.foundation ?? WALL_SURFACE_SLOT_DEFAULTS.foundation const at = getWallFinishRefs(mesh.geometry).indexOf(ref) diff --git a/packages/nodes/src/wall/panel-model.ts b/packages/nodes/src/wall/panel-model.ts index d034889a61..ef7221c7ee 100644 --- a/packages/nodes/src/wall/panel-model.ts +++ b/packages/nodes/src/wall/panel-model.ts @@ -1,6 +1,11 @@ import { type AnyNode, type AnyNodeId, + type Assembly, + BRICK_AIR_SPACE, + BRICK_VENEER, + DEFAULT_WALL_THICKNESS, + FIBER_CEMENT, faceOnLine, GROUND_SUPPORT_ID, getClampedWallCurveOffset, @@ -9,18 +14,30 @@ import { justificationForFaceOnLine, normalizeWallCurveOffset, planWallJustification, + projectWallTypes, roomSideFaces, + runAsSingleSceneHistoryStep, + SIDING_LAP, + STONE_VENEER_UNVERIFIED, + STUCCO_3_COAT, terrainSupportLift, useScene, + WALL_ASSEMBLY_PRESETS, WALL_CHAIR_RAIL_DEFAULT, WALL_CROWN_DEFAULT, WALL_SKIRTING_DEFAULT, + type WallAssemblyExteriorFinish, type WallNode, type WallTrimProfile, + wallAssemblyFromLegacy, + wallAssemblyPatch, + wallAssemblyToLegacy, wallTrimPatch, } from '@pascal-app/core' import { curveReshapeScope, + formatLinearMeasurement, + type MultiPanelControl, type PanelRow, triggerSFX, useInteractionScope, @@ -29,6 +46,7 @@ import { useViewer } from '@pascal-app/viewer' import { resolveWallOpeningCeiling } from '../shared/wall-opening-ceiling' import { hasWallCurveBlockingChildren } from './curve-eligibility' import { buildWallLengthPatch } from './length-patch' +import { railingWallRows } from './railing-wall-panel-model' type TrimKey = 'skirting' | 'crown' | 'chairRail' const profiles: Record = { @@ -196,46 +214,51 @@ export function wallSettings( number('Dimensions', 'wall-length', 'Length', length, 0.1, 1000, 0.05, (next) => { update(buildWallLengthPatch(node, next)) }) - choice( - 'Dimensions', - 'wall-top', - 'Top', - node.height == null ? 'Follows level' : 'Custom height', - () => - update( - node.height == null - ? { height: Math.max(0.1, height) } - : { height: undefined, ...repairBase() }, - ), - ) - if (node.height == null) - choice('Dimensions', 'wall-height-auto', 'Current height', `${Number(height.toFixed(3))} m`) - else - number('Dimensions', 'height', 'Height', node.height, 0.1, 1000, 0.05, (height) => - update({ height }), + // A railing keeps its own height and its posts set its thickness: its rows say so instead. + const railing = node.wallType === 'railing' + if (railing) rows.push(...railingWallRows(node, update)) + else { + choice( + 'Dimensions', + 'wall-top', + 'Top', + node.height == null ? 'Follows level' : 'Custom height', + () => + update( + node.height == null + ? { height: Math.max(0.1, height) } + : { height: undefined, ...repairBase() }, + ), ) - choice( - 'Dimensions', - 'wall-bottom', - 'Bottom', - node.fillToTerrain ? 'Fill to terrain' : 'Auto', - () => - update( - node.fillToTerrain - ? { fillToTerrain: undefined, ...repairBase() } - : { fillToTerrain: true }, - ), - ) - number( - 'Dimensions', - 'thickness', - 'Thickness', - node.thickness ?? 0.1, - 0.05, - 1000, - 0.01, - (thickness) => update({ thickness }), - ) + if (node.height == null) + choice('Dimensions', 'wall-height-auto', 'Current height', `${Number(height.toFixed(3))} m`) + else + number('Dimensions', 'height', 'Height', node.height, 0.1, 1000, 0.05, (height) => + update({ height }), + ) + choice( + 'Dimensions', + 'wall-bottom', + 'Bottom', + node.fillToTerrain ? 'Fill to terrain' : 'Auto', + () => + update( + node.fillToTerrain + ? { fillToTerrain: undefined, ...repairBase() } + : { fillToTerrain: true }, + ), + ) + number( + 'Dimensions', + 'thickness', + 'Thickness', + node.thickness ?? 0.1, + 0.05, + 1000, + 0.01, + (thickness) => update({ thickness }), + ) + } const blockedCurve = hasWallCurveBlockingChildren( (node.children ?? []).flatMap((id) => nodes[id as AnyNodeId] ? [nodes[id as AnyNodeId]!] : [], @@ -262,11 +285,15 @@ export function wallSettings( onSelect: reshape, }) } - for (const [key, title, defaults] of [ - ['skirting', 'Skirting', WALL_SKIRTING_DEFAULT], - ['crown', 'Crown molding', WALL_CROWN_DEFAULT], - ['chairRail', 'Chair rail', WALL_CHAIR_RAIL_DEFAULT], - ] as const) { + // A railing has no faces to trim. + const trims = railing + ? [] + : ([ + ['skirting', 'Skirting', WALL_SKIRTING_DEFAULT], + ['crown', 'Crown molding', WALL_CROWN_DEFAULT], + ['chairRail', 'Chair rail', WALL_CHAIR_RAIL_DEFAULT], + ] as const) + for (const [key, title, defaults] of trims) { const trim = { ...defaults, ...node[key] } const patch = (value: Partial) => update(wallTrimPatch(node, key, value)) choice(title, `${key}-enabled`, title, trim.enabled ? 'On' : 'Off', () => @@ -307,3 +334,148 @@ export function wallSettings( } return rows } + +export const EXTERIOR_FINISH_OPTIONS: Array<{ value: WallAssemblyExteriorFinish; label: string }> = + [ + { value: 'none', label: 'None' }, + { value: 'siding', label: 'Lap siding' }, + { value: 'fiber-cement', label: 'Fiber cement' }, + { value: 'stucco', label: 'Stucco' }, + { value: 'brick', label: 'Brick veneer' }, + { value: 'stone', label: 'Stone veneer' }, + ] + +/** Cited default thickness for a finish the user just switched to. */ +export const EXTERIOR_FINISH_DEFAULT: Record = { + none: 0, + siding: SIDING_LAP, + 'fiber-cement': FIBER_CEMENT, + stucco: STUCCO_3_COAT, + brick: BRICK_VENEER + BRICK_AIR_SPACE, + stone: STONE_VENEER_UNVERIFIED, +} + +const PLAIN_PRESET = 'none' +const CUSTOM_PRESET = 'custom' + +/** + * The wall types a wall's Preset can be: the cited presets, then the project's own + * (`SiteNode.wallTypes`, by label), each applied the same way (`wallAssemblyPatch`). + */ +function wallPresetChoices(): Array<{ id: string; label: string; assembly: Assembly }> { + const { nodes, rootNodeIds } = useScene.getState() + return [...WALL_ASSEMBLY_PRESETS, ...projectWallTypes(nodes, rootNodeIds)] +} + +/** + * The assembly preset a wall shows: its preset or project type, a custom stack + * (also one from a project type this project does not have), or none (a single layer). + */ +function wallPresetValue(wall: WallNode, known: ReadonlySet): string { + const presetId = wall.assembly?.presetId + if (presetId && known.has(presetId)) return presetId + return wall.assembly ? CUSTOM_PRESET : PLAIN_PRESET +} + +function shared(values: readonly T[]): T | null { + return values.length > 0 && values.every((value) => value === values[0]) ? values[0]! : null +} + +function writeWalls(updates: Array<{ id: AnyNodeId; data: Partial }>) { + if (updates.length === 0) return + runAsSingleSceneHistoryStep(useScene, () => useScene.getState().updateNodes(updates)) +} + +/** + * The wall's assembly and thickness controls for a multi-selection, as the + * single panel offers them: the Preset (wall type) and the Exterior cladding, + * each applied to every selected standard wall in one undo step, and the + * thickness of the walls whose assembly sets it, which the selection reads but + * cannot type over (the Thickness field edits the others). Curtain walls and + * railings take no assembly and are left out of all three. + */ +export function wallAssemblyMultiControls(selection: readonly WallNode[]): MultiPanelControl[] { + const walls = selection.filter( + (wall) => wall.wallType !== 'curtain' && wall.wallType !== 'railing', + ) + if (walls.length === 0) return [] + const controls: MultiPanelControl[] = [] + const withAssembly = walls.filter((wall) => wall.assembly) + if (withAssembly.length > 0) { + const { unit, metricNotation } = useViewer.getState() + const thickness = shared( + withAssembly.map((wall) => Math.round((wall.thickness ?? DEFAULT_WALL_THICKNESS) * 1e6)), + ) + // Only the walls with an assembly: the Thickness field edits the rest. + const every = withAssembly.length === selection.length + controls.push({ + id: 'wall-thickness-assembly', + kind: 'readout', + section: 'Dimensions', + label: every ? 'Thickness' : 'Assembly walls', + value: + thickness === null + ? 'Mixed' + : formatLinearMeasurement(thickness / 1e6, unit, metricNotation), + detail: every + ? 'from assembly' + : `${withAssembly.length} of ${selection.length}, from assembly`, + }) + } + const choices = wallPresetChoices() + const known = new Set(choices.map((choice) => choice.id)) + const preset = shared(walls.map((wall) => wallPresetValue(wall, known))) + controls.push({ + id: 'wall-assembly-preset', + kind: 'select', + section: 'Assembly', + label: 'Preset', + options: [ + { value: PLAIN_PRESET, label: 'None (single layer)' }, + ...(preset === CUSTOM_PRESET ? [{ value: CUSTOM_PRESET, label: 'Custom' }] : []), + ...choices.map((entry) => ({ value: entry.id, label: entry.label })), + ], + value: preset, + onChange: (value) => { + if (value === CUSTOM_PRESET) return + const chosen = choices.find((entry) => entry.id === value) + writeWalls( + walls.map((wall) => ({ + id: wall.id as AnyNodeId, + data: chosen + ? wallAssemblyPatch(structuredClone(chosen.assembly)) + : { assembly: undefined }, + })), + ) + }, + }) + // The cladding edit works on the WS5 view of each stack, as the single panel's + // Exterior row does; it shows only when every wall has one. + const legacy = walls.map((wall) => (wall.assembly ? wallAssemblyToLegacy(wall.assembly) : null)) + if (legacy.every((entry) => entry !== null)) { + controls.push({ + id: 'wall-assembly-exterior', + kind: 'select', + section: 'Assembly', + label: 'Exterior', + options: EXTERIOR_FINISH_OPTIONS, + value: shared(legacy.map((entry) => entry!.exterior?.finish ?? 'none')), + onChange: (value) => { + const finish = value as WallAssemblyExteriorFinish + writeWalls( + walls.map((wall, index) => ({ + id: wall.id as AnyNodeId, + data: wallAssemblyPatch( + wallAssemblyFromLegacy({ + ...legacy[index]!, + exterior: { finish, thickness: EXTERIOR_FINISH_DEFAULT[finish] }, + preset: undefined, + }), + ), + })), + ) + }, + }) + } + return controls +} diff --git a/packages/nodes/src/wall/panel.test.ts b/packages/nodes/src/wall/panel.test.ts new file mode 100644 index 0000000000..2b81af3fcd --- /dev/null +++ b/packages/nodes/src/wall/panel.test.ts @@ -0,0 +1,19 @@ +import { expect, test } from 'bun:test' +import { FRAMING_SPACINGS } from '@pascal-app/core' +import { formatSpacing } from '../shared/build-up-controls' +import { studSpacingChoices } from './panel' + +const INCH = 0.0254 + +// Walls offered 12/16/24 "in on centre" while roofs and ceilings offered 12/16/19.2/24 `" o.c.`. +test('wall studs offer the framing spacings roofs and ceilings do, keeping one off the list', () => { + expect(studSpacingChoices(undefined)).toEqual({ sizes: FRAMING_SPACINGS, current: null }) + expect(studSpacingChoices(16 * INCH + 1e-9).current).toBe(FRAMING_SPACINGS[1]) + expect(studSpacingChoices(0.6)).toEqual({ sizes: [0.6, ...FRAMING_SPACINGS], current: 0.6 }) + expect(FRAMING_SPACINGS.map((size) => formatSpacing(size, 'imperial'))).toEqual([ + '12" o.c.', + '16" o.c.', + '19.2" o.c.', + '24" o.c.', + ]) +}) diff --git a/packages/nodes/src/wall/panel.tsx b/packages/nodes/src/wall/panel.tsx index 7aab6e4e4c..5b19bd77c2 100644 --- a/packages/nodes/src/wall/panel.tsx +++ b/packages/nodes/src/wall/panel.tsx @@ -4,9 +4,7 @@ import { type AnyNode, type AnyNodeId, assemblyThickness, - BRICK_AIR_SPACE, - BRICK_VENEER, - FIBER_CEMENT, + FRAMING_SPACINGS, GROUND_SUPPORT_ID, GYPSUM_HALF, GYPSUM_SHEATHING, @@ -18,9 +16,6 @@ import { normalizeWallCurveOffset, resolveWallAssembly, runAsSingleSceneHistoryStep, - SIDING_LAP, - STONE_VENEER_UNVERIFIED, - STUCCO_3_COAT, terrainSupportLift, useLiveNodeOverrides, usePascalI18n, @@ -60,18 +55,28 @@ import { SliderControl, triggerSFX, useInteractionScope, + useProjectWallTypes, WallPaintRegionList, } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { Spline } from 'lucide-react' import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useShallow } from 'zustand/react/shallow' +import { formatSpacing } from '../shared/build-up-controls' +import { formatLayerThickness, parseLayerThickness } from '../shared/layer-thickness' import { resolveWallOpeningCeiling } from '../shared/wall-opening-ceiling' import { CurtainWallPanel } from './curtain-wall-panel' import { hasWallCurveBlockingChildren } from './curve-eligibility' import { buildWallLengthPatch } from './length-patch' -import { type WallReferenceValue, wallReferenceModel } from './panel-model' +import { + EXTERIOR_FINISH_DEFAULT, + EXTERIOR_FINISH_OPTIONS, + type WallReferenceValue, + wallReferenceModel, +} from './panel-model' import { createWallPropertyPreview } from './property-preview' +import { RailingWallPanel } from './railing-wall-panel' +import { railingWallTypePatch } from './railing-wall-panel-model' /** * Base half of the plane-bound repair: a stamped draft offset goes, and a @@ -296,6 +301,9 @@ export default function WallPanel() { const skirting = { ...WALL_SKIRTING_DEFAULT, ...(node.skirting ?? {}) } const crown = { ...WALL_CROWN_DEFAULT, ...(node.crown ?? {}) } const chairRail = { ...WALL_CHAIR_RAIL_DEFAULT, ...(node.chairRail ?? {}) } + // A railing keeps its own height, its posts set its thickness, and it has no assembly or faces: + // the Railing section holds its controls instead. + const railing = node.wallType === 'railing' return ( handleUpdate({ wallType })} + onChange={(wallType) => + handleUpdate(wallType === 'railing' ? railingWallTypePatch(node) : { wallType }) + } options={[ { label: i18n._({ id: 'node.wall.panel.standard', message: 'Standard' }), @@ -316,10 +326,17 @@ export default function WallPanel() { label: i18n._({ id: 'node.wall.panel.curtainWall', message: 'Curtain wall' }), value: 'curtain', }, + { + label: i18n._({ id: 'node.wall.panel.railing', message: 'Railing' }), + value: 'railing', + }, ]} value={node.wallType ?? 'standard'} /> + {node.wallType !== 'curtain' && !railing && ( + + )} -
- {i18n._({ id: 'node.wall.panel.top', message: 'Top' })} -
- - {isPlaneBound ? ( -
- {i18n._({ - id: 'node.panel.currently', - message: 'Currently {value}', - values: { value: formatLinearMeasurement(height, unit) }, - })} -
- ) : ( - - handlePreview({ - height: linearControlValueToMeters(v, unit, { maxMeters: 1000, minMeters: 0.1 }), - }) - } - precision={2} - step={0.1} - unit={unitLabel} - value={Math.round(displayHeight * 100) / 100} - /> - )} -
- {i18n._({ id: 'node.wall.panel.bottom', message: 'Bottom' })} -
- - {followsTerrain && ( -
- {i18n._({ - id: 'node.wall.panel.extendsDownwardToMeetTheTerrain', - message: 'Extends downward to meet the terrain. Height and top stay unchanged.', - })} -
- )} - {node.assembly && node.wallType !== 'curtain' ? ( - // The assembly owns the total. Editing `thickness` here would put the - // two out of sync, so the slider becomes a readout and the Assembly - // section is the only place thickness changes. -
- - {i18n._({ id: 'node.wall.panel.thickness', message: 'Thickness' })} - - - {i18n._({ - id: 'node.wall.panel.thicknessFromAssembly', - message: '{thickness} · from assembly', - values: { thickness: formatLinearMeasurement(thickness, unit) }, - })} - -
- ) : ( - - handlePreview({ - thickness: linearControlValueToMeters(v, unit, { - maxMeters: 1000, - minMeters: 0.05, - }), - }) - } - precision={3} - step={0.01} - unit={unitLabel} - value={Math.round(displayThickness * 1000) / 1000} - /> + {!railing && ( + <> +
+ {i18n._({ id: 'node.wall.panel.top', message: 'Top' })} +
+ + {isPlaneBound ? ( +
+ {i18n._({ + id: 'node.panel.currently', + message: 'Currently {value}', + values: { value: formatLinearMeasurement(height, unit) }, + })} +
+ ) : ( + + handlePreview({ + height: linearControlValueToMeters(v, unit, { + maxMeters: 1000, + minMeters: 0.1, + }), + }) + } + precision={2} + step={0.1} + unit={unitLabel} + value={Math.round(displayHeight * 100) / 100} + /> + )} +
+ {i18n._({ id: 'node.wall.panel.bottom', message: 'Bottom' })} +
+ + {followsTerrain && ( +
+ {i18n._({ + id: 'node.wall.panel.extendsDownwardToMeetTheTerrain', + message: 'Extends downward to meet the terrain. Height and top stay unchanged.', + })} +
+ )} + {node.assembly && node.wallType !== 'curtain' ? ( + // The assembly owns the total. Editing `thickness` here would put the + // two out of sync, so the slider becomes a readout and the Assembly + // section is the only place thickness changes. +
+ + {i18n._({ id: 'node.wall.panel.thickness', message: 'Thickness' })} + + + {i18n._({ + id: 'node.wall.panel.thicknessFromAssembly', + message: '{thickness} · from assembly', + values: { thickness: formatLinearMeasurement(thickness, unit) }, + })} + +
+ ) : ( + + handlePreview({ + thickness: linearControlValueToMeters(v, unit, { + maxMeters: 1000, + minMeters: 0.05, + }), + }) + } + precision={3} + step={0.01} + unit={unitLabel} + value={Math.round(displayThickness * 1000) / 1000} + /> + )} + )}
{i18n._({ id: 'node.wall.panel.reference', message: 'Reference' })} @@ -502,10 +529,18 @@ export default function WallPanel() { onCancel={handleCancel} unit={unit} /> + ) : railing ? ( + ) : ( <> - - @@ -791,68 +826,6 @@ function WallTrimSection({ // re-derived total in one `updateNode` call, so the 2D plan and the 3D wall // both move on the same store tick. -const METRES_PER_INCH = 0.0254 - -/** - * Imperial-friendly thickness input. Accepts `5/8`, `5/8"`, `7/16 in`, - * `1-1/4"`, `1 1/2"`, `12mm`, `0.012m`. A bare number means INCHES in imperial - * display and MILLIMETRES in metric — wall layers are millimetre-scale and - * typing `0.0127` for half-inch board is nobody's idea of usable. - */ -function parseLayerThickness(raw: string, unit: 'metric' | 'imperial'): number | null { - const text = raw.trim().toLowerCase().replace(/["”]/g, ' in ') - if (!text) return null - const explicit = /(mm|cm|m|in|ft|')\s*$/.exec(text) - const suffix = explicit?.[1] - const body = (suffix ? text.slice(0, explicit?.index) : text).trim() - - // `1-1/4` / `1 1/2` / `5/8` / `0.4375` - const mixed = /^(\d+(?:\.\d+)?)[\s-]+(\d+)\s*\/\s*(\d+)$/.exec(body) - const fraction = /^(\d+)\s*\/\s*(\d+)$/.exec(body) - let value: number - if (mixed) { - value = Number(mixed[1]) + Number(mixed[2]) / Number(mixed[3]) - } else if (fraction) { - value = Number(fraction[1]) / Number(fraction[2]) - } else { - value = Number(body) - } - if (!Number.isFinite(value) || value < 0) return null - - switch (suffix) { - case 'mm': - return value / 1000 - case 'cm': - return value / 100 - case 'm': - return value - case 'in': - return value * METRES_PER_INCH - case 'ft': - case "'": - return value * 12 * METRES_PER_INCH - default: - return unit === 'imperial' ? value * METRES_PER_INCH : value / 1000 - } -} - -/** Nearest common fraction of an inch, so 0.0111 m reads back as `7/16"`. */ -function formatLayerThickness(metres: number, unit: 'metric' | 'imperial'): string { - if (unit !== 'imperial') return `${Math.round(metres * 1000 * 10) / 10} mm` - const inchValue = metres / METRES_PER_INCH - const sixteenths = Math.round(inchValue * 16) - if (Math.abs(inchValue * 16 - sixteenths) > 1e-6) return `${Math.round(inchValue * 1000) / 1000}"` - const whole = Math.floor(sixteenths / 16) - let numerator = sixteenths % 16 - let denominator = 16 - while (numerator % 2 === 0 && numerator > 0) { - numerator /= 2 - denominator /= 2 - } - if (numerator === 0) return `${whole}"` - return whole > 0 ? `${whole}-${numerator}/${denominator}"` : `${numerator}/${denominator}"` -} - const SELECT_CLASS = 'h-7 w-full rounded border border-border bg-background px-2 text-[11px] text-foreground outline-none focus:border-primary' const INPUT_CLASS = @@ -904,26 +877,18 @@ function ThicknessInput({ ) } -const EXTERIOR_FINISH_OPTIONS: Array<{ - value: WallAssemblyExteriorFinish - label: MessageDescriptor -}> = [ - { value: 'none', label: /* i18n */ { id: 'node.wall.panel.none', message: 'None' } }, - { value: 'siding', label: /* i18n */ { id: 'node.wall.panel.lapSiding', message: 'Lap siding' } }, - { - value: 'fiber-cement', - label: /* i18n */ { id: 'node.wall.panel.fiberCement', message: 'Fiber cement' }, - }, - { value: 'stucco', label: /* i18n */ { id: 'node.wall.panel.stucco', message: 'Stucco' } }, - { - value: 'brick', - label: /* i18n */ { id: 'node.wall.panel.brickVeneer', message: 'Brick veneer' }, - }, - { - value: 'stone', - label: /* i18n */ { id: 'node.wall.panel.stoneVeneer', message: 'Stone veneer' }, - }, -] +/** + * The Exterior select's names for the finishes of the list it shares with the + * multi-selection controls (`EXTERIOR_FINISH_OPTIONS`, in `panel-model`). + */ +const EXTERIOR_FINISH_LABELS: Record = { + none: /* i18n */ { id: 'node.wall.panel.none', message: 'None' }, + siding: /* i18n */ { id: 'node.wall.panel.lapSiding', message: 'Lap siding' }, + 'fiber-cement': /* i18n */ { id: 'node.wall.panel.fiberCement', message: 'Fiber cement' }, + stucco: /* i18n */ { id: 'node.wall.panel.stucco', message: 'Stucco' }, + brick: /* i18n */ { id: 'node.wall.panel.brickVeneer', message: 'Brick veneer' }, + stone: /* i18n */ { id: 'node.wall.panel.stoneVeneer', message: 'Stone veneer' }, +} const SHEATHING_OPTIONS: Array<{ value: WallAssemblySheathingMaterial; label: MessageDescriptor }> = [ @@ -940,6 +905,24 @@ const FRAMING_OPTIONS: Array<{ value: WallAssemblyFramingKind; label: MessageDes { value: 'icf', label: /* i18n */ { id: 'node.wall.panel.icf', message: 'ICF' } }, ] +const spacingKey = (metres: number) => metres.toFixed(6) + +/** + * The stud spacings to pick from, the framing list roofs and ceilings use, keeping a stored one + * off the list; and the one stored (null for the framer's default). + */ +export function studSpacingChoices(stored: number | undefined) { + const listed = FRAMING_SPACINGS.find( + (size) => stored !== undefined && Math.abs(size - stored) < 1e-6, + ) + const current = stored === undefined ? null : (listed ?? stored) + return { + sizes: + current === null || listed !== undefined ? FRAMING_SPACINGS : [current, ...FRAMING_SPACINGS], + current, + } +} + const INTERIOR_FINISH_OPTIONS: Array<{ value: WallAssemblyInteriorFinish label: MessageDescriptor @@ -949,16 +932,6 @@ const INTERIOR_FINISH_OPTIONS: Array<{ { value: 'plaster', label: /* i18n */ { id: 'node.wall.panel.plaster', message: 'Plaster' } }, ] -/** Cited default thickness for a finish the user just switched to. */ -const EXTERIOR_FINISH_DEFAULT: Record = { - none: 0, - siding: SIDING_LAP, - 'fiber-cement': FIBER_CEMENT, - stucco: STUCCO_3_COAT, - brick: BRICK_VENEER + BRICK_AIR_SPACE, - stone: STONE_VENEER_UNVERIFIED, -} - function WallAssemblySection({ node, onUpdate, @@ -973,8 +946,15 @@ function WallAssemblySection({ // The cladding / sheathing / framing / interior editor works on the WS5 view // of the F2 stack; a stack it cannot express is listed read-only. const assembly = stack ? (wallAssemblyToLegacy(stack) ?? undefined) : undefined + const studSpacing = studSpacingChoices(assembly?.framing.spacing) const resolved = resolveWallAssembly(node) const presetNote = wallAssemblyUnverifiedNote(node) + // The project's own wall types sit beside the presets and apply the same way. + const projectTypes = useProjectWallTypes() + const presetId = stack?.presetId + const knownPreset = + !!presetId && + (!!getWallAssemblyPreset(presetId) || projectTypes.some((type) => type.id === presetId)) // Every write goes through wallAssemblyPatch so `thickness` is re-derived. // Changing any layer clears `preset` — the stack is no longer that preset. @@ -996,10 +976,10 @@ function WallAssemblySection({ onUpdate({ assembly: undefined }) return } - const preset = getWallAssemblyPreset(id) - if (preset) onUpdate(wallAssemblyPatch(preset.assembly)) + const preset = getWallAssemblyPreset(id) ?? projectTypes.find((type) => type.id === id) + if (preset) onUpdate(wallAssemblyPatch(structuredClone(preset.assembly))) }} - value={stack?.presetId ?? ''} + value={knownPreset ? presetId : ''} > ))} + {projectTypes.length > 0 ? ( + + {projectTypes.map((type) => ( + + ))} + + ) : null} @@ -1030,7 +1021,7 @@ function WallAssemblySection({ > {EXTERIOR_FINISH_OPTIONS.map((option) => ( ))} @@ -1092,13 +1083,16 @@ function WallAssemblySection({ { + const spacing = studSpacing.sizes.find( + (size) => spacingKey(size) === event.target.value, + ) + edit((draft) => ({ + ...draft, + framing: { + kind: draft.framing.kind, + depth: draft.framing.depth, + ...(spacing !== undefined ? { spacing } : {}), + }, + })) + }} + value={studSpacing.current === null ? '' : spacingKey(studSpacing.current)} + > + + {studSpacing.sizes.map((spacing) => ( + + ))} + + + )} + { @@ -683,6 +699,7 @@ function MezzanineEdgeArrow({ } export function RoomHandleDragPreview3D() { + const i18n = usePascalI18n() const drag = useRoomHandleDrag((s) => s.drag) const unit = useViewer((s) => s.unit) const metricNotation = useViewer((s) => s.metricNotation) @@ -773,7 +790,9 @@ export function RoomHandleDragPreview3D() { />
- Floor + + {i18n._({ id: 'editor.roomHandles.floor', message: 'Floor' })} + {formatMeasurement( drag.value - roomFloorBase(useScene.getState().nodes, drag.zoneId), diff --git a/packages/editor/src/components/ui/action-menu/view-toggles.tsx b/packages/editor/src/components/ui/action-menu/view-toggles.tsx index fe826ad8c5..bfcfb87a12 100644 --- a/packages/editor/src/components/ui/action-menu/view-toggles.tsx +++ b/packages/editor/src/components/ui/action-menu/view-toggles.tsx @@ -1018,6 +1018,7 @@ export function adjacentWallsToggle( * blue for the level below (on a roof level, the top storey), magenta for the level above. */ function AdjacentWallsControl() { + const i18n = usePascalI18n() const popupSide = useActionMenuPopupSide() const levelId = useViewer((s) => s.selection.levelId) const { shown: showBelow, setShown: setShowBelow } = useWallsBelowSetting(levelId) @@ -1030,7 +1031,7 @@ function AdjacentWallsControl() { const rows = [ { key: 'below', - label: 'Walls below', + label: i18n._({ id: 'editor.actionMenu.viewToggles.wallsBelow', message: 'Walls below' }), level: names.below, checked: showBelow, set: setShowBelow, @@ -1038,7 +1039,7 @@ function AdjacentWallsControl() { }, { key: 'above', - label: 'Walls above', + label: i18n._({ id: 'editor.actionMenu.viewToggles.wallsAbove', message: 'Walls above' }), level: names.above, checked: showAbove, set: setShowAbove, @@ -1057,7 +1058,7 @@ function AdjacentWallsControl() { : 'opacity-60 grayscale hover:bg-black/4 hover:opacity-100 hover:grayscale-0', )} disabled={!available} - label={active ? 'Walls below and above: shown' : 'Show walls below and above'} + label={active ? i18n._({ id: 'editor.actionMenu.viewToggles.wallsBelowAndAboveShown', message: 'Walls below and above: shown' }) : i18n._({ id: 'editor.actionMenu.viewToggles.showWallsBelowAndAbove', message: 'Show walls below and above' })} onClick={() => { const next = adjacentWallsToggle(active, names) if (next.below !== undefined) setShowBelow(next.below) @@ -1071,7 +1072,7 @@ function AdjacentWallsControl() { - {label} + + {label} + - Edit layers… + {i18n._({ id: 'editor.buildPanel.wallTypeControls.editLayers', message: 'Edit layers…' })} - Duplicate as a new type… + {i18n._({ + id: 'editor.buildPanel.wallTypeControls.duplicateAsANewType', + message: 'Duplicate as a new type…', + })} {onDelete ? ( - Delete from this project + {i18n._({ + id: 'editor.buildPanel.wallTypeControls.deleteFromThisProject', + message: 'Delete from this project', + })} ) : null} {blocked ? ( @@ -153,7 +175,10 @@ export function WallTypeActions({

) : onEdit ? null : (

- Presets stay as cited. Duplicate one to change its layers. + {i18n._({ + id: 'editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne', + message: 'Presets stay as cited. Duplicate one to change its layers.', + })}

)}
@@ -161,36 +186,51 @@ export function WallTypeActions({ ) } -function swatchOf(ref: string): { label: string; style: ReturnType } { +function swatchOf( + ref: string, + i18n: PascalI18n, +): { label: string; style: ReturnType } { const item = getCatalogMaterialById(getLibraryMaterialIdFromRef(ref) ?? undefined) - if (!item) return { label: ref.startsWith('#') ? ref.toUpperCase() : 'Default', style: {} } + if (!item) { + return { + label: ref.startsWith('#') + ? ref.toUpperCase() + : i18n._({ id: 'editor.buildPanel.wallTypeControls.default', message: 'Default' }), + style: {}, + } + } return { label: item.label, style: materialSwatchStyle(getMaterialSwatch(item)) } } /** * A finish new walls are drawn with: the swatch and name of the pick, and a grid of - * the products it can be (a cladding kind's, or the flat paint colours). + * the products it can be (a cladding kind's, or the flat paint colours). `finish` is + * the stable key of what it sets (the cladding kind, or `paint`), written to + * `data-wall-finish` for tests and scripts, as the label is translated. */ export function WallFinishPicker({ + finish, label, onChange, products, value, }: { + finish: string label: string onChange: (ref: string) => void products: readonly MaterialCatalogItem[] value: string }) { + const i18n = usePascalI18n() const [open, setOpen] = useState(false) - const swatch = swatchOf(value) + const swatch = swatchOf(value, i18n) return ( )}
@@ -105,6 +143,7 @@ const NO_VALUE = () => '' * lit, with what it draws and how. */ function DrawSection({ buildingId }: { buildingId: string }) { + const i18n = usePascalI18n() const option: ToolOption | undefined = nodeRegistry .get('foundation') ?.toolOptions?.find((candidate) => candidate.id === 'draw') @@ -121,7 +160,9 @@ function DrawSection({ buildingId }: { buildingId: string }) { const active = armed ? option.choices.find((choice) => choice.value === value) : undefined return (
-

Draw

+

+ {i18n._({ id: 'editor.panels.foundationViewPanel.draw', message: 'Draw' })} +

{option.choices.map((choice) => { const selected = choice === active @@ -151,29 +192,75 @@ function DrawSection({ buildingId }: { buildingId: string }) {

{active - ? `${active.description}.${active.value === 'strip' || active.value === 'pad' ? '' : ' Click its corners; click the first again, or press Enter, to close it.'}` + ? active.value === 'strip' || active.value === 'pad' + ? `${active.description}.` + : `${active.description}. ${i18n._({ + id: 'editor.panels.foundationViewPanel.clickItsCornersClickTheFirst', + message: 'Click its corners; click the first again, or press Enter, to close it.', + })}` : footings === 0 - ? 'Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts.' - : `${footings} ${footings === 1 ? 'footing' : 'footings'} drawn: click one in the plan to size or move it.`} + ? i18n._({ + id: 'editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors', + message: + 'Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts.', + }) + : i18n._({ + id: 'editor.panels.foundationViewPanel.footingsDrawn', + message: + '{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it.', + values: { footings }, + })}

) } -const CONSTRUCTIONS: { label: string; value: FoundationNode['construction'] }[] = [ - { label: 'The house’s', value: 'building' }, - { label: 'Slab', value: 'slab' }, - { label: 'Raised floor', value: 'raised' }, +const CONSTRUCTIONS: ReadonlyArray<{ + label: MessageDescriptor + value: FoundationNode['construction'] +}> = [ + { + label: /* i18n */ { + id: 'editor.panels.foundationViewPanel.theHouses', + message: 'The house’s', + comment: + 'What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own.', + }, + value: 'building', + }, + { + label: /* i18n */ { id: 'editor.panels.foundationViewPanel.slab', message: 'Slab' }, + value: 'slab', + }, + { + label: /* i18n */ { + id: 'editor.panels.foundationViewPanel.raisedFloor', + message: 'Raised floor', + }, + value: 'raised', + }, ] -const CONSTRUCTION_NOTES: Record = { - building: 'The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline.', - slab: 'A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round.', - raised: 'A raised floor on a stem wall of its own.', +const CONSTRUCTION_NOTES: Record = { + building: /* i18n */ { + id: 'editor.panels.foundationViewPanel.theHousesOwnFoundation', + message: + 'The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline.', + }, + slab: /* i18n */ { + id: 'editor.panels.foundationViewPanel.aSlabOfItsOwn', + message: + 'A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round.', + }, + raised: /* i18n */ { + id: 'editor.panels.foundationViewPanel.aRaisedFloorOnAStem', + message: 'A raised floor on a stem wall of its own.', + }, } /** A picked foundation outline: what it pours, how far its top sits under the house's floor, and deleting it. */ function PickedOutlineSection({ outline }: { outline: FoundationNode }) { + const i18n = usePascalI18n() const imperial = useViewer((state) => state.unit) === 'imperial' const [draft, setDraft] = useState(null) const update = (data: Partial) => @@ -182,23 +269,36 @@ function PickedOutlineSection({ outline }: { outline: FoundationNode }) { const fromShown = (shown: number) => (imperial ? shown * IN : shown) return (
-

{outline.name || 'This outline'}

+

+ {outline.name || + i18n._({ id: 'editor.panels.foundationViewPanel.thisOutline', message: 'This outline' })} +

update(construction === 'building' ? { construction, drop: 0 } : { construction }) } - options={CONSTRUCTIONS} + options={CONSTRUCTIONS.map((option) => ({ + label: i18n._(option.label), + value: option.value, + }))} value={outline.construction} /> -

{CONSTRUCTION_NOTES[outline.construction]}

+

{i18n._(CONSTRUCTION_NOTES[outline.construction])}

{outline.construction !== 'building' && (
setDraft(next)} @@ -224,7 +324,10 @@ function PickedOutlineSection({ outline }: { outline: FoundationNode }) { type="button" variant="outline" > - Delete this outline + {i18n._({ + id: 'editor.panels.foundationViewPanel.deleteThisOutline', + message: 'Delete this outline', + })}
) @@ -239,6 +342,7 @@ function PickedOutlineSection({ outline }: { outline: FoundationNode }) { * `onClose` (an outline's panel lets the outline go). */ export function FoundationViewPanel({ onClose }: { onClose?: () => void } = {}) { + const i18n = usePascalI18n() const buildingId = useViewer((state) => state.selection.buildingId) const pickedId = useViewer((state) => state.selection.selectedIds.length === 1 ? state.selection.selectedIds[0] : null, @@ -262,13 +366,23 @@ export function FoundationViewPanel({ onClose }: { onClose?: () => void } = {}) icon="/icons/floor.webp" kicker={building.name || undefined} onClose={onClose ?? (() => setFoundationView(false))} - title="Foundation" + title={i18n._({ id: 'editor.panels.foundationViewPanel.foundation', message: 'Foundation' })} >
{picked && } {on && } {on && } - +
) diff --git a/packages/editor/src/components/ui/panels/panel-manager.tsx b/packages/editor/src/components/ui/panels/panel-manager.tsx index 3602065f4b..d5420ae5c6 100644 --- a/packages/editor/src/components/ui/panels/panel-manager.tsx +++ b/packages/editor/src/components/ui/panels/panel-manager.tsx @@ -175,13 +175,14 @@ function MobilePanelLayer({ /** The foundation view's panel on a phone: its sheet open while the view is; closing it leaves the view. */ function MobileFoundationLayer() { + const i18n = usePascalI18n() const setFoundationView = useViewer((s) => s.setFoundationView) return ( setFoundationView(false)} open={true} - title="Foundation" + title={i18n._({ id: 'editor.panels.panelManager.foundation', message: 'Foundation' })} > diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx index d02dfe79bf..86df7ba909 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx @@ -1,3 +1,4 @@ +import { type MessageDescriptor, usePascalI18n } from '@pascal-app/core' import { Keyboard } from 'lucide-react' import { Button } from './../../../../../components/ui/primitives/button' import { @@ -21,13 +22,16 @@ type Shortcut = { } type ShortcutCategory = { - title: string + title: MessageDescriptor shortcuts: Shortcut[] } const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ { - title: 'Editor Navigation', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation', + message: 'Editor Navigation', + }, shortcuts: [ { keys: ['1'], action: 'Switch to Site' }, { keys: ['2'], action: 'Switch to Building' }, @@ -46,7 +50,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Modes & History', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory', + message: 'Modes & History', + }, shortcuts: [ { keys: TOOL_SHORTCUT_KEYS.select, action: 'Switch to Select mode' }, { keys: TOOL_SHORTCUT_KEYS.wall, action: 'Switch to Build mode, drawing a rectangle room' }, @@ -66,7 +73,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Build Tools', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools', + message: 'Build Tools', + }, shortcuts: [ { keys: TOOL_SHORTCUT_KEYS['exterior-wall'], @@ -93,7 +103,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Selection', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection', + message: 'Selection', + }, shortcuts: [ { keys: ['Cmd/Ctrl', 'C'], @@ -158,7 +171,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Direct Manipulation', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation', + message: 'Direct Manipulation', + }, shortcuts: [ { keys: ['Cmd/Ctrl', 'Left click'], @@ -178,7 +194,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Drawing Tools', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools', + message: 'Drawing Tools', + }, shortcuts: [ // Shift and Ctrl each mean one thing held and another tapped, and only // the hold was documented — which read as the taps not existing. Both @@ -206,7 +225,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Item Placement', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement', + message: 'Item Placement', + }, shortcuts: [ { keys: ['R', 'T'], @@ -225,7 +247,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [ ], }, { - title: 'Camera', + title: /* i18n */ { + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera', + message: 'Camera', + }, shortcuts: [ { keys: ['W', 'A', 'S', 'D'], @@ -260,32 +285,44 @@ function ShortcutKeys({ keys }: { keys: readonly string[] }) { } export function KeyboardShortcutsDialog() { + const i18n = usePascalI18n() return ( - Keyboard shortcuts + + {i18n._({ + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts', + message: 'Keyboard shortcuts', + })} + - Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift - during an active gesture to build freely. + {i18n._({ + id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints', + message: + 'Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely.', + })}
{SHORTCUT_CATEGORIES.map((category) => ( -
-

{category.title}

+
+

{i18n._(category.title)}

{category.shortcuts.map((shortcut, index) => (
diff --git a/packages/editor/src/i18n/locales/en-XA/messages.ts b/packages/editor/src/i18n/locales/en-XA/messages.ts index 3976829758..b9782091f5 100644 --- a/packages/editor/src/i18n/locales/en-XA/messages.ts +++ b/packages/editor/src/i18n/locales/en-XA/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"app.appShell.pascalHome\":[\"[Þášçáļ ĥöɱé ~~~]\"],\"app.buildTab.addGate\":[\"[Åðð Ĝáţé ~~~]\"],\"app.buildTab.addOpenPassage\":[\"[Åðð Öþéñ Þáššáĝé ~~~~~]\"],\"app.buildTab.addToRooms\":[\"[Åðð ţö ŕööɱš ~~~]\"],\"app.buildTab.allBuildTools\":[\"[← Åļļ ƀûîļð ţööļš ~~~~]\"],\"app.buildTab.chooseGateOrOpenPassageThen\":[\"[Çĥööšé Ĝáţé öŕ Öþéñ Þáššáĝé, ţĥéñ çļîçķ îţš þöšîţîöñ öñ áñý ƒéñçé. Ƒîñð þļáçéð ĝáţéš áñð öþéñîñĝš ûñðéŕ ţĥéîŕ ƒéñçé îñ ţĥé šçéñé ĝŕáþĥ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.clickALinesetToLayThe\":[\"[Çļîçķ á ļîñéšéţ ţö ļáý ţĥé ļîñé ƀéšîðé îţ. ~~~~~~~~~~]\"],\"app.buildTab.doubleGate\":[\"[Ðöûƀļé ĝáţé ~~~]\"],\"app.buildTab.featuresExtensions\":[\"[Ƒéáţûŕéš & éẋţéñšîöñš ~~~~~~]\"],\"app.buildTab.fenceFeatures\":[\"[Ƒéñçé ƒéáţûŕéš ~~~~]\"],\"app.buildTab.followLineset\":[\"[Ƒöļļöŵ ļîñéšéţ ~~~~]\"],\"app.buildTab.gate\":[\"[Ĝáţé ~~]\"],\"app.buildTab.gateStyle\":[\"[Ĝáţé šţýļé ~~~]\"],\"app.buildTab.horizontalBoards\":[\"[Ĥöŕîžöñţáļ ƀöáŕðš ~~~~~]\"],\"app.buildTab.hoverAFenceToPreviewClick\":[\"[Ĥöṽéŕ á ƒéñçé ţö þŕéṽîéŵ. Çļîçķ ţö þļáçé. Éšç çáñçéļš. Ļéáṽé ŕööɱ ƀéţŵééñ öþéñîñĝš. ~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.leaves\":[\"[Ļéáṽéš ~~]\"],\"app.buildTab.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"app.buildTab.matchFenceStyle\":[\"[Ṁáţçĥ ƒéñçé šţýļé ~~~~~]\"],\"app.buildTab.mep\":[\"[ṀÉÞ ~]\"],\"app.buildTab.off\":[\"[Öƒƒ ~]\"],\"app.buildTab.on\":[\"[Öñ ~]\"],\"app.buildTab.openRails\":[\"[Öþéñ ŕáîļš ~~~]\"],\"app.buildTab.opening\":[\"[Öþéñîñĝ ~~~]\"],\"app.buildTab.openingWidthM\":[\"[Öþéñîñĝ ŵîðţĥ (ɱ) ~~~~]\"],\"app.buildTab.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"app.buildTab.picket\":[\"[Þîçķéţ ~~]\"],\"app.buildTab.roofType\":[\"[Ŕööƒ ţýþé ~~~]\"],\"app.buildTab.roomsAlreadyCreateTheirFloorAnd\":[\"[Ŕööɱš áļŕéáðý çŕéáţé ţĥéîŕ ƒļööŕ áñð çéîļîñĝ. Ûšé ţĥéšé ƒöŕ þļáţƒöŕɱš áñð öñé-öƒƒ šţŕûçţûŕé. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.searchBuildToolsAndKitchenItems\":[\"[Šéáŕçĥ ƀûîļð ţööļš áñð ķîţçĥéñ îţéɱš ~~~~~~~~~~]\"],\"app.buildTab.searchToolsFridgeSink\":[\"[Šéáŕçĥ ţööļš, ƒŕîðĝé, šîñķ… ~~~~~~~]\"],\"app.buildTab.selectACurvedWallToMatch\":[\"[Šéļéçţ á çûŕṽéð ŵáļļ ţö ɱáţçĥ îţš ŕáðîûš áñð áŕç. ~~~~~~~~~~~~]\"],\"app.buildTab.singleGate\":[\"[Šîñĝļé ĝáţé ~~~]\"],\"app.buildTab.slabCeilingColumn\":[\"[Šļáƀ, çéîļîñĝ, çöļûɱñ… ~~~~~~]\"],\"app.buildTab.solidPrivacy\":[\"[Šöļîð þŕîṽáçý ~~~~]\"],\"app.buildTab.traceALineAlongsideAnExisting\":[\"[Ţŕáçé á ļîñé áļöñĝšîðé áñ éẋîšţîñĝ ļîñéšéţ (Ƒ). ~~~~~~~~~~~~]\"],\"app.buildTab.verticalSlats\":[\"[Ṽéŕţîçáļ šļáţš ~~~~]\"],\"app.editorNavbar.allProjects\":[\"[Åļļ þŕöĵéçţš ~~~~]\"],\"app.editorNavbar.preview\":[\"[Þŕéṽîéŵ ~~~]\"],\"app.editorNavbar.projectName\":[\"[Þŕöĵéçţ ñáɱé ~~~~]\"],\"app.editorNavbar.renameProject\":[\"[Ŕéñáɱé þŕöĵéçţ ~~~~]\"],\"app.editorNavbar.share\":[\"[Šĥáŕé ~~]\"],\"app.kitchenBrowser.addLabel\":[\"[Åðð \",[\"label\"],\" ~]\"],\"app.kitchenBrowser.addToKitchen\":[\"[Åðð ţö ķîţçĥéñ ~~~~]\"],\"app.kitchenBrowser.chooseAnItemThenClickTo\":[\"[Çĥööšé áñ îţéɱ, ţĥéñ çļîçķ ţö þļáçé îţ. Åþþļîáñçéš îñçļûðé ţĥéîŕ çáƀîñéţŕý. ~~~~~~~~~~~~~~~~~~~]\"],\"app.kitchenBrowser.findInThisProjectLength\":[\"[Ƒîñð îñ ţĥîš þŕöĵéçţ · \",[\"length\"],\" ~~~~~~]\"],\"app.kitchenBrowser.findName\":[\"[Ƒîñð \",[\"name\"],\" ~~]\"],\"app.kitchenBrowser.groupAdd\":[\"[Ķîţçĥéñ · Åðð ~~~]\"],\"app.kitchenBrowser.groupInThisProject\":[\"[Ķîţçĥéñ · Îñ ţĥîš þŕöĵéçţ ~~~~~~]\"],\"app.kitchenBrowser.kitchenCabinet\":[\"[Ķîţçĥéñ çáƀîñéţ ~~~~~]\"],\"app.kitchenBrowser.noMatchingKitchenItemsPlacedYet\":[\"[Ñö ɱáţçĥîñĝ ķîţçĥéñ îţéɱš þļáçéð ýéţ. ~~~~~~~~~~]\"],\"app.kitchenBrowser.selectAndShowInScene\":[\"[Šéļéçţ áñð šĥöŵ îñ šçéñé ~~~~~~]\"],\"app.lib.buildPalette.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"app.lib.buildPalette.column\":[\"[Çöļûɱñ ~~]\"],\"app.lib.buildPalette.door\":[\"[Ðööŕ ~~]\"],\"app.lib.buildPalette.duct\":[\"[Ðûçţ ~~]\"],\"app.lib.buildPalette.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"app.lib.buildPalette.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"app.lib.buildPalette.fence\":[\"[Ƒéñçé ~~]\"],\"app.lib.buildPalette.hvacUnit\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"app.lib.buildPalette.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"app.lib.buildPalette.lineset\":[\"[Ļîñéšéţ ~~~]\"],\"app.lib.buildPalette.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"app.lib.buildPalette.mep\":[\"[ṀÉÞ ~]\"],\"app.lib.buildPalette.register\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"app.lib.buildPalette.roof\":[\"[Ŕööƒ ~~]\"],\"app.lib.buildPalette.shelf\":[\"[Šĥéļƒ ~~]\"],\"app.lib.buildPalette.slab\":[\"[Šļáƀ ~~]\"],\"app.lib.buildPalette.spawnPoint\":[\"[Šþáŵñ þöîñţ ~~~]\"],\"app.lib.buildPalette.stairs\":[\"[Šţáîŕš ~~]\"],\"app.lib.buildPalette.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"app.lib.buildPalette.terrain\":[\"[Ţéŕŕáîñ ~~~]\"],\"app.lib.buildPalette.wall\":[\"[Ŵáļļ ~~]\"],\"app.lib.buildPalette.window\":[\"[Ŵîñðöŵ ~~]\"],\"app.lib.buildPanelModel.addFitting\":[\"[Åðð Ƒîţţîñĝ ~~~]\"],\"app.lib.buildPanelModel.addGate\":[\"[Åðð Ĝáţé ~~~]\"],\"app.lib.buildPanelModel.addOpenPassage\":[\"[Åðð Öþéñ Þáššáĝé ~~~~~]\"],\"app.lib.buildPanelModel.addTrap\":[\"[Åðð Ţŕáþ ~~~]\"],\"app.lib.buildPanelModel.build\":[\"[Ɓûîļð ~~]\"],\"app.lib.buildPanelModel.createFrom\":[\"[Çŕéáţé ƒŕöɱ ~~~]\"],\"app.lib.buildPanelModel.drawTheRoofFootprintWithTwo\":[\"[Ðŕáŵ ţĥé ŕööƒ ƒööţþŕîñţ ŵîţĥ ţŵö çöŕñéŕ çļîçķš. ~~~~~~~~~~~~]\"],\"app.lib.buildPanelModel.duct\":[\"[Ðûçţ ~~]\"],\"app.lib.buildPanelModel.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"app.lib.buildPanelModel.featuresExtensions\":[\"[Ƒéáţûŕéš & éẋţéñšîöñš ~~~~~~]\"],\"app.lib.buildPanelModel.fence\":[\"[Ƒéñçé ~~]\"],\"app.lib.buildPanelModel.followLineset\":[\"[Ƒöļļöŵ ļîñéšéţ ~~~~]\"],\"app.lib.buildPanelModel.gatesOpenings\":[\"[Ĝáţéš & öþéñîñĝš ~~~~]\"],\"app.lib.buildPanelModel.hoverARoomToPreviewIts\":[\"[Ĥöṽéŕ á ŕööɱ ţö þŕéṽîéŵ îţš ƀöûñðáŕý, ţĥéñ çļîçķ ţö þļáçé. ~~~~~~~~~~~~~~]\"],\"app.lib.buildPanelModel.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"app.lib.buildPanelModel.mep\":[\"[ṀÉÞ ~]\"],\"app.lib.buildPanelModel.modularCabinet\":[\"[Ṁöðûļáŕ Çáƀîñéţ ~~~~~]\"],\"app.lib.buildPanelModel.off\":[\"[Öƒƒ ~]\"],\"app.lib.buildPanelModel.on\":[\"[Öñ ~]\"],\"app.lib.buildPanelModel.painting\":[\"[Þáîñţîñĝ ~~~]\"],\"app.lib.buildPanelModel.roof\":[\"[Ŕööƒ ~~]\"],\"app.lib.buildPanelModel.roofType\":[\"[Ŕööƒ ţýþé ~~~]\"],\"app.lib.buildPanelModel.toolCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ţööļ\"],\"other\":[\"#\",\" ţööļš\"]}],\" ~~~]\"],\"app.lib.buildTabState.conical\":[\"[Çöñîçáļ ~~~]\"],\"app.lib.buildTabState.draw\":[\"[Ðŕáŵ ~~]\"],\"app.lib.buildTabState.dutch\":[\"[Ðûţçĥ ~~]\"],\"app.lib.buildTabState.flat\":[\"[Ƒļáţ ~~]\"],\"app.lib.buildTabState.gable\":[\"[Ĝáƀļé ~~]\"],\"app.lib.buildTabState.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"app.lib.buildTabState.hip\":[\"[Ĥîþ ~]\"],\"app.lib.buildTabState.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"app.lib.buildTabState.room\":[\"[Ŕööɱ ~~]\"],\"app.lib.buildTabState.shed\":[\"[Šĥéð ~~]\"],\"app.lib.buildTabState.wall\":[\"[Ŵáļļ ~~]\"],\"app.sceneLoader.anotherSessionSavedFirstRefresh\":[\"[Åñöţĥéŕ šéššîöñ šáṽéð ƒîŕšţ — ŕéƒŕéšĥ? ~~~~~~~~~~]\"],\"app.sceneLoader.build\":[\"[Ɓûîļð ~~]\"],\"app.sceneLoader.dismiss\":[\"[Ðîšɱîšš ~~~]\"],\"app.sceneLoader.items\":[\"[Îţéɱš ~~]\"],\"app.sceneLoader.paint\":[\"[Þáîñţ ~~]\"],\"app.sceneLoader.reload\":[\"[Ŕéļöáð ~~]\"],\"app.sceneLoader.scene\":[\"[Šçéñé ~~]\"],\"app.sceneLoader.settings\":[\"[Šéţţîñĝš ~~~]\"],\"app.sceneLoader.yourChangesHavenTBeenSaved\":[\"[Ýöûŕ çĥáñĝéš ĥáṽéñ'ţ ƀééñ šáṽéð. Ŕéļöáð ţö þîçķ ûþ ţĥé ļáţéšţ ṽéŕšîöñ. ~~~~~~~~~~~~~~~~~]\"],\"app.sharePanel.anyoneWithTheLinkCanView\":[\"[Åñýöñé ŵîţĥ ţĥé ļîñķ çáñ ṽîéŵ ţĥîš þŕöĵéçţ. Ţĥéý çáññöţ éðîţ îţ. ~~~~~~~~~~~~~~~~]\"],\"app.sharePanel.copied\":[\"[Çöþîéð ~~]\"],\"app.sharePanel.copy\":[\"[Çöþý ~~]\"],\"app.sharePanel.couldNotReadTheSharingState\":[\"[Çöûļð ñöţ ŕéáð ţĥé šĥáŕîñĝ šţáţé. ~~~~~~~~~]\"],\"app.sharePanel.couldNotTurnOffThePublic\":[\"[Çöûļð ñöţ ţûŕñ öƒƒ ţĥé þûƀļîç ļîñķ. ~~~~~~~~~]\"],\"app.sharePanel.couldNotTurnOnThePublic\":[\"[Çöûļð ñöţ ţûŕñ öñ ţĥé þûƀļîç ļîñķ. ~~~~~~~~~]\"],\"app.sharePanel.embedCode\":[\"[Éɱƀéð çöðé ~~~]\"],\"app.sharePanel.embedLink\":[\"[Éɱƀéð ļîñķ ~~~]\"],\"app.sharePanel.hideControls\":[\"[Ĥîðé çöñţŕöļš ~~~~]\"],\"app.sharePanel.link\":[\"[Ļîñķ ~~]\"],\"app.sharePanel.onlyThe3dViewNoHeader\":[\"[Öñļý ţĥé 3Ð ṽîéŵ, ñö ĥéáðéŕ öŕ ţööļƀáŕ. ~~~~~~~~~]\"],\"app.sharePanel.open\":[\"[Öþéñ ~~]\"],\"app.sharePanel.publicLink\":[\"[Þûƀļîç ļîñķ ~~~]\"],\"app.sharePanel.shareProject\":[\"[Šĥáŕé þŕöĵéçţ ~~~~]\"],\"app.viewerToolbar.automaticDimensions\":[\"[Åûţöɱáţîç ðîɱéñšîöñš ~~~~~~]\"],\"app.viewerToolbar.camera\":[\"[Çáɱéŕá ~~]\"],\"app.viewerToolbar.cleanPlanDimensionsAppearWithSelection\":[\"[Çļéáñ þļáñ; ðîɱéñšîöñš áþþéáŕ ŵîţĥ šéļéçţîöñ ~~~~~~~~~~~~]\"],\"app.viewerToolbar.collapseSidebar\":[\"[Çöļļáþšé šîðéƀáŕ ~~~~~]\"],\"app.viewerToolbar.crispOpaqueEdgeLines\":[\"[Çŕîšþ, öþáǫûé éðĝé ļîñéš ~~~~~~]\"],\"app.viewerToolbar.cutaway\":[\"[Çûţáŵáý ~~~]\"],\"app.viewerToolbar.default\":[\"[Ðéƒáûļţ ~~~]\"],\"app.viewerToolbar.display\":[\"[Ðîšþļáý ~~~]\"],\"app.viewerToolbar.displaySettings\":[\"[Ðîšþļáý šéţţîñĝš ~~~~~]\"],\"app.viewerToolbar.doorWindowMarks\":[\"[Ðööŕ/ŵîñðöŵ ɱáŕķš ~~~~~]\"],\"app.viewerToolbar.edges\":[\"[Éðĝéš ~~]\"],\"app.viewerToolbar.enterVr\":[\"[Éñţéŕ ṼŔ ~~~]\"],\"app.viewerToolbar.exitVr\":[\"[Éẋîţ ṼŔ ~~]\"],\"app.viewerToolbar.expandSidebar\":[\"[Éẋþáñð šîðéƀáŕ ~~~~]\"],\"app.viewerToolbar.expert\":[\"[Éẋþéŕţ ~~]\"],\"app.viewerToolbar.exploded\":[\"[Éẋþļöðéð ~~~]\"],\"app.viewerToolbar.faceOfStud\":[\"[Ƒáçé öƒ šţûð ~~~]\"],\"app.viewerToolbar.faintOutlineOfMajorCreases\":[\"[Ƒáîñţ öûţļîñé öƒ ɱáĵöŕ çŕéášéš ~~~~~~~~]\"],\"app.viewerToolbar.feetInches\":[\"[Ƒééţ & îñçĥéš ~~~]\"],\"app.viewerToolbar.finishedFaces\":[\"[Ƒîñîšĥéð ƒáçéš ~~~~]\"],\"app.viewerToolbar.flatAndFastNoAmbientOcclusion\":[\"[Ƒļáţ áñð ƒášţ — ñö áɱƀîéñţ öççļûšîöñ ~~~~~~~~~]\"],\"app.viewerToolbar.floorPlanAnnotations\":[\"[Ƒļööŕ þļáñ áññöţáţîöñš ~~~~~~]\"],\"app.viewerToolbar.floorPlanMode\":[\"[Ƒļööŕ þļáñ ɱöðé ~~~~]\"],\"app.viewerToolbar.fullAmbientOcclusion\":[\"[Ƒûļļ áɱƀîéñţ öççļûšîöñ ~~~~~~]\"],\"app.viewerToolbar.fullDocumentationAndAnnotationControls\":[\"[Ƒûļļ ðöçûɱéñţáţîöñ áñð áññöţáţîöñ çöñţŕöļš ~~~~~~~~~~~~]\"],\"app.viewerToolbar.fullHeight\":[\"[Ƒûļļ ĥéîĝĥţ ~~~]\"],\"app.viewerToolbar.fullWallThickness\":[\"[Ƒûļļ ŵáļļ ţĥîçķñéšš ~~~~~~]\"],\"app.viewerToolbar.grid\":[\"[Ĝŕîð ~~]\"],\"app.viewerToolbar.levelsMode\":[\"[Ļéṽéļš: \",[\"mode\"],\" ~~]\"],\"app.viewerToolbar.low\":[\"[Ļöŵ ~]\"],\"app.viewerToolbar.magneticSnap\":[\"[Ṁáĝñéţîç šñáþ ~~~~]\"],\"app.viewerToolbar.manual\":[\"[Ṁáñûáļ ~~]\"],\"app.viewerToolbar.manualDimensions\":[\"[Ṁáñûáļ ðîɱéñšîöñš ~~~~~]\"],\"app.viewerToolbar.measurements\":[\"[Ṁéášûŕéɱéñţš ~~~~]\"],\"app.viewerToolbar.meters\":[\"[Ṁéţéŕš ~~]\"],\"app.viewerToolbar.millimeters\":[\"[Ṁîļļîɱéţéŕš ~~~~]\"],\"app.viewerToolbar.n3dMeasurements\":[\"[3Ð ɱéášûŕéɱéñţš ~~~~]\"],\"app.viewerToolbar.noEdgeLines\":[\"[Ñö éðĝé ļîñéš ~~~~]\"],\"app.viewerToolbar.off\":[\"[Öƒƒ ~]\"],\"app.viewerToolbar.on\":[\"[Öñ ~]\"],\"app.viewerToolbar.orthographic\":[\"[Öŕţĥöĝŕáþĥîç ~~~~]\"],\"app.viewerToolbar.perspective\":[\"[Þéŕšþéçţîṽé ~~~~]\"],\"app.viewerToolbar.preview\":[\"[Þŕéṽîéŵ ~~~]\"],\"app.viewerToolbar.previewMode\":[\"[Þŕéṽîéŵ ɱöðé ~~~~]\"],\"app.viewerToolbar.render\":[\"[Ŕéñðéŕ ~~]\"],\"app.viewerToolbar.rendered\":[\"[Ŕéñðéŕéð ~~~]\"],\"app.viewerToolbar.roomLabels\":[\"[Ŕööɱ ļáƀéļš ~~~]\"],\"app.viewerToolbar.sceneTheme\":[\"[Šçéñé ţĥéɱé ~~~]\"],\"app.viewerToolbar.shadows\":[\"[Šĥáðöŵš ~~~]\"],\"app.viewerToolbar.singleWallAxis\":[\"[Šîñĝļé ŵáļļ áẋîš ~~~~~]\"],\"app.viewerToolbar.soft\":[\"[Šöƒţ ~~]\"],\"app.viewerToolbar.solid\":[\"[Šöļîð ~~]\"],\"app.viewerToolbar.solo\":[\"[Šöļö ~~]\"],\"app.viewerToolbar.stack\":[\"[Šţáçķ ~~]\"],\"app.viewerToolbar.stairAnnotations\":[\"[Šţáîŕ áññöţáţîöñš ~~~~~]\"],\"app.viewerToolbar.strong\":[\"[Šţŕöñĝ ~~]\"],\"app.viewerToolbar.structuralCoreFace\":[\"[Šţŕûçţûŕáļ çöŕé ƒáçé ~~~~~~]\"],\"app.viewerToolbar.structuralGridsColumnCenters\":[\"[Šţŕûçţûŕáļ ĝŕîðš & çöļûɱñ çéñţéŕš ~~~~~~~~~]\"],\"app.viewerToolbar.translucent\":[\"[Ţŕáñšļûçéñţ ~~~~]\"],\"app.viewerToolbar.units\":[\"[Ûñîţš ~~]\"],\"app.viewerToolbar.walkthrough\":[\"[Ŵáļķţĥŕöûĝĥ ~~~~]\"],\"app.viewerToolbar.wallCenterline\":[\"[Ŵáļļ çéñţéŕļîñé ~~~~~]\"],\"app.viewerToolbar.wallDimensions\":[\"[Ŵáļļ ðîɱéñšîöñš ~~~~~]\"],\"app.viewerToolbar.wallsLabel\":[\"[Ŵáļļš: \",[\"label\"],\" ~~]\"],\"core.level.basement\":[\"[Ɓášéɱéñţ ~~~]\"],\"core.level.basementNumber\":[\"[Ɓášéɱéñţ \",[\"number\"],\" ~~~]\"],\"core.level.floorNumber\":[\"[Ƒļööŕ \",[\"number\"],\" ~~]\"],\"core.level.groundFloor\":[\"[Ĝŕöûñð ƒļööŕ ~~~~]\"],\"editor.actionMenu.cameraActions.orbitLeft\":[\"[Öŕƀîţ Ļéƒţ ~~~]\"],\"editor.actionMenu.cameraActions.orbitRight\":[\"[Öŕƀîţ Ŕîĝĥţ ~~~]\"],\"editor.actionMenu.cameraActions.topView\":[\"[Ţöþ Ṽîéŵ ~~~]\"],\"editor.actionMenu.controlModes.delete\":[\"[Ðéļéţé ~~]\"],\"editor.actionMenu.controlModes.select\":[\"[Šéļéçţ ~~]\"],\"editor.actionMenu.followPascalPill.resumeFollowing\":[\"[Ŕéšûɱé ƒöļļöŵîñĝ ~~~~~]\"],\"editor.actionMenu.followPascalToggle.off\":[\"[Ƒöļļöŵ Þášçáļ: Öƒƒ ~~~~~]\"],\"editor.actionMenu.followPascalToggle.on\":[\"[Ƒöļļöŵ Þášçáļ: Öñ ~~~~~]\"],\"editor.actionMenu.furnishTools.appliance\":[\"[Åþþļîáñçé ~~~]\"],\"editor.actionMenu.furnishTools.bathroom\":[\"[Ɓáţĥŕööɱ ~~~]\"],\"editor.actionMenu.furnishTools.furniture\":[\"[Ƒûŕñîţûŕé ~~~]\"],\"editor.actionMenu.furnishTools.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"editor.actionMenu.furnishTools.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"editor.actionMenu.measurementControl.angle\":[\"[Åñĝļé ~~]\"],\"editor.actionMenu.measurementControl.angularDimension\":[\"[Åñĝûļáŕ ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.arcLength\":[\"[Åŕç ļéñĝţĥ ~~~]\"],\"editor.actionMenu.measurementControl.area\":[\"[Åŕéá ~~]\"],\"editor.actionMenu.measurementControl.centerMark\":[\"[Çéñţéŕ ɱáŕķ ~~~]\"],\"editor.actionMenu.measurementControl.chordDimension\":[\"[Çĥöŕð ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.continuousDimension\":[\"[Çöñţîñûöûš ðîɱéñšîöñ ~~~~~~]\"],\"editor.actionMenu.measurementControl.coordinateDimensions\":[\"[Çööŕðîñáţé ðîɱéñšîöñš ~~~~~~]\"],\"editor.actionMenu.measurementControl.diameterDimension\":[\"[Ðîáɱéţéŕ ðîɱéñšîöñ ~~~~~~]\"],\"editor.actionMenu.measurementControl.distance\":[\"[Ðîšţáñçé ~~~]\"],\"editor.actionMenu.measurementControl.floorPlan\":[\"[Ƒļööŕ þļáñ ~~~]\"],\"editor.actionMenu.measurementControl.linearDimension\":[\"[Ļîñéáŕ ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.measureSelectedlabel\":[\"[Ṁéášûŕé: \",[\"selectedLabel\"],\" ~~~]\"],\"editor.actionMenu.measurementControl.measurementOptions\":[\"[Ṁéášûŕéɱéñţ öþţîöñš ~~~~~~]\"],\"editor.actionMenu.measurementControl.measurementType\":[\"[Ṁéášûŕéɱéñţ ţýþé ~~~~~]\"],\"editor.actionMenu.measurementControl.perimeter\":[\"[Þéŕîɱéţéŕ ~~~]\"],\"editor.actionMenu.measurementControl.radiusDimension\":[\"[Ŕáðîûš ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.smart\":[\"[Šɱáŕţ ~~]\"],\"editor.actionMenu.measurementControl.volume\":[\"[Ṽöļûɱé ~~]\"],\"editor.actionMenu.pointAskButton.pointAndAsk\":[\"[Þöîñţ áñð ášķ ~~~~]\"],\"editor.actionMenu.pointAskButton.pointAndAskC\":[\"[Þöîñţ áñð ášķ (Ç) ~~~~]\"],\"editor.actionMenu.pointAskButton.pointAndAskWorksInThe\":[\"[Þöîñţ áñð ášķ ŵöŕķš îñ ţĥé 3Ð ṽîéŵ ~~~~~~~~]\"],\"editor.actionMenu.structureTools.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.actionMenu.structureTools.column\":[\"[Çöļûɱñ ~~]\"],\"editor.actionMenu.structureTools.door\":[\"[Ðööŕ ~~]\"],\"editor.actionMenu.structureTools.duct\":[\"[Ðûçţ ~~]\"],\"editor.actionMenu.structureTools.ductFitting\":[\"[Ðûçţ Ƒîţţîñĝ ~~~~]\"],\"editor.actionMenu.structureTools.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"editor.actionMenu.structureTools.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"editor.actionMenu.structureTools.fence\":[\"[Ƒéñçé ~~]\"],\"editor.actionMenu.structureTools.gableRoof\":[\"[Ĝáƀļé Ŕööƒ ~~~]\"],\"editor.actionMenu.structureTools.hvacUnit\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"editor.actionMenu.structureTools.lineset\":[\"[Ļîñéšéţ ~~~]\"],\"editor.actionMenu.structureTools.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"editor.actionMenu.structureTools.pipeFitting\":[\"[Þîþé Ƒîţţîñĝ ~~~~]\"],\"editor.actionMenu.structureTools.register\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"editor.actionMenu.structureTools.shelf\":[\"[Šĥéļƒ ~~]\"],\"editor.actionMenu.structureTools.slab\":[\"[Šļáƀ ~~]\"],\"editor.actionMenu.structureTools.spawnPoint\":[\"[Šþáŵñ Þöîñţ ~~~]\"],\"editor.actionMenu.structureTools.stairs\":[\"[Šţáîŕš ~~]\"],\"editor.actionMenu.structureTools.trap\":[\"[Ţŕáþ ~~]\"],\"editor.actionMenu.structureTools.wall\":[\"[Ŵáļļ ~~]\"],\"editor.actionMenu.structureTools.window\":[\"[Ŵîñðöŵ ~~]\"],\"editor.actionMenu.structureTools.zone\":[\"[Žöñé ~~]\"],\"editor.actionMenu.viewToggles.deleteGuideImage\":[\"[Ðéļéţé ĝûîðé îɱáĝé ~~~~~]\"],\"editor.actionMenu.viewToggles.deleteScan\":[\"[Ðéļéţé šçáñ ~~~]\"],\"editor.actionMenu.viewToggles.guideCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ĝûîðé îɱáĝé öñ ţĥîš ļéṽéļ\"],\"other\":[\"#\",\" ĝûîðé îɱáĝéš öñ ţĥîš ļéṽéļ\"]}],\" ~~~~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.guideImageNumber\":[\"[Ĝûîðé îɱáĝé \",[\"number\"],\" ~~~]\"],\"editor.actionMenu.viewToggles.guideImageSettings\":[\"[Ĝûîðé îɱáĝé šéţţîñĝš ~~~~~~]\"],\"editor.actionMenu.viewToggles.guideImages\":[\"[Ĝûîðé îɱáĝéš ~~~~]\"],\"editor.actionMenu.viewToggles.guides\":[\"[Ĝûîðéš ~~]\"],\"editor.actionMenu.viewToggles.guidesHidden\":[\"[Ĝûîðéš: Ĥîððéñ ~~~~]\"],\"editor.actionMenu.viewToggles.guidesVisible\":[\"[Ĝûîðéš: Ṽîšîƀļé ~~~~]\"],\"editor.actionMenu.viewToggles.hideGuideImages\":[\"[Ĥîðé ĝûîðé îɱáĝéš ~~~~~]\"],\"editor.actionMenu.viewToggles.hideReferenceFloor\":[\"[Ĥîðé ŕéƒéŕéñçé ƒļööŕ ~~~~~~]\"],\"editor.actionMenu.viewToggles.hideScans\":[\"[Ĥîðé šçáñš ~~~]\"],\"editor.actionMenu.viewToggles.levelsBelow\":[\"[\",[\"count\"],\" ƀéļöŵ ~~]\"],\"editor.actionMenu.viewToggles.noLowerFloorAvailable\":[\"[Ñö ļöŵéŕ ƒļööŕ áṽáîļáƀļé. ~~~~~~~]\"],\"editor.actionMenu.viewToggles.opacity\":[\"[Öþáçîţý ~~~]\"],\"editor.actionMenu.viewToggles.referenceFloor\":[\"[Ŕéƒéŕéñçé ƒļööŕ ~~~~~]\"],\"editor.actionMenu.viewToggles.referenceFloorSelectedlevelname\":[\"[Ŕéƒéŕéñçé ƒļööŕ: \",[\"selectedLevelName\"],\" ~~~~~]\"],\"editor.actionMenu.viewToggles.referenceFloorSettings\":[\"[Ŕéƒéŕéñçé ƒļööŕ šéţţîñĝš ~~~~~~~]\"],\"editor.actionMenu.viewToggles.referenceSettings\":[\"[Ŕéƒéŕéñçé šéţţîñĝš ~~~~~~]\"],\"editor.actionMenu.viewToggles.references\":[\"[Ŕéƒéŕéñçéš ~~~]\"],\"editor.actionMenu.viewToggles.referencesEmpty\":[\"[Ûþļöáð ĜĻƁ ɱéšĥéš áš šçáñ ŕéƒéŕéñçéš öŕ ƀļûéþŕîñţ îɱáĝéš áš ĝûîðé ŕéƒéŕéñçéš. ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.referencesHidden\":[\"[Ŕéƒéŕéñçéš: Ĥîððéñ ~~~~~]\"],\"editor.actionMenu.viewToggles.referencesVisible\":[\"[Ŕéƒéŕéñçéš: Ṽîšîƀļé ~~~~~~]\"],\"editor.actionMenu.viewToggles.riserDiagram\":[\"[Ŕîšéŕ ðîáĝŕáɱ ~~~~]\"],\"editor.actionMenu.viewToggles.scanCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šçáñ öñ ţĥîš ļéṽéļ\"],\"other\":[\"#\",\" šçáñš öñ ţĥîš ļéṽéļ\"]}],\" ~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.scanNumber\":[\"[Šçáñ \",[\"number\"],\" ~~]\"],\"editor.actionMenu.viewToggles.scanSettings\":[\"[Šçáñ šéţţîñĝš ~~~~]\"],\"editor.actionMenu.viewToggles.scans\":[\"[Šçáñš ~~]\"],\"editor.actionMenu.viewToggles.scansHidden\":[\"[Šçáñš: Ĥîððéñ ~~~~]\"],\"editor.actionMenu.viewToggles.scansVisible\":[\"[Šçáñš: Ṽîšîƀļé ~~~~]\"],\"editor.actionMenu.viewToggles.showGuideImages\":[\"[Šĥöŵ ĝûîðé îɱáĝéš ~~~~~]\"],\"editor.actionMenu.viewToggles.showReferenceFloor\":[\"[Šĥöŵ ŕéƒéŕéñçé ƒļööŕ ~~~~~~]\"],\"editor.actionMenu.viewToggles.showScans\":[\"[Šĥöŵ šçáñš ~~~]\"],\"editor.actionMenu.viewToggles.uploadScanOrGuideImage\":[\"[Ûþļöáð šçáñ öŕ ĝûîðé îɱáĝé ~~~~~~~]\"],\"editor.backToHome\":[\"[Ɓáçķ ţö ĥöɱé ~~~]\"],\"editor.buildPanel.advanced\":[\"[Åðṽáñçéð ~~~]\"],\"editor.buildPanel.rooms.hint\":[\"[ŵáļļš · ƒļööŕ · çéîļîñĝ ~~~~~~]\"],\"editor.buildPanel.rooms.idle\":[\"[Þîçķ á šĥáþé ţö šţáŕţ. Éṽéŕý ŕööɱ ƀŕîñĝš îţš öŵñ ŵáļļš, ƒļööŕ áñð çéîļîñĝ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.polygon.caption\":[\"[\",[\"lead\"],\" ţĥéñ ţĥé ƒîŕšţ öñé áĝáîñ ţö çļöšé. Ƒöŕ Ļ-šĥáþéš áñð áñĝļéð ŕööɱš. ~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.polygon.label\":[\"[Þöļýĝöñ ~~~]\"],\"editor.buildPanel.rooms.polygon.lead\":[\"[Çļîçķ éáçĥ çöŕñéŕ, ~~~~~]\"],\"editor.buildPanel.rooms.polygon.title\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"[\",[\"lead\"],\" öñ ţĥé ĝŕîð. Ŵáļļš, ƒļööŕ áñð çéîļîñĝ áŕé çŕéáţéð ţöĝéţĥéŕ. ~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.rectangle.label\":[\"[Ŕéçţáñĝļé ~~~]\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"[Çļîçķ ţŵö çöŕñéŕš ~~~~~]\"],\"editor.buildPanel.rooms.rectangle.title\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.buildPanel.rooms.title\":[\"[Ŕööɱš ~~]\"],\"editor.buildPanel.rooms.walls.caption\":[\"[\",[\"lead\"],\" Çļöšé á ļööþ áñð îţ ƀéçöɱéš á ŕööɱ; ļéáṽé îţ öþéñ ƒöŕ þáŕţîţîöñš. ~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.walls.label\":[\"[Ŵáļļš ~~]\"],\"editor.buildPanel.rooms.walls.lead\":[\"[Ðŕáŵ öñé ŵáļļ áţ á ţîɱé. ~~~~~~]\"],\"editor.buildPanel.rooms.walls.title\":[\"[Ŵáļļš ~~]\"],\"editor.cameraControlsHint\":[\"[Çáɱéŕá çöñţŕöļš ĥîñţ ~~~~~~]\"],\"editor.cameraHint.pan\":[\"[Þáñ ~]\"],\"editor.cameraHint.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.cameraHint.zoom\":[\"[Žööɱ ~~]\"],\"editor.controls.finishSwatch.ariaLabel\":[[\"label\"],\": \",[\"finish\"]],\"editor.controls.finishSwatch.default\":[\"[Ðéƒáûļţ ~~~]\"],\"editor.dismiss\":[\"[Ðîšɱîšš ~~~]\"],\"editor.dismissCameraControlsHint\":[\"[Ðîšɱîšš çáɱéŕá çöñţŕöļš ĥîñţ ~~~~~~~~]\"],\"editor.expandSidebar\":[\"[Éẋþáñð šîðéƀáŕ ~~~~]\"],\"editor.floatingActionMenu.axis\":[\"[Åẋîš ~~]\"],\"editor.floatingActionMenu.axis2\":[\"[⌥ áẋîš ~~]\"],\"editor.floatingActionMenu.noEquipment\":[\"[⚠ ñö éǫûîþɱéñţ ~~~~]\"],\"editor.floatingActionMenu.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"editor.floatingActionMenu.registerCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ŕéĝîšţéŕ\"],\"other\":[\"#\",\" ŕéĝîšţéŕš\"]}],\" ~~~~~~]\"],\"editor.floatingActionMenu.runCount\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕûñ\"],\"other\":[[\"number\"],\" ŕûñš\"]}],\" ~~~]\"],\"editor.floatingActionMenu.system\":[\"[Šýšţéɱ ~~]\"],\"editor.floatingLevelSelector.addAFloorUnderTheRoof\":[\"[Åðð á ƒļööŕ ûñðéŕ ţĥé ŕööƒ ~~~~~~~]\"],\"editor.floatingLevelSelector.addLevelAbove\":[\"[Åðð ļéṽéļ áƀöṽé ~~~~]\"],\"editor.floatingLevelSelector.addLevelBelow\":[\"[Åðð ļéṽéļ ƀéļöŵ ~~~~]\"],\"editor.floatingLevelSelector.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.floatingLevelSelector.delete\":[\"[Ðéļéţé ~~]\"],\"editor.floatingLevelSelector.deleteLevel\":[\"[Ðéļéţé ļéṽéļ ~~~~]\"],\"editor.floatingLevelSelector.deleteLevelConfirm\":[\"[Åŕé ýöû šûŕé ýöû ŵáñţ ţö ðéļéţé \",[\"levelName\"],\"? Åļļ ŵáļļš, ƒļööŕš, áñð öƀĵéçţš öñ ţĥîš ļéṽéļ ŵîļļ ƀé þéŕɱáñéñţļý ŕéɱöṽéð. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.dragToReorder\":[\"[Ðŕáĝ ţö ŕéöŕðéŕ ~~~~]\"],\"editor.floatingLevelSelector.duplicateLevel\":[\"[Ðûþļîçáţé ļéṽéļ ~~~~~]\"],\"editor.floatingLevelSelector.duplicateWithOptions\":[\"[Ðûþļîçáţé ŵîţĥ öþţîöñš... ~~~~~~]\"],\"editor.floatingLevelSelector.exitUnitFocus\":[\"[Éẋîţ ûñîţ ƒöçûš ~~~~]\"],\"editor.floatingLevelSelector.focusedUnitHasZonesHere\":[\"[Ƒöçûšéð ûñîţ ĥáš žöñéš ĥéŕé ~~~~~~~]\"],\"editor.floatingLevelSelector.groundFloorOverGrade\":[\"[Ţĥé ĝŕöûñð ƒļööŕ šţáñðš \",[\"distance\"],\" öṽéŕ ĝŕáðé. Šéáţ ţĥé ĝŕöûñð ƒļööŕ 8 îñ öṽéŕ ĝŕáðé ~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundFloorUnderGrade\":[\"[Ţĥé ĝŕöûñð ƒļööŕ šţáñðš \",[\"distance\"],\" ûñðéŕ ĝŕáðé. Šéáţ ţĥé ĝŕöûñð ƒļööŕ 8 îñ öṽéŕ ĝŕáðé ~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundLineTheLevelsUnderIt\":[\"[Ĝŕöûñð ļîñé: ţĥé ļéṽéļš ûñðéŕ îţ áŕé ƀéļöŵ ĝŕáðé ~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundOffset\":[\"[Ĝŕöûñð · \",[\"offset\"],\" ~~]\"],\"editor.floatingLevelSelector.insertLevelHere\":[\"[Îñšéŕţ ļéṽéļ ĥéŕé ~~~~~]\"],\"editor.floatingLevelSelector.levelHeight\":[\"[Ļéṽéļ ĥéîĝĥţ ~~~~]\"],\"editor.floatingLevelSelector.pasteCopiedSelection\":[\"[Þášţé çöþîéð šéļéçţîöñ ~~~~~~]\"],\"editor.floatingLevelSelector.reorderLevelname\":[\"[Ŕéöŕðéŕ \",[\"levelName\"],\" ~~~]\"],\"editor.floatingLevelSelector.seatTheBuildingSoThisFloor\":[\"[Šéáţ ţĥé ƀûîļðîñĝ šö ţĥîš ƒļööŕ šţáñðš áţ ĝŕáðé; ţĥé ƒļööŕš ûñðéŕ îţ áŕé ƀéļöŵ ĝŕáðé ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.setAsGroundFloor\":[\"[Šéţ áš ĝŕöûñð ƒļööŕ ~~~~~]\"],\"editor.floatingLevelSelector.theGroundLevelCannotBeDeleted\":[\"[Ţĥé ĝŕöûñð ļéṽéļ çáññöţ ƀé ðéļéţéð ~~~~~~~~~]\"],\"editor.floatingLevelSelector.thisFloorStandsAtGrade\":[\"[Ţĥîš ƒļööŕ šţáñðš áţ ĝŕáðé ~~~~~~~]\"],\"editor.floatingLevelSelector.unit\":[\"[Ûñîţ ~~]\"],\"editor.floorFootprint.area\":[\"[Ƒļööŕ áŕéá ~~~]\"],\"editor.floorFootprint.room\":[\"[\",[\"room\"],\" ƒļööŕ ~~]\"],\"editor.floorFootprint.shared\":[\"[Šĥáŕéð ƒļööŕ ~~~~]\"],\"editor.helpers.buildingHelper.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.buildingHelper.placeBuilding\":[\"[Þļáçé ƀûîļðîñĝ ~~~~]\"],\"editor.helpers.buildingHelper.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.helpers.contextualHelperPanel.cancelPlacement\":[\"[Çáñçéļ þļáçéɱéñţ ~~~~~]\"],\"editor.helpers.contextualHelperPanel.continuationClickOrPressCTo\":[\"[Çöñţîñûáţîöñ — çļîçķ öŕ þŕéšš Ç ţö çýçļé ~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.continuationLabel\":[\"[Çöñţîñûáţîöñ: \",[\"label\"],\" ~~~~]\"],\"editor.helpers.contextualHelperPanel.dragToDrawFence\":[\"[Ðŕáĝ ţö ðŕáŵ ƒéñçé ~~~~~]\"],\"editor.helpers.contextualHelperPanel.eraseScope\":[\"[Éŕášé: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift\":[\"[Éŕášé šçöþé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.eraseScopeLabel\":[\"[Éŕášé šçöþé: \",[\"scope\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT\":[\"[Ƒéñçé ţýþé — çļîçķ öŕ þŕéšš Ţ ţö šŵîţçĥ ƀéţŵééñ šţŕáîĝĥţ, çûŕṽéð áñð ƒŕééĥáñð ~~~~~~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.fewerShortcuts\":[\"[Ƒéŵéŕ šĥöŕţçûţš ~~~~~]\"],\"editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick\":[\"[Ƒîñîšĥ çûŕṽé (öŕ ðöûƀļé-çļîçķ) ~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.gridLength\":[\"[Ĝŕîð: \",[\"length\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl\":[\"[Ĝŕîð šţéþ — çļîçķ öŕ ţáþ Çţŕļ ţö çýçļé ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.gridStepLength\":[\"[Ĝŕîð šţéþ: \",[\"length\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase\":[\"[Ĥöṽéŕ á þáîñţéð šûŕƒáçé ţö éŕášé ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPaint\":[\"[Ĥöṽéŕ á šûŕƒáçé ţö þáîñţ ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts\":[\"[Ĥöṽéŕ á šûŕƒáçé ţö þîçķ îţš ɱáţéŕîáļ ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.moreShortcuts\":[\"[Ṁöŕé šĥöŕţçûţš ~~~~]\"],\"editor.helpers.contextualHelperPanel.paintScope\":[\"[Þáîñţ: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift\":[\"[Þáîñţ šçöþé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.paintScopeLabel\":[\"[Þáîñţ šçöþé: \",[\"scope\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.pickScope\":[\"[Þîçķ: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.placeGateOnAFence\":[\"[Þļáçé ĝáţé öñ á ƒéñçé ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.placePassageOnAFence\":[\"[Þļáçé þáššáĝé öñ á ƒéñçé ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.selectAMaterialToPaint\":[\"[Šéļéçţ á ɱáţéŕîáļ ţö þáîñţ ~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingAngles\":[\"[Šñáþþîñĝ: Åñĝļéš ~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingGrid\":[\"[Šñáþþîñĝ: Ĝŕîð ~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingLines\":[\"[Šñáþþîñĝ: Ļîñéš ~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift\":[\"[Šñáþþîñĝ ɱöðé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingOff\":[\"[Šñáþþîñĝ: Öƒƒ ~~~~]\"],\"editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved\":[\"[Šţŕáîĝĥţ çöñţîñûáţîöñ îš ûñáṽáîļáƀļé ƒöŕ çûŕṽéð öŕ ƒŕééĥáñð ƒéñçéš ~~~~~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightContinuous\":[\"[Šţŕáîĝĥţ: Çöñţîñûöûš ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress\":[\"[Šţŕáîĝĥţ ƒéñçé çöñţîñûáţîöñ — çļîçķ öŕ þŕéšš Ç ţö ţöĝĝļé ~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightSingle\":[\"[Šţŕáîĝĥţ: Šîñĝļé ~~~~~]\"],\"editor.helpers.helperManager.addPoint\":[\"[Åðð þöîñţ ~~~]\"],\"editor.helpers.helperManager.addPointClickTheFirstTo\":[\"[Åðð þöîñţ · çļîçķ ţĥé ƒîŕšţ ţö çļöšé ~~~~~~~~~]\"],\"editor.helpers.helperManager.brushSize\":[\"[Ɓŕûšĥ šîžé ~~~]\"],\"editor.helpers.helperManager.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.helperManager.cancelPicking\":[\"[Çáñçéļ þîçķîñĝ ~~~~]\"],\"editor.helpers.helperManager.cancelStroke\":[\"[Çáñçéļ šţŕöķé ~~~~]\"],\"editor.helpers.helperManager.done\":[\"[Ðöñé ~~]\"],\"editor.helpers.helperManager.drawTheMezzanine\":[\"[Ðŕáŵ ţĥé ɱéžžáñîñé ~~~~~]\"],\"editor.helpers.helperManager.drawTheOpening\":[\"[Ðŕáŵ ţĥé öþéñîñĝ ~~~~~]\"],\"editor.helpers.helperManager.drawTheTerrace\":[\"[Ðŕáŵ ţĥé ţéŕŕáçé ~~~~~]\"],\"editor.helpers.helperManager.exitSmartMeasure\":[\"[Éẋîţ šɱáŕţ ɱéášûŕé ~~~~~]\"],\"editor.helpers.helperManager.finish\":[\"[Ƒîñîšĥ ~~]\"],\"editor.helpers.helperManager.finishAtTheEdge\":[\"[Ƒîñîšĥ áţ ţĥé éðĝé ~~~~~]\"],\"editor.helpers.helperManager.freeMove\":[\"[Ƒŕéé ɱöṽé ~~~]\"],\"editor.helpers.helperManager.freeMoveSlideDoorsAndWindows\":[\"[Ƒŕéé ɱöṽé · šļîðé ðööŕš áñð ŵîñðöŵš ~~~~~~~~~]\"],\"editor.helpers.helperManager.holdToPickAMaterial\":[\"[Ĥöļð ţö þîçķ á ɱáţéŕîáļ ~~~~~~]\"],\"editor.helpers.helperManager.inspectSurfaceDimensions\":[\"[Îñšþéçţ šûŕƒáçé ðîɱéñšîöñš ~~~~~~~~]\"],\"editor.helpers.helperManager.paintWithThisMaterial\":[\"[Þáîñţ ŵîţĥ ţĥîš ɱáţéŕîáļ ~~~~~~~]\"],\"editor.helpers.helperManager.pickTargetHeight\":[\"[Þîçķ ţáŕĝéţ ĥéîĝĥţ ~~~~~]\"],\"editor.helpers.helperManager.pinMeasurementLens\":[\"[Þîñ ɱéášûŕéɱéñţ ļéñš ~~~~~~]\"],\"editor.helpers.helperManager.place\":[\"[Þļáçé ~~]\"],\"editor.helpers.helperManager.placeInsideTheRoom\":[\"[Þļáçé îñšîðé ţĥé ŕööɱ ~~~~~~]\"],\"editor.helpers.helperManager.pressAnywhereDragToTheOpposite\":[\"[Þŕéšš áñýŵĥéŕé, ðŕáĝ ţö ţĥé öþþöšîţé çöŕñéŕ ~~~~~~~~~~~]\"],\"editor.helpers.helperManager.raiseOrLower\":[\"[Ŕáîšé öŕ ļöŵéŕ ~~~~]\"],\"editor.helpers.helperManager.removeAPaintedPartOrReset\":[\"[Ŕéɱöṽé á þáîñţéð þáŕţ, öŕ ŕéšéţ á šûŕƒáçé ~~~~~~~~~~]\"],\"editor.helpers.helperManager.removeLastPoint\":[\"[Ŕéɱöṽé ļášţ þöîñţ ~~~~~]\"],\"editor.helpers.helperManager.resize\":[\"[Ŕéšîžé ~~]\"],\"editor.helpers.helperManager.rotate45\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.helpers.helperManager.rotateTheSelection45\":[\"[Ŕöţáţé ţĥé šéļéçţîöñ ±45° ~~~~~~]\"],\"editor.helpers.helperManager.setOneCornerThenTheOpposite\":[\"[Šéţ öñé çöŕñéŕ, ţĥéñ ţĥé öþþöšîţé ~~~~~~~~~]\"],\"editor.helpers.itemHelper.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.itemHelper.forcePlace\":[\"[Ƒöŕçé þļáçé ~~~]\"],\"editor.helpers.itemHelper.place\":[\"[Þļáçé ~~]\"],\"editor.helpers.itemHelper.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.helpers.registeredToolHelper.guidedConstraintsBypassed\":[\"[Ĝûîðéð çöñšţŕáîñţš ƀýþáššéð ~~~~~~~~]\"],\"editor.inspector.field.angle.g10zaw\":[\"[Åñĝļé ~~]\"],\"editor.inspector.field.armLengthM.1jclqgw\":[\"[Åŕɱ ļéñĝţĥ ɱ ~~~]\"],\"editor.inspector.field.attachmentSide.6n9s5v\":[\"[Åţţáçĥɱéñţ šîðé ~~~~~]\"],\"editor.inspector.field.autoHangers.10554ka\":[\"[Åûţö ĥáñĝéŕš ~~~~]\"],\"editor.inspector.field.autoMiterCorners.zxr5lt\":[\"[Åûţö ɱîţéŕ çöŕñéŕš ~~~~~]\"],\"editor.inspector.field.autoSpan.1gas9r6\":[\"[Åûţö šþáñ ~~~]\"],\"editor.inspector.field.backRatio.1ughd99\":[\"[Ɓáçķ ŕáţîö ~~~]\"],\"editor.inspector.field.baseHeight.1hs4zdf\":[\"[Ɓášé ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.baseStyle.24vty5\":[\"[Ɓášé šţýļé ~~~]\"],\"editor.inspector.field.beamHeight.1kq0oh1\":[\"[Ɓéáɱ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.beamWidth.1q1mzbe\":[\"[Ɓéáɱ ŵîðţĥ ~~~]\"],\"editor.inspector.field.bodyShape.arvkuw\":[\"[Ɓöðý šĥáþé ~~~]\"],\"editor.inspector.field.bracketStyle.1vvoptm\":[\"[Ɓŕáçķéţ šţýļé ~~~~]\"],\"editor.inspector.field.branchAngle.v56zme\":[\"[Ɓŕáñçĥ áñĝļé ~~~~]\"],\"editor.inspector.field.cabHeight.1yn34mk\":[\"[Çáƀ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.canopyForm.1czzggp\":[\"[Çáñöþý ƒöŕɱ ~~~]\"],\"editor.inspector.field.cap.s0thej\":[\"[Çáþ ~]\"],\"editor.inspector.field.capOverhang.kxxz9j\":[\"[Çáþ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.capShape.17mbxhu\":[\"[Çáþ šĥáþé ~~~]\"],\"editor.inspector.field.capThickness.u835q9\":[\"[Çáþ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.carcassHeight.1aks0zy\":[\"[Çáŕçášš ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.cleanoutStyle.127zz6z\":[\"[Çļéáñöûţ šţýļé ~~~~]\"],\"editor.inspector.field.collarDiameter.1d5k28r\":[\"[Çöļļáŕ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.collarHeight.1dqwsrf\":[\"[Çöļļáŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.collarShape.1gacv11\":[\"[Çöļļáŕ šĥáþé ~~~~]\"],\"editor.inspector.field.collarWidth.qvc9ik\":[\"[Çöļļáŕ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.columns.1sy73ks\":[\"[Çöļûɱñš ~~~]\"],\"editor.inspector.field.conicalFullCircle.1pgkpz5\":[\"[Çöñîçáļ ƒûļļ çîŕçļé ~~~~~~]\"],\"editor.inspector.field.connectionMode.1nty478\":[\"[Çöññéçţîöñ ɱöðé ~~~~~]\"],\"editor.inspector.field.connectionOffset.2m70s8\":[\"[Çöññéçţîöñ öƒƒšéţ ~~~~~]\"],\"editor.inspector.field.cornerBevel.16iniho\":[\"[Çöŕñéŕ ƀéṽéļ ~~~~]\"],\"editor.inspector.field.coveringType.95yd3k\":[\"[Çöṽéŕîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.cricketHeight.pok31n\":[\"[Çŕîçķéţ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.cricketLength.i2aht8\":[\"[Çŕîçķéţ ļéñĝţĥ ~~~~]\"],\"editor.inspector.field.cricketSide.1xnwil7\":[\"[Çŕîçķéţ šîðé ~~~~]\"],\"editor.inspector.field.cricketStyle.10j0lat\":[\"[Çŕîçķéţ šţýļé ~~~~]\"],\"editor.inspector.field.curb.5xi2ol\":[\"[Çûŕƀ ~~]\"],\"editor.inspector.field.curbHeight.1yn97sg\":[\"[Çûŕƀ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.curveOffset.l4xhvr\":[\"[Çûŕṽé öƒƒšéţ ~~~~]\"],\"editor.inspector.field.damperAngle.h5fxn7\":[\"[Ðáɱþéŕ áñĝļé ~~~~]\"],\"editor.inspector.field.deckThickness.4jmqze\":[\"[Ðéçķ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.defaultLevelId.xmrjzp\":[\"[Ðéƒáûļţ ļéṽéļ îð ~~~~~]\"],\"editor.inspector.field.depth.1lu11u2\":[\"[Ðéþţĥ ~~]\"],\"editor.inspector.field.diameter.s3robe\":[\"[Ðîáɱéţéŕ ~~~]\"],\"editor.inspector.field.diameter2.pm6cl4\":[\"[Ðîáɱéţéŕ2 ~~~]\"],\"editor.inspector.field.doorDurationMs.13nx909\":[\"[Ðööŕ ðûŕáţîöñ ɱš ~~~~~]\"],\"editor.inspector.field.doorHeight.92r9k4\":[\"[Ðööŕ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.doorPanelStyle.2qog4q\":[\"[Ðööŕ þáñéļ šţýļé ~~~~~]\"],\"editor.inspector.field.doorStyle.gerpos\":[\"[Ðööŕ šţýļé ~~~]\"],\"editor.inspector.field.doorWidth.sy732l\":[\"[Ðööŕ ŵîðţĥ ~~~]\"],\"editor.inspector.field.downspoutEnabled.1gn00nv\":[\"[Ðöŵñšþöûţ éñáƀļéð ~~~~~]\"],\"editor.inspector.field.downspoutPosition.fxcsop\":[\"[Ðöŵñšþöûţ þöšîţîöñ ~~~~~~]\"],\"editor.inspector.field.ductMaterial.i8eonq\":[\"[Ðûçţ ɱáţéŕîáļ ~~~~]\"],\"editor.inspector.field.dutchGabletRake.6paydp\":[\"[Ðûţçĥ ĝáƀļéţ ŕáķé ~~~~~]\"],\"editor.inspector.field.dutchHipHeightRatio.1etnwgg\":[\"[Ðûţçĥ ĥîþ ĥéîĝĥţ ŕáţîö ~~~~~~]\"],\"editor.inspector.field.dutchHipWidthRatio.1dopfvb\":[\"[Ðûţçĥ ĥîþ ŵîðţĥ ŕáţîö ~~~~~~]\"],\"editor.inspector.field.dutchTopRakeThickness.1w9khxt\":[\"[Ðûţçĥ ţöþ ŕáķé ţĥîçķñéšš ~~~~~~~]\"],\"editor.inspector.field.dutchWaistLengthRatio.agtoxw\":[\"[Ðûţçĥ ŵáîšţ ļéñĝţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.dwellMs.1lkk0o5\":[\"[Ðŵéļļ ɱš ~~~]\"],\"editor.inspector.field.edgeInset.1obeimx\":[\"[Éðĝé îñšéţ ~~~]\"],\"editor.inspector.field.elevation.4jbp44\":[\"[Éļéṽáţîöñ ~~~]\"],\"editor.inspector.field.endCapLeft.19jel4v\":[\"[Éñð çáþ ļéƒţ ~~~]\"],\"editor.inspector.field.endCapRight.lhse9c\":[\"[Éñð çáþ ŕîĝĥţ ~~~~]\"],\"editor.inspector.field.endCaps.4fsb5b\":[\"[Éñð çáþš ~~~]\"],\"editor.inspector.field.endPost.1guwsk0\":[\"[Éñð þöšţ ~~~]\"],\"editor.inspector.field.equipmentType.1mc2ywt\":[\"[Éǫûîþɱéñţ ţýþé ~~~~]\"],\"editor.inspector.field.fillToFloor.1oppnkf\":[\"[Ƒîļļ ţö ƒļööŕ ~~~~]\"],\"editor.inspector.field.finial.1ertf4s\":[\"[Ƒîñîáļ ~~]\"],\"editor.inspector.field.fittingType.18mz17s\":[\"[Ƒîţţîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.flashingHeight.3e8nn6\":[\"[Ƒļášĥîñĝ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.flashingProjection.hhf0fk\":[\"[Ƒļášĥîñĝ þŕöĵéçţîöñ ~~~~~~]\"],\"editor.inspector.field.flueCount.j3t2ci\":[\"[Ƒļûé çöûñţ ~~~]\"],\"editor.inspector.field.flueDiameter.cjhubg\":[\"[Ƒļûé ðîáɱéţéŕ ~~~~]\"],\"editor.inspector.field.flueHeight.1dhc4y4\":[\"[Ƒļûé ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.flueShape.1ealiy4\":[\"[Ƒļûé šĥáþé ~~~]\"],\"editor.inspector.field.flueSpacing.exhzco\":[\"[Ƒļûé šþáçîñĝ ~~~~]\"],\"editor.inspector.field.footingStyle.e3btg6\":[\"[Ƒööţîñĝ šţýļé ~~~~]\"],\"editor.inspector.field.frameDepth.1934a5n\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"editor.inspector.field.frameThickness.1agxzd8\":[\"[Ƒŕáɱé ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.framingStrategy.3jx9qe\":[\"[Ƒŕáɱîñĝ šţŕáţéĝý ~~~~~]\"],\"editor.inspector.field.fromLevelId.15ieejs\":[\"[Ƒŕöɱ ļéṽéļ îð ~~~~]\"],\"editor.inspector.field.gableOverhang.2z0obw\":[\"[Ĝáƀļé öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.gambrelLowerHeightRatio.wqq7lg\":[\"[Ĝáɱƀŕéļ ļöŵéŕ ĥéîĝĥţ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.gambrelLowerWidthRatio.uvyb1v\":[\"[Ĝáɱƀŕéļ ļöŵéŕ ŵîðţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.gapX.wjxa7\":[\"[Ĝáþ ẋ ~~]\"],\"editor.inspector.field.gapY.mkbl8\":[\"[Ĝáþ ý ~~]\"],\"editor.inspector.field.glassThickness.s97b0x\":[\"[Ĝļášš ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.groundClearance.4os7f0\":[\"[Ĝŕöûñð çļéáŕáñçé ~~~~~]\"],\"editor.inspector.field.guardInfill.16heiig\":[\"[Ĝûáŕð îñƒîļļ ~~~~]\"],\"editor.inspector.field.gutterEnabled.1oxiltf\":[\"[Ĝûţţéŕ éñáƀļéð ~~~~]\"],\"editor.inspector.field.gutterProfile.1ngzqi3\":[\"[Ĝûţţéŕ þŕöƒîļé ~~~~]\"],\"editor.inspector.field.gutterSize.jgavtz\":[\"[Ĝûţţéŕ šîžé ~~~]\"],\"editor.inspector.field.hangerMaxReach.1869epv\":[\"[Ĥáñĝéŕ ɱáẋ ŕéáçĥ ~~~~~]\"],\"editor.inspector.field.hangerSpacing.i2b5d7\":[\"[Ĥáñĝéŕ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.hangerStyle.l7v7gb\":[\"[Ĥáñĝéŕ šţýļé ~~~~]\"],\"editor.inspector.field.height.7puwki\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.inspector.field.height2.7b3jlc\":[\"[Ĥéîĝĥţ2 ~~]\"],\"editor.inspector.field.heightAboveRidge.1k99brk\":[\"[Ĥéîĝĥţ áƀöṽé ŕîðĝé ~~~~~]\"],\"editor.inspector.field.highEdgeHeight.1s7v90t\":[\"[Ĥîĝĥ éðĝé ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.highOverhang.67w55d\":[\"[Ĥîĝĥ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.highSideMode.1rc1r5j\":[\"[Ĥîĝĥ šîðé ɱöðé ~~~~]\"],\"editor.inspector.field.hoodOverhang.1q1hcin\":[\"[Ĥööð öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.infillPlacement.11dxzpo\":[\"[Îñƒîļļ þļáçéɱéñţ ~~~~~]\"],\"editor.inspector.field.inletShape.ypayh2\":[\"[Îñļéţ šĥáþé ~~~]\"],\"editor.inspector.field.innerRadius.1fkmg0j\":[\"[Îññéŕ ŕáðîûš ~~~~]\"],\"editor.inspector.field.insulated.iwziey\":[\"[Îñšûļáţéð ~~~]\"],\"editor.inspector.field.insulationR.663ufx\":[\"[Îñšûļáţîöñ ŕ ~~~~]\"],\"editor.inspector.field.lanternHeight.oz7zpm\":[\"[Ļáñţéŕñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.lanternTopScale.hkmpp2\":[\"[Ļáñţéŕñ ţöþ šçáļé ~~~~~]\"],\"editor.inspector.field.ledgerDepth.7av8k9\":[\"[Ļéðĝéŕ ðéþţĥ ~~~~]\"],\"editor.inspector.field.ledgerHeight.zo2oif\":[\"[Ļéðĝéŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.ledgerVerticalOffset.1athbgt\":[\"[Ļéðĝéŕ ṽéŕţîçáļ öƒƒšéţ ~~~~~~]\"],\"editor.inspector.field.leftEndCondition.1syqlaw\":[\"[Ļéƒţ éñð çöñðîţîöñ ~~~~~]\"],\"editor.inspector.field.leftOverhang.1ydl9ma\":[\"[Ļéƒţ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.length.1kpdcfp\":[\"[Ļéñĝţĥ ~~]\"],\"editor.inspector.field.liquidDiameter.w3pk7c\":[\"[Ļîǫûîð ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.louverCount.lhw2gr\":[\"[Ļöûṽéŕ çöûñţ ~~~~]\"],\"editor.inspector.field.lowBeamInset.1g7yw5l\":[\"[Ļöŵ ƀéáɱ îñšéţ ~~~~]\"],\"editor.inspector.field.lowEdgeHeight.47mae1\":[\"[Ļöŵ éðĝé ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.lowOverhang.t4nz1h\":[\"[Ļöŵ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.mansardSteepHeightRatio.1hbb8rq\":[\"[Ṁáñšáŕð šţééþ ĥéîĝĥţ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.mansardSteepWidthRatio.6xk6vd\":[\"[Ṁáñšáŕð šţééþ ŵîðţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.matchHostRoofMaterial.s56zwj\":[\"[Ṁáţçĥ ĥöšţ ŕööƒ ɱáţéŕîáļ ~~~~~~~]\"],\"editor.inspector.field.matchHostRoofStructure.bojht3\":[\"[Ṁáţçĥ ĥöšţ ŕööƒ šţŕûçţûŕé ~~~~~~~]\"],\"editor.inspector.field.mount.h80tay\":[\"[Ṁöûñţ ~~]\"],\"editor.inspector.field.mountingType.1xvin1e\":[\"[Ṁöûñţîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.neckHeight.19ym8n1\":[\"[Ñéçķ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.opacity.1pmpfd2\":[\"[Öþáçîţý ~~~]\"],\"editor.inspector.field.openingAngle.brhi7c\":[\"[Öþéñîñĝ áñĝļé ~~~~]\"],\"editor.inspector.field.openingOffset.wpoqt6\":[\"[Öþéñîñĝ öƒƒšéţ ~~~~]\"],\"editor.inspector.field.openingSide.18l2iwe\":[\"[Öþéñîñĝ šîðé ~~~~]\"],\"editor.inspector.field.operationState.1lwrz07\":[\"[Öþéŕáţîöñ šţáţé ~~~~~]\"],\"editor.inspector.field.outletShape.lgpqhx\":[\"[Öûţļéţ šĥáþé ~~~~]\"],\"editor.inspector.field.overhang.b5ljpv\":[\"[Öṽéŕĥáñĝ ~~~]\"],\"editor.inspector.field.panelHeight.1kf31vk\":[\"[Þáñéļ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.panelWidth.946581\":[\"[Þáñéļ ŵîðţĥ ~~~]\"],\"editor.inspector.field.patternAlignment.jd1ht0\":[\"[Þáţţéŕñ áļîĝñɱéñţ ~~~~~]\"],\"editor.inspector.field.patternCount.1r37pve\":[\"[Þáţţéŕñ çöûñţ ~~~~]\"],\"editor.inspector.field.patternDistribution.fmam51\":[\"[Þáţţéŕñ ðîšţŕîƀûţîöñ ~~~~~~]\"],\"editor.inspector.field.patternRemainder.i47p5y\":[\"[Þáţţéŕñ ŕéɱáîñðéŕ ~~~~~]\"],\"editor.inspector.field.picketProfile.1xgzuyc\":[\"[Þîçķéţ þŕöƒîļé ~~~~]\"],\"editor.inspector.field.picketRailCount.atgt6w\":[\"[Þîçķéţ ŕáîļ çöûñţ ~~~~~]\"],\"editor.inspector.field.picketSpacing.p8d98w\":[\"[Þîçķéţ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.picketTop.1tskplu\":[\"[Þîçķéţ ţöþ ~~~]\"],\"editor.inspector.field.picketTopClearance.16eq1k0\":[\"[Þîçķéţ ţöþ çļéáŕáñçé ~~~~~~]\"],\"editor.inspector.field.picketVariation.f7vl1c\":[\"[Þîçķéţ ṽáŕîáţîöñ ~~~~~]\"],\"editor.inspector.field.picketWidth.3fr3rh\":[\"[Þîçķéţ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.pipeMaterial.1ggc2wy\":[\"[Þîþé ɱáţéŕîáļ ~~~~]\"],\"editor.inspector.field.pitch.xe6ekx\":[\"[Þîţçĥ ~~]\"],\"editor.inspector.field.position.1quewx6\":[\"[Þöšîţîöñ ~~~]\"],\"editor.inspector.field.postBracing.crq8nn\":[\"[Þöšţ ƀŕáçîñĝ ~~~~]\"],\"editor.inspector.field.postCap.1xcvxrv\":[\"[Þöšţ çáþ ~~~]\"],\"editor.inspector.field.postCount.22r56c\":[\"[Þöšţ çöûñţ ~~~]\"],\"editor.inspector.field.postDepth.1ko6j1m\":[\"[Þöšţ ðéþţĥ ~~~]\"],\"editor.inspector.field.postInset.le36li\":[\"[Þöšţ îñšéţ ~~~]\"],\"editor.inspector.field.postLayoutMode.16jre62\":[\"[Þöšţ ļáýöûţ ɱöðé ~~~~~]\"],\"editor.inspector.field.postSize.1jodxek\":[\"[Þöšţ šîžé ~~~]\"],\"editor.inspector.field.postSpacing.voppfi\":[\"[Þöšţ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.postThrough.1abgu4a\":[\"[Þöšţ ţĥŕöûĝĥ ~~~~]\"],\"editor.inspector.field.postWidth.rk26fz\":[\"[Þöšţ ŵîðţĥ ~~~]\"],\"editor.inspector.field.profile.1kwlci6\":[\"[Þŕöƒîļé ~~~]\"],\"editor.inspector.field.projection.jhtig0\":[\"[Þŕöĵéçţîöñ ~~~]\"],\"editor.inspector.field.purlinHeight.eo9cjm\":[\"[Þûŕļîñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.purlinSpacing.4gbzb2\":[\"[Þûŕļîñ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.purlinWidth.r6tu3j\":[\"[Þûŕļîñ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.rafterEndInset.14c0wy5\":[\"[Ŕáƒţéŕ éñð îñšéţ ~~~~~]\"],\"editor.inspector.field.rafterHeight.1e1n99w\":[\"[Ŕáƒţéŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.rafterSpacing.19subc0\":[\"[Ŕáƒţéŕ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.rafterWidth.eksj4t\":[\"[Ŕáƒţéŕ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.railingHeight.6wf4bm\":[\"[Ŕáîļîñĝ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.railingMode.ov7xpu\":[\"[Ŕáîļîñĝ ɱöðé ~~~~]\"],\"editor.inspector.field.railingStyle.1nyd2u\":[\"[Ŕáîļîñĝ šţýļé ~~~~]\"],\"editor.inspector.field.resizeLock.b2hbr0\":[\"[Ŕéšîžé ļöçķ ~~~]\"],\"editor.inspector.field.returnDiameter.166ozhe\":[\"[Ŕéţûŕñ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.returnHeight.b9sq6y\":[\"[Ŕéţûŕñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.returnShape.1e3a27i\":[\"[Ŕéţûŕñ šĥáþé ~~~~]\"],\"editor.inspector.field.returnWidth.z9r3h3\":[\"[Ŕéţûŕñ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.rightEndCondition.tjlw3l\":[\"[Ŕîĝĥţ éñð çöñðîţîöñ ~~~~~~]\"],\"editor.inspector.field.rightOverhang.yqbeft\":[\"[Ŕîĝĥţ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.roofHeight.1uhy6qy\":[\"[Ŕööƒ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.roofStyle.pawqfy\":[\"[Ŕööƒ šţýļé ~~~]\"],\"editor.inspector.field.roofThickness.1xlr3rx\":[\"[Ŕööƒ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.roofType.1m9kv5h\":[\"[Ŕööƒ ţýþé ~~~]\"],\"editor.inspector.field.rotation.ly2rj\":[\"[Ŕöţáţîöñ ~~~]\"],\"editor.inspector.field.rows.1ba1neg\":[\"[Ŕöŵš ~~]\"],\"editor.inspector.field.scale.b6rpxt\":[\"[Šçáļé ~~]\"],\"editor.inspector.field.seamDetail.197za48\":[\"[Šéáɱ ðéţáîļ ~~~]\"],\"editor.inspector.field.segmentType.l667sy\":[\"[Šéĝɱéñţ ţýþé ~~~~]\"],\"editor.inspector.field.shaftStyle.1i5ampa\":[\"[Šĥáƒţ šţýļé ~~~]\"],\"editor.inspector.field.shaftWallThickness.bduslr\":[\"[Šĥáƒţ ŵáļļ ţĥîçķñéšš ~~~~~~]\"],\"editor.inspector.field.shape.iq9t52\":[\"[Šĥáþé ~~]\"],\"editor.inspector.field.shape2.fi0nx8\":[\"[Šĥáþé2 ~~]\"],\"editor.inspector.field.shedHighSide.bzzddg\":[\"[Šĥéð ĥîĝĥ šîðé ~~~~]\"],\"editor.inspector.field.shingleThickness.1hn5wxr\":[\"[Šĥîñĝļé ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.shoulderExtent.11j8uiv\":[\"[Šĥöûļðéŕ éẋţéñţ ~~~~~]\"],\"editor.inspector.field.shoulderHeight.xyqn6g\":[\"[Šĥöûļðéŕ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.shoulderStyle.1hkcv2w\":[\"[Šĥöûļðéŕ šţýļé ~~~~]\"],\"editor.inspector.field.showCenterColumn.1uggg45\":[\"[Šĥöŵ çéñţéŕ çöļûɱñ ~~~~~]\"],\"editor.inspector.field.showInfill.33rv7u\":[\"[Šĥöŵ îñƒîļļ ~~~]\"],\"editor.inspector.field.showStepSupports.1kg8h8m\":[\"[Šĥöŵ šţéþ šûþþöŕţš ~~~~~]\"],\"editor.inspector.field.sideFlashing.cv744o\":[\"[Šîðé ƒļášĥîñĝ ~~~~]\"],\"editor.inspector.field.size.1a4x3zw\":[\"[Šîžé ~~]\"],\"editor.inspector.field.skylightType.1a5vj3i\":[\"[Šķýļîĝĥţ ţýþé ~~~~]\"],\"editor.inspector.field.slabOpeningMode.g4f730\":[\"[Šļáƀ öþéñîñĝ ɱöðé ~~~~~]\"],\"editor.inspector.field.slatGap.slmk93\":[\"[Šļáţ ĝáþ ~~~]\"],\"editor.inspector.field.slideDirection.1527q13\":[\"[Šļîðé ðîŕéçţîöñ ~~~~~]\"],\"editor.inspector.field.span.1bmmuw1\":[\"[Šþáñ ~~]\"],\"editor.inspector.field.speed.2w2xwg\":[\"[Šþééð ~~]\"],\"editor.inspector.field.spinSpeed.1ozx746\":[\"[Šþîñ šþééð ~~~]\"],\"editor.inspector.field.stairType.1u05e02\":[\"[Šţáîŕ ţýþé ~~~]\"],\"editor.inspector.field.standoff.4wv2g0\":[\"[Šţáñðöƒƒ ~~~]\"],\"editor.inspector.field.standoffHeight.1qdnff\":[\"[Šţáñðöƒƒ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.startPost.1ood9kv\":[\"[Šţáŕţ þöšţ ~~~]\"],\"editor.inspector.field.stepCount.nzorrw\":[\"[Šţéþ çöûñţ ~~~]\"],\"editor.inspector.field.strapSpacing.1cyt3dc\":[\"[Šţŕáþ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.strapStyle.fh1oy4\":[\"[Šţŕáþ šţýļé ~~~]\"],\"editor.inspector.field.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.inspector.field.suctionDiameter.nutxtj\":[\"[Šûçţîöñ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.supplyDiameter.h5ldh5\":[\"[Šûþþļý ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.supplyHeight.zy6ru1\":[\"[Šûþþļý ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.supplyShape.k39wnb\":[\"[Šûþþļý šĥáþé ~~~~]\"],\"editor.inspector.field.supplyWidth.g66axi\":[\"[Šûþþļý ŵîðţĥ ~~~~]\"],\"editor.inspector.field.supportOffset.1w4cocr\":[\"[Šûþþöŕţ öƒƒšéţ ~~~~]\"],\"editor.inspector.field.sweepAngle.ucmmlw\":[\"[Šŵééþ áñĝļé ~~~]\"],\"editor.inspector.field.system.13qbhrw\":[\"[Šýšţéɱ ~~]\"],\"editor.inspector.field.terminal.1501knl\":[\"[Ţéŕɱîñáļ ~~~]\"],\"editor.inspector.field.terminalType.1ne8lgb\":[\"[Ţéŕɱîñáļ ţýþé ~~~~]\"],\"editor.inspector.field.thickness.1l8hu45\":[\"[Ţĥîçķñéšš ~~~]\"],\"editor.inspector.field.tiltAngle.1312yw1\":[\"[Ţîļţ áñĝļé ~~~]\"],\"editor.inspector.field.toLevelId.8i5qx7\":[\"[Ţö ļéṽéļ îð ~~~]\"],\"editor.inspector.field.topLandingDepth.ki7gk2\":[\"[Ţöþ ļáñðîñĝ ðéþţĥ ~~~~~]\"],\"editor.inspector.field.topLandingMode.zk0h7e\":[\"[Ţöþ ļáñðîñĝ ɱöðé ~~~~~]\"],\"editor.inspector.field.topRailHeight.fscisv\":[\"[Ţöþ ŕáîļ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.transitionMode.f3evgt\":[\"[Ţŕáñšîţîöñ ɱöðé ~~~~~]\"],\"editor.inspector.field.transitionWidth.rtgz3m\":[\"[Ţŕáñšîţîöñ ŵîðţĥ ~~~~~]\"],\"editor.inspector.field.vaneCount.55vtye\":[\"[Ṽáñé çöûñţ ~~~]\"],\"editor.inspector.field.wallHeight.18vmh5c\":[\"[Ŵáļļ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.wallShell.vqsyhv\":[\"[Ŵáļļ šĥéļļ ~~~]\"],\"editor.inspector.field.wallSkirtHeight.1uea2ad\":[\"[Ŵáļļ šķîŕţ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.wallThickness.17wn4iv\":[\"[Ŵáļļ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.width.gfxz9b\":[\"[Ŵîðţĥ ~~]\"],\"editor.inspector.field.width2.1nqawvr\":[\"[Ŵîðţĥ2 ~~]\"],\"editor.inspector.field.withBack.1ay8nus\":[\"[Ŵîţĥ ƀáçķ ~~~]\"],\"editor.inspector.field.withBottom.1x1z1vo\":[\"[Ŵîţĥ ƀöţţöɱ ~~~]\"],\"editor.inspector.field.withSides.uq066x\":[\"[Ŵîţĥ šîðéš ~~~]\"],\"editor.inspector.group.advanced.qwfkor\":[\"[Åðṽáñçéð ~~~]\"],\"editor.inspector.group.air.15jotdj\":[\"[Åîŕ ~]\"],\"editor.inspector.group.appearance.dli9wf\":[\"[Åþþéáŕáñçé ~~~]\"],\"editor.inspector.group.body.ign4wl\":[\"[Ɓöðý ~~]\"],\"editor.inspector.group.cab.o4z1ox\":[\"[Çáƀ ~]\"],\"editor.inspector.group.cabinet.rzvymr\":[\"[Çáƀîñéţ ~~~]\"],\"editor.inspector.group.cap.s0thej\":[\"[Çáþ ~]\"],\"editor.inspector.group.collar.1am2iqo\":[\"[Çöļļáŕ ~~]\"],\"editor.inspector.group.connection.2r1h4p\":[\"[Çöññéçţîöñ ~~~]\"],\"editor.inspector.group.connections.ai4w8u\":[\"[Çöññéçţîöñš ~~~~]\"],\"editor.inspector.group.construction.j6p9kc\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"editor.inspector.group.cricket.6iwg8g\":[\"[Çŕîçķéţ ~~~]\"],\"editor.inspector.group.curb.5xi2ol\":[\"[Çûŕƀ ~~]\"],\"editor.inspector.group.curvePoints.ztdcnn\":[\"[Çûŕṽé þöîñţš ~~~~]\"],\"editor.inspector.group.dimensions.pewa8g\":[\"[Ðîɱéñšîöñš ~~~]\"],\"editor.inspector.group.doors.18w2kpa\":[\"[Ðööŕš ~~]\"],\"editor.inspector.group.dormer.1fjs5tq\":[\"[Ðöŕɱéŕ ~~]\"],\"editor.inspector.group.dormerRoof.u9uy2g\":[\"[Ðöŕɱéŕ ŕööƒ ~~~]\"],\"editor.inspector.group.drainage.7easy0\":[\"[Ðŕáîñáĝé ~~~]\"],\"editor.inspector.group.elevation.4jbp44\":[\"[Éļéṽáţîöñ ~~~]\"],\"editor.inspector.group.endCaps.4fsb5b\":[\"[Éñð çáþš ~~~]\"],\"editor.inspector.group.equipment.1xtq0ev\":[\"[Éǫûîþɱéñţ ~~~]\"],\"editor.inspector.group.face.1531fr0\":[\"[Ƒáçé ~~]\"],\"editor.inspector.group.fitting.171z1r4\":[\"[Ƒîţţîñĝ ~~~]\"],\"editor.inspector.group.flues.nq6pye\":[\"[Ƒļûéš ~~]\"],\"editor.inspector.group.footprint.g8q2pg\":[\"[Ƒööţþŕîñţ ~~~]\"],\"editor.inspector.group.frame.q7q9fq\":[\"[Ƒŕáɱé ~~]\"],\"editor.inspector.group.geometry.a21os1\":[\"[Ĝéöɱéţŕý ~~~]\"],\"editor.inspector.group.grid.d8gwxd\":[\"[Ĝŕîð ~~]\"],\"editor.inspector.group.hangers.agnfzn\":[\"[Ĥáñĝéŕš ~~~]\"],\"editor.inspector.group.hardware.1nygwm1\":[\"[Ĥáŕðŵáŕé ~~~]\"],\"editor.inspector.group.hungWall.1ftr1gz\":[\"[Ĥûñĝ ŵáļļ ~~~]\"],\"editor.inspector.group.insulation.1kyo5e7\":[\"[Îñšûļáţîöñ ~~~]\"],\"editor.inspector.group.lantern.1h7o2xb\":[\"[Ļáñţéŕñ ~~~]\"],\"editor.inspector.group.levels.1s6yaru\":[\"[Ļéṽéļš ~~]\"],\"editor.inspector.group.line.166mv5z\":[\"[Ļîñé ~~]\"],\"editor.inspector.group.lines.12bghr0\":[\"[Ļîñéš ~~]\"],\"editor.inspector.group.motion.1be7sjr\":[\"[Ṁöţîöñ ~~]\"],\"editor.inspector.group.mounting.feutd2\":[\"[Ṁöûñţîñĝ ~~~]\"],\"editor.inspector.group.opening.dfet3\":[\"[Öþéñîñĝ ~~~]\"],\"editor.inspector.group.panelDimensions.b59qe6\":[\"[Þáñéļ ðîɱéñšîöñš ~~~~~]\"],\"editor.inspector.group.patternDistribution.fmam51\":[\"[Þáţţéŕñ ðîšţŕîƀûţîöñ ~~~~~~]\"],\"editor.inspector.group.placement.13lcvts\":[\"[Þļáçéɱéñţ ~~~]\"],\"editor.inspector.group.position.1quewx6\":[\"[Þöšîţîöñ ~~~]\"],\"editor.inspector.group.profile.1kwlci6\":[\"[Þŕöƒîļé ~~~]\"],\"editor.inspector.group.railing.19a5bnr\":[\"[Ŕáîļîñĝ ~~~]\"],\"editor.inspector.group.return.1klrjrz\":[\"[Ŕéţûŕñ ~~]\"],\"editor.inspector.group.settings.ktdb8o\":[\"[Šéţţîñĝš ~~~]\"],\"editor.inspector.group.shaft.mad9cv\":[\"[Šĥáƒţ ~~]\"],\"editor.inspector.group.shape.iq9t52\":[\"[Šĥáþé ~~]\"],\"editor.inspector.group.shoulder.1nyfx25\":[\"[Šĥöûļðéŕ ~~~]\"],\"editor.inspector.group.size.1a4x3zw\":[\"[Šîžé ~~]\"],\"editor.inspector.group.structure.gezdra\":[\"[Šţŕûçţûŕé ~~~]\"],\"editor.inspector.group.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.inspector.group.supply.1oy2zwu\":[\"[Šûþþļý ~~]\"],\"editor.inspector.group.surface.wtamqk\":[\"[Šûŕƒáçé ~~~]\"],\"editor.inspector.group.terminal.1501knl\":[\"[Ţéŕɱîñáļ ~~~]\"],\"editor.inspector.group.topology.18t8dyc\":[\"[Ţöþöļöĝý ~~~]\"],\"editor.inspector.group.transform.1c55ayj\":[\"[Ţŕáñšƒöŕɱ ~~~]\"],\"editor.inspector.group.trap.1qc7vhe\":[\"[Ţŕáþ ~~]\"],\"editor.inspector.group.type.1m2zofh\":[\"[Ţýþé ~~]\"],\"editor.inspector.option.abs.132mz8b\":[\"[Åƀš ~]\"],\"editor.inspector.option.accessPanel.13sptb5\":[\"[Åççéšš þáñéļ ~~~~]\"],\"editor.inspector.option.airHandler.1crnyg3\":[\"[Åîŕ ĥáñðļéŕ ~~~]\"],\"editor.inspector.option.alternating.5jashi\":[\"[Åļţéŕñáţîñĝ ~~~~]\"],\"editor.inspector.option.arched.14188w0\":[\"[Åŕçĥéð ~~]\"],\"editor.inspector.option.auto.540b06\":[\"[Åûţö ~~]\"],\"editor.inspector.option.automatic.r122ro\":[\"[Åûţöɱáţîç ~~~]\"],\"editor.inspector.option.back.1hzmxtu\":[\"[Ɓáçķ ~~]\"],\"editor.inspector.option.balusters.1j3w6ng\":[\"[Ɓáļûšţéŕš ~~~]\"],\"editor.inspector.option.band.160umau\":[\"[Ɓáñð ~~]\"],\"editor.inspector.option.basePlate.17fcg8k\":[\"[Ɓášé þļáţé ~~~]\"],\"editor.inspector.option.boards.u14hig\":[\"[Ɓöáŕðš ~~]\"],\"editor.inspector.option.bookshelf.ls0zqk\":[\"[Ɓööķšĥéļƒ ~~~]\"],\"editor.inspector.option.both.mkyb42\":[\"[Ɓöţĥ ~~]\"],\"editor.inspector.option.bottom.9mpruy\":[\"[Ɓöţţöɱ ~~]\"],\"editor.inspector.option.box.1nbjcuq\":[\"[Ɓöẋ ~]\"],\"editor.inspector.option.break.17spnvc\":[\"[Ɓŕéáķ ~~]\"],\"editor.inspector.option.butterfly.pvv0g4\":[\"[Ɓûţţéŕƒļý ~~~]\"],\"editor.inspector.option.cable.fp50zq\":[\"[Çáƀļé ~~]\"],\"editor.inspector.option.castIron.1q2z4qk\":[\"[Çášţ îŕöñ ~~~]\"],\"editor.inspector.option.ceiling.1t2zaqo\":[\"[Çéîļîñĝ ~~~]\"],\"editor.inspector.option.center.kzdxno\":[\"[Çéñţéŕ ~~]\"],\"editor.inspector.option.centerOpening.bn1rvw\":[\"[Çéñţéŕ öþéñîñĝ ~~~~]\"],\"editor.inspector.option.cleanout.lww8bm\":[\"[Çļéáñöûţ ~~~]\"],\"editor.inspector.option.concretePad.m1zdij\":[\"[Çöñçŕéţé þáð ~~~~]\"],\"editor.inspector.option.condenser.a74nps\":[\"[Çöñðéñšéŕ ~~~]\"],\"editor.inspector.option.corbeled.bovs4v\":[\"[Çöŕƀéļéð ~~~]\"],\"editor.inspector.option.count.1qoi4r8\":[\"[Çöûñţ ~~]\"],\"editor.inspector.option.coupling.111aap2\":[\"[Çöûþļîñĝ ~~~]\"],\"editor.inspector.option.coveringSpecific.1h9fo8w\":[\"[Çöṽéŕîñĝ šþéçîƒîç ~~~~~]\"],\"editor.inspector.option.cross.1elubzv\":[\"[Çŕöšš ~~]\"],\"editor.inspector.option.cubby.ivq4qc\":[\"[Çûƀƀý ~~]\"],\"editor.inspector.option.curved.6noz1s\":[\"[Çûŕṽéð ~~]\"],\"editor.inspector.option.cylinder.eol6vj\":[\"[Çýļîñðéŕ ~~~]\"],\"editor.inspector.option.damper.wgri80\":[\"[Ðáɱþéŕ ~~]\"],\"editor.inspector.option.destination.gfzm1r\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"editor.inspector.option.diffuser.1igj0jh\":[\"[Ðûšéŕ ~~~]\"],\"editor.inspector.option.dogEar.xhtvjt\":[\"[Ðöĝ éáŕ ~~]\"],\"editor.inspector.option.dome.1c87e56\":[\"[Ðöɱé ~~]\"],\"editor.inspector.option.double.13h5354\":[\"[Ðöûƀļé ~~]\"],\"editor.inspector.option.ductBoard.lc3o4t\":[\"[Ðûçţ ƀöáŕð ~~~]\"],\"editor.inspector.option.dutch.1djx429\":[\"[Ðûţçĥ ~~]\"],\"editor.inspector.option.elbow.1flup74\":[\"[Éļƀöŵ ~~]\"],\"editor.inspector.option.end.1llf32i\":[\"[Éñð ~]\"],\"editor.inspector.option.endCap.1p4v4na\":[\"[Éñð çáþ ~~]\"],\"editor.inspector.option.equalFit.11vw52c\":[\"[Éǫûáļ ƒîţ ~~~]\"],\"editor.inspector.option.fixedCount.1o3dznu\":[\"[Ƒîẋéð çöûñţ ~~~]\"],\"editor.inspector.option.fixedSpacing.hl4dr4\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"editor.inspector.option.flat.ly0bei\":[\"[Ƒļáţ ~~]\"],\"editor.inspector.option.flex.ktpamq\":[\"[Ƒļéẋ ~~]\"],\"editor.inspector.option.floating.1oadpln\":[\"[Ƒļöáţîñĝ ~~~]\"],\"editor.inspector.option.floor.q9caf1\":[\"[Ƒļööŕ ~~]\"],\"editor.inspector.option.flush.olwwu5\":[\"[Ƒļûšĥ ~~]\"],\"editor.inspector.option.front.uhugl4\":[\"[Ƒŕöñţ ~~]\"],\"editor.inspector.option.furnace.w0mzpf\":[\"[Ƒûŕñáçé ~~~]\"],\"editor.inspector.option.gable.1ekyx6\":[\"[Ĝáƀļé ~~]\"],\"editor.inspector.option.gambrel.1yo90d9\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"editor.inspector.option.generic.1074oey\":[\"[Ĝéñéŕîç ~~~]\"],\"editor.inspector.option.glass.163tc5n\":[\"[Ĝļášš ~~]\"],\"editor.inspector.option.glassFrame.jp9s3m\":[\"[Ĝļášš ƒŕáɱé ~~~]\"],\"editor.inspector.option.globe.1avkqoy\":[\"[Ĝļöƀé ~~]\"],\"editor.inspector.option.grounded.jwoq73\":[\"[Ĝŕöûñðéð ~~~]\"],\"editor.inspector.option.guard.1m575se\":[\"[Ĝûáŕð ~~]\"],\"editor.inspector.option.halfRound.m5hob0\":[\"[Ĥáļƒ ŕöûñð ~~~]\"],\"editor.inspector.option.hidden.gp48rd\":[\"[Ĥîððéñ ~~]\"],\"editor.inspector.option.hip.r0jnjy\":[\"[Ĥîþ ~]\"],\"editor.inspector.option.horizontal.tyapah\":[\"[Ĥöŕîžöñţáļ ~~~]\"],\"editor.inspector.option.include.1a27vnr\":[\"[Îñçļûðé ~~~]\"],\"editor.inspector.option.independentHighBeam.1tn0zvy\":[\"[Îñðéþéñðéñţ ĥîĝĥ ƀéáɱ ~~~~~~]\"],\"editor.inspector.option.industrial.8sd9u\":[\"[Îñðûšţŕîáļ ~~~]\"],\"editor.inspector.option.inline.1uh05lg\":[\"[Îñļîñé ~~]\"],\"editor.inspector.option.integrated.1xd9lv0\":[\"[Îñţéĝŕáţéð ~~~]\"],\"editor.inspector.option.joined.1x2o8ds\":[\"[Ĵöîñéð ~~]\"],\"editor.inspector.option.kStyle.m8kfwz\":[\"[Ķ šţýļé ~~]\"],\"editor.inspector.option.kickout.vt8s6p\":[\"[Ķîçķöûţ ~~~]\"],\"editor.inspector.option.knee.186hcui\":[\"[Ķñéé ~~]\"],\"editor.inspector.option.landing.tyqarg\":[\"[Ļáñðîñĝ ~~~]\"],\"editor.inspector.option.lantern.1h7o2xb\":[\"[Ļáñţéŕñ ~~~]\"],\"editor.inspector.option.leave.1xf0sbk\":[\"[Ļéáṽé ~~]\"],\"editor.inspector.option.left.14n06mo\":[\"[Ļéƒţ ~~]\"],\"editor.inspector.option.level.i4lhtp\":[\"[Ļéṽéļ ~~]\"],\"editor.inspector.option.lowProfile.1h9lgzg\":[\"[Ļöŵ þŕöƒîļé ~~~]\"],\"editor.inspector.option.mansard.dwzck9\":[\"[Ṁáñšáŕð ~~~]\"],\"editor.inspector.option.manual.1wu7r43\":[\"[Ṁáñûáļ ~~]\"],\"editor.inspector.option.maximumSpacing.n1fzl4\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"editor.inspector.option.metal.1az9fgc\":[\"[Ṁéţáļ ~~]\"],\"editor.inspector.option.metalPanel.1xdz476\":[\"[Ṁéţáļ þáñéļ ~~~]\"],\"editor.inspector.option.minimal.fwmiwg\":[\"[Ṁîñîɱáļ ~~~]\"],\"editor.inspector.option.mono.oszvd6\":[\"[Ṁöñö ~~]\"],\"editor.inspector.option.none.deku7v\":[\"[Ñöñé ~~]\"],\"editor.inspector.option.omit.okcs7w\":[\"[Öɱîţ ~~]\"],\"editor.inspector.option.open.n6hn1l\":[\"[Öþéñ ~~]\"],\"editor.inspector.option.openRack.mvt1x0\":[\"[Öþéñ ŕáçķ ~~~]\"],\"editor.inspector.option.opening.dfet3\":[\"[Öþéñîñĝ ~~~]\"],\"editor.inspector.option.oval.1jiqzxx\":[\"[Öṽáļ ~~]\"],\"editor.inspector.option.picket.zyeoy1\":[\"[Þîçķéţ ~~]\"],\"editor.inspector.option.pointed.effedk\":[\"[Þöîñţéð ~~~]\"],\"editor.inspector.option.postAndRail.lajk2u\":[\"[Þöšţ áñð ŕáîļ ~~~~]\"],\"editor.inspector.option.preserveHighEdge.y6ywf0\":[\"[Þŕéšéŕṽé ĥîĝĥ éðĝé ~~~~~]\"],\"editor.inspector.option.preserveLowEdge.196m5pk\":[\"[Þŕéšéŕṽé ļöŵ éðĝé ~~~~~]\"],\"editor.inspector.option.preservePitch.1uq973r\":[\"[Þŕéšéŕṽé þîţçĥ ~~~~]\"],\"editor.inspector.option.privacy.1th4hrb\":[\"[Þŕîṽáçý ~~~]\"],\"editor.inspector.option.purlins.od124c\":[\"[Þûŕļîñš ~~~]\"],\"editor.inspector.option.pvc.1oc4u2w\":[\"[Þṽç ~]\"],\"editor.inspector.option.pyramid.17ucsqv\":[\"[Þýŕáɱîð ~~~]\"],\"editor.inspector.option.rafters.16tzbby\":[\"[Ŕáƒţéŕš ~~~]\"],\"editor.inspector.option.rail.39zqc9\":[\"[Ŕáîļ ~~]\"],\"editor.inspector.option.raised.4kyx9h\":[\"[Ŕáîšéð ~~]\"],\"editor.inspector.option.rect.tpfvaf\":[\"[Ŕéçţ ~~]\"],\"editor.inspector.option.reducer.18byt67\":[\"[Ŕéðûçéŕ ~~~]\"],\"editor.inspector.option.return.1klrjrz\":[\"[Ŕéţûŕñ ~~]\"],\"editor.inspector.option.returnGrille.r28ncm\":[\"[Ŕéţûŕñ ĝŕîļļé ~~~~]\"],\"editor.inspector.option.right.8humpx\":[\"[Ŕîĝĥţ ~~]\"],\"editor.inspector.option.round.1vx0wyj\":[\"[Ŕöûñð ~~]\"],\"editor.inspector.option.rounded.199nwpm\":[\"[Ŕöûñðéð ~~~]\"],\"editor.inspector.option.sanitaryTee.oro3d4\":[\"[Šáñîţáŕý ţéé ~~~~]\"],\"editor.inspector.option.scalloped.1eltba0\":[\"[Šçáļļöþéð ~~~]\"],\"editor.inspector.option.scoop.qy5zdv\":[\"[Šçööþ ~~]\"],\"editor.inspector.option.segmentedPanel.5r3hwl\":[\"[Šéĝɱéñţéð þáñéļ ~~~~~]\"],\"editor.inspector.option.shed.6t994v\":[\"[Šĥéð ~~]\"],\"editor.inspector.option.sheetMetal.l164lv\":[\"[Šĥééţ ɱéţáļ ~~~]\"],\"editor.inspector.option.shingle.pfmynh\":[\"[Šĥîñĝļé ~~~]\"],\"editor.inspector.option.shingled.1nm9inf\":[\"[Šĥîñĝļéð ~~~]\"],\"editor.inspector.option.simple.12mfu73\":[\"[Šîɱþļé ~~]\"],\"editor.inspector.option.single.1uzxurt\":[\"[Šîñĝļé ~~]\"],\"editor.inspector.option.singleLeft.1atdh02\":[\"[Šîñĝļé ļéƒţ ~~~]\"],\"editor.inspector.option.singleRight.1axbu67\":[\"[Šîñĝļé ŕîĝĥţ ~~~~]\"],\"editor.inspector.option.slantBox.1ta99vy\":[\"[Šļáñţ ƀöẋ ~~~]\"],\"editor.inspector.option.slat.1p9njbz\":[\"[Šļáţ ~~]\"],\"editor.inspector.option.sliding.cd0jox\":[\"[Šļîðîñĝ ~~~]\"],\"editor.inspector.option.slope.1bn2qw\":[\"[Šļöþé ~~]\"],\"editor.inspector.option.sloped.lo4sh0\":[\"[Šļöþéð ~~]\"],\"editor.inspector.option.solid.o4uy6s\":[\"[Šöļîð ~~]\"],\"editor.inspector.option.solidPanel.2yaw62\":[\"[Šöļîð þáñéļ ~~~]\"],\"editor.inspector.option.spiral.91hcl4\":[\"[Šþîŕáļ ~~]\"],\"editor.inspector.option.splash.1uke4cm\":[\"[Šþļášĥ ~~]\"],\"editor.inspector.option.spread.1g1zysu\":[\"[Šþŕéáð ~~]\"],\"editor.inspector.option.square.1xl1lk6\":[\"[Šǫûáŕé ~~]\"],\"editor.inspector.option.stair.1xf86e\":[\"[Šţáîŕ ~~]\"],\"editor.inspector.option.standard.vuco\":[\"[Šţáñðáŕð ~~~]\"],\"editor.inspector.option.start.30xvgf\":[\"[Šţáŕţ ~~]\"],\"editor.inspector.option.step.jtn9kf\":[\"[Šţéþ ~~]\"],\"editor.inspector.option.stepped.naxzh0\":[\"[Šţéþþéð ~~~]\"],\"editor.inspector.option.straight.lxqbfr\":[\"[Šţŕáîĝĥţ ~~~]\"],\"editor.inspector.option.strap.14q0ps9\":[\"[Šţŕáþ ~~]\"],\"editor.inspector.option.supply.1oy2zwu\":[\"[Šûþþļý ~~]\"],\"editor.inspector.option.supplyRegister.1lzdbbn\":[\"[Šûþþļý ŕéĝîšţéŕ ~~~~~]\"],\"editor.inspector.option.tapered.3y5blo\":[\"[Ţáþéŕéð ~~~]\"],\"editor.inspector.option.targetSpacing.14uibc9\":[\"[Ţáŕĝéţ šþáçîñĝ ~~~~]\"],\"editor.inspector.option.tee.7d6tjp\":[\"[Ţéé ~]\"],\"editor.inspector.option.tilted.6n0u5j\":[\"[Ţîļţéð ~~]\"],\"editor.inspector.option.top.2nypcs\":[\"[Ţöþ ~]\"],\"editor.inspector.option.transition.1rziz9e\":[\"[Ţŕáñšîţîöñ ~~~]\"],\"editor.inspector.option.vent.xda5j2\":[\"[Ṽéñţ ~~]\"],\"editor.inspector.option.walkOn.1n5sg9l\":[\"[Ŵáļķ öñ ~~]\"],\"editor.inspector.option.wall.blj585\":[\"[Ŵáļļ ~~]\"],\"editor.inspector.option.wallAbutment.lza941\":[\"[Ŵáļļ áƀûţɱéñţ ~~~~]\"],\"editor.inspector.option.wallLedger.5m8ana\":[\"[Ŵáļļ ļéðĝéŕ ~~~]\"],\"editor.inspector.option.wallShelf.yiozfl\":[\"[Ŵáļļ šĥéļƒ ~~~]\"],\"editor.inspector.option.waste.3xrp79\":[\"[Ŵášţé ~~]\"],\"editor.inspector.option.wye.14vupc8\":[\"[Ŵýé ~]\"],\"editor.layers.collapse\":[\"[Çöļļáþšé ~~~]\"],\"editor.layers.defaultName.ceiling\":[\"[Çéîļîñĝ (\",[\"area\"],\") ~~~]\"],\"editor.layers.defaultName.collection\":[\"[Çöļļéçţîöñ ~~~]\"],\"editor.layers.defaultName.flight\":[\"[Ƒļîĝĥţ (\",[\"width\"],\"×\",[\"length\"],\"ɱ) ~~~]\"],\"editor.layers.defaultName.landing\":[\"[Ļáñðîñĝ (\",[\"width\"],\"×\",[\"length\"],\"ɱ) ~~~]\"],\"editor.layers.defaultName.node\":[\"[Ñöðé ~~]\"],\"editor.layers.defaultName.roof\":[\"[Ŕööƒ (\",[\"count\",\"plural\",{\"one\":[\"#\",\" šéĝɱéñţ\"],\"other\":[\"#\",\" šéĝɱéñţš\"]}],\") ~~~~~~]\"],\"editor.layers.defaultName.roofSegment\":[\"[\",[\"roofType\",\"select\",{\"hip\":[\"Ĥîþ\"],\"gable\":[\"Ĝáƀļé\"],\"shed\":[\"Šĥéð\"],\"gambrel\":[\"Ĝáɱƀŕéļ\"],\"dutch\":[\"Ðûţçĥ\"],\"mansard\":[\"Ṁáñšáŕð\"],\"flat\":[\"Ƒļáţ\"],\"conical\":[\"Çöñîçáļ\"],\"barrel\":[\"Ɓáŕŕéļ\"],\"other\":[\"Ŕööƒ\"]}],\" (\",[\"width\"],\"×\",[\"depth\"],\"ɱ) ~~~~~~~~~~~~~~~~]\"],\"editor.layers.defaultName.room\":[\"[Ŕööɱ (\",[\"area\"],\") ~~]\"],\"editor.layers.defaultName.slab\":[\"[Šļáƀ (\",[\"area\"],\") ~~]\"],\"editor.layers.defaultName.staircase\":[\"[Šţáîŕçášé (\",[\"count\",\"plural\",{\"one\":[\"#\",\" šéĝɱéñţ\"],\"other\":[\"#\",\" šéĝɱéñţš\"]}],\") ~~~~~~~~]\"],\"editor.layers.defaultName.zone\":[\"[Žöñé (\",[\"area\"],\") ~~]\"],\"editor.layers.expand\":[\"[Éẋþáñð ~~]\"],\"editor.layers.levelOptions\":[\"[\",[\"levelName\"],\" öþţîöñš ~~~]\"],\"editor.layers.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.layers.spaceColour\":[\"[\",[\"role\",\"select\",{\"room\":[\"Ŕööɱ çöļöûŕ\"],\"other\":[\"Žöñé çöļöûŕ\"]}],\" ~~~~~~]\"],\"editor.layers.unitColour\":[\"[Ûñîţ çöļöûŕ ~~~]\"],\"editor.levelDuplicateDialog.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromLevel\":[\"[Çĥööšé ŵĥáţ ţö çöþý ƒŕöɱ \",[\"levelName\"],\". ~~~~~~]\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel\":[\"[Çĥööšé ŵĥáţ ţö çöþý ƒŕöɱ ţĥîš ļéṽéļ. ~~~~~~~~~]\"],\"editor.levelDuplicateDialog.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.levelDuplicateDialog.duplicateLevel\":[\"[Ðûþļîçáţé Ļéṽéļ ~~~~~]\"],\"editor.levelDuplicateDialog.everything\":[\"[Éṽéŕýţĥîñĝ ~~~]\"],\"editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout\":[\"[Šţŕûçţûŕé, ƒîñîšĥéš, áñð þļáçéð îţéɱš, ŵîţĥöûţ ĝûîðé ŕéƒéŕéñçéš. ~~~~~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.structureFurniture\":[\"[Šţŕûçţûŕé + ƒûŕñîţûŕé ~~~~~~]\"],\"editor.levelDuplicateDialog.structureMaterials\":[\"[Šţŕûçţûŕé + ɱáţéŕîáļš ~~~~~~]\"],\"editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences\":[\"[Šţŕûçţûŕé, ɱáţéŕîáļš, ƒûŕñîţûŕé, áñð ŕéƒéŕéñçéš. ~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.structureOnly\":[\"[Šţŕûçţûŕé öñļý ~~~~]\"],\"editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd\":[\"[Šţŕûçţûŕé ŵîţĥ ţĥé çûŕŕéñţ ɱáţéŕîáļ áñð ƒîñîšĥ áššîĝñɱéñţš. ~~~~~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd\":[\"[Ŵáļļš, šļáƀš, ŕööƒš, šţáîŕš, ŵîñðöŵš, áñð ðööŕš ŵîţĥöûţ ƒîñîšĥéš. ~~~~~~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.addOrRemoveObjectsFromThe\":[\"[Åðð öŕ ŕéɱöṽé öƀĵéçţš ƒŕöɱ ţĥé šéļéçţîöñ ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.adjustOneSideSCornerRadius\":[\"[Åðĵûšţ öñé šîðé’š çöŕñéŕ ŕáðîûš ~~~~~~~~]\"],\"editor.lib.contextualHelp.clearTheSelectionOrClickOutside\":[\"[Çļéáŕ ţĥé šéļéçţîöñ (öŕ çļîçķ öûţšîðé) ~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickAHandleDotToShow\":[\"[Çļîçķ á ĥáñðļé ðöţ ţö šĥöŵ ɱöṽé áŕŕöŵš ~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickOrDragTheSelectionTo\":[\"[Çļîçķ öŕ ðŕáĝ ţĥé šéļéçţîöñ ţö ɱöṽé îţ áš öñé ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickTheHandleDotToShow\":[\"[Çļîçķ ţĥé ĥáñðļé ðöţ ţö šĥöŵ ɱöṽé + ŕöţáţé ĥáñðļéš ~~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.detachTheJointWhileDraggingAn\":[\"[Ðéţáçĥ ţĥé ĵöîñţ ŵĥîļé ðŕáĝĝîñĝ áñ áŕŕöŵ ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.dragLeftOrRightToRotate\":[\"[Ðŕáĝ ļéƒţ öŕ ŕîĝĥţ ţö ŕöţáţé šéļéçţéð öƀĵéçţ ~~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.dragSelectedMovableObject\":[\"[Ðŕáĝ šéļéçţéð ɱöṽáƀļé öƀĵéçţ ~~~~~~~~]\"],\"editor.lib.contextualHelp.groupSelectionSessionOnly\":[\"[Ĝŕöûþ šéļéçţîöñ (šéššîöñ öñļý) ~~~~~~~~]\"],\"editor.lib.contextualHelp.holdToRotateFreely\":[\"[Ĥöļð ţö ŕöţáţé ƒŕééļý ~~~~~~]\"],\"editor.lib.contextualHelp.rotate45\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.lib.contextualHelp.rotateTheSelection45\":[\"[Ŕöţáţé ţĥé šéļéçţîöñ ±45° ~~~~~~]\"],\"editor.lib.contextualHelp.rotatingFreely\":[\"[Ŕöţáţîñĝ ƒŕééļý (ñö áñĝļé šţéþ) ~~~~~~~~]\"],\"editor.lib.contextualHelp.switchTheRotationAxisYX\":[\"[Šŵîţçĥ ţĥé ŕöţáţîöñ áẋîš (Ý → Ẋ → Ž) ~~~~~~~~]\"],\"editor.lib.contextualHelp.ungroupSessionSelection\":[\"[Ûñĝŕöûþ šéššîöñ šéļéçţîöñ ~~~~~~~]\"],\"editor.lib.continuation.continuous\":[\"[Çöñţîñûöûš ~~~]\"],\"editor.lib.continuation.continuousCanopy\":[\"[Çöñţîñûöûš çáñöþý ~~~~~]\"],\"editor.lib.continuation.continuousRun\":[\"[Çöñţîñûöûš ŕûñ ~~~~]\"],\"editor.lib.continuation.curvedFence\":[\"[Çûŕṽéð ƒéñçé ~~~~]\"],\"editor.lib.continuation.freehandFence\":[\"[Ƒŕééĥáñð ƒéñçé ~~~~]\"],\"editor.lib.continuation.placeMultiple\":[\"[Þļáçé ɱûļţîþļé ~~~~]\"],\"editor.lib.continuation.placeOnce\":[\"[Þļáçé öñçé ~~~]\"],\"editor.lib.continuation.polygonRoom\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.lib.continuation.rectangleRoom\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.lib.continuation.singleCabinet\":[\"[Šîñĝļé çáƀîñéţ ~~~~]\"],\"editor.lib.continuation.singleCanopy\":[\"[Šîñĝļé çáñöþý ~~~~]\"],\"editor.lib.continuation.singleFence\":[\"[Šîñĝļé ƒéñçé ~~~~]\"],\"editor.lib.continuation.walls\":[\"[Ŵáļļš ~~]\"],\"editor.lib.hudTitle.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"editor.lib.hudTitle.divideRoom\":[\"[Ðîṽîðé ŕööɱ ~~~]\"],\"editor.lib.hudTitle.editKind\":[\"[Éðîţ \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.erase\":[\"[Éŕášé ~~]\"],\"editor.lib.hudTitle.levelTerrain\":[\"[Ļéṽéļ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.lowerTerrain\":[\"[Ļöŵéŕ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.lib.hudTitle.moveKind\":[\"[Ṁöṽé \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.moveRoom\":[\"[Ṁöṽé ŕööɱ ~~~]\"],\"editor.lib.hudTitle.moveSelection\":[\"[Ṁöṽé šéļéçţîöñ ~~~~]\"],\"editor.lib.hudTitle.paint\":[\"[Þáîñţ ~~]\"],\"editor.lib.hudTitle.paintARectangle\":[\"[Þáîñţ á ŕéçţáñĝļé ~~~~~]\"],\"editor.lib.hudTitle.paintAShape\":[\"[Þáîñţ á šĥáþé ~~~~]\"],\"editor.lib.hudTitle.pickMaterial\":[\"[Þîçķ ɱáţéŕîáļ ~~~~]\"],\"editor.lib.hudTitle.raiseTerrain\":[\"[Ŕáîšé ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.resize\":[\"[Ŕéšîžé ~~]\"],\"editor.lib.hudTitle.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.lib.hudTitle.select\":[\"[Šéļéçţ ~~]\"],\"editor.lib.hudTitle.smartMeasure\":[\"[Šɱáŕţ ɱéášûŕé ~~~~]\"],\"editor.lib.hudTitle.smoothTerrain\":[\"[Šɱööţĥ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.splitKind\":[\"[Šþļîţ \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"editor.lib.wallDrawVariant.polygonRoom\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.lib.wallDrawVariant.rectangleRoom\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.lib.wallDrawVariant.walls\":[\"[Ŵáļļš ~~]\"],\"editor.mechanismAction\":[\"[\",[\"action\",\"select\",{\"close\":[\"Çļöšé\"],\"open\":[\"Öþéñ\"],\"stop\":[\"Šţöþ\"],\"other\":[\"Þļáý\"]}],\" ~~~~~~]\"],\"editor.nodeActionMenu.curve\":[\"[Çûŕṽé ~~]\"],\"editor.nodeActionMenu.cutOut\":[\"[Çûţ öûţ ~~]\"],\"editor.nodeActionMenu.delete\":[\"[Ðéļéţé ~~]\"],\"editor.nodeActionMenu.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.nodeActionMenu.editMesh\":[\"[Éðîţ ɱéšĥ ~~~]\"],\"editor.nodeActionMenu.findInCatalog\":[\"[Ƒîñð îñ çáţáļöĝ ~~~~]\"],\"editor.nodeActionMenu.groupSelection\":[\"[Ĝŕöûþ šéļéçţîöñ ~~~~~]\"],\"editor.nodeActionMenu.move\":[\"[Ṁöṽé ~~]\"],\"editor.nodeActionMenu.rotateLeft\":[\"[Ŕöţáţé ļéƒţ ~~~]\"],\"editor.nodeActionMenu.rotateRight\":[\"[Ŕöţáţé ŕîĝĥţ ~~~~]\"],\"editor.nodeActionMenu.ungroupSelection\":[\"[Ûñĝŕöûþ šéļéçţîöñ ~~~~~]\"],\"editor.nodeDisplay.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.nodeDisplay.column\":[\"[Çöļûɱñ ~~]\"],\"editor.nodeDisplay.door\":[\"[Ðööŕ ~~]\"],\"editor.nodeDisplay.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"editor.nodeDisplay.fence\":[\"[Ƒéñçé ~~]\"],\"editor.nodeDisplay.guide\":[\"[Ĝûîðé îɱáĝé ~~~]\"],\"editor.nodeDisplay.item\":[\"[Îţéɱ ~~]\"],\"editor.nodeDisplay.roof\":[\"[Ŕööƒ ~~]\"],\"editor.nodeDisplay.roofSegment\":[\"[Ŕööƒ šéĝɱéñţ ~~~~]\"],\"editor.nodeDisplay.room\":[\"[Ŕööɱ ~~]\"],\"editor.nodeDisplay.scan\":[\"[3Ð Šçáñ ~~]\"],\"editor.nodeDisplay.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"editor.nodeDisplay.slab\":[\"[Šļáƀ ~~]\"],\"editor.nodeDisplay.stair\":[\"[Šţáîŕ ~~]\"],\"editor.nodeDisplay.stairSegment\":[\"[Šţáîŕ šéĝɱéñţ ~~~~]\"],\"editor.nodeDisplay.wall\":[\"[Ŵáļļ ~~]\"],\"editor.nodeDisplay.window\":[\"[Ŵîñðöŵ ~~]\"],\"editor.nodeDisplay.zone\":[\"[Žöñé ~~]\"],\"editor.panels.collections.collectionsPopover.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.panels.collections.collectionsPopover.collectionName\":[\"[Çöļļéçţîöñ ñáɱé… ~~~~~]\"],\"editor.panels.collections.collectionsPopover.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.panels.collections.collectionsPopover.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.collections.collectionsPopover.deleteName\":[\"[Ðéļéţé \\\"\",[\"name\"],\"\\\"? ~~]\"],\"editor.panels.collections.collectionsPopover.new\":[\"[Ñéŵ ~]\"],\"editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo\":[\"[Ñö çöļļéçţîöñš ýéţ. Çŕéáţé öñé ţö ĝŕöûþ éļéɱéñţš ţöĝéţĥéŕ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.collections.collectionsPopover.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe\":[\"[Å ţĥîçķéŕ ƒļööŕ ĝŕöŵš ûþŵáŕð: ţĥé ŕööɱš ƀéļöŵ ķééþ ţĥéîŕ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.edgeFinish\":[\"[Éðĝé ƒîñîšĥ ~~~]\"],\"editor.panels.floorFoundationPanel.floorPlate\":[\"[Ƒļööŕ þļáţé ~~~]\"],\"editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn\":[\"[Ƒļööŕ þļáţé \",[\"thicknessLabel\"],\" ţĥîçķ, ŕéšţîñĝ öñ ţĥé ŵáļļš ƀéļöŵ ~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.floorThickness\":[\"[Ƒļööŕ ţĥîçķñéšš ~~~~~]\"],\"editor.panels.floorFoundationPanel.floorTopAbove\":[\"[Ƒļööŕ ţöþ +\",[\"height\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.floorTopValue\":[\"[Ƒļööŕ ţöþ \",[\"height\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.foundationFinish\":[\"[Ƒöûñðáţîöñ ƒîñîšĥ ~~~~~]\"],\"editor.panels.floorFoundationPanel.foundationHeight\":[\"[Ƒöûñðáţîöñ ĥéîĝĥţ ~~~~~]\"],\"editor.panels.floorFoundationPanel.kicker\":[\"[Ƒļööŕ þļáţé ~~~]\"],\"editor.panels.floorFoundationPanel.kickerLevel\":[\"[Ƒļööŕ þļáţé · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.kickerRestsOn\":[\"[Ƒļööŕ þļáţé · \",[\"level\"],\" · ŕéšţš öñ \",[\"below\"],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.lowerTogether\":[\"[\",[\"count\",\"plural\",{\"2\":[\"Ļöŵéŕ ƀöţĥ\"],\"other\":[\"Ļöŵéŕ áļļ \",[\"number\"]]}],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.notToScale\":[\"[Ñöţ ţö šçáļé ~~~]\"],\"editor.panels.floorFoundationPanel.onTheGround\":[\"[Öñ ţĥé ĝŕöûñð ~~~~]\"],\"editor.panels.floorFoundationPanel.raiseTogether\":[\"[\",[\"count\",\"plural\",{\"2\":[\"Ŕáîšé ƀöţĥ\"],\"other\":[\"Ŕáîšé áļļ \",[\"number\"]]}],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.raisedOnAFoundation\":[\"[Ŕáîšéð öñ á ƒöûñðáţîöñ ~~~~~~]\"],\"editor.panels.floorFoundationPanel.section.floorPlate\":[\"[Ƒļööŕ þļáţé · \",[\"thickness\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.section.floorTop\":[\"[Ƒļööŕ ţöþ ~~~]\"],\"editor.panels.floorFoundationPanel.section.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.panels.floorFoundationPanel.section.foundationKind\":[\"[Ƒöûñðáţîöñ · \",[\"foundation\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.section.ground\":[\"[Ĝŕöûñð ~~]\"],\"editor.panels.floorFoundationPanel.section.slab\":[\"[Šļáƀ · \",[\"thickness\"],\" ~~]\"],\"editor.panels.floorFoundationPanel.section.wallsBelow\":[\"[Ŵáļļš ƀéļöŵ ~~~]\"],\"editor.panels.floorFoundationPanel.sectionOnGround\":[\"[Šļáƀ \",[\"thickness\"],\" ţĥîçķ öñ ţĥé ĝŕöûñð, ƒļööŕ ţöþ \",[\"top\"],\" áƀöṽé ţĥé ĝŕöûñð ~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.sectionRaised\":[\"[Šļáƀ \",[\"thickness\"],\" ţĥîçķ öñ á \",[\"foundation\"],\" ƒöûñðáţîöñ, ƒļööŕ ţöþ \",[\"top\"],\" áƀöṽé ţĥé ĝŕöûñð ~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.slab\":[\"[Šļáƀ ~~]\"],\"editor.panels.floorFoundationPanel.slabThickness\":[\"[Šļáƀ ţĥîçķñéšš ~~~~]\"],\"editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll\":[\"[Ţĥé ƒļööŕ'š öûţšîðé éðĝé, áļļ áŕöûñð ţĥé ƒööţþŕîñţ. ~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround\":[\"[Ţĥé ƒöûñðáţîöñ šţáñðš öñ ţĥé ĝŕöûñð; ţĥé šļáƀ šîţš öñ îţ. ~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation\":[\"[Ţĥé šļáƀ šîţš öñ ţĥé ƒöûñðáţîöñ (öŕ ţĥé ĝŕöûñð): á ţĥîçķéŕ šļáƀ ŕáîšéš ţĥé ƒļööŕ ţöþ áñð éṽéŕýţĥîñĝ öñ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.underTheFloor\":[\"[Ûñðéŕ ţĥé ƒļööŕ ~~~~]\"],\"editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt\":[\"[Å ĥáţçĥ îñ ţĥé ɱéžžáñîñé: îţ çûţš öñļý ţĥé ɱéžžáñîñé’š ƒļööŕ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow\":[\"[Åļšö çûţ ţĥé çéîļîñĝ ƀéļöŵ ~~~~~~~]\"],\"editor.panels.floorOpeningPanel.alsoCutTheFloorAbove\":[\"[Åļšö çûţ ţĥé ƒļööŕ áƀöṽé ~~~~~~]\"],\"editor.panels.floorOpeningPanel.areaDragCorners\":[\"[\",[\"area\"],\" · Ðŕáĝ îţš çöŕñéŕš ţö çĥáñĝé ţĥé šĥáþé. ~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.areaOwned\":[\"[\",[\"area\"],\" · Šĥáþé ƒöļļöŵš îţš öŵñéŕ. ~~~~~~]\"],\"editor.panels.floorOpeningPanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.floorOpeningPanel.done\":[\"[Ðöñé ~~]\"],\"editor.panels.floorOpeningPanel.kicker\":[\"[Öþéñîñĝ ~~~]\"],\"editor.panels.floorOpeningPanel.kickerLevel\":[\"[Öþéñîñĝ · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.kickerRoom\":[\"[Öþéñîñĝ · îñ \",[\"room\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.kickerRoomLevel\":[\"[Öþéñîñĝ · îñ \",[\"room\"],\" · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.oneVoidWith\":[\"[Öñ \",[\"level\"],\", áš öñé ṽöîð ŵîţĥ ţĥîš öþéñîñĝ. ~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.ownedByElevator\":[\"[Öŵñéð ƀý Éļéṽáţöŕ ~~~~~]\"],\"editor.panels.floorOpeningPanel.ownedByStaircase\":[\"[Öŵñéð ƀý Šţáîŕçášé ~~~~~]\"],\"editor.panels.floorOpeningPanel.room\":[\"[Ŕööɱ ~~]\"],\"editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis\":[\"[Ţĥéŕé îš ñö ƒļööŕ áƀöṽé ţĥîš šţöŕéý. ~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.thereIsNoStoreyBelow\":[\"[Ţĥéŕé îš ñö šţöŕéý ƀéļöŵ. ~~~~~~]\"],\"editor.panels.floorOpeningPanel.title\":[\"[\",[\"source\",\"select\",{\"stair\":[\"Šţáîŕ öþéñîñĝ\"],\"elevator\":[\"Éļéṽáţöŕ öþéñîñĝ\"],\"ceiling\":[\"Çéîļîñĝ öþéñîñĝ\"],\"other\":[\"Ƒļööŕ öþéñîñĝ\"]}],\" ~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.alreadyJoined\":[\"[Öñé öƒ ţĥéɱ áļŕéáðý ŵŕáþš áñöţĥéŕ çöŕñéŕ. Ûñŵŕáþ îţ ƒîŕšţ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.between\":[\"[\",[\"kind\",\"select\",{\"item\":[\"Ţĥéŕé îš áñ îţéɱ ƀéţŵééñ ţĥéɱ.\"],\"door\":[\"Ţĥéŕé îš á ðööŕ ƀéţŵééñ ţĥéɱ.\"],\"window\":[\"Ţĥéŕé îš á ŵîñðöŵ ƀéţŵééñ ţĥéɱ.\"],\"shelf\":[\"Ţĥéŕé îš á šĥéļƒ ƀéţŵééñ ţĥéɱ.\"],\"other\":[\"Ţĥéŕé îš šöɱéţĥîñĝ ƀéţŵééñ ţĥéɱ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.cannotMerge\":[\"[Ţĥéšé ŵîñðöŵš çáññöţ ƀé ɱéŕĝéð. ~~~~~~~~]\"],\"editor.panels.mergeRefusal.cornerAngle\":[\"[Ţĥéîŕ ŵáļļš ɱééţ áţ ţöö šĥáŕþ öŕ ţöö ƒļáţ áñ áñĝļé ƒöŕ á çöŕñéŕ ŵîñðöŵ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.heightExceedsWall\":[\"[Ţĥé ŵîñðöŵ šéļéçţéð ƒîŕšţ îš ţáļļéŕ ţĥáñ ţĥé öţĥéŕ ŵáļļ. Šéļéçţ ţĥé öţĥéŕ öñé ƒîŕšţ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notAdjacent\":[\"[Ţĥéý áŕé ţöö ƒáŕ áþáŕţ. Ŵîñðöŵš ɱéŕĝé ŵîţĥ áţ ɱöšţ \",[\"reach\"],\" ƀéţŵééñ ţĥéɱ. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notAtCorner\":[\"[Öñé öƒ ţĥéɱ îš ţöö ƒáŕ ƒŕöɱ ţĥé çöŕñéŕ. Å ŵîñðöŵ ĵöîñš öñé ŵîţĥîñ \",[\"reach\"],\" öƒ îţ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notOnWall\":[\"[Å ŕööƒ öŕ ðöŕɱéŕ ŵîñðöŵ çáññöţ ĵöîñ áñöţĥéŕ ŵîñðöŵ. ~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.overlap\":[\"[\",[\"kind\",\"select\",{\"item\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ áñ îţéɱ.\"],\"door\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á ðööŕ.\"],\"window\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á ŵîñðöŵ.\"],\"shelf\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á šĥéļƒ.\"],\"other\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ šöɱéţĥîñĝ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.wallsNotMeeting\":[\"[Ţĥéîŕ ŵáļļš ðö ñöţ ɱééţ áţ á çöŕñéŕ. ~~~~~~~~~]\"],\"editor.panels.mixed\":[\"[Ṁîẋéð ~~]\"],\"editor.panels.mobilePanelSheet.close\":[\"[Çļöšé ~~]\"],\"editor.panels.mobileSelectionBar.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.mobileSelectionBar.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.panels.mobileSelectionBar.editProperties\":[\"[Éðîţ þŕöþéŕţîéš ~~~~~]\"],\"editor.panels.mobileSelectionBar.editResolvedlabel\":[\"[Éðîţ \",[\"resolvedLabel\"],\" ~~]\"],\"editor.panels.mobileSelectionBar.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.multiHeightMode.currentlyCurrentlabel\":[\"[Çûŕŕéñţļý \",[\"currentLabel\"],\" ~~~]\"],\"editor.panels.multiHeightMode.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"editor.panels.multiHeightMode.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"editor.panels.multiHeightMode.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.multiHeightMode.top\":[\"[Ţöþ ~]\"],\"editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects\":[\"[\",[\"label\"],\" (šéššîöñ öñļý). Þļáîñ çļîçķ ŕéšéļéçţš áļļ ɱéɱƀéŕš. Ñöţ šáṽéð ŵîţĥ ţĥé þŕöĵéçţ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.multiSelectionPanel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.panels.multiSelectionPanel.group\":[\"[Ĝŕöûþ ~~]\"],\"editor.panels.multiSelectionPanel.groupCtrlCmdG\":[\"[Ĝŕöûþ (Çţŕļ/Çɱð+Ĝ) ~~~~]\"],\"editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects\":[\"[\",[\"label\"],\" (šéššîöñ öñļý). Þļáîñ çļîçķ ŕéšéļéçţš áļļ ɱéɱƀéŕš. Ñöţ šáṽéð ŵîţĥ ţĥé þŕöĵéçţ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.lengthSelected\":[\"[\",[\"length\"],\" šéļéçţéð ~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindows\":[\"[Ṁéŕĝé ŵîñðöŵš ~~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow\":[\"[Ṁéŕĝé ŵîñðöŵš îñţö á çöŕñéŕ ŵîñðöŵ: ƀöţĥ ţáķé ţĥé ĥéîĝĥţ, šîļļ áñð ţýþé öƒ ţĥé öñé ýöû šéļéçţéð ƒîŕšţ ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth\":[\"[Ṁéŕĝé ŵîñðöŵš îñţö öñé áçŕöšš ƀöţĥ: îţ ķééþš ţĥé ţýþé áñð šţýļé öƒ ţĥé öñé ýöû šéļéçţéð ƒîŕšţ ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.selectBuilding\":[\"[Šéļéçţ ƀûîļðîñĝ ~~~~~]\"],\"editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove\":[\"[Šéļéçţ ţĥé ŵĥöļé ƀûîļðîñĝ ţö ɱöṽé áļļ îţš ļéṽéļš ~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.ungroup\":[\"[Ûñĝŕöûþ ~~~]\"],\"editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG\":[\"[Ûñĝŕöûþ (Çţŕļ/Çɱð+Šĥîƒţ+Ĝ) ~~~~~~]\"],\"editor.panels.multiSelectionPanel.unwrapTheCorner\":[\"[Ûñŵŕáþ ţĥé çöŕñéŕ ~~~~~]\"],\"editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay\":[\"[Ûñŵŕáþ ţĥé çöŕñéŕ: ƀöţĥ ŵîñðöŵš šţáý, éáçĥ ƒŕáɱéð áţ ţĥé çöŕñéŕ ~~~~~~~~~~~~~~~~]\"],\"editor.panels.panelManager.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"editor.panels.panelWrapper.back\":[\"[Ɓáçķ ~~]\"],\"editor.panels.panelWrapper.close\":[\"[Çļöšé ~~]\"],\"editor.panels.panelWrapper.closeTitle\":[\"[Çļöšé \",[\"title\"],\" ~~]\"],\"editor.panels.panelWrapper.collapsePanel\":[\"[Çöļļáþšé þáñéļ ~~~~]\"],\"editor.panels.panelWrapper.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.panels.panelWrapper.expandPanel\":[\"[Éẋþáñð þáñéļ ~~~~]\"],\"editor.panels.panelWrapper.manageCollections\":[\"[Ṁáñáĝé çöļļéçţîöñš… ~~~~~~]\"],\"editor.panels.panelWrapper.openTitle\":[\"[Öþéñ \",[\"title\"],\" ~~]\"],\"editor.panels.panelWrapper.reset\":[\"[Ŕéšéţ ~~]\"],\"editor.panels.panelWrapper.titleHitAnErrorAndWas\":[\"[“\",[\"title\"],\"” ĥîţ áñ éŕŕöŕ áñð ŵáš ûñļöáðéð ƒöŕ ţĥîš šéššîöñ. ~~~~~~~~~~~~]\"],\"editor.panels.parametricFieldControl.mixed\":[\"[Ṁîẋéð ~~]\"],\"editor.panels.parametricInspector.actions\":[\"[Åçţîöñš ~~~]\"],\"editor.panels.parametricInspector.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.parametricInspector.deleteWithContents\":[\"[Ðéļéţé ŵîţĥ çöñţéñţš ~~~~~~]\"],\"editor.panels.parametricInspector.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.referencePanel.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.panels.referencePanel.capture\":[\"[Çáþţûŕé ~~~]\"],\"editor.panels.referencePanel.center\":[\"[Çéñţéŕ ~~]\"],\"editor.panels.referencePanel.clearScale\":[\"[Çļéáŕ Šçáļé ~~~]\"],\"editor.panels.referencePanel.clickBothEndsOfAKnown\":[\"[Çļîçķ ƀöţĥ éñðš öƒ á ķñöŵñ ðîšţáñçé öñ ţĥé þļáñ, ţĥéñ ţýþé îţš ŕéáļ ļéñĝţĥ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.referencePanel.drawALineOverAKnown\":[\"[Ðŕáŵ á ļîñé öṽéŕ á ķñöŵñ ðîɱéñšîöñ öñ ţĥé þļáñ, ţĥéñ ţýþé îţš ŕéáļ ļéñĝţĥ ţö šçáļé ţĥé îɱáĝé éẋáçţļý. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.editScale\":[\"[Éðîţ Šçáļé ~~~]\"],\"editor.panels.referencePanel.guideImage\":[\"[Ĝûîðé Îɱáĝé ~~~]\"],\"editor.panels.referencePanel.hide\":[\"[Ĥîðé ~~]\"],\"editor.panels.referencePanel.hideScale\":[\"[Ĥîðé Šçáļé ~~~]\"],\"editor.panels.referencePanel.image\":[\"[Îɱáĝé ~~]\"],\"editor.panels.referencePanel.lock\":[\"[Ļöçķ ~~]\"],\"editor.panels.referencePanel.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.referencePanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage\":[\"[Öṽéŕļáý îɱáĝé ûñáṽáîļáƀļé. Ŕéþļáçé ţĥé îɱáĝé ţö ŕéšţöŕé îţ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.pos\":[\"[þöš ~]\"],\"editor.panels.referencePanel.position\":[\"[Þöšîţîöñ ~~~]\"],\"editor.panels.referencePanel.quickActions\":[\"[Ǫûîçķ Åçţîöñš ~~~~]\"],\"editor.panels.referencePanel.referenceScale\":[\"[Ŕéƒéŕéñçé Šçáļé ~~~~~]\"],\"editor.panels.referencePanel.replace\":[\"[Ŕéþļáçé ~~~]\"],\"editor.panels.referencePanel.replacing\":[\"[Ŕéþļáçîñĝ... ~~~]\"],\"editor.panels.referencePanel.resetImageScale\":[\"[Ŕéšéţ Îɱáĝé Šçáļé ~~~~~]\"],\"editor.panels.referencePanel.resetRotation\":[\"[Ŕéšéţ Ŕöţáţîöñ ~~~~]\"],\"editor.panels.referencePanel.rot\":[\"[ŕöţ ~]\"],\"editor.panels.referencePanel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"editor.panels.referencePanel.scale\":[\"[šçáļé ~~]\"],\"editor.panels.referencePanel.scaleOpacity\":[\"[Šçáļé & Öþáçîţý ~~~~]\"],\"editor.panels.referencePanel.scaled\":[\"[Šçáļéð · \",[\"scale\"],\" ~~]\"],\"editor.panels.referencePanel.scaledHidden\":[\"[Šçáļéð (ĥîððéñ) · \",[\"scale\"],\" ~~~~]\"],\"editor.panels.referencePanel.setScale\":[\"[Šéţ Šçáļé ~~~]\"],\"editor.panels.referencePanel.show\":[\"[Šĥöŵ ~~]\"],\"editor.panels.referencePanel.showScale\":[\"[Šĥöŵ Šçáļé ~~~]\"],\"editor.panels.referencePanel.uncalibrated\":[\"[Ûñçáļîƀŕáţéð ~~~~]\"],\"editor.panels.referencePanel.unlock\":[\"[Ûñļöçķ ~~]\"],\"editor.panels.referencePanel.xyz\":[\"[ẊÝŽ ~]\"],\"editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness\":[\"[Å ðŕáŵñ šļáƀ: šéţ îţš ţĥîçķñéšš áñð ƒîñîšĥéš ĥéŕé. Îţ ĥáš ñö ƒööţþŕîñţ ĥéîĝĥţ öŕ ƒöûñðáţîöñ. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.addMezzanine\":[\"[+ Åðð ɱéžžáñîñé ~~~~]\"],\"editor.panels.roomConstructionRows.atFloorLevel\":[\"[Åţ ƒļööŕ ļéṽéļ ~~~~]\"],\"editor.panels.roomConstructionRows.builtOn\":[\"[Ɓûîļţ öñ ~~~]\"],\"editor.panels.roomConstructionRows.builtWith\":[\"[Ɓûîļţ ŵîţĥ ~~~]\"],\"editor.panels.roomConstructionRows.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.panels.roomConstructionRows.ceilingAt\":[\"[Åţ \",[\"height\"],\" ~]\"],\"editor.panels.roomConstructionRows.closeWithWalls\":[\"[Çļöšé ŵîţĥ ŵáļļš ~~~~~]\"],\"editor.panels.roomConstructionRows.drawnFloor\":[\"[\",[\"ambiguous\",\"select\",{\"yes\":[\"Ƒļööŕ îš šéṽéŕáļ ðŕáŵñ šļáƀš, ɱöšţļý \",[\"name\"]],\"other\":[\"Ƒļööŕ îš á ðŕáŵñ šļáƀ: \",[\"name\"]]}],[\"shared\",\"plural\",{\"0\":[],\"one\":[\" (šĥáŕéð ŵîţĥ \",[\"sharedCount\"],\" ŕööɱ)\"],\"other\":[\" (šĥáŕéð ŵîţĥ \",[\"sharedCount\"],\" ŕööɱš)\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.drawnFloorSwitchHint\":[\"[Ţĥîš ƒļööŕ îš á ðŕáŵñ šļáƀ: šéļéçţ îţ ţö éðîţ öŕ ðéļéţé îţ. ~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.drawnSlab\":[\"[Ðŕáŵñ šļáƀ ~~~]\"],\"editor.panels.roomConstructionRows.drawnSlabAt\":[\"[Ðŕáŵñ šļáƀ áţ \",[\"height\"],\" ~~~~]\"],\"editor.panels.roomConstructionRows.editShape\":[\"[Éðîţ šĥáþé ~~~]\"],\"editor.panels.roomConstructionRows.flatAtWallHeight\":[\"[Ƒļáţ, áţ ŵáļļ ĥéîĝĥţ ~~~~~]\"],\"editor.panels.roomConstructionRows.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.panels.roomConstructionRows.floorDown\":[\"[\",[\"distance\"],\" ðöŵñ ~~]\"],\"editor.panels.roomConstructionRows.floorThickness\":[\"[Ƒļööŕ ţĥîçķñéšš ~~~~~]\"],\"editor.panels.roomConstructionRows.floorUp\":[\"[\",[\"distance\"],\" ûþ ~]\"],\"editor.panels.roomConstructionRows.handDrawnCeiling\":[\"[Ĥáñð-ðŕáŵñ çéîļîñĝ ~~~~~]\"],\"editor.panels.roomConstructionRows.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.roomConstructionRows.includeFloor\":[\"[Îñçļûðé ƒļööŕ ~~~~]\"],\"editor.panels.roomConstructionRows.includePart\":[\"[\",[\"part\",\"select\",{\"floor\":[\"Îñçļûðé ƒļööŕ\"],\"walls\":[\"Îñçļûðé ŵáļļš\"],\"other\":[\"Îñçļûðé çéîļîñĝ\"]}],\" ~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.includeWalls\":[\"[Îñçļûðé ŵáļļš ~~~~]\"],\"editor.panels.roomConstructionRows.keepOutsideDimensions\":[\"[Ķééþ öûţšîðé ðîɱéñšîöñš ~~~~~~~]\"],\"editor.panels.roomConstructionRows.lowered\":[\"[Ļöŵéŕéð \",[\"lift\"],\" ~~~]\"],\"editor.panels.roomConstructionRows.newFloor\":[\"[+ Ñéŵ ƒļööŕ ~~~]\"],\"editor.panels.roomConstructionRows.noFloor\":[\"[Ñö ƒļööŕ ~~~]\"],\"editor.panels.roomConstructionRows.notIncluded\":[\"[Ñöţ îñçļûðéð ~~~~]\"],\"editor.panels.roomConstructionRows.nothingToChange\":[\"[Ñöţĥîñĝ ţö çĥáñĝé. ~~~~~]\"],\"editor.panels.roomConstructionRows.onTheGround\":[\"[Öñ ţĥé ĝŕöûñð ~~~~]\"],\"editor.panels.roomConstructionRows.onTheWallsBelow\":[\"[Öñ ţĥé ŵáļļš ƀéļöŵ ~~~~~]\"],\"editor.panels.roomConstructionRows.openSides\":[\"[\",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" öþéñ šîðé\"],\"other\":[[\"openSideCount\"],\" öþéñ šîðéš\"]}],\" ~~~~~~]\"],\"editor.panels.roomConstructionRows.partialCovered\":[\"[Þáŕţîáļ · \",[\"coverage\",\"number\",\"percent\"],\" çöṽéŕéð ~~~~~]\"],\"editor.panels.roomConstructionRows.partialWalls\":[\"[Þáŕţîáļ · \",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" ŵáļļ\"],\"other\":[[\"wallCount\"],\" ŵáļļš\"]}],\", \",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" öþéñ šîðé\"],\"other\":[[\"openSideCount\"],\" öþéñ šîðéš\"]}],\" ~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.raised\":[\"[Ŕáîšéð \",[\"lift\"],\" ~~]\"],\"editor.panels.roomConstructionRows.replace\":[\"[Ŕéþļáçé ~~~]\"],\"editor.panels.roomConstructionRows.select\":[\"[Šéļéçţ ~~]\"],\"editor.panels.roomConstructionRows.shape\":[\"[Šĥáþé ~~]\"],\"editor.panels.roomConstructionRows.sharedWallsNote\":[\"[Ţûŕñîñĝ ŵáļļš öƒƒ ķééþš ţĥé öñéš šĥáŕéð ŵîţĥ \",[\"rooms\"],\". ~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.sitsOnPreset\":[\"[Šîţš öñ: \",[\"name\"],\" · \",[\"preset\"],\" ~~]\"],\"editor.panels.roomConstructionRows.theNextRoom\":[\"[ţĥé ñéẋţ ŕööɱ ~~~~]\"],\"editor.panels.roomConstructionRows.useExisting\":[\"[Ûšé éẋîšţîñĝ ~~~~]\"],\"editor.panels.roomConstructionRows.wallCount\":[\"[\",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" ŵáļļ\"],\"other\":[[\"wallCount\"],\" ŵáļļš\"]}],\" ~~~]\"],\"editor.panels.roomConstructionRows.walls\":[\"[Ŵáļļš ~~]\"],\"editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding\":[\"[Ŵáļļš ţĥîçķéñ îñŵáŕð, šö ţĥé ƀûîļðîñĝ'š öûţéŕ šîžé šţáýš ţĥé šáɱé. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomOpeningsField.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"editor.panels.roomOpeningsField.done\":[\"[Ðöñé ~~]\"],\"editor.panels.roomOpeningsField.editFloor\":[\"[Éðîţ ƒļööŕ ~~~]\"],\"editor.panels.roomOpeningsField.elevatorOpening\":[\"[Éļéṽáţöŕ öþéñîñĝ ~~~~~]\"],\"editor.panels.roomOpeningsField.noOpeningsYetCutOneFor\":[\"[Ñö öþéñîñĝš ýéţ. Çûţ öñé ƒöŕ á šţáîŕ ṽöîð öŕ á ĥáţçĥ. ~~~~~~~~~~~~]\"],\"editor.panels.roomOpeningsField.openingNumber\":[\"[Öþéñîñĝ \",[\"number\"],\" ~~~]\"],\"editor.panels.roomOpeningsField.openings\":[\"[\",[\"count\",\"plural\",{\"0\":[\"Ñö öþéñîñĝš\"],\"one\":[[\"number\"],\" öþéñîñĝ\"],\"other\":[[\"number\"],\" öþéñîñĝš\"]}],\" ~~~~~~~~]\"],\"editor.panels.roomOpeningsField.stairOpening\":[\"[Šţáîŕ öþéñîñĝ ~~~~]\"],\"editor.panels.roomPanel.addStairs\":[\"[Åðð šţáîŕš ~~~]\"],\"editor.panels.roomPanel.addToAUnit\":[\"[Åðð ţö á ûñîţ ~~~]\"],\"editor.panels.roomPanel.area\":[\"[Åŕéá ~~]\"],\"editor.panels.roomPanel.browse\":[\"[Ɓŕöŵšé ~~]\"],\"editor.panels.roomPanel.builtWith\":[\"[Ɓûîļţ ŵîţĥ ~~~]\"],\"editor.panels.roomPanel.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.panels.roomPanel.deleteRoom\":[\"[Ðéļéţé ŕööɱ ~~~]\"],\"editor.panels.roomPanel.editShape\":[\"[Éðîţ šĥáþé ~~~]\"],\"editor.panels.roomPanel.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.panels.roomPanel.headroom\":[\"[\",[\"height\"],\" ĥéáðŕööɱ ~~~]\"],\"editor.panels.roomPanel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.roomPanel.hideCode\":[\"[Ĥîðé çöðé ~~~]\"],\"editor.panels.roomPanel.kickerLevel\":[\"[Ŕööɱ · \",[\"level\"],\" ~~]\"],\"editor.panels.roomPanel.kickerMezzanineIn\":[\"[Ṁéžžáñîñé · îñ \",[\"room\"],\" ~~~~]\"],\"editor.panels.roomPanel.kickerNotEnclosed\":[\"[Ŕööɱ · ñöţ éñçļöšéð ~~~~~]\"],\"editor.panels.roomPanel.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"editor.panels.roomPanel.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.panels.roomPanel.mezzanineName\":[\"[Ṁéžžáñîñé ñáɱé ~~~~]\"],\"editor.panels.roomPanel.mezzanineNames\":[\"[Ṁéžžáñîñé ñáɱéš ~~~~~]\"],\"editor.panels.roomPanel.newUnit\":[\"[Ñéŵ ûñîţ ~~~]\"],\"editor.panels.roomPanel.none\":[\"[Ñöñé ~~]\"],\"editor.panels.roomPanel.notEnoughHeadroom\":[\"[Ñöţ éñöûĝĥ ĥéáðŕööɱ ~~~~~~]\"],\"editor.panels.roomPanel.openRoomArea\":[\"[\",[\"area\"],\" · Çļöšé îţš ŵáļļš ţö ƀûîļð îţš ƒļööŕ áñð çéîļîñĝ. ~~~~~~~~~~~~]\"],\"editor.panels.roomPanel.openingCounts\":[\"[\",[\"doors\",\"plural\",{\"one\":[[\"doorCount\"],\" ðööŕ\"],\"other\":[[\"doorCount\"],\" ðööŕš\"]}],\" · \",[\"windows\",\"plural\",{\"one\":[[\"windowCount\"],\" ŵîñðöŵ\"],\"other\":[[\"windowCount\"],\" ŵîñðöŵš\"]}],\" ~~~~~~~]\"],\"editor.panels.roomPanel.overTheMezzanine\":[\"[Öṽéŕ ţĥé ɱéžžáñîñé ~~~~~]\"],\"editor.panels.roomPanel.perimeter\":[\"[Þéŕîɱéţéŕ ~~~]\"],\"editor.panels.roomPanel.pick\":[\"[Þîçķ ~~]\"],\"editor.panels.roomPanel.pickingItAddsANumber\":[\"[Þîçķîñĝ îţ áððš á ñûɱƀéŕ ~~~~~~]\"],\"editor.panels.roomPanel.remove\":[\"[Ŕéɱöṽé ~~]\"],\"editor.panels.roomPanel.removeFromUnit\":[\"[Ŕéɱöṽé ƒŕöɱ ûñîţ ~~~~~]\"],\"editor.panels.roomPanel.removeFromUnitNamed\":[\"[Ŕéɱöṽé ƒŕöɱ \",[\"unit\"],\" ~~~]\"],\"editor.panels.roomPanel.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.panels.roomPanel.renameNamed\":[\"[\",[\"name\"],\" · Ŕéñáɱé ~~]\"],\"editor.panels.roomPanel.revert\":[\"[Ŕéṽéŕţ ~~]\"],\"editor.panels.roomPanel.room\":[\"[Ŕööɱ ~~]\"],\"editor.panels.roomPanel.roomColourUsedInThePlan\":[\"[Ŕööɱ çöļöûŕ — ûšéð îñ ţĥé þļáñ áñð ļáƀéļš ~~~~~~~~~~]\"],\"editor.panels.roomPanel.roomName\":[\"[Ŕööɱ ñáɱé ~~~]\"],\"editor.panels.roomPanel.roomNames\":[\"[Ŕööɱ ñáɱéš ~~~]\"],\"editor.panels.roomPanel.scriptedFinish\":[\"[Šçŕîþţéð ƒîñîšĥ ~~~~~]\"],\"editor.panels.roomPanel.shape\":[\"[Šĥáþé ~~]\"],\"editor.panels.roomPanel.showMezzanineNames\":[\"[Šĥöŵ ɱéžžáñîñé ñáɱéš ~~~~~~]\"],\"editor.panels.roomPanel.showRoomNames\":[\"[Šĥöŵ ŕööɱ ñáɱéš ~~~~]\"],\"editor.panels.roomPanel.stairs\":[\"[Šţáîŕš ~~]\"],\"editor.panels.roomPanel.unit\":[\"[Ûñîţ ~~]\"],\"editor.panels.roomPanel.use\":[\"[Ûšé ~]\"],\"editor.panels.roomPanel.useTyped\":[[\"use\"],\" “\",[\"name\"],\"”\"],\"editor.panels.roomPanel.usedOnThisLevel\":[\"[\",[\"used\"],\" öñ ţĥîš ļéṽéļ ~~~~]\"],\"editor.panels.roomPanel.viewCode\":[\"[Ṽîéŵ çöðé ~~~]\"],\"editor.panels.scriptUnreadable\":[\"[Ţĥé šçŕîþţ çöûļð ñöţ ƀé ŕéáð. ~~~~~~~]\"],\"editor.panels.shapeChoice.polygon\":[\"[Þöļýĝöñ ~~~]\"],\"editor.panels.shapeChoice.rectangle\":[\"[Ŕéçţáñĝļé ~~~]\"],\"editor.panels.wallRegionList.deleteRegion\":[\"[Ðéļéţé ŕéĝîöñ ~~~~]\"],\"editor.panels.wallRegionList.paintRegions\":[\"[Þáîñţ ŕéĝîöñš ~~~~]\"],\"editor.proceduralItems.inspector.adjustDesign\":[\"[Åðĵûšţ ðéšîĝñ ~~~~]\"],\"editor.proceduralItems.inspector.back\":[\"[Ɓáçķ ~~]\"],\"editor.proceduralItems.inspector.editParts\":[\"[Éðîţ þáŕţš ~~~]\"],\"editor.proceduralItems.inspector.invalidAttachment\":[\"[Îñṽáļîð áţţáçĥɱéñţ ~~~~~~]\"],\"editor.proceduralItems.inspector.invalidEdit\":[\"[Îñṽáļîð éðîţ ~~~~]\"],\"editor.proceduralItems.inspector.invalidPlacement\":[\"[Îñṽáļîð þļáçéɱéñţ ~~~~~]\"],\"editor.proceduralItems.inspector.invalidPosition\":[\"[Îñṽáļîð þöšîţîöñ ~~~~~]\"],\"editor.proceduralItems.inspector.invalidReset\":[\"[Îñṽáļîð ŕéšéţ ~~~~]\"],\"editor.proceduralItems.inspector.pos\":[\"[þöš ~]\"],\"editor.proceduralItems.inspector.position\":[\"[Þöšîţîöñ ~~~]\"],\"editor.proceduralItems.inspector.resetThisPart\":[\"[Ŕéšéţ ţĥîš þáŕţ ~~~~]\"],\"editor.reloadEditor\":[\"[Ŕéļöáð éðîţöŕ ~~~~]\"],\"editor.riserDiagramPanel.close\":[\"[Çļöšé ŕîšéŕ ðîáĝŕáɱ ~~~~~~]\"],\"editor.riserDiagramPanel.dwvPlumbingIsometric\":[\"[ÐŴṼ þļûɱƀîñĝ îšöɱéţŕîç ~~~~~~]\"],\"editor.riserDiagramPanel.dwvRiserDiagram\":[\"[ÐŴṼ ŕîšéŕ ðîáĝŕáɱ ~~~~~]\"],\"editor.riserDiagramPanel.noDrainWasteOrVentPipes\":[\"[Ñö ðŕáîñ, ŵášţé, öŕ ṽéñţ þîþéš ýéţ. Ðŕáŵ þļûɱƀîñĝ ţö šéé ţĥé ŕîšéŕ ðîáĝŕáɱ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.riserDiagramPanel.riserDiagram\":[\"[Ŕîšéŕ Ðîáĝŕáɱ ~~~~]\"],\"editor.riserDiagramPanel.trap\":[\"[\",[\"diameter\"],\"\\\" Þ-ţŕáþ ~~]\"],\"editor.riserDiagramPanel.vent\":[\"[Ṽéñţ ~~]\"],\"editor.riserDiagramPanel.ventTermination\":[\"[\",[\"diameter\"],\"\\\" ṼŢŔ ~]\"],\"editor.riserDiagramPanel.waste\":[\"[Ŵášţé ~~]\"],\"editor.room.builtOn.above\":[\"[\",[\"current\"],\" · \",[\"distance\"],\" áƀöṽé \",[\"shared\"],\" ~~]\"],\"editor.room.builtOn.below\":[\"[\",[\"current\"],\" · \",[\"distance\"],\" ƀéļöŵ \",[\"shared\"],\" ~~]\"],\"editor.room.builtOn.levelWith\":[\"[\",[\"current\"],\" · ļéṽéļ ŵîţĥ \",[\"shared\"],\" ~~~]\"],\"editor.roomLabel.room\":[\"[Ŕööɱ ~~]\"],\"editor.roomLabel.zone\":[\"[Žöñé ~~]\"],\"editor.roomName.attic\":[\"[Åţţîç ~~]\"],\"editor.roomName.balcony\":[\"[Ɓáļçöñý ~~~]\"],\"editor.roomName.basement\":[\"[Ɓášéɱéñţ ~~~]\"],\"editor.roomName.bathroom\":[\"[Ɓáţĥŕööɱ ~~~]\"],\"editor.roomName.bedroom\":[\"[Ɓéðŕööɱ ~~~]\"],\"editor.roomName.boilerRoom\":[\"[Ɓöîļéŕ ŕööɱ ~~~]\"],\"editor.roomName.carport\":[\"[Çáŕþöŕţ ~~~]\"],\"editor.roomName.cellar\":[\"[Çéļļáŕ ~~]\"],\"editor.roomName.closet\":[\"[Çļöšéţ ~~]\"],\"editor.roomName.corridor\":[\"[Çöŕŕîðöŕ ~~~]\"],\"editor.roomName.courtyard\":[\"[Çöûŕţýáŕð ~~~]\"],\"editor.roomName.deck\":[\"[Ðéçķ ~~]\"],\"editor.roomName.den\":[\"[Ðéñ ~]\"],\"editor.roomName.diningRoom\":[\"[Ðîñîñĝ ŕööɱ ~~~]\"],\"editor.roomName.dressingRoom\":[\"[Ðŕéššîñĝ ŕööɱ ~~~~]\"],\"editor.roomName.enSuite\":[\"[Éñ-šûîţé ~~~]\"],\"editor.roomName.entrance\":[\"[Éñţŕáñçé ~~~]\"],\"editor.roomName.familyRoom\":[\"[Ƒáɱîļý ŕööɱ ~~~]\"],\"editor.roomName.gallery\":[\"[Ĝáļļéŕý ~~~]\"],\"editor.roomName.gamesRoom\":[\"[Ĝáɱéš ŕööɱ ~~~]\"],\"editor.roomName.garage\":[\"[Ĝáŕáĝé ~~]\"],\"editor.roomName.garden\":[\"[Ĝáŕðéñ ~~]\"],\"editor.roomName.gardenShed\":[\"[Ĝáŕðéñ šĥéð ~~~]\"],\"editor.roomName.guestRoom\":[\"[Ĝûéšţ ŕööɱ ~~~]\"],\"editor.roomName.gym\":[\"[Ĝýɱ ~]\"],\"editor.roomName.hallway\":[\"[Ĥáļļŵáý ~~~]\"],\"editor.roomName.homeCinema\":[\"[Ĥöɱé çîñéɱá ~~~]\"],\"editor.roomName.kidsRoom\":[\"[Ķîðš' ŕööɱ ~~~]\"],\"editor.roomName.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"editor.roomName.kitchenette\":[\"[Ķîţçĥéñéţţé ~~~~]\"],\"editor.roomName.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"editor.roomName.laundry\":[\"[Ļáûñðŕý ~~~]\"],\"editor.roomName.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.roomName.livingRoom\":[\"[Ļîṽîñĝ ŕööɱ ~~~]\"],\"editor.roomName.loft\":[\"[Ļöƒţ ~~]\"],\"editor.roomName.lounge\":[\"[Ļöûñĝé ~~]\"],\"editor.roomName.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.roomName.mudroom\":[\"[Ṁûðŕööɱ ~~~]\"],\"editor.roomName.nursery\":[\"[Ñûŕšéŕý ~~~]\"],\"editor.roomName.office\":[\"[Öƒƒîçé ~~]\"],\"editor.roomName.pantry\":[\"[Þáñţŕý ~~]\"],\"editor.roomName.patio\":[\"[Þáţîö ~~]\"],\"editor.roomName.playroom\":[\"[Þļáýŕööɱ ~~~]\"],\"editor.roomName.poolArea\":[\"[Þööļ áŕéá ~~~]\"],\"editor.roomName.porch\":[\"[Þöŕçĥ ~~]\"],\"editor.roomName.powderRoom\":[\"[Þöŵðéŕ ŕööɱ ~~~]\"],\"editor.roomName.primaryBedroom\":[\"[Þŕîɱáŕý ƀéðŕööɱ ~~~~~]\"],\"editor.roomName.readingNook\":[\"[Ŕéáðîñĝ ñööķ ~~~~]\"],\"editor.roomName.section.bathrooms\":[\"[Ɓáţĥŕööɱš ~~~]\"],\"editor.roomName.section.circulation\":[\"[Çîŕçûļáţîöñ ~~~~]\"],\"editor.roomName.section.living\":[\"[Ļîṽîñĝ ~~]\"],\"editor.roomName.section.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.roomName.section.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"editor.roomName.section.sleeping\":[\"[Šļééþîñĝ ~~~]\"],\"editor.roomName.section.utility\":[\"[Ûţîļîţý & šţöŕáĝé ~~~~~]\"],\"editor.roomName.section.work\":[\"[Ŵöŕķ & ĥöƀƀîéš ~~~~]\"],\"editor.roomName.showerRoom\":[\"[Šĥöŵéŕ ŕööɱ ~~~]\"],\"editor.roomName.stairwell\":[\"[Šţáîŕŵéļļ ~~~]\"],\"editor.roomName.storage\":[\"[Šţöŕáĝé ~~~]\"],\"editor.roomName.studio\":[\"[Šţûðîö ~~]\"],\"editor.roomName.study\":[\"[Šţûðý ~~]\"],\"editor.roomName.sunroom\":[\"[Šûñŕööɱ ~~~]\"],\"editor.roomName.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"editor.roomName.toilet\":[\"[Ţöîļéţ ~~]\"],\"editor.roomName.utilityRoom\":[\"[Ûţîļîţý ŕööɱ ~~~~]\"],\"editor.roomName.veranda\":[\"[Ṽéŕáñðá ~~~]\"],\"editor.roomName.walkInCloset\":[\"[Ŵáļķ-îñ çļöšéţ ~~~~]\"],\"editor.roomName.workshop\":[\"[Ŵöŕķšĥöþ ~~~]\"],\"editor.sidebar.iconRail.settings\":[\"[Šéţţîñĝš ~~~]\"],\"editor.sidebar.iconRail.site\":[\"[Šîţé ~~]\"],\"editor.sidebar.panels.itemsPanel.all\":[\"[Åļļ ~]\"],\"editor.sidebar.panels.itemsPanel.community\":[\"[Çöɱɱûñîţý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.all\":[\"[Åļļ ~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.community\":[\"[Çöɱɱûñîţý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.mine\":[\"[Ṁîñé ~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch\":[\"[Ñö ŕéšûļţš ƒöŕ “\",[\"search\"],\"” ~~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.search\":[\"[Šéáŕçĥ... ~~]\"],\"editor.sidebar.panels.itemsPanel.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.sidebar.panels.itemsPanel.mine\":[\"[Ṁîñé ~~]\"],\"editor.sidebar.panels.itemsPanel.noResultsForSearch\":[\"[Ñö ŕéšûļţš ƒöŕ “\",[\"search\"],\"” ~~~~]\"],\"editor.sidebar.panels.itemsPanel.search\":[\"[Šéáŕçĥ... ~~]\"],\"editor.sidebar.panels.sitePanel.addLevel\":[\"[Åðð ļéṽéļ ~~~]\"],\"editor.sidebar.panels.sitePanel.addPoint\":[\"[Åðð þöîñţ ~~~]\"],\"editor.sidebar.panels.sitePanel.area\":[\"[Åŕéá: ~~]\"],\"editor.sidebar.panels.sitePanel.baseElevation\":[\"[Ɓášé éļéṽáţîöñ ~~~~]\"],\"editor.sidebar.panels.sitePanel.building\":[\"[Ɓûîļðîñĝ ~~~]\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel\":[\"[Åðð ñéŵ ļéṽéļ ~~~~]\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.building\":[\"[Ɓûîļðîñĝ ~~~]\"],\"editor.sidebar.panels.sitePanel.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.capture\":[\"[Çáþţûŕé ~~~]\"],\"editor.sidebar.panels.sitePanel.clearSelection\":[\"[Çļéáŕ šéļéçţîöñ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo\":[\"[Çļöšé ŵáļļš áŕöûñð á šþáçé ţö ɱáķé á ŕööɱ. ~~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour\":[\"[Çöļļéçţîöñ çöļöûŕ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection\":[\"[Ðéļéţé çöļļéçţîöñ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.sidebar.panels.sitePanel.deleteLevel\":[\"[Ðéļéţé ļéṽéļ ~~~~]\"],\"editor.sidebar.panels.sitePanel.drawAZone\":[\"[Ðŕáŵ á žöñé ~~~]\"],\"editor.sidebar.panels.sitePanel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateLevel\":[\"[Ðûþļîçáţé ļéṽéļ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateLevelWithOptions\":[\"[Ðûþļîçáţé ļéṽéļ ŵîţĥ öþţîöñš ~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateWithOptions\":[\"[Ðûþļîçáţé ŵîţĥ öþţîöñš... ~~~~~~]\"],\"editor.sidebar.panels.sitePanel.elements\":[\"[Éļéɱéñţš ~~~]\"],\"editor.sidebar.panels.sitePanel.guide\":[\"[Ĝûîðé ~~]\"],\"editor.sidebar.panels.sitePanel.hide\":[\"[Ĥîðé ~~]\"],\"editor.sidebar.panels.sitePanel.level\":[\"[Ļéṽéļ ~~]\"],\"editor.sidebar.panels.sitePanel.newUnit\":[\"[Ñéŵ ûñîţ ~~~]\"],\"editor.sidebar.panels.sitePanel.noBuildingsYet\":[\"[Ñö ƀûîļðîñĝš ýéţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.noElementsOnThisLevel\":[\"[Ñö éļéɱéñţš öñ ţĥîš ļéṽéļ ~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.noLevelsYet\":[\"[Ñö ļéṽéļš ýéţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.objectsSelected\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" öƀĵéçţ šéļéçţéð\"],\"other\":[\"#\",\" öƀĵéçţš šéļéçţéð\"]}],\" ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.otherZones\":[\"[Öţĥéŕ žöñéš ~~~]\"],\"editor.sidebar.panels.sitePanel.perimeter\":[\"[Þéŕîɱéţéŕ: ~~~]\"],\"editor.sidebar.panels.sitePanel.propertyLine\":[\"[Þŕöþéŕţý Ļîñé ~~~~]\"],\"editor.sidebar.panels.sitePanel.rooms\":[\"[Ŕööɱš ~~]\"],\"editor.sidebar.panels.sitePanel.selectALevelToViewContent\":[\"[Šéļéçţ á ļéṽéļ ţö ṽîéŵ çöñţéñţ ~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.show\":[\"[Šĥöŵ ~~]\"],\"editor.sidebar.panels.sitePanel.site\":[\"[Šîţé ~~]\"],\"editor.sidebar.panels.sitePanel.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted\":[\"[Ţĥé ĝŕöûñð ļéṽéļ çáññöţ ƀé ðéļéţéð ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.hide\":[\"[Ĥîðé ~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.show\":[\"[Šĥöŵ ~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit\":[\"[Ðéļéţé ûñîţ ~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.focused\":[\"[Ƒöçûšéð ~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit\":[\"[Ŕéɱöṽé ƒŕöɱ ûñîţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.room\":[\"[Ŕööɱ ~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.zone\":[\"[Žöñé ~~]\"],\"editor.sidebar.panels.sitePanel.units\":[\"[Ûñîţš ~~]\"],\"editor.sidebar.panels.sitePanel.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.uploadScanFloorplan\":[\"[Ûþļöáð šçáñ/ƒļööŕþļáñ ~~~~~~]\"],\"editor.sidebar.panels.sitePanel.uploadingProgress\":[\"[\",[\"assetType\",\"select\",{\"guide\":[\"Ûþļöáðîñĝ ĝûîðé... \",[\"progress\"],\"%\"],\"other\":[\"Ûþļöáðîñĝ šçáñ... \",[\"progress\"],\"%\"]}],\" ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname\":[\"[Îñ \",[\"unitName\"],\" ~]\"],\"editor.sidebar.panels.zonePanel.actions\":[\"[Åçţîöñš ~~~]\"],\"editor.sidebar.panels.zonePanel.addOne\":[\"[Åðð öñé ~~]\"],\"editor.sidebar.panels.zonePanel.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.zonePanel.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.zonePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.sidebar.panels.zonePanel.deleteWithContents\":[\"[Ðéļéţé ŵîţĥ çöñţéñţš ~~~~~~]\"],\"editor.sidebar.panels.zonePanel.noZonesOnThisLevel\":[\"[Ñö žöñéš öñ ţĥîš ļéṽéļ. ~~~~~~]\"],\"editor.sidebar.panels.zonePanel.saveToCatalog\":[\"[Šáṽé ţö çáţáļöĝ ~~~~]\"],\"editor.sidebar.panels.zonePanel.selectALevelToViewAnd\":[\"[Šéļéçţ á ļéṽéļ ţö ṽîéŵ áñð çŕéáţé žöñéš ~~~~~~~~~~]\"],\"editor.sidebar.panels.zonePanel.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.zonePanel.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.zonePanel.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.usePluginPanels.labelPluginCrashed\":[\"[\\\"\",[\"label\"],\"\\\" þļûĝîñ çŕášĥéð ~~~~]\"],\"editor.sidebar.usePluginPanels.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"editor.sidebar.usePluginPanels.plugins\":[\"[Þļûĝîñš ~~~]\"],\"editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd\":[\"[Ţĥîš þáñéļ ĥîţ áñ éŕŕöŕ áñð ŵáš ûñļöáðéð ƒöŕ ţĥîš šéššîöñ. Ţĥé ŕéšţ öƒ ţĥé éðîţöŕ îš ûñჃéçţéð — ŕéļöáð ţö ţŕý áĝáîñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.snapTarget.ceiling\":[\"[Çéîļîñĝ áţţáçĥɱéñţ ~~~~~~]\"],\"editor.snapTarget.roof\":[\"[Ŕööƒ áţţáçĥɱéñţ ~~~~~]\"],\"editor.snapTarget.wall\":[\"[Ŵáļļ áţţáçĥɱéñţ ~~~~~]\"],\"editor.theEditorSceneFailedToRender\":[\"[Ţĥé éðîţöŕ šçéñé ƒáîļéð ţö ŕéñðéŕ ~~~~~~~~~]\"],\"editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i\":[\"[Å çûŕţáîñ ŵáļļ ţáķéš ñö çöŕñéŕ ŵîñðöŵ ~~~~~~~~~~]\"],\"editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5\":[\"[Å çûŕṽéð ŵáļļ ţáķéš ñö çöŕñéŕ ŵîñðöŵ ~~~~~~~~~]\"],\"editor.toolHint.addACornerClickTheFirst.o4r67t\":[\"[Åðð á çöŕñéŕ · çļîçķ ţĥé ƒîŕšţ ţö çļöšé ~~~~~~~~~]\"],\"editor.toolHint.anotherOpeningIsInTheWay.11l9dd0\":[\"[Åñöţĥéŕ öþéñîñĝ îš îñ ţĥé ŵáý áţ ţĥé çöŕñéŕ ~~~~~~~~~~~]\"],\"editor.toolHint.autoHangers.10554ka\":[\"[Åûţö ĥáñĝéŕš ~~~~]\"],\"editor.toolHint.autoHangersClickOrPressH.rwa3ka\":[\"[Åûţö ĥáñĝéŕš - çļîçķ öŕ þŕéšš Ĥ ţö ţöĝĝļé ~~~~~~~~~~]\"],\"editor.toolHint.autoHangersOff.7yx01x\":[\"[Åûţö ĥáñĝéŕš: Öƒƒ ~~~~~]\"],\"editor.toolHint.autoHangersOn.10b1umh\":[\"[Åûţö ĥáñĝéŕš: Öñ ~~~~]\"],\"editor.toolHint.bypassSnapping.w5lmdv\":[\"[Ɓýþášš šñáþþîñĝ ~~~~~]\"],\"editor.toolHint.cancel.ew9em3\":[\"[Çáñçéļ ~~]\"],\"editor.toolHint.cancelMove.lvp31c\":[\"[Çáñçéļ ɱöṽé ~~~]\"],\"editor.toolHint.cancelRunExit.zqjf3h\":[\"[Çáñçéļ ŕûñ / éẋîţ ~~~~]\"],\"editor.toolHint.cancelStartPoint.1xbq2lh\":[\"[Çáñçéļ šţáŕţ þöîñţ ~~~~~]\"],\"editor.toolHint.cornerWindow.1avkzpo\":[\"[Çöŕñéŕ ŵîñðöŵ ~~~~]\"],\"editor.toolHint.cuts.1y0axps\":[\"[Çûţš ~~]\"],\"editor.toolHint.cutsValue.ouzhvb\":[\"[Çûţš: \",[\"value\"],\" ~~]\"],\"editor.toolHint.cutsValueEven.1ngsz3\":[\"[Çûţš: \",[\"value\"],\", éṽéñ ~~~]\"],\"editor.toolHint.cycleMonoGableButterfly.1poga3m\":[\"[Çýçļé ɱöñö / ĝáƀļé / ƀûţţéŕƒļý ~~~~~~~]\"],\"editor.toolHint.cycleSnappingMode.1ct0gn6\":[\"[Çýçļé šñáþþîñĝ ɱöðé ~~~~~~]\"],\"editor.toolHint.doorStyleClickOrPressL.33pe65\":[\"[Ðööŕ šţýļé — çļîçķ öŕ þŕéšš Ļ ţö çýçļé ~~~~~~~~~]\"],\"editor.toolHint.doorTypeClickOrPressO.1hhh8gt\":[\"[Ðööŕ ţýþé — çļîçķ öŕ þŕéšš Ö ţö çýçļé ~~~~~~~~~]\"],\"editor.toolHint.dropDownspoutFromOutlet.1k4h1m4\":[\"[Ðŕöþ ðöŵñšþöûţ ƒŕöɱ öûţļéţ ~~~~~~~]\"],\"editor.toolHint.ductDiameterDownUp.1wdz5vq\":[\"[Ðûçţ ðîáɱéţéŕ ðöŵñ / ûþ ~~~~~~]\"],\"editor.toolHint.endRunCancel.59dh2w\":[\"[Éñð ŕûñ / çáñçéļ ~~~~]\"],\"editor.toolHint.exit.engmg5\":[\"[Éẋîţ ~~]\"],\"editor.toolHint.exitDrawing.9noxyb\":[\"[Éẋîţ ðŕáŵîñĝ ~~~~]\"],\"editor.toolHint.finishAndContinue.1gpfsks\":[\"[Ƒîñîšĥ áñð çöñţîñûé ~~~~~~]\"],\"editor.toolHint.finishCeiling.1c1lfel\":[\"[Ƒîñîšĥ çéîļîñĝ ~~~~]\"],\"editor.toolHint.finishGridAxis.1wvrfhr\":[\"[Ƒîñîšĥ ĝŕîð áẋîš ~~~~~]\"],\"editor.toolHint.finishMeasurement.1e4gv16\":[\"[Ƒîñîšĥ ɱéášûŕéɱéñţ ~~~~~~]\"],\"editor.toolHint.finishMultiPointWitnesses.8wzskz\":[\"[Ƒîñîšĥ ɱûļţî-þöîñţ ŵîţñéššéš ~~~~~~~~]\"],\"editor.toolHint.finishSlab.xmnm8e\":[\"[Ƒîñîšĥ šļáƀ ~~~]\"],\"editor.toolHint.flipSide.172xku9\":[\"[Ƒļîþ šîðé ~~~]\"],\"editor.toolHint.followTraceALineset.mgquwe\":[\"[Ƒöļļöŵ: ţŕáçé á ļîñéšéţ ~~~~~~]\"],\"editor.toolHint.forcePlace.1hq1nr\":[\"[Ƒöŕçé þļáçé ~~~]\"],\"editor.toolHint.freePlacement.1nztdac\":[\"[Ƒŕéé þļáçéɱéñţ ~~~~]\"],\"editor.toolHint.goVerticalClickToPlace.dg8g6m\":[\"[Ĝö ṽéŕţîçáļ ↕, çļîçķ ţö þļáçé ~~~~~~~]\"],\"editor.toolHint.highlightOutlet.65hoq8\":[\"[Ĥîĝĥļîĝĥţ öûţļéţ ~~~~~]\"],\"editor.toolHint.joinAsACornerWindow.jvdp4j\":[\"[Ĵöîñ áš á çöŕñéŕ ŵîñðöŵ ~~~~~~]\"],\"editor.toolHint.key.altDrag.ai3wn9\":[\"[Åļţ + ðŕáĝ ~~~]\"],\"editor.toolHint.key.backspace.qy6qx8\":[\"[Ɓáçķšþáçé ~~~]\"],\"editor.toolHint.key.click.m8sun\":[\"[Çļîçķ ~~]\"],\"editor.toolHint.key.clickAgain.190ws9j\":[\"[Çļîçķ áĝáîñ ~~~]\"],\"editor.toolHint.key.command.1j2mkia\":[\"[Çöɱɱáñð ~~~]\"],\"editor.toolHint.key.control.10fbl8e\":[\"[Çöñţŕöļ ~~~]\"],\"editor.toolHint.key.delete.oay2cq\":[\"[Ðéļéţé ~~]\"],\"editor.toolHint.key.doubleClick.vlma6o\":[\"[Ðöûƀļé çļîçķ ~~~~]\"],\"editor.toolHint.key.drag.8pwtpf\":[\"[Ðŕáĝ ~~]\"],\"editor.toolHint.key.enter.1ctpui5\":[\"[Éñţéŕ ~~]\"],\"editor.toolHint.key.hover.6xw79p\":[\"[Ĥöṽéŕ ~~]\"],\"editor.toolHint.key.hoverADuctEnd.19axr3b\":[\"[Ĥöṽéŕ á ðûçţ éñð ~~~~]\"],\"editor.toolHint.key.hoverAPipeEnd.1jbum5f\":[\"[Ĥöṽéŕ á þîþé éñð ~~~~]\"],\"editor.toolHint.key.hoverGutter.1dwcsno\":[\"[Ĥöṽéŕ ĝûţţéŕ ~~~~]\"],\"editor.toolHint.key.leftClick.s4gwfc\":[\"[Ļéƒţ çļîçķ ~~~]\"],\"editor.toolHint.key.middleClick.1jbaan4\":[\"[Ṁîððļé çļîçķ ~~~~]\"],\"editor.toolHint.key.rShiftR.13bnobh\":[\"[Ŕ / Šĥîƒţ+Ŕ ~~~]\"],\"editor.toolHint.key.rightClick.1ovld0h\":[\"[Ŕîĝĥţ çļîçķ ~~~]\"],\"editor.toolHint.key.scroll.ai6wr0\":[\"[Šçŕöļļ ~~]\"],\"editor.toolHint.key.scrollWheel.18j0ix7\":[\"[Šçŕöļļ ŵĥééļ ~~~~]\"],\"editor.toolHint.key.space.1hrh82t\":[\"[Šþáçé ~~]\"],\"editor.toolHint.key.tab.1xsal0s\":[\"[Ţáƀ ~]\"],\"editor.toolHint.mountFloorCeilingWall.32dxpv\":[\"[Ṁöûñţ: ƒļööŕ / çéîļîñĝ / ŵáļļ ~~~~~~~]\"],\"editor.toolHint.noRoomForTheMatchingWindow.1755948\":[\"[Ñö ŕööɱ ƒöŕ ţĥé ɱáţçĥîñĝ ŵîñðöŵ öñ ţĥé öţĥéŕ ŵáļļ ~~~~~~~~~~~~]\"],\"editor.toolHint.noWallMeetsHere.19n99yc\":[\"[Ñö ŵáļļ ɱééţš ĥéŕé ~~~~~]\"],\"editor.toolHint.numberOfCutsScrollOrClick.1kqe84b\":[\"[Ñûɱƀéŕ öƒ çûţš — šçŕöļļ öŕ çļîçķ ţö çĥáñĝé ~~~~~~~~~~]\"],\"editor.toolHint.pickWitnessPoint.k3hgh1\":[\"[Þîçķ ŵîţñéšš þöîñţ ~~~~~]\"],\"editor.toolHint.pipeSizeDownUp.wp04og\":[\"[Þîþé šîžé ðöŵñ / ûþ ~~~~~]\"],\"editor.toolHint.placeAndContinue.qfwkrq\":[\"[Þļáçé áñð çöñţîñûé ~~~~~]\"],\"editor.toolHint.placeBlock.1aylwrx\":[\"[Þļáçé ƀļöçķ ~~~]\"],\"editor.toolHint.placeBoxVentOnRoof.kf88e5\":[\"[Þļáçé ƀöẋ ṽéñţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeCabinet.b0ouyk\":[\"[Þļáçé çáƀîñéţ ~~~~]\"],\"editor.toolHint.placeCanopyOrSetTheNext.12l7zf8\":[\"[Þļáçé çáñöþý öŕ šéţ ţĥé ñéẋţ ŕûñ þöîñţ ~~~~~~~~~~]\"],\"editor.toolHint.placeChimneyOnRoof.19dbqu\":[\"[Þļáçé çĥîɱñéý öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeColumn.1w2vq86\":[\"[Þļáçé çöļûɱñ ~~~~]\"],\"editor.toolHint.placeCupolaOnRoof.ofx3bn\":[\"[Þļáçé çûþöļá öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj\":[\"[Þļáçé ðîɱéñšîöñ ļîñé ŵĥéñ ñééðéð ~~~~~~~~~]\"],\"editor.toolHint.placeDoorOnWall.1jwadf\":[\"[Þļáçé ðööŕ öñ ŵáļļ ~~~~~]\"],\"editor.toolHint.placeDormerOnRoof.1w518a6\":[\"[Þļáçé ðöŕɱéŕ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeElevator.1f65zva\":[\"[Þļáçé éļéṽáţöŕ ~~~~]\"],\"editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn\":[\"[Þļáçé éýéƀŕöŵ ṽéñţ öñ ŕööƒ ~~~~~~~]\"],\"editor.toolHint.placeFitting.1pmx3vn\":[\"[Þļáçé ƒîţţîñĝ ~~~~]\"],\"editor.toolHint.placeGutterOnRoofEave.llwzf9\":[\"[Þļáçé ĝûţţéŕ öñ ŕööƒ éáṽé ~~~~~~~]\"],\"editor.toolHint.placeItLockedTo45.16e4csk\":[\"[Þļáçé îţ (ļöçķéð ţö 45°) ~~~~~]\"],\"editor.toolHint.placeItem.teynf\":[\"[Þļáçé îţéɱ ~~~]\"],\"editor.toolHint.placeMeasurementPoint.bsatli\":[\"[Þļáçé ɱéášûŕéɱéñţ þöîñţ ~~~~~~~]\"],\"editor.toolHint.placeOnAFence.1dptstr\":[\"[Þļáçé öñ á ƒéñçé ~~~~]\"],\"editor.toolHint.placeRegister.1uebycd\":[\"[Þļáçé ŕéĝîšţéŕ ~~~~]\"],\"editor.toolHint.placeRidgeVentOnRoof.2tpd8r\":[\"[Þļáçé ŕîðĝé ṽéñţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeShelf.y4l3iw\":[\"[Þļáçé šĥéļƒ ~~~]\"],\"editor.toolHint.placeSkylightOnRoof.e215pe\":[\"[Þļáçé šķýļîĝĥţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn\":[\"[Þļáçé šöļáŕ þáñéļ áŕŕáý öñ ŕööƒ ~~~~~~~~]\"],\"editor.toolHint.placeSpawnPoint.205o25\":[\"[Þļáçé šþáŵñ þöîñţ ~~~~~]\"],\"editor.toolHint.placeStairs.zwxxu2\":[\"[Þļáçé šţáîŕš ~~~~]\"],\"editor.toolHint.placeTrap.1gxemn3\":[\"[Þļáçé ţŕáþ ~~~]\"],\"editor.toolHint.placeTurbineVentOnRoof.1iqeluf\":[\"[Þļáçé ţûŕƀîñé ṽéñţ öñ ŕööƒ ~~~~~~~]\"],\"editor.toolHint.placeUnit.8zkx4q\":[\"[Þļáçé ûñîţ ~~~]\"],\"editor.toolHint.placeWindowOnWall.1k3qnfb\":[\"[Þļáçé ŵîñðöŵ öñ ŵáļļ ~~~~~~]\"],\"editor.toolHint.placeWithoutTheCornerSnap.1nifqew\":[\"[Þļáçé ŵîţĥöûţ ţĥé çöŕñéŕ šñáþ ~~~~~~~~]\"],\"editor.toolHint.placement.13lcvts\":[\"[Þļáçéɱéñţ ~~~]\"],\"editor.toolHint.placementAuto.1caa52d\":[\"[Þļáçéɱéñţ: Åûţö ~~~~]\"],\"editor.toolHint.placementGround.1ruz145\":[\"[Þļáçéɱéñţ: Ĝŕöûñð ~~~~~]\"],\"editor.toolHint.placementRoof.renkw8\":[\"[Þļáçéɱéñţ: Ŕööƒ ~~~~]\"],\"editor.toolHint.placementSurfaceClickOrPressP.w56h19\":[\"[Þļáçéɱéñţ šûŕƒáçé - çļîçķ öŕ þŕéšš Þ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.toolHint.placementType.1mdpl60\":[\"[Þļáçéɱéñţ ţýþé ~~~~]\"],\"editor.toolHint.placementTypeClickOrPressI.10cgxh2\":[\"[Þļáçéɱéñţ ţýþé — çļîçķ öŕ þŕéšš Î ţö ţöĝĝļé ~~~~~~~~~~~]\"],\"editor.toolHint.removeLastPoint.2dmdcx\":[\"[Ŕéɱöṽé ļášţ þöîñţ ~~~~~]\"],\"editor.toolHint.removeLastWitness.1b6vvzw\":[\"[Ŕéɱöṽé ļášţ ŵîţñéšš ~~~~~~]\"],\"editor.toolHint.rotate.1thqa0a\":[\"[Ŕöţáţé ~~]\"],\"editor.toolHint.rotate45.9zq68u\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.toolHint.rotate45FloorCeiling.m9t57d\":[\"[Ŕöţáţé ±45° (ƒļööŕ / çéîļîñĝ) ~~~~~~]\"],\"editor.toolHint.rotateGhost15.rsa52c\":[\"[Ŕöţáţé ĝĥöšţ ±15° ~~~~]\"],\"editor.toolHint.rotateOrFlipTheRunSide.t5ior7\":[\"[Ŕöţáţé öŕ ƒļîþ ţĥé ŕûñ šîðé ~~~~~~~]\"],\"editor.toolHint.rotateRoofDirection90.tvbji2\":[\"[Ŕöţáţé ŕööƒ ðîŕéçţîöñ 90° ~~~~~~]\"],\"editor.toolHint.rotateSpawnPoint.1opcuu7\":[\"[Ŕöţáţé šþáŵñ þöîñţ ~~~~~]\"],\"editor.toolHint.roundRectangularOval.e4nh6x\":[\"[Ŕöûñð / ŕéçţáñĝûļáŕ / öṽáļ ~~~~~~]\"],\"editor.toolHint.setFenceStartEnd.1phbwsq\":[\"[Šéţ ƒéñçé šţáŕţ / éñð ~~~~~]\"],\"editor.toolHint.setOneCornerThenTheOpposite.8tsl49\":[\"[Šéţ öñé çöŕñéŕ, ţĥéñ ţĥé öþþöšîţé ~~~~~~~~~]\"],\"editor.toolHint.setRoofFootprint.p50n0o\":[\"[Šéţ ŕööƒ ƒööţþŕîñţ ~~~~~]\"],\"editor.toolHint.setWallStartEnd.1r8ubcx\":[\"[Šéţ ŵáļļ šţáŕţ / éñð ~~~~~]\"],\"editor.toolHint.slopeLevel.ftqp67\":[\"[Šļöþé / ļéṽéļ ~~~]\"],\"editor.toolHint.snapOntoTheRun.86yhmp\":[\"[Šñáþ öñţö ţĥé ŕûñ ~~~~~]\"],\"editor.toolHint.splitAtTheMarks.x5tpdd\":[\"[Šþļîţ áţ ţĥé ɱáŕķš ~~~~~]\"],\"editor.toolHint.startGridAxis.k92yza\":[\"[Šţáŕţ ĝŕîð áẋîš ~~~~]\"],\"editor.toolHint.startLineset.23q8dh\":[\"[Šţáŕţ ļîñéšéţ ~~~~]\"],\"editor.toolHint.startLiquidLine.1rlxsgd\":[\"[Šţáŕţ ļîǫûîð ļîñé ~~~~~]\"],\"editor.toolHint.startRun.r6fc5u\":[\"[Šţáŕţ ŕûñ ~~~]\"],\"editor.toolHint.startSegment.rolym6\":[\"[Šţáŕţ šéĝɱéñţ ~~~~]\"],\"editor.toolHint.stepBackOrCancel.zbfdkn\":[\"[Šţéþ ƀáçķ öŕ çáñçéļ ~~~~~]\"],\"editor.toolHint.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.toolHint.styleBarn.k9n1x\":[\"[Šţýļé: Ɓáŕñ ~~~]\"],\"editor.toolHint.styleCasement.ziklec\":[\"[Šţýļé: Çášéɱéñţ ~~~~]\"],\"editor.toolHint.styleCraftsman.52e6a9\":[\"[Šţýļé: Çŕáƒţšɱáñ ~~~~~]\"],\"editor.toolHint.styleDoubleHung.1lg8pkg\":[\"[Šţýļé: Ðöûƀļé-ĥûñĝ ~~~~~]\"],\"editor.toolHint.styleFrench.1ue3j64\":[\"[Šţýļé: Ƒŕéñçĥ ~~~~]\"],\"editor.toolHint.styleGlass.qudnb4\":[\"[Šţýļé: Ĝļášš ~~~]\"],\"editor.toolHint.styleGrid.1vuq4po\":[\"[Šţýļé: Ĝŕîð ~~~]\"],\"editor.toolHint.styleHalfLouvered.13b1few\":[\"[Šţýļé: Ĥáļƒ-ļöûṽéŕéð ~~~~~~]\"],\"editor.toolHint.styleHorizontalBands.12ethxk\":[\"[Šţýļé: Ĥöŕîžöñţáļ ƀáñðš ~~~~~~]\"],\"editor.toolHint.styleModern.1fh8hhn\":[\"[Šţýļé: Ṁöðéŕñ ~~~~]\"],\"editor.toolHint.stylePanel.922jze\":[\"[Šţýļé: Þáñéļ ~~~]\"],\"editor.toolHint.stylePaneledGlass.17a17en\":[\"[Šţýļé: Þáñéļéð ĝļášš ~~~~~~]\"],\"editor.toolHint.styleShaker.49z17k\":[\"[Šţýļé: Šĥáķéŕ ~~~~]\"],\"editor.toolHint.styleSingle.6f3cm0\":[\"[Šţýļé: Šîñĝļé ~~~~]\"],\"editor.toolHint.styleSixPanel.u49laj\":[\"[Šţýļé: Šîẋ-þáñéļ ~~~~]\"],\"editor.toolHint.styleSliding.q81s0i\":[\"[Šţýļé: Šļîðîñĝ ~~~~]\"],\"editor.toolHint.styleTallGrid.1eotg3x\":[\"[Šţýļé: Ţáļļ ĝŕîð ~~~~]\"],\"editor.toolHint.styleTransom.kb9bh6\":[\"[Šţýļé: Ţŕáñšöɱ ~~~~]\"],\"editor.toolHint.styleTripleHung.pxnqi3\":[\"[Šţýļé: Ţŕîþļé-ĥûñĝ ~~~~~]\"],\"editor.toolHint.styleWideGrid.12n27et\":[\"[Šţýļé: Ŵîðé ĝŕîð ~~~~]\"],\"editor.toolHint.switchRotationAxisYXZ.15lohm6\":[\"[Šŵîţçĥ ŕöţáţîöñ áẋîš (Ý → Ẋ → Ž) ~~~~~~~]\"],\"editor.toolHint.theWallsMeetAtTooSharp.kh3wi3\":[\"[Ţĥé ŵáļļš ɱééţ áţ ţöö šĥáŕþ öŕ ţöö ƒļáţ áñ áñĝļé ~~~~~~~~~~~~]\"],\"editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4\":[\"[Ţĥé ŵîñðöŵ ŵáîţîñĝ ţĥéŕé îš ţáļļéŕ ţĥáñ ţĥîš ŵáļļ ~~~~~~~~~~~~~]\"],\"editor.toolHint.threeOrMoreWallsMeetHere.rgifif\":[\"[Ţĥŕéé öŕ ɱöŕé ŵáļļš ɱééţ ĥéŕé ~~~~~~~~]\"],\"editor.toolHint.traceCeilingOutline.1b639wv\":[\"[Ţŕáçé çéîļîñĝ öûţļîñé ~~~~~~]\"],\"editor.toolHint.traceSlabOutline.z0j2lc\":[\"[Ţŕáçé šļáƀ öûţļîñé ~~~~~]\"],\"editor.toolHint.type.1m2zofh\":[\"[Ţýþé ~~]\"],\"editor.toolHint.typeAwning.1cj59al\":[\"[Ţýþé: Åŵñîñĝ ~~~]\"],\"editor.toolHint.typeBarn.sbnygq\":[\"[Ţýþé: Ɓáŕñ ~~~]\"],\"editor.toolHint.typeBay.1200hc1\":[\"[Ţýþé: Ɓáý ~~~]\"],\"editor.toolHint.typeBow.14s2t9p\":[\"[Ţýþé: Ɓöŵ ~~~]\"],\"editor.toolHint.typeCabinet.1r68je5\":[\"[Ţýþé: Çáƀîñéţ ~~~~]\"],\"editor.toolHint.typeCasement.176uo9j\":[\"[Ţýþé: Çášéɱéñţ ~~~~]\"],\"editor.toolHint.typeDouble.af7w4i\":[\"[Ţýþé: Ðöûƀļé ~~~]\"],\"editor.toolHint.typeDoubleHung.t482fu\":[\"[Ţýþé: Ðöûƀļé Ĥûñĝ ~~~~~]\"],\"editor.toolHint.typeFixed.v1cjez\":[\"[Ţýþé: Ƒîẋéð ~~~]\"],\"editor.toolHint.typeFolding.sxxyng\":[\"[Ţýþé: Ƒöļðîñĝ ~~~~]\"],\"editor.toolHint.typeFrench.1y57z1b\":[\"[Ţýþé: Ƒŕéñçĥ ~~~]\"],\"editor.toolHint.typeHinged.1xrcfju\":[\"[Ţýþé: Ĥîñĝéð ~~~]\"],\"editor.toolHint.typeIsland.ufr3ce\":[\"[Ţýþé: Κļáñð ~~~]\"],\"editor.toolHint.typeLouvered.oxl1cz\":[\"[Ţýþé: Ļöûṽéŕéð ~~~~]\"],\"editor.toolHint.typePocket.hc65x9\":[\"[Ţýþé: Þöçķéţ ~~~]\"],\"editor.toolHint.typeSingleHung.p53pyl\":[\"[Ţýþé: Šîñĝļé Ĥûñĝ ~~~~~]\"],\"editor.toolHint.typeSliding.nwmr3f\":[\"[Ţýþé: Šļîðîñĝ ~~~~]\"],\"editor.toolHint.verticalStackClickToPlace.ijcn4q\":[\"[Ṽéŕţîçáļ šţáçķ ↕, çļîçķ ţö þļáçé ~~~~~~~~]\"],\"editor.toolHint.wasteVent.1hvi3vj\":[\"[Ŵášţé / ṽéñţ ~~~]\"],\"editor.toolHint.windowStyleClickOrPressL.ah7qm9\":[\"[Ŵîñðöŵ šţýļé — çļîçķ öŕ þŕéšš Ļ ţö çýçļé ~~~~~~~~~~]\"],\"editor.toolHint.windowTypeClickOrPressO.wv4x35\":[\"[Ŵîñðöŵ ţýþé — çļîçķ öŕ þŕéšš Ö ţö çýçļé ~~~~~~~~~]\"],\"editor.tools.distribution.distributionRunTool.lengthField\":[\"[Ļéñĝţĥ: \",[\"field\"],\" ɱ ~~~]\"],\"editor.tools.distribution.distributionRunTool.tabDirection\":[\"[Ţáƀ: ðîŕéçţîöñ ~~~~]\"],\"editor.tools.distribution.distributionRunTool.typeALength\":[\"[ţýþé á ļéñĝţĥ ~~~~]\"],\"editor.views.plugins\":[\"[Þļûĝîñš ~~~]\"],\"editor.views.view2d\":[\"[2Ð ~]\"],\"editor.views.view3d\":[\"[3Ð ~]\"],\"editor.views.viewBar.auto\":[\"[Åûţö ~~]\"],\"editor.views.viewBar.browsePlugins\":[\"[Ɓŕöŵšé þļûĝîñš ~~~~]\"],\"editor.views.viewBar.closeLabel\":[\"[Çļöšé \",[\"label\"],\" ~~]\"],\"editor.views.viewBar.closeSplit\":[\"[Çļöšé šþļîţ (\\\\) ~~~]\"],\"editor.views.viewBar.ground\":[\"[Ĝŕöûñð ~~]\"],\"editor.views.viewBar.groundSolid\":[\"[\",[\"standing\",\"select\",{\"partly\":[\"Þáŕţļý ƀéļöŵ ĝŕáðé · ĝŕöûñð šöļîð\"],\"below\":[\"Ɓéļöŵ ĝŕáðé · ĝŕöûñð šöļîð\"],\"other\":[\"Ĝŕöûñð šöļîð\"]}],\" ~~~~~~~~~~~~~~~~~~]\"],\"editor.views.viewBar.groundVeiled\":[\"[\",[\"standing\",\"select\",{\"partly\":[\"Þáŕţļý ƀéļöŵ ĝŕáðé · ĝŕöûñð ṽéîļéð\"],\"below\":[\"Ɓéļöŵ ĝŕáðé · ĝŕöûñð ṽéîļéð\"],\"other\":[\"Ĝŕöûñð ṽéîļéð\"]}],\" ~~~~~~~~~~~~~~~~~~~]\"],\"editor.views.viewBar.keepTheGroundSolid\":[\"[Ķééþ ţĥé ĝŕöûñð šöļîð ~~~~~~]\"],\"editor.views.viewBar.moreViews\":[\"[Ṁöŕé ṽîéŵš ~~~]\"],\"editor.views.viewBar.paneViews\":[\"[Þáñé \",[\"pane\"],\" ṽîéŵš ~~~]\"],\"editor.views.viewBar.pinLabel\":[\"[Þîñ \",[\"label\"],\" ~]\"],\"editor.views.viewBar.pinToTheViewBar\":[\"[Þîñ ţö ţĥé ṽîéŵ ƀáŕ ~~~~~]\"],\"editor.views.viewBar.solid\":[\"[Šöļîð ~~]\"],\"editor.views.viewBar.split\":[\"[Šþļîţ (\\\\) ~~]\"],\"editor.views.viewBar.split2\":[\"[Šþļîţ ~~]\"],\"editor.views.viewBar.swapPanes\":[\"[Šŵáþ þáñéš ~~~]\"],\"editor.views.viewBar.unpinFromTheViewBar\":[\"[Ûñþîñ ƒŕöɱ ţĥé ṽîéŵ ƀáŕ ~~~~~~]\"],\"editor.views.viewBar.unpinLabel\":[\"[Ûñþîñ \",[\"label\"],\" ~~]\"],\"editor.views.viewBar.veilTheGroundOverALevel\":[\"[Ṽéîļ ţĥé ĝŕöûñð öṽéŕ á ļéṽéļ ƀéļöŵ ĝŕáðé ~~~~~~~~~~]\"],\"editor.views.viewBar.views\":[\"[Ṽîéŵš ~~]\"],\"editor.youCanRetryTheSceneOr\":[\"[Ýöû çáñ ŕéţŕý ţĥé šçéñé öŕ ŕéţûŕñ ĥöɱé ŵîţĥöûţ ŕéļöáðîñĝ ţĥé ŵĥöļé áþþ šĥéļļ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.block.description\":[\"[Å ţöþöļöĝý-ƀáçķéð šöļîð éðîţéð ðîŕéçţļý îñ ţĥé çáñṽáš. ~~~~~~~~~~~~~~]\"],\"node.block.label\":[\"[Ɓļöçķ ~~]\"],\"node.boxVent.description\":[\"[Šɱáļļ ļöûṽéŕéð éẋĥáûšţ ṽéñţ ţĥáţ šîţš öñ á ŕööƒ šļöþé. ~~~~~~~~~~~~~~]\"],\"node.boxVent.label\":[\"[Ɓöẋ Ṽéñţ ~~~]\"],\"node.boxVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.boxVent.panel.baseFlange\":[\"[Ɓášé Ƒļáñĝé ~~~]\"],\"node.boxVent.panel.baseHeight\":[\"[Ɓášé Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.baseInset\":[\"[Ɓášé Îñšéţ ~~~]\"],\"node.boxVent.panel.box\":[\"[Ɓöẋ ~]\"],\"node.boxVent.panel.boxVent\":[\"[Ɓöẋ Ṽéñţ ~~~]\"],\"node.boxVent.panel.cap\":[\"[Çáþ ~]\"],\"node.boxVent.panel.capHeight\":[\"[Çáþ Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.cornerBevel\":[\"[Çöŕñéŕ Ɓéṽéļ ~~~~]\"],\"node.boxVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.boxVent.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.boxVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.boxVent.panel.dome\":[\"[Ðöɱé ~~]\"],\"node.boxVent.panel.domeCurvature\":[\"[Ðöɱé Çûŕṽáţûŕé ~~~~]\"],\"node.boxVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.boxVent.panel.gapHeight\":[\"[Ĝáþ Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.boxVent.panel.hoodOverhang\":[\"[Ĥööð Öṽéŕĥáñĝ ~~~~]\"],\"node.boxVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.boxVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.boxVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.boxVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.boxVent.panel.topTaper\":[\"[Ţöþ Ţáþéŕ ~~~]\"],\"node.boxVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.building.description\":[\"[Å ƀûîļðîñĝ çöñţáîñéŕ ĥöļðîñĝ öñé öŕ ɱöŕé ļéṽéļš. ~~~~~~~~~~~~]\"],\"node.building.label\":[\"[Ɓûîļðîñĝ ~~~]\"],\"node.cabinet.compartmentCard.baskets\":[\"[Ɓášķéţš ~~~]\"],\"node.cabinet.compartmentCard.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.cabinet.compartmentCard.bottomFreezer\":[\"[Ɓöţţöɱ Ƒŕééžéŕ ~~~~]\"],\"node.cabinet.compartmentCard.bowls\":[\"[Ɓöŵļš ~~]\"],\"node.cabinet.compartmentCard.burnersOn\":[\"[Ɓûŕñéŕš öñ ~~~]\"],\"node.cabinet.compartmentCard.chimney\":[\"[Çĥîɱñéý ~~~]\"],\"node.cabinet.compartmentCard.decrease\":[\"[Ðéçŕéášé \",[\"label\"],\" ~~~]\"],\"node.cabinet.compartmentCard.door\":[\"[Ðööŕ ~~]\"],\"node.cabinet.compartmentCard.double\":[\"[Ðöûƀļé ~~]\"],\"node.cabinet.compartmentCard.drawer\":[\"[Ðŕáŵéŕ ~~]\"],\"node.cabinet.compartmentCard.drawers\":[\"[Ðŕáŵéŕš ~~~]\"],\"node.cabinet.compartmentCard.flipHinge\":[\"[Ƒļîþ ĥîñĝé ~~~]\"],\"node.cabinet.compartmentCard.flipTheDoorHingeToThe\":[\"[Ƒļîþ ţĥé ðööŕ ĥîñĝé ţö ţĥé öþþöšîţé šîðé ~~~~~~~~~~]\"],\"node.cabinet.compartmentCard.fridge\":[\"[Ƒŕîðĝé ~~]\"],\"node.cabinet.compartmentCard.gas\":[\"[Ĝáš ~]\"],\"node.cabinet.compartmentCard.glass\":[\"[Ĝļášš ~~]\"],\"node.cabinet.compartmentCard.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cabinet.compartmentCard.hob\":[\"[Ĥöƀ ~]\"],\"node.cabinet.compartmentCard.increase\":[\"[Îñçŕéášé \",[\"label\"],\" ~~~]\"],\"node.cabinet.compartmentCard.induction\":[\"[Îñðûçţîöñ ~~~]\"],\"node.cabinet.compartmentCard.layout\":[\"[Ļáýöûţ ~~]\"],\"node.cabinet.compartmentCard.left\":[\"[Ļéƒţ ~~]\"],\"node.cabinet.compartmentCard.micro\":[\"[Ṁîçŕö ~~]\"],\"node.cabinet.compartmentCard.moveDown\":[\"[Ṁöṽé ðöŵñ ~~~]\"],\"node.cabinet.compartmentCard.moveUp\":[\"[Ṁöṽé ûþ ~~]\"],\"node.cabinet.compartmentCard.oven\":[\"[Öṽéñ ~~]\"],\"node.cabinet.compartmentCard.pullout\":[\"[Þûļļöûţ ~~~]\"],\"node.cabinet.compartmentCard.rack\":[\"[Ŕáçķ ~~]\"],\"node.cabinet.compartmentCard.removeCompartment\":[\"[Ŕéɱöṽé çöɱþáŕţɱéñţ ~~~~~~]\"],\"node.cabinet.compartmentCard.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.cabinet.compartmentCard.shelf\":[\"[Šĥéļƒ ~~]\"],\"node.cabinet.compartmentCard.shelves\":[\"[Šĥéļṽéš ~~~]\"],\"node.cabinet.compartmentCard.shelvesInside\":[\"[Šĥéļṽéš îñšîðé ~~~~]\"],\"node.cabinet.compartmentCard.single\":[\"[Šîñĝļé ~~]\"],\"node.cabinet.compartmentCard.sink\":[\"[Šîñķ ~~]\"],\"node.cabinet.compartmentCard.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.compartmentCard.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.cabinet.compartmentCard.top\":[\"[Ţöþ ~]\"],\"node.cabinet.compartmentCard.topFreezer\":[\"[Ţöþ Ƒŕééžéŕ ~~~]\"],\"node.cabinet.compartmentCard.topGrate\":[\"[Ţöþ ĝŕáţé ~~~]\"],\"node.cabinet.compartmentCard.tray\":[\"[Ţŕáý ~~]\"],\"node.cabinet.compartmentCard.type\":[\"[Ţýþé ~~]\"],\"node.cabinet.compartmentCard.washer\":[\"[Ŵášĥéŕ ~~]\"],\"node.cabinet.compartmentCard.wire\":[\"[Ŵîŕé ~~]\"],\"node.cabinet.description\":[\"[Å çöñƒîĝûŕáƀļé þáŕáɱéţŕîç ƀášé çáƀîñéţ. ~~~~~~~~~~~]\"],\"node.cabinet.essentialControls.bottomFreezer\":[\"[Ɓöţţöɱ ƒŕééžéŕ ~~~~]\"],\"node.cabinet.essentialControls.cooktopType\":[\"[Çööķţöþ ţýþé ~~~~]\"],\"node.cabinet.essentialControls.doorDirection\":[\"[Ðööŕ ðîŕéçţîöñ ~~~~]\"],\"node.cabinet.essentialControls.double\":[\"[Ðöûƀļé ~~]\"],\"node.cabinet.essentialControls.fridgeType\":[\"[Ƒŕîðĝé ţýþé ~~~]\"],\"node.cabinet.essentialControls.gas\":[\"[Ĝáš ~]\"],\"node.cabinet.essentialControls.glass\":[\"[Ĝļášš ~~]\"],\"node.cabinet.essentialControls.induction\":[\"[Îñðûçţîöñ ~~~]\"],\"node.cabinet.essentialControls.itemOptions\":[\"[Îţéɱ öþţîöñš ~~~~]\"],\"node.cabinet.essentialControls.leftHinge\":[\"[Ļéƒţ ĥîñĝé ~~~]\"],\"node.cabinet.essentialControls.rightHinge\":[\"[Ŕîĝĥţ ĥîñĝé ~~~]\"],\"node.cabinet.essentialControls.single\":[\"[Šîñĝļé ~~]\"],\"node.cabinet.essentialControls.sinkLayout\":[\"[Šîñķ ļáýöûţ ~~~]\"],\"node.cabinet.essentialControls.topFreezer\":[\"[Ţöþ ƒŕééžéŕ ~~~]\"],\"node.cabinet.kitchenCollection.chooseLabel\":[\"[Çĥööšé \",[\"label\"],\" ~~]\"],\"node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge\":[\"[Ñö ķîţçĥéñ îţéɱš ɱáţçĥ. Ţŕý “ƒŕîðĝé”, “šîñķ”, öŕ “çáƀîñéţ”. ~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenCollection.searchFridgeSinkCabinets\":[\"[Šéáŕçĥ ƒŕîðĝé, šîñķ, çáƀîñéţš… ~~~~~~~~]\"],\"node.cabinet.kitchenCollection.searchKitchenItems\":[\"[Šéáŕçĥ ķîţçĥéñ îţéɱš ~~~~~~]\"],\"node.cabinet.kitchenGroup.appliances\":[\"[Åþþļîáñçéš & ƒîẋţûŕéš ~~~~~~]\"],\"node.cabinet.kitchenGroup.cabinets\":[\"[Çáƀîñéţš ~~~]\"],\"node.cabinet.kitchenModuleActions.addBeside\":[\"[Åðð ƀéšîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.addBesideSelectedItem\":[\"[Åðð ƀéšîðé šéļéçţéð îţéɱ ~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.addLabel\":[\"[Åðð \",[\"label\"],\" ~]\"],\"node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis\":[\"[Åñ ûþþéŕ çáƀîñéţ áţţáçĥéð ţö ţĥîš îţéɱ ŵîļļ ƀé ŕéɱöṽéð ţö ɱáķé ŕööɱ. ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.cabinet\":[\"[Çáƀîñéţ ~~~]\"],\"node.cabinet.kitchenModuleActions.cancel\":[\"[Çáñçéļ ~~]\"],\"node.cabinet.kitchenModuleActions.cannotFit\":[\"[Ţĥîš \",[\"width\"],\" îţéɱ çáññöţ ƒîţ ŵîţĥîñ ţĥé çûŕŕéñţ ŕûñ áñð îţš ŵáļļ öŕ çöŕñéŕ ļîɱîţš. Ṁöṽé áñ éñð çáƀîñéţ öŕ ƒŕéé šþáçé, ţĥéñ ţŕý áĝáîñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.insertsBeside\":[\"[\",[\"side\",\"select\",{\"left\":[\"Îñšéŕţš öñ ţĥé ļéƒţ öƒ ţĥé šéļéçţéð îţéɱ.\"],\"other\":[\"Îñšéŕţš öñ ţĥé ŕîĝĥţ öƒ ţĥé šéļéçţéð îţéɱ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.kitchenChanged\":[\"[Ţĥé ķîţçĥéñ çĥáñĝéð. Šéļéçţ áñ îţéɱ áĝáîñ ţö ŕéƒŕéšĥ ţĥé þŕéṽîéŵ. ~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs\":[\"[Ķîţçĥéñ ŕûñ þŕéṽîéŵ, ĥîĝĥļîĝĥţéð îţéɱ îš ţĥé þŕöþöšéð çĥáñĝé ~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.leftSide\":[\"[Ļéƒţ šîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.moved\":[\"[\",[\"name\"],\": ɱöṽéš \",[\"distance\"],\" ~~]\"],\"node.cabinet.kitchenModuleActions.previewItem\":[[\"name\"],\": \",[\"width\"]],\"node.cabinet.kitchenModuleActions.replaceSelectedItem\":[\"[Ŕéþļáçé šéļéçţéð îţéɱ ~~~~~~]\"],\"node.cabinet.kitchenModuleActions.replaceWith\":[\"[Ŕéþļáçé ŵîţĥ… ~~~~]\"],\"node.cabinet.kitchenModuleActions.replaceWithLabel\":[\"[Ŕéþļáçé ŵîţĥ \",[\"label\"],\" ~~~~]\"],\"node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem\":[\"[Ŕéþļáçéš öñļý ţĥé šéļéçţéð îţéɱ. ~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.resized\":[[\"name\"],\": \",[\"before\"],\" → \",[\"after\"]],\"node.cabinet.kitchenModuleActions.rightSide\":[\"[Ŕîĝĥţ šîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen\":[\"[Ûñáƀļé ţö ûþðáţé ţĥîš ķîţçĥéñ. ~~~~~~~~]\"],\"node.cabinet.label\":[\"[Ṁöðûļáŕ Çáƀîñéţ ~~~~~]\"],\"node.cabinet.panel.aboveCeiling\":[\"[Ƒîñîšĥéð ĥéîĝĥţ éẋţéñðš \",[\"overflow\"],\" ɱɱ áƀöṽé ţĥé çéîļîñĝ. ~~~~~~~~~~~~]\"],\"node.cabinet.panel.addChimney\":[\"[Åðð çĥîɱñéý ~~~]\"],\"node.cabinet.panel.addCompartment\":[\"[Åðð çöɱþáŕţɱéñţ ~~~~~]\"],\"node.cabinet.panel.addWallCabinet\":[\"[Åðð ŵáļļ çáƀîñéţ ~~~~~]\"],\"node.cabinet.panel.advanced\":[\"[Åðṽáñçéð ~~~]\"],\"node.cabinet.panel.appliance\":[\"[Åþþļîáñçé ~~~]\"],\"node.cabinet.panel.applianceFront\":[\"[Åþþļîáñçé ƒŕöñţ ~~~~~]\"],\"node.cabinet.panel.auto\":[\"[Åûţö ~~]\"],\"node.cabinet.panel.bar\":[\"[Ɓáŕ ~]\"],\"node.cabinet.panel.baseCabinet\":[\"[Ɓášé Çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview\":[\"[Çáƀîñéţ çöñšţŕûçţîöñ, çöɱþáŕţɱéñţš, áñð öþéñîñĝ þŕéṽîéŵ. ~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.cabinetPanel\":[\"[Çáƀîñéţ þáñéļ ~~~~]\"],\"node.cabinet.panel.cabinetType\":[\"[Çáƀîñéţ Ţýþé ~~~~]\"],\"node.cabinet.panel.center\":[\"[Çéñţéŕ ~~]\"],\"node.cabinet.panel.close\":[\"[Çļöšé ~~]\"],\"node.cabinet.panel.closeCabinet\":[\"[Çļöšé çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.commonWallCabinetHeightsUseThe\":[\"[Çöɱɱöñ ŵáļļ-çáƀîñéţ ĥéîĝĥţš. Ûšé ţĥé šļîðéŕ ƀéļöŵ ƒöŕ á çûšţöɱ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.compartments\":[\"[Çöɱþáŕţɱéñţš ~~~~]\"],\"node.cabinet.panel.cutout\":[\"[Çûţöûţ ~~]\"],\"node.cabinet.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cabinet.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.cabinet.panel.editKitchenRun\":[\"[Éðîţ ķîţçĥéñ ŕûñ ~~~~~]\"],\"node.cabinet.panel.fillToCeiling\":[\"[Ƒîļļ ţö çéîļîñĝ ~~~~]\"],\"node.cabinet.panel.finish\":[\"[Ƒîñîšĥ ~~]\"],\"node.cabinet.panel.frontConstruction\":[\"[Ƒŕöñţ çöñšţŕûçţîöñ ~~~~~~]\"],\"node.cabinet.panel.handles\":[\"[Ĥáñðļéš ~~~]\"],\"node.cabinet.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cabinet.panel.heightPreset\":[\"[Ĥéîĝĥţ þŕéšéţ ~~~~]\"],\"node.cabinet.panel.hole\":[\"[Ĥöļé ~~]\"],\"node.cabinet.panel.inset\":[\"[Îñšéţ ~~]\"],\"node.cabinet.panel.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"node.cabinet.panel.kitchenRun\":[\"[Ķîţçĥéñ ŕûñ ~~~]\"],\"node.cabinet.panel.knob\":[\"[Ķñöƀ ~~]\"],\"node.cabinet.panel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.cabinet.panel.none\":[\"[Ñöñé ~~]\"],\"node.cabinet.panel.open\":[\"[Öþéñ ~~]\"],\"node.cabinet.panel.openAnimation\":[\"[Öþéñ Åñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.openCabinet\":[\"[Öþéñ çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.overlay\":[\"[Öṽéŕļáý ~~~]\"],\"node.cabinet.panel.panelReady\":[\"[Þáñéļ-ŕéáðý ~~~]\"],\"node.cabinet.panel.planningChecks\":[\"[Þļáññîñĝ çĥéçķš ~~~~~]\"],\"node.cabinet.panel.play\":[\"[Þļáý ~~]\"],\"node.cabinet.panel.playAnimation\":[\"[Þļáý áñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cabinet.panel.raisedArch\":[\"[Ŕáîšéð Åŕçĥ ~~~]\"],\"node.cabinet.panel.removeWallCabinet\":[\"[Ŕéɱöṽé ŵáļļ çáƀîñéţ ~~~~~~]\"],\"node.cabinet.panel.replaceWith\":[\"[Ŕéþļáçé ŵîţĥ… ~~~~]\"],\"node.cabinet.panel.revealGap\":[\"[Ŕéṽéáļ ĝáþ ~~~]\"],\"node.cabinet.panel.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.cabinet.panel.sharedCountertopPlinthAndFinishes\":[\"[Šĥáŕéð çöûñţéŕţöþ, þļîñţĥ, áñð ƒîñîšĥéš ~~~~~~~~~~]\"],\"node.cabinet.panel.slab\":[\"[Šļáƀ ~~]\"],\"node.cabinet.panel.solidMatchingFrontBetweenTheCabinet\":[\"[Šöļîð ɱáţçĥîñĝ ƒŕöñţ ƀéţŵééñ ţĥé çáƀîñéţ áñð ŵáļļ. Çöûñţéŕţöþ áñð ţöé-ķîçķ çöñţîñûé áçŕöšš ţĥîš ƒîļļéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.standardWidth\":[\"[Šţáñðáŕð ŵîðţĥ ~~~~]\"],\"node.cabinet.panel.stop\":[\"[Šţöþ ~~]\"],\"node.cabinet.panel.stopAnimation\":[\"[Šţöþ áñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.panel.tallCabinet\":[\"[Ţáļļ Çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.top\":[\"[Ţöþ ~]\"],\"node.cabinet.panel.topCabinet\":[\"[Ţöþ Çáƀîñéţ ~~~]\"],\"node.cabinet.panel.topCeiling\":[\"[Ţöþ / Çéîļîñĝ ~~~]\"],\"node.cabinet.panel.topDepth\":[\"[Ţöþ ðéþţĥ ~~~]\"],\"node.cabinet.panel.topHeight\":[\"[Ţöþ ĥéîĝĥţ ~~~]\"],\"node.cabinet.panel.trimSoffit\":[\"[Ţŕîɱ / Šöƒƒîţ ~~~]\"],\"node.cabinet.panel.usesTheCabinetFrontStyleReveal\":[\"[Ûšéš ţĥé çáƀîñéţ ƒŕöñţ šţýļé, ŕéṽéáļ, ĥáñðļé, áñð ƒŕöñţ ɱáţéŕîáļ šéţţîñĝš ƀéļöŵ. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.wallFiller\":[\"[Ŵáļļ ƒîļļéŕ ~~~]\"],\"node.cabinet.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.cabinet.preset.baseCabinet\":[\"[Ɓášé çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.dishwasher\":[\"[Ðîšĥŵášĥéŕ ~~~]\"],\"node.cabinet.preset.drawerCabinet\":[\"[Ðŕáŵéŕ çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.fridge\":[\"[Ƒŕîðĝé ~~]\"],\"node.cabinet.preset.gasCooktop\":[\"[Ĝáš çööķţöþ ~~~]\"],\"node.cabinet.preset.inductionCooktop\":[\"[Îñðûçţîöñ çööķţöþ ~~~~~]\"],\"node.cabinet.preset.ovenTower\":[\"[Öṽéñ Ţöŵéŕ ~~~]\"],\"node.cabinet.preset.sinkCabinet\":[\"[Šîñķ çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.tallPantry\":[\"[Ţáļļ Þáñţŕý ~~~]\"],\"node.cabinet.preset.wallCabinet\":[\"[Ŵáļļ çáƀîñéţ ~~~~]\"],\"node.cabinet.runPanel.addLeft\":[\"[Åðð ļéƒţ ~~~]\"],\"node.cabinet.runPanel.addRight\":[\"[Åðð ŕîĝĥţ ~~~]\"],\"node.cabinet.runPanel.alignCabinetsHandles\":[\"[Åļîĝñ çáƀîñéţš & ĥáñðļéš ~~~~~~]\"],\"node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop\":[\"[Åþþļîéš ðéþţĥ, çáŕçášš, þļîñţĥ, áñð çöûñţéŕţöþ ţĥîçķñéšš ţö ţĥîš ŕûñ. ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayCreate\":[\"[\",[\"count\",\"plural\",{\"one\":[\"Çŕéáţé \",[\"number\"],\" ɱöðûļé çöþý\"],\"other\":[\"Çŕéáţé \",[\"number\"],\" ɱöðûļé çöþîéš\"]}],\" ~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayCreateNamed\":[\"[\",[\"count\",\"plural\",{\"one\":[\"Çŕéáţé \",[\"number\"],\" \",[\"name\"],\" çöþý\"],\"other\":[\"Çŕéáţé \",[\"number\"],\" \",[\"name\"],\" çöþîéš\"]}],\" ~~~~~~~]\"],\"node.cabinet.runPanel.arrayInvalidOptions\":[\"[Çĥööšé á çöþý çöûñţ ƒŕöɱ 1 ţö 20 áñð šþáçîñĝ ƒŕöɱ 0 ţö 2 ɱ ~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayNoRoom\":[\"[Ţĥéŕé îš ñöţ éñöûĝĥ ŕööɱ îñ ţĥîš ŕûñ ƒöŕ ţĥé ŕéǫûéšţéð áŕŕáý ~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayNoSource\":[\"[Çĥööšé á šţáñðáŕð öŕ áþþļîáñçé ɱöðûļé áš ţĥé šöûŕçé ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.auto\":[\"[Åûţö ~~]\"],\"node.cabinet.runPanel.back\":[\"[Ɓáçķ ~~]\"],\"node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping\":[\"[Ɓáļáñçéš šţáñðáŕð ƀášé çáƀîñéţš ŵĥîļé ķééþîñĝ áþþļîáñçé áñð çöŕñéŕ-ƒîļļéŕ ŵîðţĥš ƒîẋéð. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.bar\":[\"[Ɓáŕ ~]\"],\"node.cabinet.runPanel.barCounter\":[\"[Ɓáŕ çöûñţéŕ ~~~]\"],\"node.cabinet.runPanel.barDepth\":[\"[Ɓáŕ ðéþţĥ ~~~]\"],\"node.cabinet.runPanel.barHeight\":[\"[Ɓáŕ ĥéîĝĥţ ~~~]\"],\"node.cabinet.runPanel.carcassHeight\":[\"[Çáŕçášš ĥéîĝĥţ ~~~~]\"],\"node.cabinet.runPanel.center\":[\"[Çéñţéŕ ~~]\"],\"node.cabinet.runPanel.commonWallCabinetHeightsUseThe\":[\"[Çöɱɱöñ ŵáļļ-çáƀîñéţ ĥéîĝĥţš. Ûšé ţĥé šļîðéŕ ƀéļöŵ ƒöŕ á çûšţöɱ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.copies\":[\"[Çöþîéš ~~]\"],\"node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure\":[\"[Çöþîéš îñçļûðé ţĥé šöûŕçé çáƀîñéţ šţŕûçţûŕé áñð áñý áţţáçĥéð ŵáļļ çáƀîñéţ. Éẋîšţîñĝ ɱöðûļéš šţáý ƒîẋéð; ţĥé ŕéǫûéšţéð áŕŕáý ɱûšţ ƒîţ îñ ţĥé áṽáîļáƀļé ŕûñ šþáçé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.cornerFillersCannotBeUsedAs\":[\"[Çöŕñéŕ ƒîļļéŕš çáññöţ ƀé ûšéð áš áŕŕáý šöûŕçéš ~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.countertopDepth\":[\"[Çöûñţéŕţöþ ðéþţĥ ~~~~~]\"],\"node.cabinet.runPanel.countertopHeight\":[\"[Çöûñţéŕţöþ ĥéîĝĥţ ~~~~~]\"],\"node.cabinet.runPanel.createArray\":[\"[Çŕéáţé áŕŕáý ~~~~]\"],\"node.cabinet.runPanel.cutout\":[\"[Çûţöûţ ~~]\"],\"node.cabinet.runPanel.deleteModule\":[\"[Ðéļéţé \",[\"name\"],\" ~~]\"],\"node.cabinet.runPanel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cabinet.runPanel.direction\":[\"[Ðîŕéçţîöñ ~~~]\"],\"node.cabinet.runPanel.duplicateAlongRun\":[\"[Ðûþļîçáţé áļöñĝ ŕûñ ~~~~~~]\"],\"node.cabinet.runPanel.equalizeAll\":[\"[Éǫûáļîžé áļļ ŕéšîžéáƀļé šţáñðáŕð ƀášé çáƀîñéţš îñ ţĥîš ŕûñ ~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeAlreadyEqual\":[\"[Ţĥé ŕéšîžéáƀļé çáƀîñéţ ŵîðţĥš áŕé áļŕéáðý éǫûáļ ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeNeedsTwo\":[\"[Åţ ļéášţ ţŵö šţáñðáŕð ƀášé çáƀîñéţš áŕé ŕéǫûîŕéð ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeNoFit\":[\"[Ţĥé áṽáîļáƀļé ŕûñ ŵîðţĥ çáññöţ šáţý ţĥé çáƀîñéţ ŵîðţĥ ļîɱîţš ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeWidths\":[\"[Éǫûáļîžé ŵîðţĥš ~~~~~]\"],\"node.cabinet.runPanel.fillerPlanSide\":[\"[\",[\"side\",\"select\",{\"left\":[\"Ļéƒţ: \",[\"width\"],\" ɱɱ\"],\"other\":[\"Ŕîĝĥţ: \",[\"width\"],\" ɱɱ\"]}],\" ~~~~]\"],\"node.cabinet.runPanel.finishAlign\":[\"[Ƒîñîšĥ & áļîĝñ ~~~~]\"],\"node.cabinet.runPanel.finishedBack\":[\"[Ƒîñîšĥéð ƀáçķ ~~~~]\"],\"node.cabinet.runPanel.finishedExposedEnds\":[\"[Ƒîñîšĥéð éẋþöšéð éñðš ~~~~~~]\"],\"node.cabinet.runPanel.fitWallFillers\":[\"[Ƒîţ ŵáļļ ƒîļļéŕš ~~~~~]\"],\"node.cabinet.runPanel.fits50150MmGapsAt\":[\"[Ƒîţš 50–150 ɱɱ ĝáþš áţ ŕûñ éñðš ƀéšîðé þéŕþéñðîçûļáŕ ŵáļļš. ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.fronts\":[\"[Ƒŕöñţš ~~]\"],\"node.cabinet.runPanel.handles\":[\"[Ĥáñðļéš ~~~]\"],\"node.cabinet.runPanel.heightPreset\":[\"[Ĥéîĝĥţ þŕéšéţ ~~~~]\"],\"node.cabinet.runPanel.hole\":[\"[Ĥöļé ~~]\"],\"node.cabinet.runPanel.inset\":[\"[Îñšéţ ~~]\"],\"node.cabinet.runPanel.islandBar\":[\"[Κļáñð & Ɓáŕ ~~~]\"],\"node.cabinet.runPanel.kitchenRun\":[\"[Ķîţçĥéñ ŕûñ ~~~]\"],\"node.cabinet.runPanel.knob\":[\"[Ķñöƀ ~~]\"],\"node.cabinet.runPanel.left\":[\"[Ļéƒţ ~~]\"],\"node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd\":[\"[Ṁáţçĥ ƀášé ĥéîĝĥţš, ƀáçķš, ŕéṽéáļš áñð ĥáñðļéš. Åļîĝñ ûþþéŕ çáƀîñéţš; þŕéšéŕṽé ţáļļ áþþļîáñçéš áñð çûšţöɱ çöɱþáŕţɱéñţš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.metricProfile\":[\"[Ṁéţŕîç · 600 ɱɱ ~~~]\"],\"node.cabinet.runPanel.moduleNumber\":[\"[Ṁöðûļé \",[\"number\"],\" ~~]\"],\"node.cabinet.runPanel.modules\":[\"[Ṁöðûļéš ~~~]\"],\"node.cabinet.runPanel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.cabinet.runPanel.noEligibleModule\":[\"[Ñö éļîĝîƀļé ɱöðûļé ~~~~~]\"],\"node.cabinet.runPanel.none\":[\"[Ñöñé ~~]\"],\"node.cabinet.runPanel.overlay\":[\"[Öṽéŕļáý ~~~]\"],\"node.cabinet.runPanel.plinthHeight\":[\"[Þļîñţĥ ĥéîĝĥţ ~~~~]\"],\"node.cabinet.runPanel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cabinet.runPanel.raisedArch\":[\"[Ŕáîšéð Åŕçĥ ~~~]\"],\"node.cabinet.runPanel.revealGap\":[\"[Ŕéṽéáļ ĝáþ ~~~]\"],\"node.cabinet.runPanel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.cabinet.runPanel.seatingOverhang\":[\"[Šéáţîñĝ öṽéŕĥáñĝ ~~~~~]\"],\"node.cabinet.runPanel.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.cabinet.runPanel.sharedPlinthCountertop\":[\"[Šĥáŕéð Þļîñţĥ & Çöûñţéŕţöþ ~~~~~~~]\"],\"node.cabinet.runPanel.showCountertop\":[\"[Šĥöŵ çöûñţéŕţöþ ~~~~~]\"],\"node.cabinet.runPanel.showPlinth\":[\"[Šĥöŵ þļîñţĥ ~~~]\"],\"node.cabinet.runPanel.slab\":[\"[Šļáƀ ~~]\"],\"node.cabinet.runPanel.sourceModule\":[\"[Šöûŕçé ɱöðûļé ~~~~]\"],\"node.cabinet.runPanel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.cabinet.runPanel.standardDimensions\":[\"[Šţáñðáŕð ðîɱéñšîöñš ~~~~~~]\"],\"node.cabinet.runPanel.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.runPanel.summaryCompartment\":[\"[\",[\"type\",\"select\",{\"shelf\":[\"Šĥéļƒ\"],\"drawer\":[\"Ðŕáŵéŕ\"],\"door\":[\"Ðööŕ\"],\"oven\":[\"Öṽéñ\"],\"microwave\":[\"Ṁîçŕöŵáṽé\"],\"dishwasher\":[\"Ðîšĥŵášĥéŕ\"],\"sink\":[\"Šîñķ\"],\"cooktop_gas\":[\"Ĝáš çööķţöþ\"],\"cooktop_induction\":[\"Îñðûçţîöñ çööķţöþ\"],\"pull_out_pantry\":[\"Þûļļ-öûţ þáñţŕý\"],\"fridge_single\":[\"Ƒŕîðĝé\"],\"fridge_double\":[\"Ðöûƀļé ƒŕîðĝé\"],\"fridge_top_freezer\":[\"Ţöþ-ƒŕééžéŕ ƒŕîðĝé\"],\"fridge_bottom_freezer\":[\"Ɓöţţöɱ-ƒŕééžéŕ ƒŕîðĝé\"],\"hood_pyramid\":[\"Þýŕáɱîð ĥööð\"],\"hood_curved_glass\":[\"Çûŕṽéð ĝļášš ĥööð\"],\"other\":[[\"raw\"]]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.summaryCompartments\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" çöɱþáŕţɱéñţ\"],\"other\":[[\"number\"],\" çöɱþáŕţɱéñţš\"]}],\" ~~~~~~~]\"],\"node.cabinet.runPanel.summaryEmpty\":[\"[Éɱþţý ~~]\"],\"node.cabinet.runPanel.summaryTall\":[\"[Ţáļļ çáƀîñéţ ~~~~]\"],\"node.cabinet.runPanel.top\":[\"[Ţöþ ~]\"],\"node.cabinet.runPanel.unableToFinish\":[\"[Ûñáƀļé ţö ƒîñîšĥ ţĥîš ķîţçĥéñ ŕûñ. ~~~~~~~~~]\"],\"node.cabinet.runPanel.usProfile\":[\"[ÛŠ · 24 îñ ~~]\"],\"node.cabinet.runPanel.useAsArraySource\":[\"[Ûšé \",[\"name\"],\" áš áŕŕáý šöûŕçé ~~~~~]\"],\"node.cabinet.runPanel.useThisModuleAsTheArray\":[\"[Ûšé ţĥîš ɱöðûļé áš ţĥé áŕŕáý šöûŕçé ~~~~~~~~~]\"],\"node.cabinet.runPanel.waterfallEnds\":[\"[Ŵáţéŕƒáļļ éñðš ~~~~]\"],\"node.cabinetModule.description\":[\"[Åñ éðîţáƀļé ɱöðûļé îñšîðé á ɱöðûļáŕ çáƀîñéţ ŕûñ. ~~~~~~~~~~~~]\"],\"node.cabinetModule.label\":[\"[Çáƀîñéţ Ṁöðûļé ~~~~]\"],\"node.ceiling.description\":[\"[Å þöļýĝöñ-ƀöûñðéð çéîļîñĝ šûŕƒáçé ţĥáţ ĥöšţš çéîļîñĝ-ɱöûñţéð îţéɱš. ~~~~~~~~~~~~~~~~~~]\"],\"node.ceiling.label\":[\"[Çéîļîñĝ ~~~]\"],\"node.ceiling.panel.addHole\":[\"[Åðð Ĥöļé ~~~]\"],\"node.ceiling.panel.area\":[\"[Åŕéá ~~]\"],\"node.ceiling.panel.auto\":[\"[Åûţö ~~]\"],\"node.ceiling.panel.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"node.ceiling.panel.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"node.ceiling.panel.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"node.ceiling.panel.done\":[\"[Ðöñé ~~]\"],\"node.ceiling.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.ceiling.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.ceiling.panel.high30m\":[\"[Ĥîĝĥ (3.0ɱ) ~~]\"],\"node.ceiling.panel.high90\":[\"[Ĥîĝĥ (9'0\\\") ~~]\"],\"node.ceiling.panel.holes\":[\"[Ĥöļéš ~~]\"],\"node.ceiling.panel.info\":[\"[Îñƒö ~~]\"],\"node.ceiling.panel.limitedByLevel\":[\"[Ļîɱîţéð ƀý ţĥé ļéṽéļ ţö \",[\"height\"],\" — ŕáîšé ţĥé ļéṽéļ ĥéîĝĥţ ƒöŕ á ţáļļéŕ çéîļîñĝ. ~~~~~~~~~~~~~~~~~]\"],\"node.ceiling.panel.low24m\":[\"[Ļöŵ (2.4ɱ) ~~]\"],\"node.ceiling.panel.low80\":[\"[Ļöŵ (8'0\\\") ~]\"],\"node.ceiling.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.ceiling.panel.noHoles\":[\"[Ñö ĥöļéš ~~~]\"],\"node.ceiling.panel.standard25m\":[\"[Šţáñðáŕð (2.5ɱ) ~~~]\"],\"node.ceiling.panel.standard86\":[\"[Šţáñðáŕð (8'6\\\") ~~~]\"],\"node.ceiling.panel.tallerThanLevel\":[\"[Ţáļļéŕ ţĥáñ ţĥîš ļéṽéļ (\",[\"available\"],\" áṽáîļáƀļé). Ŕáîšé ţĥé ļéṽéļ ĥéîĝĥţ ƒîŕšţ. ~~~~~~~~~~~~~~~~]\"],\"node.chimney.description\":[\"[Ṽéŕţîçáļ ɱášöñŕý šţáçķ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~]\"],\"node.chimney.label\":[\"[Çĥîɱñéý ~~~]\"],\"node.chimney.panel.aboveRidge\":[\"[Åƀöṽé Ŕîðĝé ~~~]\"],\"node.chimney.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.chimney.panel.back\":[\"[Ɓáçķ ~~]\"],\"node.chimney.panel.bands\":[\"[Ɓáñðš ~~]\"],\"node.chimney.panel.cap\":[\"[Çáþ ~]\"],\"node.chimney.panel.chimney\":[\"[Çĥîɱñéý ~~~]\"],\"node.chimney.panel.chimneyType\":[\"[Çĥîɱñéý Ţýþé ~~~~]\"],\"node.chimney.panel.corbeled\":[\"[Çöŕƀéļéð ~~~]\"],\"node.chimney.panel.cornerBevel\":[\"[Çöŕñéŕ Ɓéṽéļ ~~~~]\"],\"node.chimney.panel.count\":[\"[Çöûñţ ~~]\"],\"node.chimney.panel.cricket\":[\"[Çŕîçķéţ ~~~]\"],\"node.chimney.panel.cutoutOffset\":[\"[Çûţöûţ Öƒƒšéţ ~~~~]\"],\"node.chimney.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.chimney.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.chimney.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.chimney.panel.double\":[\"[Ðöûƀļé ~~]\"],\"node.chimney.panel.extent\":[\"[Éẋţéñţ ~~]\"],\"node.chimney.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.chimney.panel.flues\":[\"[Ƒļûéš ~~]\"],\"node.chimney.panel.footprint\":[\"[Ƒööţþŕîñţ ~~~]\"],\"node.chimney.panel.front\":[\"[Ƒŕöñţ ~~]\"],\"node.chimney.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.chimney.panel.hollowDepth\":[\"[Ĥöļļöŵ Ðéþţĥ ~~~~]\"],\"node.chimney.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.chimney.panel.none\":[\"[Ñöñé ~~]\"],\"node.chimney.panel.offset\":[\"[Öƒƒšéţ ~~]\"],\"node.chimney.panel.overhang\":[\"[Öṽéŕĥáñĝ ~~~]\"],\"node.chimney.panel.panels\":[\"[Þáñéļš ~~]\"],\"node.chimney.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.chimney.panel.rectangular\":[\"[Ŕéçţáñĝûļáŕ ~~~~]\"],\"node.chimney.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.chimney.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.chimney.panel.shoulder\":[\"[Šĥöûļðéŕ ~~~]\"],\"node.chimney.panel.sideMargin\":[\"[Šîðé Ṁáŕĝîñ ~~~]\"],\"node.chimney.panel.simple\":[\"[Šîɱþļé ~~]\"],\"node.chimney.panel.single\":[\"[Šîñĝļé ~~]\"],\"node.chimney.panel.sloped\":[\"[Šļöþéð ~~]\"],\"node.chimney.panel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.chimney.panel.square\":[\"[Šǫûáŕé ~~]\"],\"node.chimney.panel.stepped\":[\"[Šţéþþéð ~~~]\"],\"node.chimney.panel.style\":[\"[Šţýļé ~~]\"],\"node.chimney.panel.tapered\":[\"[Ţáþéŕéð ~~~]\"],\"node.chimney.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.chimney.panel.topOffset\":[\"[Ţöþ Öƒƒšéţ ~~~]\"],\"node.chimney.panel.wallThickness\":[\"[Ŵáļļ Ţĥîçķñéšš ~~~~]\"],\"node.chimney.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.chimney.preset.brick\":[\"[Ɓŕîçķ ~~]\"],\"node.chimney.preset.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.chimney.preset.round\":[\"[Ŕöûñð ~~]\"],\"node.column.description\":[\"[Å þáŕáɱéţŕîç çöļûɱñ ŵîţĥ çöñƒîĝûŕáƀļé çŕöšš-šéçţîöñ, ƀášé, áñð çáþîţáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.column.label\":[\"[Çöļûɱñ ~~]\"],\"node.column.panel.aFrame\":[\"[Å-Ƒŕáɱé ~~]\"],\"node.column.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.column.panel.applyPreset\":[\"[Åþþļý þŕéšéţ... ~~~~]\"],\"node.column.panel.applyProportion\":[\"[Åþþļý þŕöþöŕţîöñ... ~~~~~]\"],\"node.column.panel.bottomDepth\":[\"[Ɓöţţöɱ Ðéþţĥ ~~~~]\"],\"node.column.panel.bottomHeight\":[\"[Ɓöţţöɱ Ĥéîĝĥţ ~~~~]\"],\"node.column.panel.bottomSpread\":[\"[Ɓöţţöɱ Šþŕéáð ~~~~]\"],\"node.column.panel.bottomStepSpread\":[\"[Ɓöţţöɱ Šţéþ Šþŕéáð ~~~~~]\"],\"node.column.panel.bottomTiers\":[\"[Ɓöţţöɱ Ţîéŕš ~~~~]\"],\"node.column.panel.bottomWidth\":[\"[Ɓöţţöɱ Ŵîðţĥ ~~~~]\"],\"node.column.panel.boxFrame\":[\"[Ɓöẋ Ƒŕáɱé ~~~]\"],\"node.column.panel.braceDepth\":[\"[Ɓŕáçé Ðéþţĥ ~~~]\"],\"node.column.panel.braceWidth\":[\"[Ɓŕáçé Ŵîðţĥ ~~~]\"],\"node.column.panel.bulge\":[\"[Ɓûļĝé ~~]\"],\"node.column.panel.bulged\":[\"[Ɓûļĝéð ~~]\"],\"node.column.panel.column\":[\"[Çöļûɱñ ~~]\"],\"node.column.panel.connectorPlates\":[\"[Çöññéçţöŕ Þļáţéš ~~~~~]\"],\"node.column.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.column.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.column.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.column.panel.edgeSoftness\":[\"[Éðĝé Šöƒţñéšš ~~~~]\"],\"node.column.panel.endWidth\":[\"[Éñð Ŵîðţĥ ~~~]\"],\"node.column.panel.ends\":[\"[Éñðš ~~]\"],\"node.column.panel.forkSpread\":[\"[Ƒöŕķ Šþŕéáð ~~~]\"],\"node.column.panel.heavy\":[\"[Ĥéáṽý ~~]\"],\"node.column.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.column.panel.heightAndFootprintAreControlledBy\":[\"[Ĥéîĝĥţ áñð ƒööţþŕîñţ áŕé çöñţŕöļļéð ƀý ţĥé ļéáñ-ţö éẋţéñšîöñ. Ŕöţáţé öŕ çĥáñĝé ţĥé šûþþöŕţ šţýļé ĥéŕé; ŕéšîžé ƒŕöɱ ţĥé þáŕéñţ ļéáñ-ţö. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.column.panel.hourglass\":[\"[Ĥöûŕĝļášš ~~~]\"],\"node.column.panel.kBrace\":[\"[Ķ Ɓŕáçé ~~]\"],\"node.column.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.column.panel.neckWidth\":[\"[Ñéçķ Ŵîðţĥ ~~~]\"],\"node.column.panel.noBottom\":[\"[Ñö Ɓöţţöɱ ~~~]\"],\"node.column.panel.noTop\":[\"[Ñö Ţöþ ~~]\"],\"node.column.panel.plinthThickness\":[\"[Þļîñţĥ Ţĥîçķñéšš ~~~~~]\"],\"node.column.panel.portalFrame\":[\"[Þöŕţáļ Ƒŕáɱé ~~~~]\"],\"node.column.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.column.panel.rectangular\":[\"[Ŕéçţáñĝûļáŕ ~~~~]\"],\"node.column.panel.ringPairs\":[\"[Ŕîñĝ Þáîŕš ~~~]\"],\"node.column.panel.ringSpread\":[\"[Ŕîñĝ Šþŕéáð ~~~]\"],\"node.column.panel.ringThickness\":[\"[Ŕîñĝ Ţĥîçķñéšš ~~~~]\"],\"node.column.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.column.panel.roundBandWidth\":[\"[Ŕöûñð Ɓáñð Ŵîðţĥ ~~~~~]\"],\"node.column.panel.roundedBottom\":[\"[Ŕöûñðéð Ɓöţţöɱ ~~~~]\"],\"node.column.panel.roundedTop\":[\"[Ŕöûñðéð Ţöþ ~~~]\"],\"node.column.panel.segmentTwist\":[\"[Šéĝɱéñţ Ţŵîšţ ~~~~]\"],\"node.column.panel.shaft\":[\"[Šĥáƒţ ~~]\"],\"node.column.panel.shaftCornerRadius\":[\"[Šĥáƒţ Çöŕñéŕ Ŕáðîûš ~~~~~~]\"],\"node.column.panel.shaftWidth\":[\"[Šĥáƒţ Ŵîðţĥ ~~~]\"],\"node.column.panel.shape\":[\"[Šĥáþé ~~]\"],\"node.column.panel.shortStout\":[\"[Šĥöŕţ / Šţöûţ ~~~]\"],\"node.column.panel.simpleBlockBottom\":[\"[Šîɱþļé Ɓļöçķ Ɓöţţöɱ ~~~~~~]\"],\"node.column.panel.simpleTop\":[\"[Šîɱþļé Ţöþ ~~~]\"],\"node.column.panel.singleStrut\":[\"[Šîñĝļé Šţŕûţ ~~~~]\"],\"node.column.panel.slender\":[\"[Šļéñðéŕ ~~~]\"],\"node.column.panel.square\":[\"[Šǫûáŕé ~~]\"],\"node.column.panel.squarePlinthBottom\":[\"[Šǫûáŕé Þļîñţĥ Ɓöţţöɱ ~~~~~~]\"],\"node.column.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.column.panel.steppedBottom\":[\"[Šţéþþéð Ɓöţţöɱ ~~~~]\"],\"node.column.panel.steppedTop\":[\"[Šţéþþéð Ţöþ ~~~]\"],\"node.column.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.column.panel.taper\":[\"[Ţáþéŕ ~~]\"],\"node.column.panel.tapered\":[\"[Ţáþéŕéð ~~~]\"],\"node.column.panel.topDepth\":[\"[Ţöþ Ðéþţĥ ~~~]\"],\"node.column.panel.topHeight\":[\"[Ţöþ Ĥéîĝĥţ ~~~]\"],\"node.column.panel.topSpread\":[\"[Ţöþ Šþŕéáð ~~~]\"],\"node.column.panel.topStepSpread\":[\"[Ţöþ Šţéþ Šþŕéáð ~~~~]\"],\"node.column.panel.topTiers\":[\"[Ţöþ Ţîéŕš ~~~]\"],\"node.column.panel.topWidth\":[\"[Ţöþ Ŵîðţĥ ~~~]\"],\"node.column.panel.transform\":[\"[Ţŕáñšƒöŕɱ ~~~]\"],\"node.column.panel.trestle\":[\"[Ţŕéšţļé ~~~]\"],\"node.column.panel.tripod\":[\"[Ţŕîþöð ~~]\"],\"node.column.panel.twistSegments\":[\"[Ţŵîšţ Šéĝɱéñţš ~~~~]\"],\"node.column.panel.vSupport\":[\"[Ṽ Šûþþöŕţ ~~~]\"],\"node.column.panel.vertical\":[\"[Ṽéŕţîçáļ ~~~]\"],\"node.column.panel.waist\":[\"[Ŵáîšţ ~~]\"],\"node.column.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.column.panel.xBrace\":[\"[Ẋ Ɓŕáçé ~~]\"],\"node.column.panel.ySupport\":[\"[Ý Šûþþöŕţ ~~~]\"],\"node.column.panel.yaw\":[\"[Ýáŵ ~]\"],\"node.column.preset.aFrameSupport\":[\"[Å-Ƒŕáɱé Šûþþöŕţ ~~~~]\"],\"node.column.preset.basicPillar\":[\"[Šţŕáîĝĥţ Ŕöûñð ~~~~]\"],\"node.column.preset.boxFrameSupport\":[\"[Ɓöẋ Ƒŕáɱé ~~~]\"],\"node.column.preset.bulgedPillar\":[\"[Šöƒţ Ɓûļĝéð ~~~]\"],\"node.column.preset.hourglassPillar\":[\"[Ĥöûŕĝļášš ~~~]\"],\"node.column.preset.kBraceSupport\":[\"[Ķ Ɓŕáçé ~~]\"],\"node.column.preset.portalFrameSupport\":[\"[Þöŕţáļ Ƒŕáɱé ~~~~]\"],\"node.column.preset.singleStrutSupport\":[\"[Šîñĝļé Šţŕûţ ~~~~]\"],\"node.column.preset.squarePillar\":[\"[Šǫûáŕé Ɓļöçķ ~~~~]\"],\"node.column.preset.taperedPillar\":[\"[Ţáþéŕéð Ŕöûñð ~~~~]\"],\"node.column.preset.trestleSupport\":[\"[Ţŕéšţļé Ƒŕáɱé ~~~~]\"],\"node.column.preset.tripodSupport\":[\"[Ţŕîþöð Šûþþöŕţ ~~~~]\"],\"node.column.preset.vFrameSupport\":[\"[Ṽ Šûþþöŕţ ~~~]\"],\"node.column.preset.xBraceSupport\":[\"[Ẋ Ɓŕáçé ~~]\"],\"node.column.preset.yFrameSupport\":[\"[Ý Šûþþöŕţ ~~~]\"],\"node.constructionDimension.description\":[\"[Åššöçîáţîṽé ļîñéáŕ, çûŕṽéð, çîŕçûļáŕ, áñĝûļáŕ, öŕ çööŕðîñáţé þļáñ ðîɱéñšîöñ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.constructionDimension.label\":[\"[Çöñšţŕûçţîöñ Ðîɱéñšîöñ ~~~~~~~]\"],\"node.cornerRadius.bottomLeft\":[\"[Ɓöţţöɱ Ļéƒţ ~~~]\"],\"node.cornerRadius.bottomRight\":[\"[Ɓöţţöɱ Ŕîĝĥţ ~~~~]\"],\"node.cornerRadius.topLeft\":[\"[Ţöþ Ļéƒţ ~~~]\"],\"node.cornerRadius.topRight\":[\"[Ţöþ Ŕîĝĥţ ~~~]\"],\"node.cupola.description\":[\"[Ļöûṽéŕéð ŕööƒ ļáñţéŕñ ŵîţĥ á ðöɱé öŕ þýŕáɱîð çáþ áñð öþţîöñáļ ƒîñîáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cupola.label\":[\"[Çûþöļá ~~]\"],\"node.cupola.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.cupola.panel.cupola\":[\"[Çûþöļá ~~]\"],\"node.cupola.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.cupola.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cupola.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.cupola.panel.dome\":[\"[Ðöɱé ~~]\"],\"node.cupola.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.cupola.panel.finial\":[\"[Ƒîñîáļ ~~]\"],\"node.cupola.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cupola.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.cupola.panel.noFinial\":[\"[Ñö Ƒîñîáļ ~~~]\"],\"node.cupola.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cupola.panel.pyramid\":[\"[Þýŕáɱîð ~~~]\"],\"node.cupola.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.cupola.panel.style\":[\"[Šţýļé ~~]\"],\"node.cupola.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.cutout.autoElevator\":[\"[Åûţö éļéṽáţöŕ çûţöûţ ~~~~~~]\"],\"node.cutout.autoStair\":[\"[Åûţö šţáîŕ çûţöûţ ~~~~~]\"],\"node.cutout.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.cutout.room\":[\"[Ŕööɱ çûţöûţ ~~~]\"],\"node.door.description\":[\"[Å ðööŕ çûţ îñţö á ŵáļļ. Åñîɱáţéð öþéñ/çļöšé šţáţé. ~~~~~~~~~~~~]\"],\"node.door.label\":[\"[Ðööŕ ~~]\"],\"node.door.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.door.panel.addSegment\":[\"[+ Åðð Šéĝɱéñţ ~~~]\"],\"node.door.panel.all\":[\"[Åļļ ~]\"],\"node.door.panel.arch\":[\"[Åŕçĥ ~~]\"],\"node.door.panel.archHeight\":[\"[Åŕçĥ Ĥéîĝĥţ ~~~]\"],\"node.door.panel.auto\":[\"[Åûţö ~~]\"],\"node.door.panel.autoFollowsTheWall\":[\"[Åûţö ƒöļļöŵš ţĥé ŵáļļ ~~~~~~]\"],\"node.door.panel.barHeight\":[\"[Ɓáŕ Ĥéîĝĥţ ~~~]\"],\"node.door.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.door.panel.contentPadding\":[\"[Çöñţéñţ Þáððîñĝ ~~~~~]\"],\"node.door.panel.cornerRadius\":[\"[Çöŕñéŕ Ŕáðîûš ~~~~]\"],\"node.door.panel.customTheSegmentsMatchNoStyle\":[\"[Çûšţöɱ: ţĥé šéĝɱéñţš ɱáţçĥ ñö šţýļé. ~~~~~~~~~]\"],\"node.door.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.door.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.door.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.door.panel.direction\":[\"[Ðîŕéçţîöñ ~~~]\"],\"node.door.panel.divider\":[\"[Ðîṽîðéŕ ~~~]\"],\"node.door.panel.door\":[\"[Ðööŕ ~~]\"],\"node.door.panel.doorCloser\":[\"[Ðööŕ Çļöšéŕ ~~~]\"],\"node.door.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.door.panel.empty\":[\"[Éɱþţý ~~]\"],\"node.door.panel.enableHandle\":[\"[Éñáƀļé Ĥáñðļé ~~~~]\"],\"node.door.panel.enableThreshold\":[\"[Éñáƀļé Ţĥŕéšĥöļð ~~~~~]\"],\"node.door.panel.flipSide\":[\"[Ƒļîþ Šîðé ~~~]\"],\"node.door.panel.fold\":[\"[Ƒöļð ~~]\"],\"node.door.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.door.panel.frameDepthAuto\":[\"[Ðéþţĥ (áûţö) ~~~]\"],\"node.door.panel.garage\":[\"[Ĝáŕáĝé ~~]\"],\"node.door.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.door.panel.handle\":[\"[Ĥáñðļé ~~]\"],\"node.door.panel.handleSide\":[\"[Ĥáñðļé Šîðé ~~~]\"],\"node.door.panel.hardware\":[\"[Ĥáŕðŵáŕé ~~~]\"],\"node.door.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.door.panel.hingesSide\":[\"[Ĥîñĝéš Šîðé ~~~]\"],\"node.door.panel.horizontal\":[\"[Ĥöŕîžöñţáļ ~~~]\"],\"node.door.panel.individual\":[\"[Îñðîṽîðûáļ ~~~]\"],\"node.door.panel.inset\":[\"[Îñšéţ ~~]\"],\"node.door.panel.inward\":[\"[Îñŵáŕð ~~]\"],\"node.door.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.door.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.door.panel.open\":[\"[Öþéñ ~~]\"],\"node.door.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.door.panel.openingShape\":[\"[Öþéñîñĝ Šĥáþé ~~~~]\"],\"node.door.panel.outward\":[\"[Öûţŵáŕð ~~~]\"],\"node.door.panel.panel\":[\"[Þáñéļ ~~]\"],\"node.door.panel.panels\":[\"[Þáñéļš ~~]\"],\"node.door.panel.panicBar\":[\"[Þáñîç Ɓáŕ ~~~]\"],\"node.door.panel.pocket\":[\"[Þöçķéţ ~~]\"],\"node.door.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.door.panel.rail\":[\"[Ŕáîļ ~~]\"],\"node.door.panel.rect\":[\"[Ŕéçţ ~~]\"],\"node.door.panel.remove\":[\"[- Ŕéɱöṽé ~~]\"],\"node.door.panel.revealRadius\":[\"[Ŕéṽéáļ Ŕáðîûš ~~~~]\"],\"node.door.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.door.panel.rounded\":[\"[Ŕöûñðéð ~~~]\"],\"node.door.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.door.panel.sizeIsLimitedToTheWall\":[\"[Šîžé îš ļîɱîţéð ţö ţĥé ŵáļļ, îñçļûðîñĝ çļéáŕáñçé ƒöŕ ţĥé öþéñîñĝ ƒŕáɱé. ~~~~~~~~~~~~~~~~~~]\"],\"node.door.panel.slide\":[\"[Šļîðé ~~]\"],\"node.door.panel.style\":[\"[Šţýļé ~~]\"],\"node.door.panel.swing\":[\"[Šŵîñĝ ~~]\"],\"node.door.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.door.panel.threshold\":[\"[Ţĥŕéšĥöļð ~~~]\"],\"node.door.panel.topShape\":[\"[Ţöþ Šĥáþé ~~~]\"],\"node.door.panel.type\":[\"[Ţýþé ~~]\"],\"node.door.panel.vertical\":[\"[Ṽéŕţîçáļ ~~~]\"],\"node.door.panel.wall\":[\"[ŵáļļ ~~]\"],\"node.door.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.door.style.barn\":[\"[Ɓáŕñ ~~]\"],\"node.door.style.craftsman\":[\"[Çŕáƒţšɱáñ ~~~]\"],\"node.door.style.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.door.style.glass\":[\"[Ĝļášš ~~]\"],\"node.door.style.halfLouvered\":[\"[Ĥáļƒ-ļöûṽéŕéð ~~~~]\"],\"node.door.style.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.door.style.panel\":[\"[Þáñéļ ~~]\"],\"node.door.style.paneledGlass\":[\"[Þáñéļéð ĝļášš ~~~~]\"],\"node.door.style.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.door.style.sixPanel\":[\"[Šîẋ-þáñéļ ~~~]\"],\"node.door.type.barn\":[\"[Ɓáŕñ ~~]\"],\"node.door.type.double\":[\"[Ðöûƀļé ~~]\"],\"node.door.type.folding\":[\"[Ƒöļðîñĝ ~~~]\"],\"node.door.type.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.door.type.garageRollup\":[\"[Ŕöļļ-ûþ ~~]\"],\"node.door.type.garageSectional\":[\"[Šéçţîöñáļ ~~~]\"],\"node.door.type.garageTiltup\":[\"[Ţîļţ-ûþ ~~]\"],\"node.door.type.hinged\":[\"[Ĥîñĝéð ~~]\"],\"node.door.type.pocket\":[\"[Þöçķéţ ~~]\"],\"node.door.type.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.dormer.description\":[\"[Ĥöûšé-šĥáþéð þŕöţŕûšîöñ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~]\"],\"node.dormer.label\":[\"[Ðöŕɱéŕ ~~]\"],\"node.dormer.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.dormer.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.dormer.panel.dormer\":[\"[Ðöŕɱéŕ ~~]\"],\"node.dormer.panel.dutch\":[\"[Ðûţçĥ ~~]\"],\"node.dormer.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.dormer.panel.gable\":[\"[Ĝáƀļé ~~]\"],\"node.dormer.panel.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"node.dormer.panel.hip\":[\"[Ĥîþ ~]\"],\"node.dormer.panel.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"node.dormer.panel.pitchDirection\":[\"[Þîţçĥ Ðîŕéçţîöñ ~~~~~]\"],\"node.dormer.panel.pitchRise\":[\"[Þîţçĥ Ŕîšé ~~~]\"],\"node.dormer.panel.riseBack\":[\"[Ŕîšé Ɓáçķ ~~~]\"],\"node.dormer.panel.riseFront\":[\"[Ŕîšé Ƒŕöñţ ~~~]\"],\"node.dormer.panel.roofHeight\":[\"[Ŕööƒ Ĥéîĝĥţ ~~~]\"],\"node.dormer.panel.roofType\":[\"[Ŕööƒ Ţýþé ~~~]\"],\"node.dormer.panel.section\":[\"[Šéçţîöñ ~~~]\"],\"node.dormer.panel.shed\":[\"[Šĥéð ~~]\"],\"node.dormer.panel.wallHeight\":[\"[Ŵáļļ Ĥéîĝĥţ ~~~]\"],\"node.dormer.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.dormer.panel.windows\":[\"[Ŵîñðöŵš ~~~]\"],\"node.dormer.panelActionsSection.actions\":[\"[Åçţîöñš ~~~]\"],\"node.dormer.panelActionsSection.delete\":[\"[Ðéļéţé ~~]\"],\"node.dormer.panelActionsSection.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.dormer.panelActionsSection.move\":[\"[Ṁöṽé ~~]\"],\"node.dormer.panelPositionSection.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.dormer.panelPositionSection.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.dormer.panelWindowsSection.addWindow\":[\"[Åðð Ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.editNamed\":[\"[Éðîţ \",[\"name\"],\" ~~]\"],\"node.dormer.panelWindowsSection.editWindow\":[\"[Éðîţ ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd\":[\"[Îñçŕéášé ţĥé ðöŕɱéŕ ŵîðţĥ ţö áðð áñöţĥéŕ ŵîñðöŵ. ~~~~~~~~~~~~]\"],\"node.dormer.panelWindowsSection.move\":[\"[Ṁöṽé ~~]\"],\"node.dormer.panelWindowsSection.moveNamed\":[\"[Ṁöṽé \",[\"name\"],\" ~~]\"],\"node.dormer.panelWindowsSection.moveWindow\":[\"[Ṁöṽé ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.noWindows\":[\"[Ñö ŵîñðöŵš ~~~]\"],\"node.dormer.panelWindowsSection.windowNumber\":[\"[Ŵîñðöŵ \",[\"number\"],\" ~~]\"],\"node.dormer.panelWindowsSection.windowSummary\":[\"[\",[\"face\",\"select\",{\"back\":[\"Ɓáçķ\"],\"left\":[\"Ļéƒţ\"],\"right\":[\"Ŕîĝĥţ\"],\"other\":[\"Ƒŕöñţ\"]}],\" · \",[\"width\"],\" × \",[\"height\"],\" ɱ ~~~~~~]\"],\"node.dormer.panelWindowsSection.windowsLength\":[\"[Ŵîñðöŵš (\",[\"length\"],\") ~~~]\"],\"node.downspout.description\":[\"[Ṽéŕţîçáļ ðŕöþ þîþé ƒŕöɱ á ĝûţţéŕ öûţļéţ ţö ţĥé ĝŕöûñð. ~~~~~~~~~~~~~~]\"],\"node.downspout.inspectorEditors.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.downspout.label\":[\"[Ðöŵñšþöûţ ~~~]\"],\"node.ductFitting.description\":[\"[Éļƀöŵ, ţéé, ŕéðûçéŕ, öŕ šǫûáŕé-ţö-ŕöûñð ţŕáñšîţîöñ çöññéçţîñĝ ðûçţ ŕûñš. ~~~~~~~~~~~~~~~~~~]\"],\"node.ductFitting.inspectorEditors.swapWH\":[\"[Šŵáþ Ŵ/Ĥ ~~]\"],\"node.ductFitting.inspectorEditors.swapWidthAndHeight\":[\"[Šŵáþ ŵîðţĥ áñð ĥéîĝĥţ ~~~~~~]\"],\"node.ductFitting.label\":[\"[Ðûçţ Ƒîţţîñĝ ~~~~]\"],\"node.ductFitting.tool.axis\":[\"[Åẋîš ~~]\"],\"node.ductFitting.tool.axis2\":[\"[⌥ áẋîš ~~]\"],\"node.ductFitting.tool.hoverADuctFaceThatFits\":[\"[Ĥöṽéŕ á ðûçţ ƒáçé ţĥáţ ƒîţš ţĥé ðööŕ ~~~~~~~~~]\"],\"node.ductFitting.tool.placeAccessDoor\":[\"[Þļáçé áççéšš ðööŕ ~~~~~]\"],\"node.ductFitting.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.ductSegment.description\":[\"[ĤṼÅÇ ðûçţ ŕûñ — þöļýļîñé öƒ ŕöûñð, ŕéçţ, öŕ ƒļáţ-öṽáļ šéçţîöñš. ~~~~~~~~~~~~~~~]\"],\"node.ductSegment.label\":[\"[Ðûçţ ~~]\"],\"node.ductTerminal.description\":[\"[Ðûçţ ţéŕɱîñáļ — šûþþļý ŕéĝîšţéŕ, çéîļîñĝ ðûšéŕ, öŕ ŕéţûŕñ ĝŕîļļé. Ðûçţ ŕûñš éñð áţ îţš çöļļáŕ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.ductTerminal.label\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"node.ductTerminal.tool.mSurface\":[\"[Ṁ šûŕƒáçé ~~~]\"],\"node.ductTerminal.tool.mount\":[\"[\",[\"mount\",\"select\",{\"floor\":[\"Ṁöûñţ Ƒļööŕ\"],\"ceiling\":[\"Ṁöûñţ Çéîļîñĝ\"],\"wall\":[\"Ṁöûñţ Ŵáļļ\"],\"other\":[\"Ṁöûñţ \",[\"label\"]]}],\" ~~~~~~~~~~~]\"],\"node.ductTerminal.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.ductTerminal.tool.snappedToDuct\":[\"[Šñáþþéð ţö ðûçţ ~~~~]\"],\"node.elevator.description\":[\"[Å ɱûļţî-ļéṽéļ éļéṽáţöŕ šĥáƒţ ŵîţĥ çöñƒîĝûŕáƀļé öþéñîñĝš þéŕ ļéṽéļ. ~~~~~~~~~~~~~~~~~]\"],\"node.elevator.label\":[\"[Éļéṽáţöŕ ~~~]\"],\"node.elevator.panel.access\":[\"[Åççéšš ~~]\"],\"node.elevator.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.elevator.panel.cab\":[\"[Çáƀ ~]\"],\"node.elevator.panel.cabHeight\":[\"[Çáƀ Ĥéîĝĥţ ~~~]\"],\"node.elevator.panel.centerOpening\":[\"[Çéñţéŕ öþéñîñĝ ~~~~]\"],\"node.elevator.panel.defaultFloor\":[\"[Ðéƒáûļţ Ƒļööŕ ~~~~]\"],\"node.elevator.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.elevator.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.elevator.panel.destination\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"node.elevator.panel.disabled\":[\"[Ðîšáƀļéð ~~~]\"],\"node.elevator.panel.doorHeight\":[\"[Ðööŕ Ĥéîĝĥţ ~~~]\"],\"node.elevator.panel.doorTime\":[\"[Ðööŕ Ţîɱé ~~~]\"],\"node.elevator.panel.doorType\":[\"[Ðööŕ Ţýþé ~~~]\"],\"node.elevator.panel.doorWidth\":[\"[Ðööŕ Ŵîðţĥ ~~~]\"],\"node.elevator.panel.doors\":[\"[Ðööŕš ~~]\"],\"node.elevator.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.elevator.panel.dwell\":[\"[Ðŵéļļ ~~]\"],\"node.elevator.panel.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"node.elevator.panel.from\":[\"[Ƒŕöɱ ~~]\"],\"node.elevator.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.elevator.panel.glassFrame\":[\"[Ĝļášš ƒŕáɱé ~~~]\"],\"node.elevator.panel.motion\":[\"[Ṁöţîöñ ~~]\"],\"node.elevator.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.elevator.panel.openingStyle\":[\"[Öþéñîñĝ Šţýļé ~~~~]\"],\"node.elevator.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.elevator.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.elevator.panel.segmentedPanel\":[\"[Šéĝɱéñţéð þáñéļ ~~~~~]\"],\"node.elevator.panel.service\":[\"[Šéŕṽîçé ~~~]\"],\"node.elevator.panel.shaft\":[\"[Šĥáƒţ ~~]\"],\"node.elevator.panel.shaftDepth\":[\"[Šĥáƒţ Ðéþţĥ ~~~]\"],\"node.elevator.panel.shaftStyle\":[\"[Šĥáƒţ Šţýļé ~~~]\"],\"node.elevator.panel.shaftWidth\":[\"[Šĥáƒţ Ŵîðţĥ ~~~]\"],\"node.elevator.panel.singleLeft\":[\"[Šîñĝļé ļéƒţ ~~~]\"],\"node.elevator.panel.singleRight\":[\"[Šîñĝļé ŕîĝĥţ ~~~~]\"],\"node.elevator.panel.solid\":[\"[Šöļîð ~~]\"],\"node.elevator.panel.solidPanel\":[\"[Šöļîð þáñéļ ~~~]\"],\"node.elevator.panel.speed\":[\"[Šþééð ~~]\"],\"node.elevator.panel.stopStoporder\":[\"[Šţöþ \",[\"stopOrder\"],\" ~~]\"],\"node.elevator.panel.to\":[\"[Ţö ~]\"],\"node.elevator.panel.wallThickness\":[\"[Ŵáļļ Ţĥîçķñéšš ~~~~]\"],\"node.elevator.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.elevator.panel.yaw\":[\"[Ýáŵ ~]\"],\"node.eyebrowVent.description\":[\"[Ļöŵ çûŕṽéð ļéñš-šĥáþéð ŕööƒ ṽéñţ ŵîţĥ á ļöûṽéŕéð ƒŕöñţ. ~~~~~~~~~~~~~~]\"],\"node.eyebrowVent.label\":[\"[Éýéƀŕöŵ Ṽéñţ ~~~~]\"],\"node.eyebrowVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.eyebrowVent.panel.backHeight\":[\"[Ɓáçķ ĥéîĝĥţ ~~~]\"],\"node.eyebrowVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.eyebrowVent.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.eyebrowVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.eyebrowVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.eyebrowVent.panel.eyebrowVent\":[\"[Éýéƀŕöŵ Ṽéñţ ~~~~]\"],\"node.eyebrowVent.panel.halfRound\":[\"[Ĥáļƒ-ŕöûñð ~~~]\"],\"node.eyebrowVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.eyebrowVent.panel.louvers\":[\"[Ļöûṽéŕš ~~~]\"],\"node.eyebrowVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.eyebrowVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.eyebrowVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.eyebrowVent.panel.scoop\":[\"[Šçööþ ~~]\"],\"node.eyebrowVent.panel.slantBox\":[\"[Šļáñţ-ƀöẋ ~~~]\"],\"node.eyebrowVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.eyebrowVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.fence.description\":[\"[Å šţŕáîĝĥţ öŕ çûŕṽéð ƒéñçé šéĝɱéñţ ŵîţĥ çöñƒîĝûŕáƀļé þöšţš áñð îñƒîļļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.addPointAfter\":[\"[Åðð þöîñţ áƒţéŕ ~~~~]\"],\"node.fence.inspectorEditors.chooseASlabOrShapedSurface\":[\"[Çĥööšé á šļáƀ öŕ šĥáþéð šûŕƒáçé öñ ţĥîš ļéṽéļ. ~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.chooseASurface\":[\"[Çĥööšé á šûŕƒáçé ~~~~~]\"],\"node.fence.inspectorEditors.corner\":[\"[Çöŕñéŕ ~~]\"],\"node.fence.inspectorEditors.curve\":[\"[Çûŕṽé ~~]\"],\"node.fence.inspectorEditors.curved\":[\"[Çûŕṽéð ~~]\"],\"node.fence.inspectorEditors.distributionInfo\":[\"[\",[\"style\",\"select\",{\"picket\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš þîçķéţš îñ éáçĥ ƀáý ƀéţŵééñ þöšţš.\"],\"horizontal\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš þöšţš áļöñĝ ţĥé ƒûļļ ƒéñçé.\"],\"other\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš îñƒîļļ þîéçéš ƀéţŵééñ ţĥé ţŵö éñð þöšţš.\"]}],\" Šþáçîñĝ îš ɱéášûŕéð ƀéţŵééñ çéñţéŕš. Ţĥé áṽáîļáƀļé ŵîðţĥ ļîɱîţš ţĥé çöûñţ šö þîéçéš ðö ñöţ öṽéŕļáþ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.editPath\":[\"[Éðîţ þáţĥ ~~~]\"],\"node.fence.inspectorEditors.extendPath\":[\"[Éẋţéñð þáţĥ ~~~]\"],\"node.fence.inspectorEditors.followHighestSurface\":[\"[Ƒöļļöŵ ĥîĝĥéšţ šûŕƒáçé ~~~~~~]\"],\"node.fence.inspectorEditors.followSelectedSurface\":[\"[Ƒöļļöŵ šéļéçţéð šûŕƒáçé ~~~~~~~]\"],\"node.fence.inspectorEditors.holdStartingHeight\":[\"[Ĥöļð šţáŕţîñĝ ĥéîĝĥţ ~~~~~~]\"],\"node.fence.inspectorEditors.join\":[\"[Ĵöîñ ~~]\"],\"node.fence.inspectorEditors.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.fence.inspectorEditors.pathSummary\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" þöîñţ\"],\"other\":[[\"number\"],\" þöîñţš\"]}],\" · \",[\"length\"],\" ɱ ~~~~]\"],\"node.fence.inspectorEditors.picketRailProjection\":[\"[Þîçķéţ ŕáîļ þŕöĵéçţîöñ ~~~~~~]\"],\"node.fence.inspectorEditors.point\":[\"[Þöîñţ ~~]\"],\"node.fence.inspectorEditors.removePoint\":[\"[Ŕéɱöṽé þöîñţ ~~~~]\"],\"node.fence.inspectorEditors.setEachSpanToStraightOr\":[\"[Šéţ éáçĥ šþáñ ţö šţŕáîĝĥţ öŕ çûŕṽéð, ţĥéñ ðŕáĝ îţš þöîñţš áñð çûŕṽé ĥáñðļéš îñ éîţĥéŕ ṽîéŵ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.smooth\":[\"[Šɱööţĥ ~~]\"],\"node.fence.inspectorEditors.spanToPointNumber\":[\"[Šþáñ ţö þöîñţ \",[\"number\"],\" ~~~~]\"],\"node.fence.inspectorEditors.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.fence.inspectorEditors.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.fence.inspectorEditors.surfaceBehavior\":[\"[Šûŕƒáçé ƀéĥáṽîöŕ ~~~~~]\"],\"node.fence.label\":[\"[Ƒéñçé ~~]\"],\"node.fence.moveEndpointTool.detachEndpoint\":[\"[Ðéţáçĥ éñðþöîñţ ~~~~~]\"],\"node.fence.moveEndpointTool.dragEndpoint\":[\"[Ðŕáĝ éñðþöîñţ ~~~~]\"],\"node.fenceFeature.actions.close\":[\"[Çļöšé ~~]\"],\"node.fenceFeature.actions.open\":[\"[Öþéñ ~~]\"],\"node.fenceFeature.inspector.boardWidth\":[\"[Ɓöáŕð ŵîðţĥ ~~~]\"],\"node.fenceFeature.inspector.bracing\":[\"[Ɓŕáçîñĝ ~~~]\"],\"node.fenceFeature.inspector.distanceFromStart\":[\"[Ðîšţáñçé ƒŕöɱ šţáŕţ ~~~~~~]\"],\"node.fenceFeature.inspector.fenceUnavailable\":[\"[Ƒéñçé ûñáṽáîļáƀļé. ~~~~~]\"],\"node.fenceFeature.inspector.frameWidth\":[\"[Ƒŕáɱé ŵîðţĥ ~~~]\"],\"node.fenceFeature.inspector.gateHeight\":[\"[Ĝáţé ĥéîĝĥţ ~~~]\"],\"node.fenceFeature.inspector.gateStyle\":[\"[Ĝáţé šţýļé ~~~]\"],\"node.fenceFeature.inspector.groundClearance\":[\"[Ĝŕöûñð çļéáŕáñçé ~~~~~]\"],\"node.fenceFeature.inspector.hingeSide\":[\"[Ĥîñĝé šîðé ~~~]\"],\"node.fenceFeature.inspector.hingesAndLatch\":[\"[Ĥîñĝéš áñð ļáţçĥ ~~~~~]\"],\"node.fenceFeature.inspector.horizontalBoards\":[\"[Ĥöŕîžöñţáļ ƀöáŕðš ~~~~~]\"],\"node.fenceFeature.inspector.infillSpacing\":[\"[Îñƒîļļ šþáçîñĝ ~~~~]\"],\"node.fenceFeature.inspector.jambPosts\":[\"[Ĵáɱƀ þöšţš ~~~]\"],\"node.fenceFeature.inspector.keepTheOpeningInsideTheFence\":[\"[Ķééþ ţĥé öþéñîñĝ îñšîðé ţĥé ƒéñçé áñð çļéáŕ öƒ öţĥéŕ ĝáţéš öŕ þáššáĝéš. ~~~~~~~~~~~~~~~~~~]\"],\"node.fenceFeature.inspector.leafThickness\":[\"[Ļéრţĥîçķñéšš ~~~~]\"],\"node.fenceFeature.inspector.leaves\":[\"[Ļéáṽéš ~~]\"],\"node.fenceFeature.inspector.leftLeafProportion\":[\"[Ļéƒţ ļéრþŕöþöŕţîöñ ~~~~~~]\"],\"node.fenceFeature.inspector.matchFenceHeight\":[\"[Ṁáţçĥ ƒéñçé ĥéîĝĥţ ~~~~~]\"],\"node.fenceFeature.inspector.matchFenceStyle\":[\"[Ṁáţçĥ ƒéñçé šţýļé ~~~~~]\"],\"node.fenceFeature.inspector.openRails\":[\"[Öþéñ ŕáîļš ~~~]\"],\"node.fenceFeature.inspector.openingAngle\":[\"[Öþéñîñĝ áñĝļé ~~~~]\"],\"node.fenceFeature.inspector.openingWidth\":[\"[Öþéñîñĝ ŵîðţĥ ~~~~]\"],\"node.fenceFeature.inspector.option.cross\":[\"[Çŕöšš ~~]\"],\"node.fenceFeature.inspector.option.diagonal\":[\"[Ðîáĝöñáļ ~~~]\"],\"node.fenceFeature.inspector.option.doubleGate\":[\"[Ðöûƀļé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.option.inward\":[\"[Îñŵáŕð ~~]\"],\"node.fenceFeature.inspector.option.left\":[\"[Ļéƒţ ~~]\"],\"node.fenceFeature.inspector.option.none\":[\"[Ñöñé ~~]\"],\"node.fenceFeature.inspector.option.outward\":[\"[Öûţŵáŕð ~~~]\"],\"node.fenceFeature.inspector.option.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.fenceFeature.inspector.option.singleGate\":[\"[Šîñĝļé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.picket\":[\"[Þîçķéţ ~~]\"],\"node.fenceFeature.inspector.postHeight\":[\"[Þöšţ ĥéîĝĥţ ~~~]\"],\"node.fenceFeature.inspector.removeGate\":[\"[Ŕéɱöṽé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.removeOpening\":[\"[Ŕéɱöṽé öþéñîñĝ ~~~~]\"],\"node.fenceFeature.inspector.solidPrivacy\":[\"[Šöļîð þŕîṽáçý ~~~~]\"],\"node.fenceFeature.inspector.swingDirection\":[\"[Šŵîñĝ ðîŕéçţîöñ ~~~~~]\"],\"node.fenceFeature.inspector.verticalSlats\":[\"[Ṽéŕţîçáļ šļáţš ~~~~]\"],\"node.fenceGate.description\":[\"[Å ĝáţé ĥöšţéð ƀý á ƒéñçé. ~~~~~~]\"],\"node.fenceGate.label\":[\"[Ĝáţé ~~]\"],\"node.fenceOpening.description\":[\"[Åñ îñţéñţîöñáļ öþéñîñĝ îñ á ƒéñçé. ~~~~~~~~~]\"],\"node.fenceOpening.label\":[\"[Öþéñ Þáššáĝé ~~~~]\"],\"node.floorOpening.label\":[\"[Ƒļööŕ öþéñîñĝ ~~~~]\"],\"node.guide.description\":[\"[Å ɱéášûŕéɱéñţ / ŕéƒéŕéñçé áññöţáţîöñ (ļîñéáŕ, áŕéá, öŕ áŕç). ~~~~~~~~~~~~~~]\"],\"node.guide.label\":[\"[Ĝûîðé ~~]\"],\"node.gutter.description\":[\"[Ŕáîñ-ŵáţéŕ çĥáññéļ ŕûññîñĝ áļöñĝ ţĥé éáṽé öƒ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~~]\"],\"node.gutter.downspoutsPanel.addDownspout\":[\"[Åðð Ðöŵñšþöûţ ~~~~]\"],\"node.gutter.downspoutsPanel.downspoutNumber\":[\"[Ðöŵñšþöûţ \",[\"number\"],\" ~~~]\"],\"node.gutter.downspoutsPanel.downspouts\":[\"[Ðöŵñšþöûţš ~~~]\"],\"node.gutter.downspoutsPanel.removeDownspout\":[\"[Ŕéɱöṽé ðöŵñšþöûţ ~~~~~]\"],\"node.gutter.label\":[\"[Ĝûţţéŕ ~~]\"],\"node.hole.manual\":[\"[Ṁáñûáļ ~~]\"],\"node.holeEditing\":[\"[Ĥöļé \",[\"number\"],\" (Éðîţîñĝ) ~~~~]\"],\"node.holeNumber\":[\"[Ĥöļé \",[\"number\"],\" ~~]\"],\"node.holeSummary\":[\"[\",[\"area\"],\" ɱ² · \",[\"count\",\"plural\",{\"one\":[[\"points\"],\" þţ\"],\"other\":[[\"points\"],\" þţš\"]}],\" · \",[\"source\"],\" ~~]\"],\"node.hvacEquipment.description\":[\"[Ƒûŕñáçé, áîŕ ĥáñðļéŕ, öŕ çöñðéñšéŕ — ðûçţ ŕûñš çöññéçţ ţö îţš šûþþļý/ŕéţûŕñ çöļļáŕš. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.hvacEquipment.label\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"node.hvacEquipment.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.importedMesh.description\":[\"[Ĝéöɱéţŕý þŕéšéŕṽéð ƒŕöɱ áñ îɱþöŕţéð ɱöðéļ ŵĥéñ ñö ñáţîṽé Þášçáļ šĥáþé éẋîšţš. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.importedMesh.label\":[\"[Îɱþöŕţéð Ṁéšĥ ~~~~]\"],\"node.item.authoredParams.couldnTRebuildWithTheseValues\":[\"[Çöûļðñ'ţ ŕéƀûîļð ŵîţĥ ţĥéšé ṽáļûéš. ~~~~~~~~~]\"],\"node.item.authoredParams.parameters\":[\"[Þáŕáɱéţéŕš ~~~]\"],\"node.item.authoredParams.rebuilding\":[\"[Ŕéƀûîļðîñĝ… ~~~]\"],\"node.item.authoredParams.rebuildingInBlender\":[\"[Ŕéƀûîļðîñĝ îñ Ɓļéñðéŕ… ~~~~~~]\"],\"node.item.authoredParams.resetParam\":[\"[Ŕéšéţ \",[\"param\"],\" ~~]\"],\"node.item.authoredParams.resetParameters\":[\"[Ŕéšéţ þáŕáɱéţéŕš ~~~~~]\"],\"node.item.authoredParams.resetToDefault\":[\"[Ŕéšéţ ţö ðéƒáûļţ ~~~~~]\"],\"node.item.description\":[\"[Å çáţáļöĝ-ƀáçķéð îţéɱ (ƒûŕñîţûŕé, ƒîẋţûŕéš, ðéçöŕáţîöñš). ~~~~~~~~~~~~~~]\"],\"node.item.label\":[\"[Îţéɱ ~~]\"],\"node.item.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.item.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.item.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.item.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.item.panel.info\":[\"[Îñƒö ~~]\"],\"node.item.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.item.panel.pos\":[\"[þöš ~]\"],\"node.item.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.item.panel.rot\":[\"[ŕöţ ~]\"],\"node.item.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.item.panel.scale\":[\"[Šçáļé ~~]\"],\"node.item.panel.scale2\":[\"[šçáļé ~~]\"],\"node.item.panel.uniformScale\":[\"[Ûñîƒöŕɱ Šçáļé ~~~~]\"],\"node.item.panel.xyz\":[\"[ẊÝŽ ~]\"],\"node.leanToExtension.description\":[\"[Åñ áţţáçĥéð ɱöñö-þîţçĥ öŕ ƒŕééšţáñðîñĝ ɱöñö, ĝáƀļé, öŕ ƀûţţéŕƒļý çáñöþý ŵîţĥ ɱáñáĝéð šţŕûçţûŕé áñð ðŕáîñáĝé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.leanToExtension.label\":[\"[Çáñöþý ~~]\"],\"node.level.description\":[\"[Å šîñĝļé ƒļööŕ öƒ á ƀûîļðîñĝ, ĥöļðîñĝ ŵáļļš / šļáƀš / çéîļîñĝš / îţéɱš. ~~~~~~~~~~~~~~~~]\"],\"node.level.label\":[\"[Ļéṽéļ ~~]\"],\"node.lineset.description\":[\"[Ŕéƒŕîĝéŕáñţ ļîñéšéţ — çöþþéŕ šûçţîöñ + ļîǫûîð þáîŕ ĵöîñîñĝ á çöñðéñšéŕ ţö ţĥé îñðööŕ çöîļ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.lineset.label\":[\"[Ļîñéšéţ ~~~]\"],\"node.liquidLine.description\":[\"[Šţáñðáļöñé ŕéƒŕîĝéŕáñţ ļîǫûîð ļîñé — á ţĥîñ ƀáŕé-çöþþéŕ ŕûñ; Ƒöļļöŵ ɱöðé ţŕáçéš áñ éẋîšţîñĝ ļîñéšéţ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.liquidLine.label\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"node.liquidLine.tool.clickToTraceThisLinesetRun\":[\"[Çļîçķ ţö ţŕáçé ţĥîš ļîñéšéţ ŕûñ ~~~~~~~~]\"],\"node.liquidLine.tool.followHoverALineset\":[\"[Ƒöļļöŵ: ĥöṽéŕ á ļîñéšéţ ~~~~~~]\"],\"node.measurement.description\":[\"[Å þéŕšîšţéñţ ðîšţáñçé, áñĝļé, áŕéá, þéŕîɱéţéŕ, öŕ ṽöļûɱé áññöţáţîöñ. ~~~~~~~~~~~~~~~~~]\"],\"node.measurement.label\":[\"[Ṁéášûŕéɱéñţ ~~~~]\"],\"node.numberedName\":[[\"kind\"],\" \",[\"number\"]],\"node.panel.currently\":[\"[Çûŕŕéñţļý \",[\"value\"],\" ~~~]\"],\"node.pipeFitting.description\":[\"[ÐŴṼ ĵöîñţ — éļƀöŵ ƀéñð, 45° ŵýé, öŕ šáñîţáŕý ţéé. ~~~~~~~~~~]\"],\"node.pipeFitting.label\":[\"[Þîþé Ƒîţţîñĝ ~~~~]\"],\"node.pipeFitting.tool.axis\":[\"[Åẋîš ~~]\"],\"node.pipeFitting.tool.axis2\":[\"[⌥ áẋîš ~~]\"],\"node.pipeFitting.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.pipeSegment.description\":[\"[Ðŕáîñ / ŵášţé / ṽéñţ þîþé ŕûñ — ðŕáŵ ļéṽéļ öŕ ţöĝĝļé á ¼″ þéŕ ƒööţ ƒáļļ ŵîţĥ Š. ~~~~~~~~~~~~~~~~~]\"],\"node.pipeSegment.label\":[\"[ÐŴṼ Þîþé ~~~]\"],\"node.pipeTrap.description\":[\"[ÐŴṼ Þ-ţŕáþ — ŵáţéŕ šéáļ öñ ţĥé ŵášţé ļîñé. Ţĥé ţŕáþ áŕɱ ŕûñš ţö ţĥé ṽéñţ. ~~~~~~~~~~~~~~~~~]\"],\"node.pipeTrap.label\":[\"[Ţŕáþ ~~]\"],\"node.pipeTrap.tool.diameterTrap\":[\"[\",[\"diameter\"],\"\\\" Ţŕáþ ~~]\"],\"node.pipeTrap.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.proceduralItem.description\":[\"[Éẋþéŕîɱéñţáļ éðîţáƀļé ŕéçîþé ~~~~~~~~]\"],\"node.proceduralItem.label\":[\"[Þŕöçéðûŕáļ îţéɱ ~~~~~]\"],\"node.ridgeVent.description\":[\"[Ṽéñţîļáţîöñ šţŕîþ ŕûññîñĝ áļöñĝ ţĥé ŕîðĝé öƒ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~~]\"],\"node.ridgeVent.label\":[\"[Ŕîðĝé Ṽéñţ ~~~]\"],\"node.ridgeVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.ridgeVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.ridgeVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.ridgeVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.ridgeVent.panel.endCaps\":[\"[Éñð Çáþš ~~~]\"],\"node.ridgeVent.panel.flanged\":[\"[Ƒļáñĝéð ~~~]\"],\"node.ridgeVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.ridgeVent.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.ridgeVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.ridgeVent.panel.open\":[\"[Öþéñ ~~]\"],\"node.ridgeVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.ridgeVent.panel.ridgeVent\":[\"[Ŕîðĝé Ṽéñţ ~~~]\"],\"node.ridgeVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.ridgeVent.panel.shingled\":[\"[Šĥîñĝļéð ~~~]\"],\"node.ridgeVent.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.ridgeVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.ridgeVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.roof.barrelPanel.archBays\":[\"[Åŕçĥ ƀáýš ~~~]\"],\"node.roof.barrelPanel.barrelArch\":[\"[Ɓáŕŕéļ áŕçĥ ~~~]\"],\"node.roof.barrelPanel.flatPanesFollowACircularArch\":[\"[Ƒļáţ þáñéš ƒöļļöŵ á çîŕçûļáŕ áŕçĥ. Ŕîšé îš ɱéášûŕéð ƒŕöɱ ţĥé éáṽéš. ~~~~~~~~~~~~~~~~~]\"],\"node.roof.barrelPanel.rise\":[\"[Ŕîšé ~~]\"],\"node.roof.description\":[\"[Å þîţçĥéð / ĥîþ / ĝáƀļé ŕööƒ çöɱþöšéð öƒ öñé öŕ ɱöŕé šéĝɱéñţš. ~~~~~~~~~~~~~~~]\"],\"node.roof.glazing.face.archBay\":[\"[Åŕçĥ ƀáý \",[\"number\"],\" ~~~]\"],\"node.roof.glazing.face.back\":[\"[Ɓáçķ šļöþé ~~~]\"],\"node.roof.glazing.face.endLeft\":[\"[Ļéƒţ éñð ~~~]\"],\"node.roof.glazing.face.endRight\":[\"[Ŕîĝĥţ éñð ~~~]\"],\"node.roof.glazing.face.front\":[\"[Ƒŕöñţ šļöþé ~~~]\"],\"node.roof.glazing.face.slope\":[\"[Šĥéð šļöþé ~~~]\"],\"node.roof.glazingControls.alignment\":[\"[Åļîĝñɱéñţ ~~~]\"],\"node.roof.glazingControls.center\":[\"[Çéñţéŕ ~~]\"],\"node.roof.glazingControls.chooseALayoutToEditThe\":[\"[Çĥööšé á ļáýöûţ ţö éðîţ ţĥé ĝŕîð. ~~~~~~~~]\"],\"node.roof.glazingControls.empty\":[\"[Éɱþţý ~~]\"],\"node.roof.glazingControls.end\":[\"[Éñð ~]\"],\"node.roof.glazingControls.fixedSpacing\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"node.roof.glazingControls.glass\":[\"[Ĝļášš ~~]\"],\"node.roof.glazingControls.layout\":[\"[Ļáýöûţ ~~]\"],\"node.roof.glazingControls.maxSpacing\":[\"[Ṁáẋ šþáçîñĝ ~~~]\"],\"node.roof.glazingControls.maximumSpacing\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"node.roof.glazingControls.mixed\":[\"[Ṁîẋéð ~~]\"],\"node.roof.glazingControls.panelCount\":[\"[Þáñéļ çöûñţ ~~~]\"],\"node.roof.glazingControls.panels\":[\"[Þáñéļš ~~]\"],\"node.roof.glazingControls.solid\":[\"[Šöļîð ~~]\"],\"node.roof.glazingControls.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.roof.glazingControls.start\":[\"[Šţáŕţ ~~]\"],\"node.roof.glazingControls.titleAlignment\":[\"[\",[\"title\"],\" áļîĝñɱéñţ ~~~]\"],\"node.roof.glazingPanel.changesApplyToAllLengthRoof\":[\"[Çĥáñĝéš áþþļý ţö áļļ \",[\"length\"],\" ŕööƒ šéĝɱéñţš. ~~~~~~~~~]\"],\"node.roof.glazingPanel.chooseOneRoofSystemToEdit\":[\"[Çĥööšé öñé ŕööƒ šýšţéɱ ţö éðîţ šéţţîñĝš ƒöŕ áļļ šéļéçţéð šéĝɱéñţš. ~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.closeTheRoofEnds\":[\"[Çļöšé ţĥé ŕööƒ éñðš ~~~~~]\"],\"node.roof.glazingPanel.defaultPanelInfill\":[\"[Ðéƒáûļţ þáñéļ îñƒîļļ ~~~~~~]\"],\"node.roof.glazingPanel.dividesEachRoofFaceIntoColumns\":[\"[Ðîṽîðéš éáçĥ ŕööƒ ƒáçé îñţö çöļûɱñš. ~~~~~~~~~]\"],\"node.roof.glazingPanel.dividesTheLengthOfEachArch\":[\"[Ðîṽîðéš ţĥé ļéñĝţĥ öƒ éáçĥ áŕçĥ ƀáý îñţö çöļûɱñš. ~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.endInfillFillsTheSpaceAbove\":[\"[Éñð îñƒîļļ ƒîļļš ţĥé šþáçé áƀöṽé ţĥé ŵáļļš. Éðîţ ŕööɱ ŵáļļš áñð çéîļîñĝš šéþáŕáţéļý. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.frameColor\":[\"[Ƒŕáɱé çöļöŕ ~~~]\"],\"node.roof.glazingPanel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.roof.glazingPanel.frameWidth\":[\"[Ƒŕáɱé ŵîðţĥ ~~~]\"],\"node.roof.glazingPanel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.roof.glazingPanel.glass\":[\"[Ĝļášš ~~]\"],\"node.roof.glazingPanel.glassTint\":[\"[Ĝļášš ţîñţ ~~~]\"],\"node.roof.glazingPanel.glazed\":[\"[Ĝļážéð ~~]\"],\"node.roof.glazingPanel.glazingIsAvailableForGableStraight\":[\"[Ĝļážîñĝ îš áṽáîļáƀļé ƒöŕ ĝáƀļé, šţŕáîĝĥţ šĥéð, áñð ƀáŕŕéļ ŕööƒš. ~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.gridAcrossTheRoof\":[\"[Ĝŕîð áçŕöšš ţĥé ŕööƒ ~~~~~~]\"],\"node.roof.glazingPanel.gridAlongTheLength\":[\"[Ĝŕîð áļöñĝ ţĥé ļéñĝţĥ ~~~~~~]\"],\"node.roof.glazingPanel.gridAlongTheSlope\":[\"[Ĝŕîð áļöñĝ ţĥé šļöþé ~~~~~~]\"],\"node.roof.glazingPanel.gridWithinEachArchBay\":[\"[Ĝŕîð ŵîţĥîñ éáçĥ áŕçĥ ƀáý ~~~~~~~]\"],\"node.roof.glazingPanel.infill\":[\"[Îñƒîļļ ~~]\"],\"node.roof.glazingPanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"node.roof.glazingPanel.paneThickness\":[\"[Þáñé ţĥîçķñéšš ~~~~]\"],\"node.roof.glazingPanel.roofSystem\":[\"[Ŕööƒ šýšţéɱ ~~~]\"],\"node.roof.glazingPanel.roughness\":[\"[Ŕöûĝĥñéšš ~~~]\"],\"node.roof.glazingPanel.spacingIsMeasuredOnTheSlope\":[\"[Šþáçîñĝ îš ɱéášûŕéð öñ ţĥé šļöþé, ñöţ îţš ĥöŕîžöñţáļ þŕöĵéçţîöñ. ~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.roof.glazingPanel.subdividesEachFlatArchBayArch\":[\"[Šûƀðîṽîðéš éáçĥ ƒļáţ áŕçĥ ƀáý. Åŕçĥ ƀáýš çöñţŕöļš ţĥé ðîṽîšîöñš áŕöûñð ţĥé çûŕṽé. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanelEditor.chooseAPanelToOverrideIts\":[\"[Çĥööšé á þáñéļ ţö öṽéŕŕîðé îţš ðéƒáûļţ îñƒîļļ. ~~~~~~~~~~~~]\"],\"node.roof.glazingPanelEditor.column2\":[\"[Çöļûɱñ ~~]\"],\"node.roof.glazingPanelEditor.custom\":[\"[Çûšţöɱ ~~]\"],\"node.roof.glazingPanelEditor.default\":[\"[Ðéƒáûļţ ~~~]\"],\"node.roof.glazingPanelEditor.grid\":[\"[\",[\"columns\",\"plural\",{\"one\":[[\"columnCount\"],\" çöļûɱñ\"],\"other\":[[\"columnCount\"],\" çöļûɱñš\"]}],\" · \",[\"rows\",\"plural\",{\"one\":[[\"rowCount\"],\" ŕöŵ\"],\"other\":[[\"rowCount\"],\" ŕöŵš\"]}],\" ~~~~~~]\"],\"node.roof.glazingPanelEditor.individualPanels\":[\"[Îñðîṽîðûáļ þáñéļš ~~~~~]\"],\"node.roof.glazingPanelEditor.panelInfill\":[\"[Þáñéļ îñƒîļļ ~~~~]\"],\"node.roof.glazingPanelEditor.resetToDefault\":[\"[Ŕéšéţ ţö ðéƒáûļţ ~~~~~]\"],\"node.roof.glazingPanelEditor.roofFace\":[\"[Ŕööƒ ƒáçé ~~~]\"],\"node.roof.glazingPanelEditor.row2\":[\"[Ŕöŵ ~]\"],\"node.roof.glazingPanelEditor.selectedPanelInfill\":[\"[Šéļéçţéð þáñéļ îñƒîļļ ~~~~~~]\"],\"node.roof.label\":[\"[Ŕööƒ ~~]\"],\"node.roof.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.roof.panel.addChimney\":[\"[Åðð Çĥîɱñéý ~~~]\"],\"node.roof.panel.addCupola\":[\"[Åðð Çûþöļá ~~~]\"],\"node.roof.panel.addDormer\":[\"[Åðð Ðöŕɱéŕ ~~~]\"],\"node.roof.panel.addEyebrowVent\":[\"[Åðð Éýéƀŕöŵ Ṽéñţ ~~~~~]\"],\"node.roof.panel.addGutter\":[\"[Åðð Ĝûţţéŕ ~~~]\"],\"node.roof.panel.addSkylight\":[\"[Åðð Šķýļîĝĥţ ~~~~]\"],\"node.roof.panel.addSolarPanel\":[\"[Åðð Šöļáŕ Þáñéļ ~~~~]\"],\"node.roof.panel.addVent\":[\"[Åðð Ṽéñţ ~~~]\"],\"node.roof.panel.box\":[\"[Ɓöẋ ~]\"],\"node.roof.panel.boxVent\":[\"[ƀöẋ ṽéñţ ~~~]\"],\"node.roof.panel.chimney\":[\"[çĥîɱñéý ~~~]\"],\"node.roof.panel.custom\":[\"[Çûšţöɱ ~~]\"],\"node.roof.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.roof.panel.dormer\":[\"[ðöŕɱéŕ ~~]\"],\"node.roof.panel.drawSegment\":[\"[Ðŕáŵ Šéĝɱéñţ ~~~~]\"],\"node.roof.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.roof.panel.elements\":[\"[Éļéɱéñţš ~~~]\"],\"node.roof.panel.followsWalls\":[\"[Ƒöļļöŵš ŵáļļš ~~~~]\"],\"node.roof.panel.gutter\":[\"[ĝûţţéŕ ~~]\"],\"node.roof.panel.level\":[\"[Ļéṽéļ ~~]\"],\"node.roof.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.roof.panel.moveToLevel\":[\"[Ṁöṽé ţö \",[\"level\"],\" ~~]\"],\"node.roof.panel.moveToTheRoofLevel\":[\"[Ṁöṽé ţö ţĥé ŕööƒ ļéṽéļ ~~~~~~]\"],\"node.roof.panel.onStoreyWalls\":[\"[Öñ \",[\"storey\"],\" ŵîţĥ îţš ŵáļļš, šö îţ ĥîðéš ţĥáţ ƒļööŕ'š þļáñ îñ šöļö áñð éẋþļöðéð ṽîéŵš.\",[\"roofs\",\"plural\",{\"0\":[],\"1\":[],\"other\":[\" Ţĥé ƒļööŕ'š \",[\"roofCount\"],\" ŕööƒš ɱöṽé ţöĝéţĥéŕ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.roof.panel.ridge\":[\"[Ŕîðĝé ~~]\"],\"node.roof.panel.ridgeVent\":[\"[ŕîðĝé ṽéñţ ~~~]\"],\"node.roof.panel.roof\":[\"[Ŕööƒ ~~]\"],\"node.roof.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.roof.panel.segmentType\":[\"[\",[\"roofType\",\"select\",{\"hip\":[\"Ĥîþ\"],\"gable\":[\"Ĝáƀļé\"],\"shed\":[\"Šĥéð\"],\"gambrel\":[\"Ĝáɱƀŕéļ\"],\"dutch\":[\"Ðûţçĥ\"],\"mansard\":[\"Ṁáñšáŕð\"],\"flat\":[\"Ƒļáţ\"],\"conical\":[\"Çöñîçáļ\"],\"barrel\":[\"Ɓáŕŕéļ\"],\"other\":[\"Ŕööƒ\"]}],\" ~~~~~~~~~~~~~~~~]\"],\"node.roof.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.roof.panel.skylight\":[\"[šķýļîĝĥţ ~~~]\"],\"node.roof.panel.solarPanel\":[\"[šöļáŕ þáñéļ ~~~]\"],\"node.roof.panel.turbine\":[\"[Ţûŕƀîñé ~~~]\"],\"node.roof.panel.turbineVent\":[\"[ţûŕƀîñé ṽéñţ ~~~~]\"],\"node.roofSegment.description\":[\"[Å šîñĝļé þîţçĥéð þļáñé öƒ á þáŕéñţ ŕööƒ. ~~~~~~~~~~]\"],\"node.roofSegment.label\":[\"[Ŕööƒ Šéĝɱéñţ ~~~~]\"],\"node.roofSegment.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.roofSegment.panel.angle\":[\"[Åñĝļé ~~]\"],\"node.roofSegment.panel.arc\":[\"[Åŕç ~]\"],\"node.roofSegment.panel.autoGutters\":[\"[Åûţö ĝûţţéŕš ~~~~]\"],\"node.roofSegment.panel.autoRidgeVent\":[\"[Åûţö ŕîðĝé ṽéñţ ~~~~]\"],\"node.roofSegment.panel.barrel\":[\"[Ɓáŕŕéļ ~~]\"],\"node.roofSegment.panel.clippedVersion\":[\"[Çļîþþéð ṽéŕšîöñ ~~~~~]\"],\"node.roofSegment.panel.conical\":[\"[Çöñîçáļ ~~~]\"],\"node.roofSegment.panel.conicalShape\":[\"[Çöñîçáļ Šĥáþé ~~~~]\"],\"node.roofSegment.panel.deckThick\":[\"[Ðéçķ Ţĥîçķ. ~~~]\"],\"node.roofSegment.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.roofSegment.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.roofSegment.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.roofSegment.panel.doneEditing\":[\"[Ðöñé éðîţîñĝ ~~~~]\"],\"node.roofSegment.panel.drainage\":[\"[Ðŕáîñáĝé ~~~]\"],\"node.roofSegment.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.roofSegment.panel.dutch\":[\"[Ðûţçĥ ~~]\"],\"node.roofSegment.panel.eaveOverhang\":[\"[Éáṽé öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.editFootprint\":[\"[Éðîţ ƒööţþŕîñţ ~~~~]\"],\"node.roofSegment.panel.endOverhang\":[\"[Éñð öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.fascia\":[\"[Ƒášçîá ~~]\"],\"node.roofSegment.panel.fasciaHeight\":[\"[Ƒášçîá ĥéîĝĥţ ~~~~]\"],\"node.roofSegment.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.roofSegment.panel.footprint\":[\"[Ƒööţþŕîñţ ~~~]\"],\"node.roofSegment.panel.gable\":[\"[Ĝáƀļé ~~]\"],\"node.roofSegment.panel.gableOverhang\":[\"[Ĝáƀļé öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"node.roofSegment.panel.hip\":[\"[Ĥîþ ~]\"],\"node.roofSegment.panel.kinkDepth\":[\"[Ķîñķ Ðéþţĥ ~~~]\"],\"node.roofSegment.panel.kinkHeight\":[\"[Ķîñķ Ĥéîĝĥţ ~~~]\"],\"node.roofSegment.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.roofSegment.panel.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"node.roofSegment.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.roofSegment.panel.pitch\":[\"[Þîţçĥ ~~]\"],\"node.roofSegment.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.roofSegment.panel.reset\":[\"[Ŕéšéţ ~~]\"],\"node.roofSegment.panel.roofSegment\":[\"[Ŕööƒ Šéĝɱéñţ ~~~~]\"],\"node.roofSegment.panel.roofType\":[\"[Ŕööƒ Ţýþé ~~~]\"],\"node.roofSegment.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.roofSegment.panel.shape\":[\"[Šĥáþé ~~]\"],\"node.roofSegment.panel.shed\":[\"[Šĥéð ~~]\"],\"node.roofSegment.panel.shingleThick\":[\"[Šĥîñĝļé Ţĥîçķ. ~~~~]\"],\"node.roofSegment.panel.span\":[\"[Šþáñ ~~]\"],\"node.roofSegment.panel.startAngle\":[\"[Šţáŕţ Åñĝļé ~~~]\"],\"node.roofSegment.panel.structure\":[\"[Šţŕûçţûŕé ~~~]\"],\"node.roofSegment.panel.topRakeLength\":[\"[Ţöþ Ŕáķé Ļéñĝţĥ ~~~~]\"],\"node.roofSegment.panel.topRakeThick\":[\"[Ţöþ Ŕáķé Ţĥîçķ. ~~~~]\"],\"node.roofSegment.panel.trim\":[\"[Ţŕîɱ ~~]\"],\"node.roofSegment.panel.waistHeight\":[\"[Ŵáîšţ Ĥéîĝĥţ ~~~~]\"],\"node.roofSegment.panel.waistLength\":[\"[Ŵáîšţ Ļéñĝţĥ ~~~~]\"],\"node.roofSegment.panel.waistWidth\":[\"[Ŵáîšţ Ŵîðţĥ ~~~]\"],\"node.roofSegment.panel.wall\":[\"[Ŵáļļ ~~]\"],\"node.roofSegment.panel.wallHeight\":[\"[Ŵáļļ Ĥéîĝĥţ ~~~]\"],\"node.roofSegment.panel.wallThick\":[\"[Ŵáļļ Ţĥîçķ. ~~~]\"],\"node.roofSegment.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.scan.description\":[\"[Å çáþţûŕéð šéššîöñ ŵîţĥ öþţîöñáļ ɱéšĥ, ɱöţîöñ, ɱéðîá, áñð šéñšöŕ ðáţá. ~~~~~~~~~~~~~~~~~]\"],\"node.scan.label\":[\"[Çáþţûŕé ~~~]\"],\"node.segmentNumber\":[\"[Šéĝɱéñţ \",[\"number\"],\" ~~~]\"],\"node.separator.label\":[\"[Šéþáŕáţöŕ ~~~]\"],\"node.shared.itemInteractionActions.turnLightOff\":[\"[Ţûŕñ ļîĝĥţ öƒƒ ~~~~]\"],\"node.shared.itemInteractionActions.turnLightOn\":[\"[Ţûŕñ ļîĝĥţ öñ ~~~~]\"],\"node.shared.runHangerInspector.automaticSupport\":[\"[Åûţöɱáţîç šûþþöŕţ ~~~~~]\"],\"node.shared.runHangerInspector.hangerOnSegment\":[\"[Ĥáñĝéŕ \",[\"number\"],\" · šéĝɱéñţ \",[\"segment\"],\" ~~~~]\"],\"node.shared.runHangerInspector.individualHangers\":[\"[Îñðîṽîðûáļ ĥáñĝéŕš ~~~~~~]\"],\"node.shared.runHangerInspector.missingSupport\":[\"[Ṁîššîñĝ šûþþöŕţ ~~~~~]\"],\"node.shared.runHangerInspector.noSupportWithinReachMoveThe\":[\"[Ñö šûþþöŕţ ŵîţĥîñ ŕéáçĥ. Ṁöṽé ţĥé ĥáñĝéŕ öŕ çĥööšé áñöţĥéŕ ĥöšţ. ~~~~~~~~~~~~~~~~]\"],\"node.shared.runHangerInspector.positionLabel\":[\"[Ĥáñĝéŕ \",[\"number\"],\" þöšîţîöñ îñ ɱéţŕéš ~~~~~~~]\"],\"node.shared.runHangerInspector.positionM\":[\"[Þöšîţîöñ (ɱ) ~~~]\"],\"node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath\":[\"[Þöšîţîöñš áŕé ɱéášûŕéð áļöñĝ éáçĥ þáţĥ šéĝɱéñţ. Çĥáñĝîñĝ šþáçîñĝ ŕéĝéñéŕáţéš ţĥé ñûɱƀéŕéð šļöţš. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.shared.runHangerInspector.revealSupport\":[\"[Ŕéṽéáļ šûþþöŕţ ~~~~]\"],\"node.shared.runHangerInspector.skip\":[\"[Šķîþ ~~]\"],\"node.shared.runHangerInspector.supportLabel\":[\"[Ĥáñĝéŕ \",[\"number\"],\" šûþþöŕţ ~~~~]\"],\"node.shared.systemCheckPanel.aboutTheseChecks\":[\"[Åƀöûţ ţĥéšé çĥéçķš ~~~~~]\"],\"node.shared.systemCheckPanel.allFindings\":[\"[Åļļ ƒîñðîñĝš ~~~~]\"],\"node.shared.systemCheckPanel.allSystems\":[\"[Åļļ šýšţéɱš ~~~]\"],\"node.shared.systemCheckPanel.connections\":[\"[Çöññéçţîöñš ~~~~]\"],\"node.shared.systemCheckPanel.drainage\":[\"[Ðŕáîñáĝé ~~~]\"],\"node.shared.systemCheckPanel.error\":[\"[Éŕŕöŕ ~~]\"],\"node.shared.systemCheckPanel.errors\":[\"[Éŕŕöŕš ~~]\"],\"node.shared.systemCheckPanel.filterSystemChecks\":[\"[Ƒîļţéŕ šýšţéɱ çĥéçķš ~~~~~~]\"],\"node.shared.systemCheckPanel.hangers\":[\"[Ĥáñĝéŕš ~~~]\"],\"node.shared.systemCheckPanel.intersections\":[\"[Îñţéŕšéçţîöñš ~~~~]\"],\"node.shared.systemCheckPanel.locate\":[\"[Ļöçáţé \",[\"name\"],\" (\",[\"id\"],\") ~~]\"],\"node.shared.systemCheckPanel.missingItem\":[\"[Ṁîššîñĝ îţéɱ ~~~~]\"],\"node.shared.systemCheckPanel.noFindings\":[\"[Ñö ƒîñðîñĝš ~~~]\"],\"node.shared.systemCheckPanel.noFindingsInThisCategory\":[\"[Ñö ƒîñðîñĝš îñ ţĥîš çáţéĝöŕý ~~~~~~~~]\"],\"node.shared.systemCheckPanel.openEnds\":[\"[Öþéñ éñðš ~~~]\"],\"node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay\":[\"[Öþéñ éñðš áñð šéþáŕáţé ƀŕáñçĥéš ɱáý ƀé îñţéñţîöñáļ. Îñţéŕšéçţîöñ çĥéçķš ûšé ƀöûñðîñĝ ƀöẋéš; îñšþéçţ öþéñîñĝš áñð çļéáŕáñçéš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.shared.systemCheckPanel.selectedConnectedSystem\":[\"[Šéļéçţéð çöññéçţéð šýšţéɱ ~~~~~~~]\"],\"node.shared.systemCheckPanel.selectedRuns\":[\"[Šéļéçţéð: \",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕûñ\"],\"other\":[[\"number\"],\" ŕûñš\"]}],\" · \",[\"length\"],\" ɱ ~~~~~]\"],\"node.shared.systemCheckPanel.separateBranches\":[\"[Šéþáŕáţé ƀŕáñçĥéš ~~~~~]\"],\"node.shared.systemCheckPanel.showAllFindings\":[\"[Šĥöŵ áļļ ƒîñðîñĝš ~~~~~]\"],\"node.shared.systemCheckPanel.systemChecks\":[\"[Šýšţéɱ çĥéçķš ~~~~]\"],\"node.shared.systemCheckPanel.systemFinding\":[\"[Šýšţéɱ ƒîñðîñĝ ~~~~]\"],\"node.shared.systemCheckPanel.systemFindings\":[\"[Šýšţéɱ ƒîñðîñĝš ~~~~~]\"],\"node.shared.systemCheckPanel.warning\":[\"[Ŵáŕñîñĝ ~~~]\"],\"node.shared.systemCheckPanel.warnings\":[\"[Ŵáŕñîñĝš ~~~]\"],\"node.shelf.description\":[\"[Å çöñƒîĝûŕáƀļé šĥéļṽîñĝ ûñîţ. Îţéɱš ĥöšţ öñ éáçĥ ŕöŵ. ~~~~~~~~~~~~~]\"],\"node.shelf.label\":[\"[Šĥéļƒ ~~]\"],\"node.site.description\":[\"[Ţĥé ţöþ-ļéṽéļ çöñţáîñéŕ ĥöļðîñĝ ƀûîļðîñĝš, žöñéš, áñð ţĥé þŕöþéŕţý ƀöûñðáŕý. ~~~~~~~~~~~~~~~~~~~]\"],\"node.site.label\":[\"[Šîţé ~~]\"],\"node.site.panel.address\":[\"[Åððŕéšš ~~~]\"],\"node.site.panel.apn\":[\"[ÅÞÑ ~]\"],\"node.site.panel.autoMostNorthFacingEdge\":[\"[Åûţö — ɱöšţ ñöŕţĥ-ƒáçîñĝ éðĝé ~~~~~~~]\"],\"node.site.panel.contourIntervalSitePlanAnd3d\":[\"[Çöñţöûŕ îñţéŕṽáļ (šîţé þļáñ áñð 3Ð) ~~~~~~~~~]\"],\"node.site.panel.county\":[\"[Çöûñţý ~~]\"],\"node.site.panel.eGRs60\":[\"[é.ĝ. ŔŠ-60 ~~]\"],\"node.site.panel.eGTampaLdc27156\":[\"[é.ĝ. Ţáɱþá ĻÐÇ §27-156 ~~~]\"],\"node.site.panel.every12In\":[\"[éṽéŕý 12 îñ ~~~]\"],\"node.site.panel.every24In\":[\"[éṽéŕý 24 îñ ~~~]\"],\"node.site.panel.every6In\":[\"[éṽéŕý 6 îñ ~~~]\"],\"node.site.panel.findParcel\":[\"[Ƒîñð þáŕçéļ ~~~]\"],\"node.site.panel.finding\":[\"[Ƒîñðîñĝ… ~~~]\"],\"node.site.panel.frontStreetEdge\":[\"[Ƒŕöñţ (šţŕééţ) éðĝé ~~~~~]\"],\"node.site.panel.lookingUpTheParcel\":[\"[Ļööķîñĝ ûþ ţĥé þáŕçéļ áñð ţĥé šţŕééţš áŕöûñð îţ… ~~~~~~~~~~~~]\"],\"node.site.panel.lotArea\":[\"[Ļöţ áŕéá ~~~]\"],\"node.site.panel.n1200WCassStTampaFl\":[\"[1200 Ŵ Çášš Šţ, Ţáɱþá, ƑĻ ~~~~~]\"],\"node.site.panel.noSurveyedLinesContoursAreDrawn\":[\"[Ñö šûŕṽéýéð ļîñéš — çöñţöûŕš áŕé ðŕáŵñ ƒŕöɱ ţĥé šçûļþţéð ĝŕöûñð. ~~~~~~~~~~~~~~~~]\"],\"node.site.panel.none\":[\"[ñöñé ~~]\"],\"node.site.panel.northRotation\":[\"[Ñöŕţĥ ŕöţáţîöñ ~~~~]\"],\"node.site.panel.orientation\":[\"[Öŕîéñţáţîöñ ~~~~]\"],\"node.site.panel.parcel\":[\"[Þáŕçéļ ~~]\"],\"node.site.panel.parcelLookupFailed\":[\"[Þáŕçéļ ļööķûþ ƒáîļéð. ~~~~~~]\"],\"node.site.panel.projectLook\":[\"[Þŕöĵéçţ ļööķ ~~~~]\"],\"node.site.panel.saveCurrentLook\":[\"[Šáṽé çûŕŕéñţ ļööķ ~~~~~]\"],\"node.site.panel.saveTheCurrentLightingAndDisplay\":[\"[Šáṽé ţĥé çûŕŕéñţ ļîĝĥţîñĝ áñð ðîšþļáý áš ţĥîš þŕöĵéçţ'š ðéƒáûļţ. Þéŕšöñáļ ðîšþļáý çĥáñĝéš šţáý îñ ţĥîš ƀŕöŵšéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.site.panel.setbacks\":[\"[Šéţƀáçķš ~~~]\"],\"node.site.panel.showGround\":[\"[Šĥöŵ ĝŕöûñð ~~~]\"],\"node.site.panel.showTheContourLinesIn3d\":[\"[Šĥöŵ ţĥé çöñţöûŕ ļîñéš îñ 3Ð ~~~~~~~]\"],\"node.site.panel.side\":[\"[šîðé ~~]\"],\"node.site.panel.site\":[\"[Šîţé ~~]\"],\"node.site.panel.source\":[\"[Šöûŕçé ~~]\"],\"node.site.panel.sourceCitation\":[\"[Šöûŕçé / çîţáţîöñ ~~~~~]\"],\"node.site.panel.streetAddress\":[\"[Šţŕééţ áððŕéšš ~~~~]\"],\"node.site.panel.survey\":[\"[šûŕṽéý ~~]\"],\"node.site.panel.surveyedContours\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"lineCount\"],\" šûŕṽéýéð ļîñé\"],\"other\":[[\"lineCount\"],\" šûŕṽéýéð ļîñéš\"]}],\" áţ \",[\"interval\"],\" ƒţ (\",[\"datum\"],\") — \",[\"source\"],\" ~~~~~~~~~]\"],\"node.site.panel.terrain\":[\"[Ţéŕŕáîñ ~~~]\"],\"node.site.panel.typeAnAddressFirst\":[\"[Ţýþé áñ áððŕéšš ƒîŕšţ. ~~~~~~]\"],\"node.site.panel.useProjectLook\":[\"[Ûšé þŕöĵéçţ ļööķ ~~~~~]\"],\"node.site.panel.zone\":[\"[Žöñé ~~]\"],\"node.site.setback.front\":[\"[Ƒŕöñţ ~~]\"],\"node.site.setback.leftOverride\":[\"[Ļéƒţ (öṽéŕŕîðé) ~~~~]\"],\"node.site.setback.rear\":[\"[Ŕéáŕ ~~]\"],\"node.site.setback.rightOverride\":[\"[Ŕîĝĥţ (öṽéŕŕîðé) ~~~~]\"],\"node.site.setback.side\":[\"[Šîðé ~~]\"],\"node.skylight.description\":[\"[Ƒŕáɱéð ĝļášš öþéñîñĝ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~]\"],\"node.skylight.label\":[\"[Šķýļîĝĥţ ~~~]\"],\"node.skylight.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.skylight.panel.alongX\":[\"[Åļöñĝ Ẋ ~~]\"],\"node.skylight.panel.alongZ\":[\"[Åļöñĝ Ž ~~]\"],\"node.skylight.panel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.skylight.panel.curb\":[\"[Çûŕƀ ~~]\"],\"node.skylight.panel.cutoutOffset\":[\"[Çûţöûţ Öƒƒšéţ ~~~~]\"],\"node.skylight.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.skylight.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.skylight.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.skylight.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.skylight.panel.glassThickness\":[\"[Ĝļášš Ţĥîçķñéšš ~~~~~]\"],\"node.skylight.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.skylight.panel.lanternHeight\":[\"[Ļáñţéŕñ Ĥéîĝĥţ ~~~~]\"],\"node.skylight.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.skylight.panel.motor\":[\"[Ṁöţöŕ ~~]\"],\"node.skylight.panel.motorHousing\":[\"[Ṁöţöŕ Ĥöûšîñĝ ~~~~]\"],\"node.skylight.panel.no\":[\"[Ñö ~]\"],\"node.skylight.panel.noMotor\":[\"[Ñö Ṁöţöŕ ~~~]\"],\"node.skylight.panel.open\":[\"[Öþéñ ~~]\"],\"node.skylight.panel.openingAngle\":[\"[Öþéñîñĝ Åñĝļé ~~~~]\"],\"node.skylight.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.skylight.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.skylight.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.skylight.panel.skylight\":[\"[Šķýļîĝĥţ ~~~]\"],\"node.skylight.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.skylight.panel.top\":[\"[Ţöþ ~]\"],\"node.skylight.panel.topScale\":[\"[Ţöþ Šçáļé ~~~]\"],\"node.skylight.panel.trackWidth\":[\"[Ţŕáçķ Ŵîðţĥ ~~~]\"],\"node.skylight.panel.type\":[\"[Ţýþé ~~]\"],\"node.skylight.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.skylight.panel.yes\":[\"[Ýéš ~]\"],\"node.skylight.type.flat\":[\"[Ƒļáţ ŕööƒ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.lantern\":[\"[Ŕööƒ ļáñţéŕñ ~~~~]\"],\"node.skylight.type.opening\":[\"[Öþéñîñĝ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.sliding\":[\"[Šļîðîñĝ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.walkOn\":[\"[Ŵáļķ-öñ ŕööƒļîĝĥţ ~~~~~]\"],\"node.slab.description\":[\"[Å þöļýĝöñ-ƀöûñðéð ƒļööŕ šûŕƒáçé ţĥáţ ĥöšţš îţéɱš öñ ţöþ. ~~~~~~~~~~~~~~]\"],\"node.slab.label\":[\"[Šļáƀ ~~]\"],\"node.slab.panel.addHole\":[\"[Åðð Ĥöļé ~~~]\"],\"node.slab.panel.area\":[\"[Åŕéá ~~]\"],\"node.slab.panel.auto\":[\"[Åûţö ~~]\"],\"node.slab.panel.base\":[\"[Ɓášé ~~]\"],\"node.slab.panel.cutHatch\":[\"[Çûţ ĥáţçĥ ~~~]\"],\"node.slab.panel.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"node.slab.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.slab.panel.done\":[\"[Ðöñé ~~]\"],\"node.slab.panel.elevation\":[\"[Éļéṽáţîöñ ~~~]\"],\"node.slab.panel.extendsThePerimeterDownToTerrain\":[\"[Éẋţéñðš ţĥé þéŕîɱéţéŕ ðöŵñ ţö ţéŕŕáîñ. Ţĥé ƒļáţ šûŕƒáçé, ƀášé, áñð ţĥîçķñéšš šţáý ûñçĥáñĝéð. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.slab.panel.fixed\":[\"[Ƒîẋéð ~~]\"],\"node.slab.panel.floor\":[\"[Ƒļööŕ ~~]\"],\"node.slab.panel.followsTerrain\":[\"[Ƒöļļöŵš ţéŕŕáîñ ~~~~~]\"],\"node.slab.panel.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"node.slab.panel.holes\":[\"[Ĥöļéš ~~]\"],\"node.slab.panel.info\":[\"[Îñƒö ~~]\"],\"node.slab.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.slab.panel.noHoles\":[\"[Ñö ĥöļéš ~~~]\"],\"node.slab.panel.rim\":[\"[Ŕîɱ ~]\"],\"node.slab.panel.slab\":[\"[Šļáƀ ~~]\"],\"node.slab.panel.standard2\":[\"[Šţáñðáŕð (2\\\") ~~~]\"],\"node.slab.panel.standard5cm\":[\"[Šţáñðáŕð (5çɱ) ~~~]\"],\"node.slab.panel.sunken15cm\":[\"[Šûñķéñ (15çɱ) ~~~]\"],\"node.slab.panel.sunken6\":[\"[Šûñķéñ (6\\\") ~~]\"],\"node.slab.panel.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.slab.panel.thick15cm\":[\"[Ţĥîçķ (15çɱ) ~~~]\"],\"node.slab.panel.thick6\":[\"[Ţĥîçķ (6\\\") ~~]\"],\"node.slab.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.slab.panel.thin1\":[\"[Ţĥîñ (1\\\") ~~]\"],\"node.slab.panel.thin2cm\":[\"[Ţĥîñ (2çɱ) ~~]\"],\"node.solarPanel.description\":[\"[Ĝŕîð öƒ þĥöţöṽöļţáîç þáñéļš ɱöûñţéð öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~]\"],\"node.solarPanel.label\":[\"[Šöļáŕ Þáñéļ ~~~]\"],\"node.solarPanel.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.solarPanel.panel.array\":[\"[Åŕŕáý ~~]\"],\"node.solarPanel.panel.autoFitToRoof\":[\"[Åûţö-ƒîţ ţö ŕööƒ ~~~~]\"],\"node.solarPanel.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.solarPanel.panel.customDimensionsDonTMatchAny\":[\"[Çûšţöɱ — ðîɱéñšîöñš ðöñ'ţ ɱáţçĥ áñý þŕéšéţ ~~~~~~~~~~~]\"],\"node.solarPanel.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.solarPanel.panel.flipOrientation\":[\"[Ƒļîþ öŕîéñţáţîöñ ~~~~~]\"],\"node.solarPanel.panel.flush\":[\"[Ƒļûšĥ ~~]\"],\"node.solarPanel.panel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.solarPanel.panel.frameThickness\":[\"[Ƒŕáɱé ţĥîçķñéšš ~~~~~]\"],\"node.solarPanel.panel.gapX\":[\"[Ĝáþ Ẋ ~~]\"],\"node.solarPanel.panel.gapY\":[\"[Ĝáþ Ý ~~]\"],\"node.solarPanel.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.solarPanel.panel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.solarPanel.panel.panel\":[\"[Þáñéļ ~~]\"],\"node.solarPanel.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.solarPanel.panel.rows\":[\"[Ŕöŵš ~~]\"],\"node.solarPanel.panel.setbacksTooLarge\":[\"[Šéţƀáçķš ţöö ļáŕĝé ţö ƒîţ á þáñéļ. ~~~~~~~~~]\"],\"node.solarPanel.panel.solarPanel\":[\"[Šöļáŕ Þáñéļ ~~~]\"],\"node.solarPanel.panel.standoff\":[\"[Šţáñðöƒƒ ~~~]\"],\"node.solarPanel.panel.tiltAngle\":[\"[Ţîļţ áñĝļé ~~~]\"],\"node.solarPanel.panel.tilted\":[\"[Ţîļţéð ~~]\"],\"node.solarPanel.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.solarPanel.preset.compact\":[\"[Çöɱþáçţ ~~~]\"],\"node.solarPanel.preset.frameless\":[\"[Ƒŕáɱéļéšš ~~~]\"],\"node.solarPanel.preset.residential\":[\"[Ŕéšîðéñţîáļ ~~~~]\"],\"node.solarPanel.preset.residentialLarge\":[\"[Ŕéšîðéñţîáļ Ļáŕĝé ~~~~~]\"],\"node.spawn.description\":[\"[Þļáýéŕ öŕ çáɱéŕá öŕîĝîñ ŵîţĥîñ á ļéṽéļ. Öñé þéŕ ļéṽéļ. ~~~~~~~~~~~~~]\"],\"node.spawn.label\":[\"[Šþáŵñ Þöîñţ ~~~]\"],\"node.stair.constructionControls.body\":[\"[Ɓöðý ~~]\"],\"node.stair.constructionControls.centerStringer\":[\"[Çéñţéŕ šţŕîñĝéŕ ~~~~~]\"],\"node.stair.constructionControls.closedRisers\":[\"[Çļöšéð ŕîšéŕš ~~~~]\"],\"node.stair.constructionControls.inherit\":[\"[Îñĥéŕîţ ~~~]\"],\"node.stair.constructionControls.nosing\":[\"[Ñöšîñĝ ~~]\"],\"node.stair.constructionControls.open\":[\"[Öþéñ ~~]\"],\"node.stair.constructionControls.original\":[\"[Öŕîĝîñáļ ~~~]\"],\"node.stair.constructionControls.riserThickness\":[\"[Ŕîšéŕ ţĥîçķñéšš ~~~~~]\"],\"node.stair.constructionControls.sideStringers\":[\"[Šîðé šţŕîñĝéŕš ~~~~]\"],\"node.stair.constructionControls.solid\":[\"[Šöļîð ~~]\"],\"node.stair.constructionControls.stringerDepth\":[\"[Šţŕîñĝéŕ ðéþţĥ ~~~~]\"],\"node.stair.constructionControls.stringerWidth\":[\"[Šţŕîñĝéŕ ŵîðţĥ ~~~~]\"],\"node.stair.constructionControls.treadFinish\":[\"[Ţŕéáð ƒîñîšĥ ~~~~]\"],\"node.stair.constructionControls.treadThickness\":[\"[Ţŕéáð ţĥîçķñéšš ~~~~~]\"],\"node.stair.constructionControls.waist\":[\"[Ŵáîšţ ~~]\"],\"node.stair.constructionControls.waistThickness\":[\"[Ŵáîšţ ţĥîçķñéšš ~~~~~]\"],\"node.stair.description\":[\"[Å šţáîŕ çöɱþöšéð öƒ öñé öŕ ɱöŕé ƒļîĝĥţš ŵîţĥ çöñƒîĝûŕáƀļé ţŕéáðš, ŕîšéŕš, ŕáîļîñĝš. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.arrivalMismatch\":[\"[Ţĥé ƒļîĝĥţ çĥáîñ ðöéš ñöţ ŕéáçĥ ţĥé ðéçļáŕéð ðéšţîñáţîöñ. ~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.comfortTarget\":[\"[Ţŵö ŕîšéŕš þļûš ĝöîñĝ îš öûţšîðé ţĥé 0.60–0.65 ɱ ðéšîĝñ ŕáñĝé. ~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.goingTarget\":[\"[Ĝöîñĝ îš ƀéļöŵ ţĥé ðéšîĝñ ţáŕĝéţ. ~~~~~~~~~]\"],\"node.stair.diagnostic.headroomTarget\":[\"[Öṽéŕĥéáð šûŕƒáçéš ļéáṽé ļéšš ţĥáñ ţĥé ţáŕĝéţ ĥéáðŕööɱ. ~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.headroomUnresolved\":[\"[Ðéţáîļéð ĥéáðŕööɱ çöûļð ñöţ ƀé éṽáļûáţéð: çĥéçķ ŕîšéŕ çöûñţš áñð ţĥé šûŕƒáçé ǫûéŕý ƀûðĝéţ. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.invalidFlight\":[\"[Ƒļîĝĥţ ðîɱéñšîöñš áñð îñţéĝéŕ ŕîšéŕ çöûñţ ɱûšţ ƀé þöšîţîṽé. ~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.invalidRise\":[\"[Ţĥé ðéšţîñáţîöñ ɱûšţ ƀé áƀöṽé ţĥé šţáîŕ ƀášé. ~~~~~~~~~~~~]\"],\"node.stair.diagnostic.landingOverlap\":[\"[Ļáñðîñĝ ðéþţĥ éẋçééðš á ƒûļļ çîŕçļé áţ ţĥé ŵáļķîñĝ ļîñé; ţĥé ṽîšîƀļé ļáñðîñĝ îš ļîɱîţéð ţö öñé çîŕçļé. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.narrowWinderEnd\":[\"[Ţĥé ñáŕŕöŵ éñð öƒ á ŵîñðéŕ ţŕéáð îš ƀéļöŵ 0.10 ɱ; çĥéçķ ţĥé îñţéñðéð šţáîŕ ûšé. ~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.nonuniformRisers\":[\"[Ƒļîĝĥţ ŕîšéŕ ĥéîĝĥţš ðéŕ. Ûšé ûñîƒöŕɱ-ŕîšéŕ ŕéþáîŕ öñļý îƒ ţĥîš îš á þŕöþöšéð ðéšîĝñ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.raisedLanding\":[\"[Å ļáñðîñĝ ĥáš á šţöŕéð ŕîšé; îñšþéçţ îţš ţŕáñšîţîöñ ƀéƒöŕé ŕéþáîŕ. ~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.riserTarget\":[\"[Ŕîšéŕ ĥéîĝĥţ éẋçééðš ţĥé ðéšîĝñ ţáŕĝéţ. ~~~~~~~~~~]\"],\"node.stair.label\":[\"[Šţáîŕ ~~]\"],\"node.stair.layout.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stair.layout.turn\":[\"[\",[\"layout\"],\" ţûŕñ ~~]\"],\"node.stair.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.stair.panel.addFlight\":[\"[Åðð ƒļîĝĥţ ~~~]\"],\"node.stair.panel.addLanding\":[\"[Åðð ļáñðîñĝ ~~~]\"],\"node.stair.panel.applyLabelLayout\":[\"[Åþþļý \",[\"label\"],\" ļáýöûţ ~~~~]\"],\"node.stair.panel.autoCutout\":[\"[Åûţö Çûţöûţ ~~~]\"],\"node.stair.panel.balusters\":[\"[Ɓáļûšţéŕš ~~~]\"],\"node.stair.panel.boards\":[\"[Ɓöáŕðš ~~]\"],\"node.stair.panel.both\":[\"[Ɓöţĥ ~~]\"],\"node.stair.panel.cable\":[\"[Çáƀļé ~~]\"],\"node.stair.panel.centerColumn\":[\"[Çéñţéŕ Çöļûɱñ ~~~~]\"],\"node.stair.panel.clockwise\":[\"[Çļöçķŵîšé ~~~]\"],\"node.stair.panel.connectedTo\":[\"[Çöññéçţéð ţö \",[\"surface\"],\". Ṁöṽé ţĥé šţáîŕ ţö çöññéçţ ţö áñöţĥéŕ éðĝé. ~~~~~~~~~~~~~~]\"],\"node.stair.panel.connectedToSurface\":[\"[Çöññéçţéð ţö á šûŕƒáçé. Ṁöṽé ţĥé šţáîŕ ţö çöññéçţ ţö áñöţĥéŕ éðĝé. ~~~~~~~~~~~~~~~~]\"],\"node.stair.panel.construction\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"node.stair.panel.counterclockwise\":[\"[Çöûñţéŕçļöçķŵîšé ~~~~~]\"],\"node.stair.panel.curved\":[\"[Çûŕṽéð ~~]\"],\"node.stair.panel.customRise\":[\"[Çûšţöɱ ŕîšé ~~~]\"],\"node.stair.panel.deck\":[\"[Ðéçķ ~~]\"],\"node.stair.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.stair.panel.designTargets\":[\"[Ðéšîĝñ ţáŕĝéţš ~~~~]\"],\"node.stair.panel.destination\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"node.stair.panel.dragNearADeckPatioConcrete\":[\"[Ðŕáĝ ñéáŕ á ðéçķ, þáţîö, çöñçŕéţé šļáƀ, öŕ ļáñðîñĝ éðĝé ţö ƒîţ ţĥé šţáîŕ. ~~~~~~~~~~~~~~~~~]\"],\"node.stair.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.stair.panel.equalAngle\":[\"[Éǫûáļ áñĝļé ~~~]\"],\"node.stair.panel.equalGoing\":[\"[Éǫûáļ ĝöîñĝ ~~~]\"],\"node.stair.panel.fitRisersAndGoing\":[\"[Ƒîţ ŕîšéŕš áñð ĝöîñĝ ~~~~~~]\"],\"node.stair.panel.fitToFloor\":[\"[Ƒîţ Ţö Ƒļööŕ ~~~]\"],\"node.stair.panel.fitUniformRisers\":[\"[Ƒîţ ûñîƒöŕɱ ŕîšéŕš ~~~~~]\"],\"node.stair.panel.flightMeasurements\":[\"[Ƒļîĝĥţ \",[\"number\"],\": \",[\"count\",\"plural\",{\"one\":[[\"riserCount\"],\" ŕîšéŕ\"],\"other\":[[\"riserCount\"],\" ŕîšéŕš\"]}],\" · Ŕ \",[\"riser\"],\" · Ţ \",[\"going\"],\" · \",[\"slope\"],\" ~~~~~~]\"],\"node.stair.panel.floorsCeilingsAndStairsOnly\":[\"[Ƒļööŕš, çéîļîñĝš áñð šţáîŕš öñļý. ~~~~~~~~~]\"],\"node.stair.panel.followsDeck\":[\"[Ƒöļļöŵš ðéçķ ~~~~]\"],\"node.stair.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.stair.panel.fromLevel\":[\"[Ƒŕöɱ Ļéṽéļ ~~~]\"],\"node.stair.panel.geometry\":[\"[Ĝéöɱéţŕý ~~~]\"],\"node.stair.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.stair.panel.headroom\":[\"[Ĥéáðŕööɱ: ~~~]\"],\"node.stair.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.stair.panel.innerGap\":[\"[Îññéŕ ĝáþ ~~~]\"],\"node.stair.panel.innerRadius\":[\"[Îññéŕ Ŕáðîûš ~~~~]\"],\"node.stair.panel.integrated\":[\"[Îñţéĝŕáţéð ~~~]\"],\"node.stair.panel.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"node.stair.panel.layout\":[\"[Ļáýöûţ ~~]\"],\"node.stair.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.stair.panel.leftTurn\":[\"[Ļéƒţ ţûŕñ ~~~]\"],\"node.stair.panel.measurements\":[\"[Ṁéášûŕéɱéñţš ~~~~]\"],\"node.stair.panel.metal\":[\"[Ṁéţáļ ~~]\"],\"node.stair.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.stair.panel.movingThisStairKeepsItsCurrent\":[\"[Ṁöṽîñĝ ţĥîš šţáîŕ ķééþš îţš çûŕŕéñţ ŕîšé, šţéþš, áñð ŕûñ. ~~~~~~~~~~~~~~]\"],\"node.stair.panel.noOverheadSurface\":[\"[Ñö öṽéŕĥéáð šûŕƒáçé ~~~~~~]\"],\"node.stair.panel.none\":[\"[Ñöñé ~~]\"],\"node.stair.panel.notEvaluated\":[\"[Ñöţ éṽáļûáţéð ~~~~]\"],\"node.stair.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.stair.panel.openingOffset\":[\"[Öþéñîñĝ Öƒƒšéţ ~~~~]\"],\"node.stair.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.stair.panel.postRail\":[\"[Þöšţ & ŕáîļ ~~~]\"],\"node.stair.panel.railing\":[\"[Ŕáîļîñĝ ~~~]\"],\"node.stair.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stair.panel.rightTurn\":[\"[Ŕîĝĥţ ţûŕñ ~~~]\"],\"node.stair.panel.rise\":[\"[Ŕîšé ~~]\"],\"node.stair.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.stair.panel.segmentType\":[\"[\",[\"type\",\"select\",{\"landing\":[\"Ļáñðîñĝ\"],\"other\":[\"Šţáîŕ\"]}],\" ~~~~]\"],\"node.stair.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.stair.panel.sizing\":[\"[Šîžîñĝ ~~]\"],\"node.stair.panel.snapToNearbySurfaces\":[\"[Šñáþ ţö ñéáŕƀý šûŕƒáçéš ~~~~~~]\"],\"node.stair.panel.spiral\":[\"[Šþîŕáļ ~~]\"],\"node.stair.panel.staircase\":[\"[Šţáîŕçášé ~~~]\"],\"node.stair.panel.stepSupports\":[\"[Šţéþ Šûþþöŕţš ~~~~]\"],\"node.stair.panel.steps\":[\"[Šţéþš ~~]\"],\"node.stair.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stair.panel.style\":[\"[Šţýļé ~~]\"],\"node.stair.panel.surfaceConnection\":[\"[Šûŕƒáçé çöññéçţîöñ ~~~~~~]\"],\"node.stair.panel.sweep\":[\"[Šŵééþ ~~]\"],\"node.stair.panel.targetGoing\":[\"[Ţáŕĝéţ ĝöîñĝ ~~~~]\"],\"node.stair.panel.targetHeadroom\":[\"[Ţáŕĝéţ ĥéáðŕööɱ ~~~~~]\"],\"node.stair.panel.targetMaxRiser\":[\"[Ţáŕĝéţ ɱáẋ ŕîšéŕ ~~~~~]\"],\"node.stair.panel.targetMinimumGoing\":[\"[Ţáŕĝéţ ɱîñîɱûɱ ĝöîñĝ ~~~~~~]\"],\"node.stair.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.stair.panel.to\":[\"[Ţö ~]\"],\"node.stair.panel.topLanding\":[\"[Ţöþ Ļáñðîñĝ ~~~]\"],\"node.stair.panel.type\":[\"[Ţýþé ~~]\"],\"node.stair.panel.unavailable\":[\"[Ûñáṽáîļáƀļé ~~~~]\"],\"node.stair.panel.walkingLineOffset\":[\"[Ŵáļķîñĝ ļîñé öƒƒšéţ ~~~~~~]\"],\"node.stair.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.stair.panel.winder\":[\"[Ŵîñðéŕ ~~]\"],\"node.stair.railingControls.both\":[\"[Ɓöţĥ ~~]\"],\"node.stair.railingControls.bottomExtension\":[\"[Ɓöţţöɱ šļöþéð éẋţéñšîöñ ~~~~~~~]\"],\"node.stair.railingControls.continuous\":[\"[Çöñţîñûöûš ~~~]\"],\"node.stair.railingControls.floor\":[\"[Ƒļööŕ ~~]\"],\"node.stair.railingControls.handrailDiameter\":[\"[Ĥáñðŕáîļ ðîáɱéţéŕ ~~~~~]\"],\"node.stair.railingControls.handrailHeight\":[\"[Ĥáñðŕáîļ ĥéîĝĥţ ~~~~~]\"],\"node.stair.railingControls.handrailInset\":[\"[Ĥáñðŕáîļ îñšéţ ~~~~]\"],\"node.stair.railingControls.left\":[\"[Ļéƒţ ~~]\"],\"node.stair.railingControls.noReturn\":[\"[Ñö ŕéţûŕñ ~~~]\"],\"node.stair.railingControls.none\":[\"[Ñöñé ~~]\"],\"node.stair.railingControls.originalLayout\":[\"[Öŕîĝîñáļ ļáýöûţ ~~~~~]\"],\"node.stair.railingControls.postFacing\":[\"[Þöšţ-ƒáçîñĝ ~~~]\"],\"node.stair.railingControls.returnLength\":[\"[Ŕéţûŕñ ļéñĝţĥ ~~~~]\"],\"node.stair.railingControls.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stair.railingControls.separateHandrail\":[\"[Šéþáŕáţé ĥáñðŕáîļ ~~~~~]\"],\"node.stair.railingControls.setTheLengthToReachThe\":[\"[Šéţ ţĥé ļéñĝţĥ ţö ŕéáçĥ ţĥé ŵáļļ öŕ þöšţ. ~~~~~~~~~~]\"],\"node.stair.railingControls.topExtension\":[\"[Ţöþ ĥöŕîžöñţáļ éẋţéñšîöñ ~~~~~~~]\"],\"node.stair.railingControls.wallFacing\":[\"[Ŵáļļ-ƒáçîñĝ ~~~]\"],\"node.stairSegment.description\":[\"[Å šîñĝļé ƒļîĝĥţ öƒ á þáŕéñţ šţáîŕ. ~~~~~~~~~]\"],\"node.stairSegment.label\":[\"[Šţáîŕ Šéĝɱéñţ ~~~~]\"],\"node.stairSegment.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.stairSegment.panel.attachment\":[\"[Åţţáçĥɱéñţ ~~~]\"],\"node.stairSegment.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.stairSegment.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.stairSegment.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.stairSegment.panel.editingSwitchesTheStairToCustom\":[\"[Éðîţîñĝ šŵîţçĥéš ţĥé šţáîŕ ţö Çûšţöɱ ŕîšé ~~~~~~~~~~~]\"],\"node.stairSegment.panel.equalAngle\":[\"[Éǫûáļ áñĝļé ~~~]\"],\"node.stairSegment.panel.equalGoing\":[\"[Éǫûáļ ĝöîñĝ ~~~]\"],\"node.stairSegment.panel.fillToFloor\":[\"[Ƒîļļ ţö ƒļööŕ ~~~~]\"],\"node.stairSegment.panel.flight\":[\"[Ƒļîĝĥţ ~~]\"],\"node.stairSegment.panel.flightShape\":[\"[Ƒļîĝĥţ šĥáþé ~~~~]\"],\"node.stairSegment.panel.front\":[\"[Ƒŕöñţ ~~]\"],\"node.stairSegment.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.stairSegment.panel.innerGap\":[\"[Îññéŕ ĝáþ ~~~]\"],\"node.stairSegment.panel.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"node.stairSegment.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.stairSegment.panel.leftTurn\":[\"[Ļéƒţ ţûŕñ ~~~]\"],\"node.stairSegment.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.stairSegment.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.stairSegment.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.stairSegment.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stairSegment.panel.rightTurn\":[\"[Ŕîĝĥţ ţûŕñ ~~~]\"],\"node.stairSegment.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.stairSegment.panel.stairSegment\":[\"[Šţáîŕ Šéĝɱéñţ ~~~~]\"],\"node.stairSegment.panel.steps\":[\"[Šţéþš ~~]\"],\"node.stairSegment.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stairSegment.panel.structure\":[\"[Šţŕûçţûŕé ~~~]\"],\"node.stairSegment.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.stairSegment.panel.type\":[\"[Ţýþé ~~]\"],\"node.stairSegment.panel.walkingLineOffset\":[\"[Ŵáļķîñĝ ļîñé öƒƒšéţ ~~~~~~]\"],\"node.stairSegment.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.stairSegment.panel.winder\":[\"[Ŵîñðéŕ ~~]\"],\"node.structuralGrid.description\":[\"[Þéŕšîšţéñţ çöñšţŕûçţîöñ ĝŕîð áẋîš ŵîţĥ îðéñţîƒîçáţîöñ ƀûƀƀļéš. ~~~~~~~~~~~~~~~~~]\"],\"node.structuralGrid.label\":[\"[Šţŕûçţûŕáļ Ĝŕîð ~~~~~]\"],\"node.turbineVent.description\":[\"[Ŵîñð-ðŕîṽéñ šþîññîñĝ ŵĥîŕļýƀîŕð éẋĥáûšţ ṽéñţ ƒöŕ á ŕööƒ šļöþé. ~~~~~~~~~~~~~~~~]\"],\"node.turbineVent.label\":[\"[Ţûŕƀîñé Ṽéñţ ~~~~]\"],\"node.turbineVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.turbineVent.panel.cylinder\":[\"[Çýļîñðéŕ ~~~]\"],\"node.turbineVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.turbineVent.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.turbineVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.turbineVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.turbineVent.panel.globe\":[\"[Ĝļöƀé ~~]\"],\"node.turbineVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.turbineVent.panel.motion\":[\"[Ṁöţîöñ ~~]\"],\"node.turbineVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.turbineVent.panel.neckHeight\":[\"[Ñéçķ Ĥéîĝĥţ ~~~]\"],\"node.turbineVent.panel.pause\":[\"[Þáûšé ~~]\"],\"node.turbineVent.panel.play\":[\"[Þļáý ~~]\"],\"node.turbineVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.turbineVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.turbineVent.panel.spinSpeed\":[\"[Šþîñ Šþééð ~~~]\"],\"node.turbineVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.turbineVent.panel.turbineVent\":[\"[Ţûŕƀîñé Ṽéñţ ~~~~]\"],\"node.turbineVent.panel.vanes\":[\"[Ṽáñéš ~~]\"],\"node.unit.description\":[\"[Å ñáɱéð ĝŕöûþ öƒ žöñéš ƒöŕɱîñĝ öñé áþáŕţɱéñţ, ĥöţéļ ŕööɱ öŕ çöɱɱéŕçîáļ ļöţ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.unit.kind.apartment\":[\"[Åþáŕţɱéñţ ~~~]\"],\"node.unit.kind.commercial\":[\"[Çöɱɱéŕçîáļ ~~~]\"],\"node.unit.kind.common\":[\"[Çöɱɱöñ ~~]\"],\"node.unit.kind.hotelRoom\":[\"[Ĥöţéļ ŕööɱ ~~~]\"],\"node.unit.label\":[\"[Ûñîţ ~~]\"],\"node.unit.panel.memberRoom\":[\"[Ŕööɱ ~~]\"],\"node.unit.panel.memberZone\":[\"[Žöñé ~~]\"],\"node.unit.panel.noZonesAssigned\":[\"[Ñö žöñéš áššîĝñéð ýéţ. ~~~~~~]\"],\"node.unit.panel.nonAdjacentLevels\":[\"[Ṁéɱƀéŕš šîţ öñ ļéṽéļš ţĥáţ áŕé ñöţ áðĵáçéñţ. ~~~~~~~~~~~]\"],\"node.unit.panel.removeMember\":[\"[Ŕéɱöṽé \",[\"name\"],\" ƒŕöɱ ûñîţ ~~~~~]\"],\"node.unit.panel.removeZone\":[\"[Ŕéɱöṽé žöñé ƒŕöɱ ûñîţ ~~~~~~]\"],\"node.unit.unitPanel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.unit.unitPanel.color\":[\"[Çöļöŕ ~~]\"],\"node.unit.unitPanel.delete\":[\"[Ðéļéţé ~~]\"],\"node.unit.unitPanel.focus\":[\"[Ƒöçûš ~~]\"],\"node.unit.unitPanel.grossArea\":[\"[Ĝŕöšš áŕéá ~~~]\"],\"node.unit.unitPanel.kind\":[\"[Ķîñð ~~]\"],\"node.unit.unitPanel.levels\":[\"[Ļéṽéļš ~~]\"],\"node.unit.unitPanel.members\":[\"[Ṁéɱƀéŕš ~~~]\"],\"node.unit.unitPanel.name\":[\"[Ñáɱé ~~]\"],\"node.unit.unitPanel.noRoomsYetPickThisUnit\":[\"[Ñö ŕööɱš ýéţ. Þîçķ ţĥîš ûñîţ ƒŕöɱ á ŕööɱ'š þáñéļ, öŕ ðŕáŵ žöñéš ŵĥîļé îţ îš ƒöçûšéð. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.unit.unitPanel.report\":[\"[Ŕéþöŕţ ~~]\"],\"node.unit.unitPanel.unit\":[\"[Ûñîţ ~~]\"],\"node.wall.actions.mergeWalls\":[\"[Ṁéŕĝé ŵáļļš ~~~]\"],\"node.wall.actions.splitWall\":[\"[Šþļîţ ŵáļļ ~~~]\"],\"node.wall.curtain.cell\":[\"[Çöļûɱñ \",[\"column\"],\", ŕöŵ \",[\"row\"],\": \",[\"type\",\"select\",{\"glass\":[\"ĝļášš\"],\"solid\":[\"šöļîð\"],\"other\":[\"éɱþţý\"]}],\" ~~~~~~~~]\"],\"node.wall.curtain.columnSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" çöļûɱñ\"],\"other\":[[\"number\"],\" çöļûɱñš\"]}],\" · \",[\"spacing\"],\" ĝŕîð šþáçîñĝ ~~~~~~~~]\"],\"node.wall.curtain.noPanels\":[\"[Ñö þáñéļš ~~~]\"],\"node.wall.curtain.rowSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕöŵ\"],\"other\":[[\"number\"],\" ŕöŵš\"]}],\" · \",[\"spacing\"],\" ĝŕîð šþáçîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.applyTint\":[\"[Åþþļý ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.borderOpeningFrame\":[\"[Ɓöŕðéŕ / öþéñîñĝ ƒŕáɱé ~~~~~~]\"],\"node.wall.curtainWallPanel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.wall.curtainWallPanel.bottomRow\":[\"[Ɓöţţöɱ ŕöŵ ~~~]\"],\"node.wall.curtainWallPanel.cancelTint\":[\"[Çáñçéļ ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.center\":[\"[Çéñţéŕ ~~]\"],\"node.wall.curtainWallPanel.chooseACellOrEnterIts\":[\"[Çĥööšé á çéļļ öŕ éñţéŕ îţš çöļûɱñ áñð ŕöŵ. Ûšé áŕŕöŵ ķéýš ţö ɱöṽé ƀéţŵééñ çéļļš. Çöļûɱñš ŕûñ ƒŕöɱ ţĥé ŵáļļ šţáŕţ; ŕöŵš çöûñţ ûþŵáŕð. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.clearHiddenOverrides\":[\"[Çļéáŕ ĥîððéñ öṽéŕŕîðéš ~~~~~~]\"],\"node.wall.curtainWallPanel.column\":[\"[Çöļûɱñ ~~]\"],\"node.wall.curtainWallPanel.columnLayout\":[\"[Çöļûɱñ ļáýöûţ ~~~~]\"],\"node.wall.curtainWallPanel.columnRow\":[\"[Çöļûɱñ \",[\"column\"],\", ŕöŵ \",[\"row\"],\" ~~~]\"],\"node.wall.curtainWallPanel.construction\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong\":[\"[Çöñţîñûöûš ɱûļļîöñš áñð ţŕáñšöɱš áššéɱƀļéð áļöñĝ ţĥé ŵáļļ. ~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.curtainWallPanelSelection\":[\"[Çûŕţáîñ ŵáļļ þáñéļ šéļéçţîöñ ~~~~~~~~]\"],\"node.wall.curtainWallPanel.customInfill\":[\"[Çûšţöɱ îñƒîļļ ~~~~]\"],\"node.wall.curtainWallPanel.defaultPanel\":[\"[Ðéƒáûļţ þáñéļ ~~~~]\"],\"node.wall.curtainWallPanel.editIndividualPanels\":[\"[Éðîţ îñðîṽîðûáļ þáñéļš ~~~~~~]\"],\"node.wall.curtainWallPanel.effectiveThicknessLimited\":[\"[Ƀƒéçţîṽé ţĥîçķñéšš: \",[\"thickness\"],\", ļîɱîţéð ƀý ƒŕáɱé ðéþţĥ. ~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.empty\":[\"[Éɱþţý ~~]\"],\"node.wall.curtainWallPanel.enablePanelEditing\":[\"[Éñáƀļé þáñéļ éðîţîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.end\":[\"[Éñð ~]\"],\"node.wall.curtainWallPanel.fixedSpacing\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"node.wall.curtainWallPanel.followingWallDefaults\":[\"[Ƒöļļöŵîñĝ ŵáļļ ðéƒáûļţš ~~~~~~~]\"],\"node.wall.curtainWallPanel.frameDimensions\":[\"[Ƒŕáɱé ðîɱéñšîöñš ~~~~~]\"],\"node.wall.curtainWallPanel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.wall.curtainWallPanel.fullyCapped\":[\"[Ƒûļļý çáþþéð ~~~~]\"],\"node.wall.curtainWallPanel.glass\":[\"[Ĝļášš ~~]\"],\"node.wall.curtainWallPanel.glassAppearance\":[\"[Ĝļášš áþþéáŕáñçé ~~~~~]\"],\"node.wall.curtainWallPanel.glassAppearanceIsControlledByThe\":[\"[Ĝļášš áþþéáŕáñçé îš çöñţŕöļļéð ƀý ţĥé þáîñţ ţööļ. ~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.glassTint\":[\"[Ĝļášš ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.glazingGap\":[\"[Ĝļážîñĝ ĝáþ ~~~]\"],\"node.wall.curtainWallPanel.gridLimitedTo32PanelsPer\":[\"[Ĝŕîð ļîɱîţéð ţö 32 þáñéļš þéŕ áẋîš. Šþáçîñĝ éẋþáñðš ţö ƒîţ ţĥîš ŵáļļ. ~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.hiddenCustomPanelCount\":[\"[\",[\"length\",\"plural\",{\"one\":[[\"number\"],\" çûšţöɱ þáñéļ îš öûţšîðé ţĥîš ĝŕîð. Îţ ŕéţûŕñš îƒ ţĥé ĝŕîð éẋþáñðš.\"],\"other\":[[\"number\"],\" çûšţöɱ þáñéļš áŕé öûţšîðé ţĥîš ĝŕîð. Ţĥéý ŕéţûŕñ îƒ ţĥé ĝŕîð éẋþáñðš.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.horizontalCaps\":[\"[Ĥöŕîžöñţáļ çáþš ~~~~~]\"],\"node.wall.curtainWallPanel.horizontalFrameWidth\":[\"[Ĥöŕîžöñţáļ ƒŕáɱé ŵîðţĥ ~~~~~~]\"],\"node.wall.curtainWallPanel.layout\":[\"[Ļáýöûţ ~~]\"],\"node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness\":[\"[Ļöŵ ŕöûĝĥñéšš îš šɱööţĥ; ĥîĝĥ ŕöûĝĥñéšš ĝîṽéš á ɱáţţé šûŕƒáçé îñ ŕéñðéŕéð šĥáðîñĝ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.maximumSpacing\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"node.wall.curtainWallPanel.moduleJointWidth\":[\"[Ṁöðûļé ĵöîñţ ŵîðţĥ ~~~~~]\"],\"node.wall.curtainWallPanel.none\":[\"[Ñöñé ~~]\"],\"node.wall.curtainWallPanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"node.wall.curtainWallPanel.opaqueBand\":[\"[Öþáǫûé ƀáñð ~~~]\"],\"node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow\":[\"[Öþáǫûé ƀáñðš ŕéþļáçé á ƒûļļ ŕöŵ ŵîţĥ šöļîð þáñéļš. Åþþļý ţĥéîŕ ƒîñîšĥ ŵîţĥ ţĥé þáîñţ ţööļ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.panelCount\":[\"[Þáñéļ çöûñţ ~~~]\"],\"node.wall.curtainWallPanel.panelInfill\":[\"[Þáñéļ îñƒîļļ ~~~~]\"],\"node.wall.curtainWallPanel.panelThickness\":[\"[Þáñéļ ţĥîçķñéšš ~~~~~]\"],\"node.wall.curtainWallPanel.panels\":[\"[Þáñéļš ~~]\"],\"node.wall.curtainWallPanel.resetThisPanel\":[\"[Ŕéšéţ ţĥîš þáñéļ ~~~~~]\"],\"node.wall.curtainWallPanel.row2\":[\"[Ŕöŵ ~]\"],\"node.wall.curtainWallPanel.rowLayout\":[\"[Ŕöŵ ļáýöûţ ~~~]\"],\"node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween\":[\"[Šéþáŕáţé ƒŕáɱéð ɱöðûļéš ŵîţĥ ĵöîñţš ƀéţŵééñ ûñîţš. ~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.solid\":[\"[Šöļîð ~~]\"],\"node.wall.curtainWallPanel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.wall.curtainWallPanel.start\":[\"[Šţáŕţ ~~]\"],\"node.wall.curtainWallPanel.stickBuilt\":[\"[Šţîçķ-ƀûîļţ ~~~]\"],\"node.wall.curtainWallPanel.structuralGlazing\":[\"[Šţŕûçţûŕáļ ĝļážîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.surfaceRoughness\":[\"[Šûŕƒáçé ŕöûĝĥñéšš ~~~~~]\"],\"node.wall.curtainWallPanel.top\":[\"[Ţöþ ~]\"],\"node.wall.curtainWallPanel.topRow\":[\"[Ţöþ ŕöŵ ~~]\"],\"node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing\":[\"[Ûñçáþþéð ɱéɱƀéŕš šîţ ƀéĥîñð ţĥé ĝļážîñĝ. ~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.unitized\":[\"[Ûñîţîžéð ~~~]\"],\"node.wall.curtainWallPanel.useGlassSettingsInstead\":[\"[Ûšé ĝļášš šéţţîñĝš îñšţéáð ~~~~~~~]\"],\"node.wall.curtainWallPanel.useTheDoorOrWindowTool\":[\"[Ûšé ţĥé Ðööŕ öŕ Ŵîñðöŵ ţööļ ţö áðð áñ öþéñîñĝ ţĥŕöûĝĥ ţĥé ŵáļļ. ~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.verticalCaps\":[\"[Ṽéŕţîçáļ çáþš ~~~~]\"],\"node.wall.curtainWallPanel.verticalFrameWidth\":[\"[Ṽéŕţîçáļ ƒŕáɱé ŵîðţĥ ~~~~~~]\"],\"node.wall.curtainWallPanel.wallSystem\":[\"[Ŵáļļ šýšţéɱ ~~~]\"],\"node.wall.description\":[\"[Å šţŕáîĝĥţ öŕ çûŕṽéð ŵáļļ šéĝɱéñţ. Ĥöšţš ðööŕš, ŵîñðöŵš, ļéáñ-ţö éẋţéñšîöñš, áñð ŵáļļ-ɱöûñţéð îţéɱš. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.label\":[\"[Ŵáļļ ~~]\"],\"node.wall.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.wall.panel.assembly\":[\"[Åššéɱƀļý ~~~]\"],\"node.wall.panel.auto\":[\"[Åûţö ~~]\"],\"node.wall.panel.both\":[\"[Ɓöţĥ ~~]\"],\"node.wall.panel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.wall.panel.brickVeneer\":[\"[Ɓŕîçķ ṽéñééŕ ~~~~]\"],\"node.wall.panel.chairRail\":[\"[Çĥáîŕ ŕáîļ ~~~]\"],\"node.wall.panel.cmu\":[\"[ÇṀÛ ~]\"],\"node.wall.panel.colonial\":[\"[Çöļöñîáļ ~~~]\"],\"node.wall.panel.cove\":[\"[Çöṽé ~~]\"],\"node.wall.panel.craft\":[\"[Çŕáƒţ ~~]\"],\"node.wall.panel.crownMolding\":[\"[Çŕöŵñ ɱöļðîñĝ ~~~~]\"],\"node.wall.panel.curtainWall\":[\"[Çûŕţáîñ ŵáļļ ~~~~]\"],\"node.wall.panel.curve\":[\"[Çûŕṽé ~~]\"],\"node.wall.panel.custom\":[\"[Çûšţöɱ ~~]\"],\"node.wall.panel.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"node.wall.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.wall.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.wall.panel.drywall\":[\"[Ðŕýŵáļļ ~~~]\"],\"node.wall.panel.extendsDownwardToMeetTheTerrain\":[\"[Éẋţéñðš ðöŵñŵáŕð ţö ɱééţ ţĥé ţéŕŕáîñ. Ĥéîĝĥţ áñð ţöþ šţáý ûñçĥáñĝéð. ~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.exterior\":[\"[Éẋţéŕîöŕ ~~~]\"],\"node.wall.panel.fiberCement\":[\"[Ƒîƀéŕ çéɱéñţ ~~~~]\"],\"node.wall.panel.fillToTerrain\":[\"[Ƒîļļ ţö ţéŕŕáîñ ~~~~]\"],\"node.wall.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.wall.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.wall.panel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.wall.panel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.wall.panel.gypsum\":[\"[Ĝýþšûɱ ~~]\"],\"node.wall.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.wall.panel.hideCode\":[\"[Ĥîðé çöðé ~~~]\"],\"node.wall.panel.icf\":[\"[ÎÇƑ ~]\"],\"node.wall.panel.interior\":[\"[Îñţéŕîöŕ ~~~]\"],\"node.wall.panel.lapSiding\":[\"[Ļáþ šîðîñĝ ~~~]\"],\"node.wall.panel.layerThicknessesFollowThe2021Irc\":[\"[Ļáýéŕ ţĥîçķñéššéš ƒöļļöŵ ţĥé 2021 ÎŔÇ áššéɱƀļý ðáţá. Ðŕáƒţîñĝ áîð, ñöţ éñĝîñééŕîñĝ — ṽéŕîƒý ŵîţĥ ţĥé áûţĥöŕîţý ĥáṽîñĝ ĵûŕîšðîçţîöñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.layered\":[\"[Ļáýéŕéð ~~~]\"],\"node.wall.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.wall.panel.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"node.wall.panel.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.wall.panel.none\":[\"[Ñöñé ~~]\"],\"node.wall.panel.noneSingleLayer\":[\"[Ñöñé (šîñĝļé ļáýéŕ) ~~~~~]\"],\"node.wall.panel.offset\":[\"[Öƒƒšéţ ~~]\"],\"node.wall.panel.ogee\":[\"[Öĝéé ~~]\"],\"node.wall.panel.osb\":[\"[ÖŠƁ ~]\"],\"node.wall.panel.partitionTheInteriorFinishIsApplied\":[\"[Þáŕţîţîöñ: ţĥé îñţéŕîöŕ ƒîñîšĥ îš áþþļîéð ţö ƀöţĥ ƒáçéš. ~~~~~~~~~~~~~~]\"],\"node.wall.panel.picture\":[\"[Þîçţûŕé ~~~]\"],\"node.wall.panel.plaster\":[\"[Þļášţéŕ ~~~]\"],\"node.wall.panel.plywood\":[\"[Þļýŵööð ~~~]\"],\"node.wall.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.wall.panel.reference\":[\"[Ŕéƒéŕéñçé ~~~]\"],\"node.wall.panel.remove\":[\"[Ŕéɱöṽé ~~]\"],\"node.wall.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.wall.panel.scriptedFinish\":[\"[Šçŕîþţéð ƒîñîšĥ ~~~~~]\"],\"node.wall.panel.sheathing\":[\"[Šĥéáţĥîñĝ ~~~]\"],\"node.wall.panel.shoe\":[\"[Šĥöé ~~]\"],\"node.wall.panel.sideA\":[\"[Šîðé Å ~~]\"],\"node.wall.panel.sideB\":[\"[Šîðé Ɓ ~~]\"],\"node.wall.panel.sideFace\":[\"[Šîðé \",[\"face\"],\" ~~]\"],\"node.wall.panel.skirting\":[\"[Šķîŕţîñĝ ~~~]\"],\"node.wall.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.wall.panel.steelStuds\":[\"[Šţééļ šţûðš ~~~]\"],\"node.wall.panel.step\":[\"[Šţéþ ~~]\"],\"node.wall.panel.stoneVeneer\":[\"[Šţöñé ṽéñééŕ ~~~~]\"],\"node.wall.panel.stucco\":[\"[Šţûççö ~~]\"],\"node.wall.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.wall.panel.thicknessFromAssembly\":[\"[\",[\"thickness\"],\" · ƒŕöɱ áššéɱƀļý ~~~~]\"],\"node.wall.panel.top\":[\"[Ţöþ ~]\"],\"node.wall.panel.totalThickness\":[\"[Ţöţáļ ţĥîçķñéšš ~~~~~]\"],\"node.wall.panel.unverifiedPresetnote\":[\"[Ûñṽéŕîƒîéð: \",[\"presetNote\"],\" ~~~]\"],\"node.wall.panel.viewCode\":[\"[Ṽîéŵ çöðé ~~~]\"],\"node.wall.panel.wall\":[\"[Ŵáļļ ~~]\"],\"node.wall.panel.wallType\":[\"[Ŵáļļ ţýþé ~~~]\"],\"node.wall.panel.whichFaceIsOutsideIsUndetermined\":[\"[Ŵĥîçĥ ƒáçé îš öûţšîðé îš ûñðéţéŕɱîñéð (ñö ŕööɱ ðéţéçţéð öñ éîţĥéŕ šîðé) — ţĥé éẋţéŕîöŕ ļáýéŕš áŕé ðŕáŵñ öñ šîðé Ɓ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.woodStuds\":[\"[Ŵööð šţûðš ~~~]\"],\"node.wall.reference.center\":[\"[Çéñţéŕ ~~]\"],\"node.wall.reference.inside\":[\"[Îñšîðé ƒáçé ~~~]\"],\"node.wall.reference.left\":[\"[Ļéƒţ ~~]\"],\"node.wall.reference.outside\":[\"[Öûţšîðé ƒáçé ~~~~]\"],\"node.wall.reference.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.wall.trim.hide\":[\"[\",[\"trim\",\"select\",{\"skirting\":[\"Ĥîðé šķîŕţîñĝ\"],\"crown\":[\"Ĥîðé çŕöŵñ ɱöļðîñĝ\"],\"other\":[\"Ĥîðé çĥáîŕ ŕáîļ\"]}],\" ~~~~~~~~~~~~~]\"],\"node.wall.trim.show\":[\"[\",[\"trim\",\"select\",{\"skirting\":[\"Šĥöŵ šķîŕţîñĝ\"],\"crown\":[\"Šĥöŵ çŕöŵñ ɱöļðîñĝ\"],\"other\":[\"Šĥöŵ çĥáîŕ ŕáîļ\"]}],\" ~~~~~~~~~~~~~]\"],\"node.window.description\":[\"[Å ŵîñðöŵ çûţ îñţö á ŵáļļ. Åñîɱáţéð öþéñ/çļöšé ƒöŕ öþéñîñĝ ŵîñðöŵš. ~~~~~~~~~~~~~~~~]\"],\"node.window.label\":[\"[Ŵîñðöŵ ~~]\"],\"node.window.operation.raise\":[\"[Ŕáîšé ~~]\"],\"node.window.operation.slats\":[\"[Šļáţš ~~]\"],\"node.window.operation.slide\":[\"[Šļîðé ~~]\"],\"node.window.operation.swing\":[\"[Šŵîñĝ ~~]\"],\"node.window.operation.tilt\":[\"[Ţîļţ ~~]\"],\"node.window.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.window.panel.all\":[\"[Åļļ ~]\"],\"node.window.panel.arch\":[\"[Åŕçĥ ~~]\"],\"node.window.panel.archHeight\":[\"[Åŕçĥ Ĥéîĝĥţ ~~~]\"],\"node.window.panel.auto\":[\"[Åûţö ~~]\"],\"node.window.panel.autoFollowsTheWall\":[\"[Åûţö ƒöļļöŵš ţĥé ŵáļļ ~~~~~~]\"],\"node.window.panel.colWidths\":[\"[Çöļ Ŵîðţĥš ~~~]\"],\"node.window.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.window.panel.corner\":[\"[Çöŕñéŕ ~~]\"],\"node.window.panel.cornerPost\":[\"[Çöŕñéŕ þöšţ ~~~]\"],\"node.window.panel.cornerRadius\":[\"[Çöŕñéŕ Ŕáðîûš ~~~~]\"],\"node.window.panel.customThePanesMatchNoStyle\":[\"[Çûšţöɱ: ţĥé þáñéš ɱáţçĥ ñö šţýļé. ~~~~~~~~]\"],\"node.window.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.window.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.window.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.window.panel.divider\":[\"[Ðîṽîðéŕ ~~~]\"],\"node.window.panel.down\":[\"[Ðöŵñ ~~]\"],\"node.window.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.window.panel.enableSill\":[\"[Éñáƀļé Šîļļ ~~~]\"],\"node.window.panel.flipSide\":[\"[Ƒļîþ Šîðé ~~~]\"],\"node.window.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.window.panel.frameDepthAuto\":[\"[Ðéþţĥ (áûţö) ~~~]\"],\"node.window.panel.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.window.panel.fusedGlass\":[\"[Ƒûšéð ĝļášš ~~~]\"],\"node.window.panel.grid\":[\"[Ĝŕîð ~~]\"],\"node.window.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.window.panel.individual\":[\"[Îñðîṽîðûáļ ~~~]\"],\"node.window.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.window.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.window.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.window.panel.openingShape\":[\"[Öþéñîñĝ Šĥáþé ~~~~]\"],\"node.window.panel.otherSide\":[\"[Öţĥéŕ šîðé ~~~]\"],\"node.window.panel.pos\":[\"[þöš ~]\"],\"node.window.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.window.panel.rect\":[\"[Ŕéçţ ~~]\"],\"node.window.panel.revealRadius\":[\"[Ŕéṽéáļ Ŕáðîûš ~~~~]\"],\"node.window.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.window.panel.rounded\":[\"[Ŕöûñðéð ~~~]\"],\"node.window.panel.rowHeights\":[\"[Ŕöŵ Ĥéîĝĥţš ~~~]\"],\"node.window.panel.rows\":[\"[Ŕöŵš ~~]\"],\"node.window.panel.sill\":[\"[Šîļļ ~~]\"],\"node.window.panel.single\":[\"[Šîñĝļé ~~]\"],\"node.window.panel.sizeIsLimitedToTheWall\":[\"[Šîžé îš ļîɱîţéð ţö ţĥé ŵáļļ, îñçļûðîñĝ çļéáŕáñçé ƒöŕ ţĥé öþéñîñĝ ƒŕáɱé. ~~~~~~~~~~~~~~~~~~]\"],\"node.window.panel.style\":[\"[Šţýļé ~~]\"],\"node.window.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.window.panel.thisSide\":[\"[Ţĥîš šîðé ~~~]\"],\"node.window.panel.topShape\":[\"[Ţöþ Šĥáþé ~~~]\"],\"node.window.panel.type\":[\"[Ţýþé ~~]\"],\"node.window.panel.unwrap\":[\"[Ûñŵŕáþ ~~]\"],\"node.window.panel.up\":[\"[Ûþ ~]\"],\"node.window.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.window.panel.window\":[\"[Ŵîñðöŵ ~~]\"],\"node.window.panel.windowType\":[\"[Ŵîñðöŵ Ţýþé ~~~]\"],\"node.window.panel.wrapTheCorner\":[\"[Ŵŕáþ ţĥé çöŕñéŕ ~~~~]\"],\"node.window.panel.wrapsTheCornerWithNamed\":[\"[Ŵŕáþš ţĥé çöŕñéŕ ŵîţĥ \",[\"partner\"],\" ~~~~~~]\"],\"node.window.panel.wrapsTheCornerWithPartner\":[\"[Ŵŕáþš ţĥé çöŕñéŕ ŵîţĥ îţš þáŕţñéŕ ~~~~~~~~~]\"],\"node.window.style.casement\":[\"[Çášéɱéñţ ~~~]\"],\"node.window.style.doubleHung\":[\"[Ðöûƀļé-ĥûñĝ ~~~]\"],\"node.window.style.grid\":[\"[Ĝŕîð ~~]\"],\"node.window.style.horizontalBands\":[\"[Ĥöŕîžöñţáļ ƀáñðš ~~~~~]\"],\"node.window.style.picture\":[\"[Þîçţûŕé ~~~]\"],\"node.window.style.single\":[\"[Šîñĝļé ~~]\"],\"node.window.style.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.window.style.tallGrid\":[\"[Ţáļļ ĝŕîð ~~~]\"],\"node.window.style.transom\":[\"[Ţŕáñšöɱ ~~~]\"],\"node.window.style.tripleHung\":[\"[Ţŕîþļé-ĥûñĝ ~~~]\"],\"node.window.style.wideGrid\":[\"[Ŵîðé ĝŕîð ~~~]\"],\"node.window.type.awning\":[\"[Åŵñîñĝ ~~]\"],\"node.window.type.bay\":[\"[Ɓáý ~]\"],\"node.window.type.bow\":[\"[Ɓöŵ ~]\"],\"node.window.type.casement\":[\"[Çášéɱéñţ ~~~]\"],\"node.window.type.doubleHung\":[\"[Ðöûƀļé Ĥûñĝ ~~~]\"],\"node.window.type.fixed\":[\"[Ƒîẋéð ~~]\"],\"node.window.type.hopper\":[\"[Ĥöþþéŕ ~~]\"],\"node.window.type.louvered\":[\"[Ļöûṽéŕéð ~~~]\"],\"node.window.type.singleHung\":[\"[Šîñĝļé Ĥûñĝ ~~~]\"],\"node.window.type.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.zone.description\":[\"[Å þöļýĝöñáļ šîţé žöñé (ļáŵñ, ŵáţéŕ, þáṽîñĝ) ŵîţĥ á ŢŠĻ ĝŕáðîéñţ ɱáţéŕîáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.zone.label\":[\"[Žöñé ~~]\"],\"node.zone.quantitiesPanel.enclosedRoom\":[\"[Éñçļöšéð ŕööɱ ~~~~]\"],\"node.zone.quantitiesPanel.floorSurface\":[\"[Ƒļööŕ šûŕƒáçé ~~~~]\"],\"node.zone.quantitiesPanel.footprintOnly\":[\"[Ƒööţþŕîñţ öñļý ~~~~]\"],\"node.zone.quantitiesPanel.none\":[\"[Ñöñé ~~]\"],\"node.zone.quantitiesPanel.notProven\":[\"[Ñöţ þŕöṽéñ ~~~]\"],\"node.zone.quantitiesPanel.roomQuantities\":[\"[Ŕööɱ ǫûáñţîţîéš ~~~~~]\"],\"node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions\":[\"[Ţöþ ṽîéŵ ŵîţĥ žöñé éðĝé ðîɱéñšîöñš ~~~~~~~~~]\"],\"node.zone.quantitiesPanel.unit\":[\"[Ûñîţ ~~]\"],\"node.zone.quantitiesPanel.volume\":[\"[Ṽöļûɱé ~~]\"],\"node.zone.quantitiesPanel.wallSurface\":[\"[Ŵáļļ šûŕƒáçé ~~~~]\"],\"node.zone.quantitiesPanel.zoneBoundaryUnavailable\":[\"[Žöñé ƀöûñðáŕý ûñáṽáîļáƀļé ~~~~~~~]\"],\"node.zone.quantitiesPanel.zoneQuantities\":[\"[Žöñé ǫûáñţîţîéš ~~~~~]\"],\"viewer.camera.orbitLeft\":[\"[Öŕƀîţ ļéƒţ ~~~]\"],\"viewer.camera.orbitRight\":[\"[Öŕƀîţ ŕîĝĥţ ~~~]\"],\"viewer.camera.topView\":[\"[Ţöþ ṽîéŵ ~~~]\"],\"viewer.common.off\":[\"[Öƒƒ ~]\"],\"viewer.common.on\":[\"[Öñ ~]\"],\"viewer.display.camera\":[\"[Çáɱéŕá ~~]\"],\"viewer.display.colored\":[\"[Çöļöŕéð ~~~]\"],\"viewer.display.colors\":[\"[Çöļöŕš ~~]\"],\"viewer.display.edges.off\":[\"[Öƒƒ ~]\"],\"viewer.display.edges.offDetail\":[\"[Ñö éðĝé ļîñéš ~~~~]\"],\"viewer.display.edges.soft\":[\"[Šöƒţ ~~]\"],\"viewer.display.edges.softDetail\":[\"[Ƒáîñţ öûţļîñé öƒ ɱáĵöŕ çŕéášéš ~~~~~~~~]\"],\"viewer.display.edges.strong\":[\"[Šţŕöñĝ ~~]\"],\"viewer.display.edges.strongDetail\":[\"[Çŕîšþ, öþáǫûé éðĝé ļîñéš ~~~~~~]\"],\"viewer.display.edges.title\":[\"[Éðĝéš ~~]\"],\"viewer.display.monochrome\":[\"[Ṁöñöçĥŕöɱé ~~~]\"],\"viewer.display.orthographic\":[\"[Öŕţĥöĝŕáþĥîç ~~~~]\"],\"viewer.display.perspective\":[\"[Þéŕšþéçţîṽé ~~~~]\"],\"viewer.display.render.rendered\":[\"[Ŕéñðéŕéð ~~~]\"],\"viewer.display.render.renderedDetail\":[\"[Ƒûļļ áɱƀîéñţ öççļûšîöñ ~~~~~~]\"],\"viewer.display.render.solid\":[\"[Šöļîð ~~]\"],\"viewer.display.render.solidDetail\":[\"[Ƒļáţ áñð ƒášţ — ñö áɱƀîéñţ öççļûšîöñ ~~~~~~~~~]\"],\"viewer.display.render.title\":[\"[Ŕéñðéŕ ~~]\"],\"viewer.display.shadows\":[\"[Šĥáðöŵš ~~~]\"],\"viewer.display.title\":[\"[Ðîšþļáý šéţţîñĝš ~~~~~]\"],\"viewer.floorplan.alignNorth\":[\"[Åļîĝñ ṽîéŵ ţö ñöŕţĥ ~~~~~]\"],\"viewer.floorplan.empty\":[\"[Ñö ƒļööŕ þļáñš áŕé áṽáîļáƀļé ƒöŕ ţĥîš šçéñé. ~~~~~~~~~~~]\"],\"viewer.floorplan.fit\":[\"[Ƒîţ ƒļööŕ þļáñ ~~~~]\"],\"viewer.floorplan.floor\":[\"[Ƒļööŕ ~~]\"],\"viewer.floorplan.floorPlan\":[\"[Ƒļööŕ þļáñ ~~~]\"],\"viewer.floorplan.viewLabel\":[\"[\",[\"floor\"],\" 2Ð ṽîéŵ ~~]\"],\"viewer.floorplan.zoomIn\":[\"[Žööɱ îñ ~~]\"],\"viewer.floorplan.zoomOut\":[\"[Žööɱ öûţ ~~~]\"],\"viewer.header.back\":[\"[Ɓáçķ ~~]\"],\"viewer.header.levels\":[\"[Ļéṽéļš ~~]\"],\"viewer.header.rooms\":[\"[Ŕööɱš ~~]\"],\"viewer.header.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"viewer.header.units\":[\"[Ûñîţš ~~]\"],\"viewer.header.untitled\":[\"[Ûñţîţļéð ~~~]\"],\"viewer.header.zones\":[\"[Žöñéš ~~]\"],\"viewer.levels.exploded\":[\"[Éẋþļöðéð ~~~]\"],\"viewer.levels.label\":[\"[Ļéṽéļš: \",[\"mode\"],\" ~~]\"],\"viewer.levels.manual\":[\"[Ṁáñûáļ ~~]\"],\"viewer.levels.solo\":[\"[Šöļö ~~]\"],\"viewer.levels.stacked\":[\"[Šţáçķéð ~~~]\"],\"viewer.stage.layout\":[\"[Ṽîéŵéŕ ļáýöûţ ~~~~]\"],\"viewer.stage.split\":[\"[Šþļîţ ~~]\"],\"viewer.theme.blueprint\":[\"[Ɓļûéþŕîñţ ~~~]\"],\"viewer.theme.mediterranean\":[\"[Ṁéðîţéŕŕáñéáñ ~~~~]\"],\"viewer.theme.night\":[\"[Ñîĝĥţ ~~]\"],\"viewer.theme.overcast\":[\"[Öṽéŕçášţ ~~~]\"],\"viewer.theme.paper\":[\"[Þáþéŕ ~~]\"],\"viewer.theme.studio\":[\"[Šţûðîö ~~]\"],\"viewer.theme.sunset\":[\"[Šûñšéţ ~~]\"],\"viewer.theme.title\":[\"[Šçéñé ţĥéɱé ~~~]\"],\"viewer.theme.twilight\":[\"[Ţŵîļîĝĥţ ~~~]\"],\"viewer.theme.verdant\":[\"[Ṽéŕðáñţ ~~~]\"],\"viewer.visibility.guides\":[\"[Ĝûîðéš ~~]\"],\"viewer.visibility.scans\":[\"[Šçáñš ~~]\"],\"viewer.visibility.title\":[\"[Ṽîšîƀîļîţý ~~~]\"],\"viewer.walkthrough.label\":[\"[Ŵáļķţĥŕöûĝĥ: \",[\"state\"],\" ~~~~]\"],\"viewer.walls.cutaway\":[\"[Çûţáŵáý ~~~]\"],\"viewer.walls.fullHeight\":[\"[Ƒûļļ ĥéîĝĥţ ~~~]\"],\"viewer.walls.label\":[\"[Ŵáļļš: \",[\"mode\"],\" ~~]\"],\"viewer.walls.low\":[\"[Ļöŵ ~]\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"app.appShell.pascalHome\":[\"[Þášçáļ ĥöɱé ~~~]\"],\"app.buildTab.addGate\":[\"[Åðð Ĝáţé ~~~]\"],\"app.buildTab.addOpenPassage\":[\"[Åðð Öþéñ Þáššáĝé ~~~~~]\"],\"app.buildTab.addToRooms\":[\"[Åðð ţö ŕööɱš ~~~]\"],\"app.buildTab.allBuildTools\":[\"[← Åļļ ƀûîļð ţööļš ~~~~]\"],\"app.buildTab.chooseGateOrOpenPassageThen\":[\"[Çĥööšé Ĝáţé öŕ Öþéñ Þáššáĝé, ţĥéñ çļîçķ îţš þöšîţîöñ öñ áñý ƒéñçé. Ƒîñð þļáçéð ĝáţéš áñð öþéñîñĝš ûñðéŕ ţĥéîŕ ƒéñçé îñ ţĥé šçéñé ĝŕáþĥ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.clickALinesetToLayThe\":[\"[Çļîçķ á ļîñéšéţ ţö ļáý ţĥé ļîñé ƀéšîðé îţ. ~~~~~~~~~~]\"],\"app.buildTab.doubleGate\":[\"[Ðöûƀļé ĝáţé ~~~]\"],\"app.buildTab.featuresExtensions\":[\"[Ƒéáţûŕéš & éẋţéñšîöñš ~~~~~~]\"],\"app.buildTab.fenceFeatures\":[\"[Ƒéñçé ƒéáţûŕéš ~~~~]\"],\"app.buildTab.followLineset\":[\"[Ƒöļļöŵ ļîñéšéţ ~~~~]\"],\"app.buildTab.gate\":[\"[Ĝáţé ~~]\"],\"app.buildTab.gateStyle\":[\"[Ĝáţé šţýļé ~~~]\"],\"app.buildTab.horizontalBoards\":[\"[Ĥöŕîžöñţáļ ƀöáŕðš ~~~~~]\"],\"app.buildTab.hoverAFenceToPreviewClick\":[\"[Ĥöṽéŕ á ƒéñçé ţö þŕéṽîéŵ. Çļîçķ ţö þļáçé. Éšç çáñçéļš. Ļéáṽé ŕööɱ ƀéţŵééñ öþéñîñĝš. ~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.leaves\":[\"[Ļéáṽéš ~~]\"],\"app.buildTab.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"app.buildTab.matchFenceStyle\":[\"[Ṁáţçĥ ƒéñçé šţýļé ~~~~~]\"],\"app.buildTab.mep\":[\"[ṀÉÞ ~]\"],\"app.buildTab.off\":[\"[Öƒƒ ~]\"],\"app.buildTab.on\":[\"[Öñ ~]\"],\"app.buildTab.openRails\":[\"[Öþéñ ŕáîļš ~~~]\"],\"app.buildTab.opening\":[\"[Öþéñîñĝ ~~~]\"],\"app.buildTab.openingWidthM\":[\"[Öþéñîñĝ ŵîðţĥ (ɱ) ~~~~]\"],\"app.buildTab.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"app.buildTab.picket\":[\"[Þîçķéţ ~~]\"],\"app.buildTab.roofType\":[\"[Ŕööƒ ţýþé ~~~]\"],\"app.buildTab.roomsAlreadyCreateTheirFloorAnd\":[\"[Ŕööɱš áļŕéáðý çŕéáţé ţĥéîŕ ƒļööŕ áñð çéîļîñĝ. Ûšé ţĥéšé ƒöŕ þļáţƒöŕɱš áñð öñé-öƒƒ šţŕûçţûŕé. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"app.buildTab.searchBuildToolsAndKitchenItems\":[\"[Šéáŕçĥ ƀûîļð ţööļš áñð ķîţçĥéñ îţéɱš ~~~~~~~~~~]\"],\"app.buildTab.searchToolsFridgeSink\":[\"[Šéáŕçĥ ţööļš, ƒŕîðĝé, šîñķ… ~~~~~~~]\"],\"app.buildTab.selectACurvedWallToMatch\":[\"[Šéļéçţ á çûŕṽéð ŵáļļ ţö ɱáţçĥ îţš ŕáðîûš áñð áŕç. ~~~~~~~~~~~~]\"],\"app.buildTab.singleGate\":[\"[Šîñĝļé ĝáţé ~~~]\"],\"app.buildTab.slabCeilingColumn\":[\"[Šļáƀ, çéîļîñĝ, çöļûɱñ… ~~~~~~]\"],\"app.buildTab.solidPrivacy\":[\"[Šöļîð þŕîṽáçý ~~~~]\"],\"app.buildTab.traceALineAlongsideAnExisting\":[\"[Ţŕáçé á ļîñé áļöñĝšîðé áñ éẋîšţîñĝ ļîñéšéţ (Ƒ). ~~~~~~~~~~~~]\"],\"app.buildTab.verticalSlats\":[\"[Ṽéŕţîçáļ šļáţš ~~~~]\"],\"app.editorNavbar.allProjects\":[\"[Åļļ þŕöĵéçţš ~~~~]\"],\"app.editorNavbar.preview\":[\"[Þŕéṽîéŵ ~~~]\"],\"app.editorNavbar.projectName\":[\"[Þŕöĵéçţ ñáɱé ~~~~]\"],\"app.editorNavbar.renameProject\":[\"[Ŕéñáɱé þŕöĵéçţ ~~~~]\"],\"app.editorNavbar.share\":[\"[Šĥáŕé ~~]\"],\"app.kitchenBrowser.addLabel\":[\"[Åðð \",[\"label\"],\" ~]\"],\"app.kitchenBrowser.addToKitchen\":[\"[Åðð ţö ķîţçĥéñ ~~~~]\"],\"app.kitchenBrowser.chooseAnItemThenClickTo\":[\"[Çĥööšé áñ îţéɱ, ţĥéñ çļîçķ ţö þļáçé îţ. Åþþļîáñçéš îñçļûðé ţĥéîŕ çáƀîñéţŕý. ~~~~~~~~~~~~~~~~~~~]\"],\"app.kitchenBrowser.findInThisProjectLength\":[\"[Ƒîñð îñ ţĥîš þŕöĵéçţ · \",[\"length\"],\" ~~~~~~]\"],\"app.kitchenBrowser.findName\":[\"[Ƒîñð \",[\"name\"],\" ~~]\"],\"app.kitchenBrowser.groupAdd\":[\"[Ķîţçĥéñ · Åðð ~~~]\"],\"app.kitchenBrowser.groupInThisProject\":[\"[Ķîţçĥéñ · Îñ ţĥîš þŕöĵéçţ ~~~~~~]\"],\"app.kitchenBrowser.kitchenCabinet\":[\"[Ķîţçĥéñ çáƀîñéţ ~~~~~]\"],\"app.kitchenBrowser.noMatchingKitchenItemsPlacedYet\":[\"[Ñö ɱáţçĥîñĝ ķîţçĥéñ îţéɱš þļáçéð ýéţ. ~~~~~~~~~~]\"],\"app.kitchenBrowser.selectAndShowInScene\":[\"[Šéļéçţ áñð šĥöŵ îñ šçéñé ~~~~~~]\"],\"app.lib.buildPalette.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"app.lib.buildPalette.column\":[\"[Çöļûɱñ ~~]\"],\"app.lib.buildPalette.door\":[\"[Ðööŕ ~~]\"],\"app.lib.buildPalette.duct\":[\"[Ðûçţ ~~]\"],\"app.lib.buildPalette.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"app.lib.buildPalette.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"app.lib.buildPalette.fence\":[\"[Ƒéñçé ~~]\"],\"app.lib.buildPalette.hvacUnit\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"app.lib.buildPalette.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"app.lib.buildPalette.lineset\":[\"[Ļîñéšéţ ~~~]\"],\"app.lib.buildPalette.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"app.lib.buildPalette.mep\":[\"[ṀÉÞ ~]\"],\"app.lib.buildPalette.post\":[\"[Þöšţ ~~]\"],\"app.lib.buildPalette.register\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"app.lib.buildPalette.roof\":[\"[Ŕööƒ ~~]\"],\"app.lib.buildPalette.shelf\":[\"[Šĥéļƒ ~~]\"],\"app.lib.buildPalette.slab\":[\"[Šļáƀ ~~]\"],\"app.lib.buildPalette.spawnPoint\":[\"[Šþáŵñ þöîñţ ~~~]\"],\"app.lib.buildPalette.stairs\":[\"[Šţáîŕš ~~]\"],\"app.lib.buildPalette.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"app.lib.buildPalette.terrain\":[\"[Ţéŕŕáîñ ~~~]\"],\"app.lib.buildPalette.wall\":[\"[Ŵáļļ ~~]\"],\"app.lib.buildPalette.window\":[\"[Ŵîñðöŵ ~~]\"],\"app.lib.buildPanelModel.addFitting\":[\"[Åðð Ƒîţţîñĝ ~~~]\"],\"app.lib.buildPanelModel.addGate\":[\"[Åðð Ĝáţé ~~~]\"],\"app.lib.buildPanelModel.addOpenPassage\":[\"[Åðð Öþéñ Þáššáĝé ~~~~~]\"],\"app.lib.buildPanelModel.addTrap\":[\"[Åðð Ţŕáþ ~~~]\"],\"app.lib.buildPanelModel.build\":[\"[Ɓûîļð ~~]\"],\"app.lib.buildPanelModel.createFrom\":[\"[Çŕéáţé ƒŕöɱ ~~~]\"],\"app.lib.buildPanelModel.drawTheRoofFootprintWithTwo\":[\"[Ðŕáŵ ţĥé ŕööƒ ƒööţþŕîñţ ŵîţĥ ţŵö çöŕñéŕ çļîçķš. ~~~~~~~~~~~~]\"],\"app.lib.buildPanelModel.duct\":[\"[Ðûçţ ~~]\"],\"app.lib.buildPanelModel.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"app.lib.buildPanelModel.featuresExtensions\":[\"[Ƒéáţûŕéš & éẋţéñšîöñš ~~~~~~]\"],\"app.lib.buildPanelModel.fence\":[\"[Ƒéñçé ~~]\"],\"app.lib.buildPanelModel.followLineset\":[\"[Ƒöļļöŵ ļîñéšéţ ~~~~]\"],\"app.lib.buildPanelModel.gatesOpenings\":[\"[Ĝáţéš & öþéñîñĝš ~~~~]\"],\"app.lib.buildPanelModel.hoverARoomToPreviewIts\":[\"[Ĥöṽéŕ á ŕööɱ ţö þŕéṽîéŵ îţš ƀöûñðáŕý, ţĥéñ çļîçķ ţö þļáçé. ~~~~~~~~~~~~~~]\"],\"app.lib.buildPanelModel.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"app.lib.buildPanelModel.mep\":[\"[ṀÉÞ ~]\"],\"app.lib.buildPanelModel.modularCabinet\":[\"[Ṁöðûļáŕ Çáƀîñéţ ~~~~~]\"],\"app.lib.buildPanelModel.off\":[\"[Öƒƒ ~]\"],\"app.lib.buildPanelModel.on\":[\"[Öñ ~]\"],\"app.lib.buildPanelModel.painting\":[\"[Þáîñţîñĝ ~~~]\"],\"app.lib.buildPanelModel.roof\":[\"[Ŕööƒ ~~]\"],\"app.lib.buildPanelModel.roofType\":[\"[Ŕööƒ ţýþé ~~~]\"],\"app.lib.buildPanelModel.toolCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ţööļ\"],\"other\":[\"#\",\" ţööļš\"]}],\" ~~~]\"],\"app.lib.buildTabState.conical\":[\"[Çöñîçáļ ~~~]\"],\"app.lib.buildTabState.draw\":[\"[Ðŕáŵ ~~]\"],\"app.lib.buildTabState.dutch\":[\"[Ðûţçĥ ~~]\"],\"app.lib.buildTabState.flat\":[\"[Ƒļáţ ~~]\"],\"app.lib.buildTabState.gable\":[\"[Ĝáƀļé ~~]\"],\"app.lib.buildTabState.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"app.lib.buildTabState.hip\":[\"[Ĥîþ ~]\"],\"app.lib.buildTabState.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"app.lib.buildTabState.room\":[\"[Ŕööɱ ~~]\"],\"app.lib.buildTabState.shed\":[\"[Šĥéð ~~]\"],\"app.lib.buildTabState.wall\":[\"[Ŵáļļ ~~]\"],\"app.sceneLoader.anotherSessionSavedFirstRefresh\":[\"[Åñöţĥéŕ šéššîöñ šáṽéð ƒîŕšţ — ŕéƒŕéšĥ? ~~~~~~~~~~]\"],\"app.sceneLoader.build\":[\"[Ɓûîļð ~~]\"],\"app.sceneLoader.dismiss\":[\"[Ðîšɱîšš ~~~]\"],\"app.sceneLoader.items\":[\"[Îţéɱš ~~]\"],\"app.sceneLoader.paint\":[\"[Þáîñţ ~~]\"],\"app.sceneLoader.reload\":[\"[Ŕéļöáð ~~]\"],\"app.sceneLoader.scene\":[\"[Šçéñé ~~]\"],\"app.sceneLoader.settings\":[\"[Šéţţîñĝš ~~~]\"],\"app.sceneLoader.yourChangesHavenTBeenSaved\":[\"[Ýöûŕ çĥáñĝéš ĥáṽéñ'ţ ƀééñ šáṽéð. Ŕéļöáð ţö þîçķ ûþ ţĥé ļáţéšţ ṽéŕšîöñ. ~~~~~~~~~~~~~~~~~]\"],\"app.sharePanel.anyoneWithTheLinkCanView\":[\"[Åñýöñé ŵîţĥ ţĥé ļîñķ çáñ ṽîéŵ ţĥîš þŕöĵéçţ. Ţĥéý çáññöţ éðîţ îţ. ~~~~~~~~~~~~~~~~]\"],\"app.sharePanel.copied\":[\"[Çöþîéð ~~]\"],\"app.sharePanel.copy\":[\"[Çöþý ~~]\"],\"app.sharePanel.couldNotReadTheSharingState\":[\"[Çöûļð ñöţ ŕéáð ţĥé šĥáŕîñĝ šţáţé. ~~~~~~~~~]\"],\"app.sharePanel.couldNotTurnOffThePublic\":[\"[Çöûļð ñöţ ţûŕñ öƒƒ ţĥé þûƀļîç ļîñķ. ~~~~~~~~~]\"],\"app.sharePanel.couldNotTurnOnThePublic\":[\"[Çöûļð ñöţ ţûŕñ öñ ţĥé þûƀļîç ļîñķ. ~~~~~~~~~]\"],\"app.sharePanel.embedCode\":[\"[Éɱƀéð çöðé ~~~]\"],\"app.sharePanel.embedLink\":[\"[Éɱƀéð ļîñķ ~~~]\"],\"app.sharePanel.hideControls\":[\"[Ĥîðé çöñţŕöļš ~~~~]\"],\"app.sharePanel.link\":[\"[Ļîñķ ~~]\"],\"app.sharePanel.onlyThe3dViewNoHeader\":[\"[Öñļý ţĥé 3Ð ṽîéŵ, ñö ĥéáðéŕ öŕ ţööļƀáŕ. ~~~~~~~~~]\"],\"app.sharePanel.open\":[\"[Öþéñ ~~]\"],\"app.sharePanel.publicLink\":[\"[Þûƀļîç ļîñķ ~~~]\"],\"app.sharePanel.shareProject\":[\"[Šĥáŕé þŕöĵéçţ ~~~~]\"],\"app.viewerToolbar.automaticDimensions\":[\"[Åûţöɱáţîç ðîɱéñšîöñš ~~~~~~]\"],\"app.viewerToolbar.camera\":[\"[Çáɱéŕá ~~]\"],\"app.viewerToolbar.ceilings\":[\"[Çéîļîñĝš ~~~]\"],\"app.viewerToolbar.cleanPlanDimensionsAppearWithSelection\":[\"[Çļéáñ þļáñ; ðîɱéñšîöñš áþþéáŕ ŵîţĥ šéļéçţîöñ ~~~~~~~~~~~~]\"],\"app.viewerToolbar.collapseSidebar\":[\"[Çöļļáþšé šîðéƀáŕ ~~~~~]\"],\"app.viewerToolbar.crispOpaqueEdgeLines\":[\"[Çŕîšþ, öþáǫûé éðĝé ļîñéš ~~~~~~]\"],\"app.viewerToolbar.cutaway\":[\"[Çûţáŵáý ~~~]\"],\"app.viewerToolbar.default\":[\"[Ðéƒáûļţ ~~~]\"],\"app.viewerToolbar.display\":[\"[Ðîšþļáý ~~~]\"],\"app.viewerToolbar.displaySettings\":[\"[Ðîšþļáý šéţţîñĝš ~~~~~]\"],\"app.viewerToolbar.doorWindowMarks\":[\"[Ðööŕ/ŵîñðöŵ ɱáŕķš ~~~~~]\"],\"app.viewerToolbar.edges\":[\"[Éðĝéš ~~]\"],\"app.viewerToolbar.enterVr\":[\"[Éñţéŕ ṼŔ ~~~]\"],\"app.viewerToolbar.exitVr\":[\"[Éẋîţ ṼŔ ~~]\"],\"app.viewerToolbar.expandSidebar\":[\"[Éẋþáñð šîðéƀáŕ ~~~~]\"],\"app.viewerToolbar.expert\":[\"[Éẋþéŕţ ~~]\"],\"app.viewerToolbar.exploded\":[\"[Éẋþļöðéð ~~~]\"],\"app.viewerToolbar.faceOfStud\":[\"[Ƒáçé öƒ šţûð ~~~]\"],\"app.viewerToolbar.faintOutlineOfMajorCreases\":[\"[Ƒáîñţ öûţļîñé öƒ ɱáĵöŕ çŕéášéš ~~~~~~~~]\"],\"app.viewerToolbar.feetInches\":[\"[Ƒééţ & îñçĥéš ~~~]\"],\"app.viewerToolbar.finishedFaces\":[\"[Ƒîñîšĥéð ƒáçéš ~~~~]\"],\"app.viewerToolbar.flatAndFastNoAmbientOcclusion\":[\"[Ƒļáţ áñð ƒášţ — ñö áɱƀîéñţ öççļûšîöñ ~~~~~~~~~]\"],\"app.viewerToolbar.floorPlanAnnotations\":[\"[Ƒļööŕ þļáñ áññöţáţîöñš ~~~~~~]\"],\"app.viewerToolbar.floorPlanMode\":[\"[Ƒļööŕ þļáñ ɱöðé ~~~~]\"],\"app.viewerToolbar.fullAmbientOcclusion\":[\"[Ƒûļļ áɱƀîéñţ öççļûšîöñ ~~~~~~]\"],\"app.viewerToolbar.fullDocumentationAndAnnotationControls\":[\"[Ƒûļļ ðöçûɱéñţáţîöñ áñð áññöţáţîöñ çöñţŕöļš ~~~~~~~~~~~~]\"],\"app.viewerToolbar.fullHeight\":[\"[Ƒûļļ ĥéîĝĥţ ~~~]\"],\"app.viewerToolbar.fullWallThickness\":[\"[Ƒûļļ ŵáļļ ţĥîçķñéšš ~~~~~~]\"],\"app.viewerToolbar.grid\":[\"[Ĝŕîð ~~]\"],\"app.viewerToolbar.levelsMode\":[\"[Ļéṽéļš: \",[\"mode\"],\" ~~]\"],\"app.viewerToolbar.low\":[\"[Ļöŵ ~]\"],\"app.viewerToolbar.magneticSnap\":[\"[Ṁáĝñéţîç šñáþ ~~~~]\"],\"app.viewerToolbar.manual\":[\"[Ṁáñûáļ ~~]\"],\"app.viewerToolbar.manualDimensions\":[\"[Ṁáñûáļ ðîɱéñšîöñš ~~~~~]\"],\"app.viewerToolbar.measurements\":[\"[Ṁéášûŕéɱéñţš ~~~~]\"],\"app.viewerToolbar.meters\":[\"[Ṁéţéŕš ~~]\"],\"app.viewerToolbar.millimeters\":[\"[Ṁîļļîɱéţéŕš ~~~~]\"],\"app.viewerToolbar.n3dMeasurements\":[\"[3Ð ɱéášûŕéɱéñţš ~~~~]\"],\"app.viewerToolbar.noEdgeLines\":[\"[Ñö éðĝé ļîñéš ~~~~]\"],\"app.viewerToolbar.off\":[\"[Öƒƒ ~]\"],\"app.viewerToolbar.on\":[\"[Öñ ~]\"],\"app.viewerToolbar.orthographic\":[\"[Öŕţĥöĝŕáþĥîç ~~~~]\"],\"app.viewerToolbar.perspective\":[\"[Þéŕšþéçţîṽé ~~~~]\"],\"app.viewerToolbar.preview\":[\"[Þŕéṽîéŵ ~~~]\"],\"app.viewerToolbar.previewMode\":[\"[Þŕéṽîéŵ ɱöðé ~~~~]\"],\"app.viewerToolbar.render\":[\"[Ŕéñðéŕ ~~]\"],\"app.viewerToolbar.rendered\":[\"[Ŕéñðéŕéð ~~~]\"],\"app.viewerToolbar.roomLabels\":[\"[Ŕööɱ ļáƀéļš ~~~]\"],\"app.viewerToolbar.sceneTheme\":[\"[Šçéñé ţĥéɱé ~~~]\"],\"app.viewerToolbar.shadows\":[\"[Šĥáðöŵš ~~~]\"],\"app.viewerToolbar.singleWallAxis\":[\"[Šîñĝļé ŵáļļ áẋîš ~~~~~]\"],\"app.viewerToolbar.soft\":[\"[Šöƒţ ~~]\"],\"app.viewerToolbar.solid\":[\"[Šöļîð ~~]\"],\"app.viewerToolbar.solo\":[\"[Šöļö ~~]\"],\"app.viewerToolbar.stack\":[\"[Šţáçķ ~~]\"],\"app.viewerToolbar.stairAnnotations\":[\"[Šţáîŕ áññöţáţîöñš ~~~~~]\"],\"app.viewerToolbar.strong\":[\"[Šţŕöñĝ ~~]\"],\"app.viewerToolbar.structuralCoreFace\":[\"[Šţŕûçţûŕáļ çöŕé ƒáçé ~~~~~~]\"],\"app.viewerToolbar.structuralGridsColumnCenters\":[\"[Šţŕûçţûŕáļ ĝŕîðš & çöļûɱñ çéñţéŕš ~~~~~~~~~]\"],\"app.viewerToolbar.translucent\":[\"[Ţŕáñšļûçéñţ ~~~~]\"],\"app.viewerToolbar.units\":[\"[Ûñîţš ~~]\"],\"app.viewerToolbar.walkthrough\":[\"[Ŵáļķţĥŕöûĝĥ ~~~~]\"],\"app.viewerToolbar.wallCenterline\":[\"[Ŵáļļ çéñţéŕļîñé ~~~~~]\"],\"app.viewerToolbar.wallDimensions\":[\"[Ŵáļļ ðîɱéñšîöñš ~~~~~]\"],\"app.viewerToolbar.wallsLabel\":[\"[Ŵáļļš: \",[\"label\"],\" ~~]\"],\"core.level.basement\":[\"[Ɓášéɱéñţ ~~~]\"],\"core.level.basementNumber\":[\"[Ɓášéɱéñţ \",[\"number\"],\" ~~~]\"],\"core.level.floorNumber\":[\"[Ƒļööŕ \",[\"number\"],\" ~~]\"],\"core.level.groundFloor\":[\"[Ĝŕöûñð ƒļööŕ ~~~~]\"],\"editor.actionMenu.cameraActions.orbitLeft\":[\"[Öŕƀîţ Ļéƒţ ~~~]\"],\"editor.actionMenu.cameraActions.orbitRight\":[\"[Öŕƀîţ Ŕîĝĥţ ~~~]\"],\"editor.actionMenu.cameraActions.topView\":[\"[Ţöþ Ṽîéŵ ~~~]\"],\"editor.actionMenu.controlModes.delete\":[\"[Ðéļéţé ~~]\"],\"editor.actionMenu.controlModes.select\":[\"[Šéļéçţ ~~]\"],\"editor.actionMenu.followPascalPill.resumeFollowing\":[\"[Ŕéšûɱé ƒöļļöŵîñĝ ~~~~~]\"],\"editor.actionMenu.followPascalToggle.off\":[\"[Ƒöļļöŵ Þášçáļ: Öƒƒ ~~~~~]\"],\"editor.actionMenu.followPascalToggle.on\":[\"[Ƒöļļöŵ Þášçáļ: Öñ ~~~~~]\"],\"editor.actionMenu.furnishTools.appliance\":[\"[Åþþļîáñçé ~~~]\"],\"editor.actionMenu.furnishTools.bathroom\":[\"[Ɓáţĥŕööɱ ~~~]\"],\"editor.actionMenu.furnishTools.furniture\":[\"[Ƒûŕñîţûŕé ~~~]\"],\"editor.actionMenu.furnishTools.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"editor.actionMenu.furnishTools.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"editor.actionMenu.measurementControl.angle\":[\"[Åñĝļé ~~]\"],\"editor.actionMenu.measurementControl.angularDimension\":[\"[Åñĝûļáŕ ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.arcLength\":[\"[Åŕç ļéñĝţĥ ~~~]\"],\"editor.actionMenu.measurementControl.area\":[\"[Åŕéá ~~]\"],\"editor.actionMenu.measurementControl.centerMark\":[\"[Çéñţéŕ ɱáŕķ ~~~]\"],\"editor.actionMenu.measurementControl.chordDimension\":[\"[Çĥöŕð ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.continuousDimension\":[\"[Çöñţîñûöûš ðîɱéñšîöñ ~~~~~~]\"],\"editor.actionMenu.measurementControl.coordinateDimensions\":[\"[Çööŕðîñáţé ðîɱéñšîöñš ~~~~~~]\"],\"editor.actionMenu.measurementControl.diameterDimension\":[\"[Ðîáɱéţéŕ ðîɱéñšîöñ ~~~~~~]\"],\"editor.actionMenu.measurementControl.distance\":[\"[Ðîšţáñçé ~~~]\"],\"editor.actionMenu.measurementControl.floorPlan\":[\"[Ƒļööŕ þļáñ ~~~]\"],\"editor.actionMenu.measurementControl.linearDimension\":[\"[Ļîñéáŕ ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.measureSelectedlabel\":[\"[Ṁéášûŕé: \",[\"selectedLabel\"],\" ~~~]\"],\"editor.actionMenu.measurementControl.measurementOptions\":[\"[Ṁéášûŕéɱéñţ öþţîöñš ~~~~~~]\"],\"editor.actionMenu.measurementControl.measurementType\":[\"[Ṁéášûŕéɱéñţ ţýþé ~~~~~]\"],\"editor.actionMenu.measurementControl.perimeter\":[\"[Þéŕîɱéţéŕ ~~~]\"],\"editor.actionMenu.measurementControl.radiusDimension\":[\"[Ŕáðîûš ðîɱéñšîöñ ~~~~~]\"],\"editor.actionMenu.measurementControl.smart\":[\"[Šɱáŕţ ~~]\"],\"editor.actionMenu.measurementControl.volume\":[\"[Ṽöļûɱé ~~]\"],\"editor.actionMenu.pointAskButton.pointAndAsk\":[\"[Þöîñţ áñð ášķ ~~~~]\"],\"editor.actionMenu.pointAskButton.pointAndAskC\":[\"[Þöîñţ áñð ášķ (Ç) ~~~~]\"],\"editor.actionMenu.pointAskButton.pointAndAskWorksInThe\":[\"[Þöîñţ áñð ášķ ŵöŕķš îñ ţĥé 3Ð ṽîéŵ ~~~~~~~~]\"],\"editor.actionMenu.structureTools.beam\":[\"[Ɓéáɱ ~~]\"],\"editor.actionMenu.structureTools.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.actionMenu.structureTools.column\":[\"[Çöļûɱñ ~~]\"],\"editor.actionMenu.structureTools.door\":[\"[Ðööŕ ~~]\"],\"editor.actionMenu.structureTools.duct\":[\"[Ðûçţ ~~]\"],\"editor.actionMenu.structureTools.ductFitting\":[\"[Ðûçţ Ƒîţţîñĝ ~~~~]\"],\"editor.actionMenu.structureTools.dwvPipe\":[\"[ÐŴṼ Þîþé ~~~]\"],\"editor.actionMenu.structureTools.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"editor.actionMenu.structureTools.fence\":[\"[Ƒéñçé ~~]\"],\"editor.actionMenu.structureTools.footing\":[\"[Ƒööţîñĝ ~~~]\"],\"editor.actionMenu.structureTools.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.actionMenu.structureTools.gableRoof\":[\"[Ĝáƀļé Ŕööƒ ~~~]\"],\"editor.actionMenu.structureTools.hvacUnit\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"editor.actionMenu.structureTools.lineset\":[\"[Ļîñéšéţ ~~~]\"],\"editor.actionMenu.structureTools.liquidLine\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"editor.actionMenu.structureTools.pipeFitting\":[\"[Þîþé Ƒîţţîñĝ ~~~~]\"],\"editor.actionMenu.structureTools.register\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"editor.actionMenu.structureTools.shelf\":[\"[Šĥéļƒ ~~]\"],\"editor.actionMenu.structureTools.slab\":[\"[Šļáƀ ~~]\"],\"editor.actionMenu.structureTools.spawnPoint\":[\"[Šþáŵñ Þöîñţ ~~~]\"],\"editor.actionMenu.structureTools.stairs\":[\"[Šţáîŕš ~~]\"],\"editor.actionMenu.structureTools.trap\":[\"[Ţŕáþ ~~]\"],\"editor.actionMenu.structureTools.wall\":[\"[Ŵáļļ ~~]\"],\"editor.actionMenu.structureTools.window\":[\"[Ŵîñðöŵ ~~]\"],\"editor.actionMenu.structureTools.zone\":[\"[Žöñé ~~]\"],\"editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels\":[\"[Ðášĥéð öûţļîñéš öƒ ţĥé ñéîĝĥƀöûŕîñĝ ļéṽéļš’ ŵáļļš, ţö ļîñé ŵáļļš ûþ áñð çĥéçķ á ŕööƒ áĝáîñšţ ţĥé ŵáļļš ûñðéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.deleteGuideImage\":[\"[Ðéļéţé ĝûîðé îɱáĝé ~~~~~]\"],\"editor.actionMenu.viewToggles.deleteScan\":[\"[Ðéļéţé šçáñ ~~~]\"],\"editor.actionMenu.viewToggles.guideCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ĝûîðé îɱáĝé öñ ţĥîš ļéṽéļ\"],\"other\":[\"#\",\" ĝûîðé îɱáĝéš öñ ţĥîš ļéṽéļ\"]}],\" ~~~~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.guideImageNumber\":[\"[Ĝûîðé îɱáĝé \",[\"number\"],\" ~~~]\"],\"editor.actionMenu.viewToggles.guideImageSettings\":[\"[Ĝûîðé îɱáĝé šéţţîñĝš ~~~~~~]\"],\"editor.actionMenu.viewToggles.guideImages\":[\"[Ĝûîðé îɱáĝéš ~~~~]\"],\"editor.actionMenu.viewToggles.guides\":[\"[Ĝûîðéš ~~]\"],\"editor.actionMenu.viewToggles.guidesHidden\":[\"[Ĝûîðéš: Ĥîððéñ ~~~~]\"],\"editor.actionMenu.viewToggles.guidesVisible\":[\"[Ĝûîðéš: Ṽîšîƀļé ~~~~]\"],\"editor.actionMenu.viewToggles.hideGuideImages\":[\"[Ĥîðé ĝûîðé îɱáĝéš ~~~~~]\"],\"editor.actionMenu.viewToggles.hideReferenceFloor\":[\"[Ĥîðé ŕéƒéŕéñçé ƒļööŕ ~~~~~~]\"],\"editor.actionMenu.viewToggles.hideScans\":[\"[Ĥîðé šçáñš ~~~]\"],\"editor.actionMenu.viewToggles.levelsBelow\":[\"[\",[\"count\"],\" ƀéļöŵ ~~]\"],\"editor.actionMenu.viewToggles.noLevelAbove\":[\"[Ñö ļéṽéļ áƀöṽé ~~~~]\"],\"editor.actionMenu.viewToggles.noLevelBelow\":[\"[Ñö ļéṽéļ ƀéļöŵ ~~~~]\"],\"editor.actionMenu.viewToggles.noLowerFloorAvailable\":[\"[Ñö ļöŵéŕ ƒļööŕ áṽáîļáƀļé. ~~~~~~~]\"],\"editor.actionMenu.viewToggles.opacity\":[\"[Öþáçîţý ~~~]\"],\"editor.actionMenu.viewToggles.referenceFloor\":[\"[Ŕéƒéŕéñçé ƒļööŕ ~~~~~]\"],\"editor.actionMenu.viewToggles.referenceFloorSelectedlevelname\":[\"[Ŕéƒéŕéñçé ƒļööŕ: \",[\"selectedLevelName\"],\" ~~~~~]\"],\"editor.actionMenu.viewToggles.referenceFloorSettings\":[\"[Ŕéƒéŕéñçé ƒļööŕ šéţţîñĝš ~~~~~~~]\"],\"editor.actionMenu.viewToggles.referenceSettings\":[\"[Ŕéƒéŕéñçé šéţţîñĝš ~~~~~~]\"],\"editor.actionMenu.viewToggles.references\":[\"[Ŕéƒéŕéñçéš ~~~]\"],\"editor.actionMenu.viewToggles.referencesEmpty\":[\"[Ûþļöáð ĜĻƁ ɱéšĥéš áš šçáñ ŕéƒéŕéñçéš öŕ ƀļûéþŕîñţ îɱáĝéš áš ĝûîðé ŕéƒéŕéñçéš. ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.referencesHidden\":[\"[Ŕéƒéŕéñçéš: Ĥîððéñ ~~~~~]\"],\"editor.actionMenu.viewToggles.referencesVisible\":[\"[Ŕéƒéŕéñçéš: Ṽîšîƀļé ~~~~~~]\"],\"editor.actionMenu.viewToggles.riserDiagram\":[\"[Ŕîšéŕ ðîáĝŕáɱ ~~~~]\"],\"editor.actionMenu.viewToggles.scanCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šçáñ öñ ţĥîš ļéṽéļ\"],\"other\":[\"#\",\" šçáñš öñ ţĥîš ļéṽéļ\"]}],\" ~~~~~~~~~~]\"],\"editor.actionMenu.viewToggles.scanNumber\":[\"[Šçáñ \",[\"number\"],\" ~~]\"],\"editor.actionMenu.viewToggles.scanSettings\":[\"[Šçáñ šéţţîñĝš ~~~~]\"],\"editor.actionMenu.viewToggles.scans\":[\"[Šçáñš ~~]\"],\"editor.actionMenu.viewToggles.scansHidden\":[\"[Šçáñš: Ĥîððéñ ~~~~]\"],\"editor.actionMenu.viewToggles.scansVisible\":[\"[Šçáñš: Ṽîšîƀļé ~~~~]\"],\"editor.actionMenu.viewToggles.showGuideImages\":[\"[Šĥöŵ ĝûîðé îɱáĝéš ~~~~~]\"],\"editor.actionMenu.viewToggles.showReferenceFloor\":[\"[Šĥöŵ ŕéƒéŕéñçé ƒļööŕ ~~~~~~]\"],\"editor.actionMenu.viewToggles.showScans\":[\"[Šĥöŵ šçáñš ~~~]\"],\"editor.actionMenu.viewToggles.showWallsBelowAndAbove\":[\"[Šĥöŵ ŵáļļš ƀéļöŵ áñð áƀöṽé ~~~~~~~]\"],\"editor.actionMenu.viewToggles.uploadScanOrGuideImage\":[\"[Ûþļöáð šçáñ öŕ ĝûîðé îɱáĝé ~~~~~~~]\"],\"editor.actionMenu.viewToggles.wallsAbove\":[\"[Ŵáļļš áƀöṽé ~~~]\"],\"editor.actionMenu.viewToggles.wallsBelow\":[\"[Ŵáļļš ƀéļöŵ ~~~]\"],\"editor.actionMenu.viewToggles.wallsBelowAndAbove\":[\"[Ŵáļļš ƀéļöŵ áñð áƀöṽé ~~~~~~]\"],\"editor.actionMenu.viewToggles.wallsBelowAndAboveSettings\":[\"[Ŵáļļš ƀéļöŵ áñð áƀöṽé šéţţîñĝš ~~~~~~~~]\"],\"editor.actionMenu.viewToggles.wallsBelowAndAboveShown\":[\"[Ŵáļļš ƀéļöŵ áñð áƀöṽé: šĥöŵñ ~~~~~~~]\"],\"editor.backToHome\":[\"[Ɓáçķ ţö ĥöɱé ~~~]\"],\"editor.buildPanel.advanced\":[\"[Åðṽáñçéð ~~~]\"],\"editor.buildPanel.rooms.exteriorWall.caption\":[\"[\",[\"lead\"],\" éṽéŕý ŵáļļ ţáķéš ţĥé éẋţéŕîöŕ ţýþé, ŵĥéŕéṽéŕ îţ ļáñðš. Éšç öŕ ðöûƀļé-çļîçķ éñðš ţĥé ŕûñ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.exteriorWall.label\":[\"[Éẋţéŕîöŕ ŵáļļ ~~~~]\"],\"editor.buildPanel.rooms.exteriorWall.title\":[\"[Éẋţéŕîöŕ ŵáļļš: éṽéŕý ŵáļļ ðŕáŵñ ţáķéš ţĥé éẋţéŕîöŕ ţýþé áñð çļáððîñĝ ~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.hint\":[\"[ŵáļļš · ƒļööŕ · çéîļîñĝ ~~~~~~]\"],\"editor.buildPanel.rooms.idle\":[\"[Þîçķ á šĥáþé ţö šţáŕţ. Éṽéŕý ŕööɱ ƀŕîñĝš îţš öŵñ ŵáļļš, ƒļööŕ áñð çéîļîñĝ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.interiorWall.caption\":[\"[\",[\"lead\"],\" éṽéŕý ŵáļļ ţáķéš ţĥé îñţéŕîöŕ ţýþé, ŵĥéŕéṽéŕ îţ ļáñðš. Éšç öŕ ðöûƀļé-çļîçķ éñðš ţĥé ŕûñ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.interiorWall.label\":[\"[Îñţéŕîöŕ ŵáļļ ~~~~]\"],\"editor.buildPanel.rooms.interiorWall.title\":[\"[Îñţéŕîöŕ ŵáļļš: éṽéŕý ŵáļļ ðŕáŵñ ţáķéš ţĥé îñţéŕîöŕ ţýþé áñð þáîñţ ~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.invisibleWall.caption\":[\"[\",[\"lead\"],\" éṽéŕý ļîñé îš áñ îñṽîšîƀļé ŵáļļ: ŕööɱš çļöšé öñ îţ, ñöţĥîñĝ îš ƀûîļţ. Çļöšéð öƒƒ öûţšîðé ţĥé ĥöûšé, á ŕööɱ ƀéçöɱéš á þöŕçĥ. Éšç öŕ ðöûƀļé-çļîçķ éñðš ţĥé ŕûñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.invisibleWall.label\":[\"[Îñṽîšîƀļé ŵáļļ ~~~~]\"],\"editor.buildPanel.rooms.invisibleWall.title\":[\"[Îñṽîšîƀļé ŵáļļš: ðîṽîðé á ŕööɱ öŕ çļöšé öƒƒ á þöŕçĥ ŵîţĥöûţ ƀûîļðîñĝ á ŵáļļ. Ŕööɱš çļöšé öñ ţĥéɱ; ţĥéý ñéṽéŕ çûţ á ŵáļļ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.polygon.caption\":[\"[\",[\"lead\"],\" ţĥéñ ţĥé ƒîŕšţ öñé áĝáîñ ţö çļöšé. Ƒöŕ Ļ-šĥáþéš áñð áñĝļéð ŕööɱš. ~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.polygon.label\":[\"[Þöļýĝöñ ~~~]\"],\"editor.buildPanel.rooms.polygon.lead\":[\"[Çļîçķ éáçĥ çöŕñéŕ, ~~~~~]\"],\"editor.buildPanel.rooms.polygon.title\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.buildPanel.rooms.railing.caption\":[\"[\",[\"lead\"],\" éṽéŕý ļîñé îš á ŕáîļîñĝ: á ĝûáŕð ŕööɱš çļöšé öñ, ŵîţĥ ñö šţûðš, šîðîñĝ öŕ ƒööţîñĝ. Þļáçé á ðööŕ öñ îţ ƒöŕ áñ öþéñîñĝ. Éšç öŕ ðöûƀļé-çļîçķ éñðš ţĥé ŕûñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.railing.label\":[\"[Ŕáîļîñĝ ~~~]\"],\"editor.buildPanel.rooms.railing.title\":[\"[Ŕáîļîñĝš: á 36 îñ ĝûáŕð ðŕáŵñ ļîķé ŵáļļš, þöšţš áţ ɱöšţ 6 ƒţ áþáŕţ. Ŕööɱš çļöšé öñ îţ áñð îţ ţáķéš öþéñîñĝš; šţýļé áñð ĥéîĝĥţ îñ ţĥé ŵáļļ þáñéļ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"[\",[\"lead\"],\" öñ ţĥé ĝŕîð. Ŵáļļš, ƒļööŕ áñð çéîļîñĝ áŕé çŕéáţéð ţöĝéţĥéŕ. ~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.rectangle.label\":[\"[Ŕéçţáñĝļé ~~~]\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"[Çļîçķ ţŵö çöŕñéŕš ~~~~~]\"],\"editor.buildPanel.rooms.rectangle.title\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.buildPanel.rooms.title\":[\"[Ŕööɱš ~~]\"],\"editor.buildPanel.rooms.walls.caption\":[\"[\",[\"lead\"],\" éṽéŕý ŵáļļ šţáŕţš ŵĥéŕé ţĥé ļášţ éñðéð. Éšç öŕ ðöûƀļé-çļîçķ éñðš ţĥé ŕûñ; çļöšé á ļööþ áñð îţ ƀéçöɱéš á ŕööɱ. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.rooms.walls.label\":[\"[Ŵáļļš ~~]\"],\"editor.buildPanel.rooms.walls.lead\":[\"[Çļîçķ éáçĥ çöŕñéŕ; ~~~~~]\"],\"editor.buildPanel.rooms.walls.title\":[\"[Ŵáļļš ~~]\"],\"editor.buildPanel.wallTypeControls.default\":[\"[Ðéƒáûļţ ~~~]\"],\"editor.buildPanel.wallTypeControls.deleteFromThisProject\":[\"[Ðéļéţé ƒŕöɱ ţĥîš þŕöĵéçţ ~~~~~~~]\"],\"editor.buildPanel.wallTypeControls.duplicateAsANewType\":[\"[Ðûþļîçáţé áš á ñéŵ ţýþé… ~~~~~~]\"],\"editor.buildPanel.wallTypeControls.editLayers\":[\"[Éðîţ ļáýéŕš… ~~~]\"],\"editor.buildPanel.wallTypeControls.editOrDuplicateLabel\":[\"[Éðîţ öŕ ðûþļîçáţé \",[\"label\"],\" ~~~~~]\"],\"editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType\":[\"[Éðîţ öŕ ðûþļîçáţé ţĥîš ŵáļļ ţýþé ~~~~~~~~~]\"],\"editor.buildPanel.wallTypeControls.newWallType\":[\"[Ñéŵ ŵáļļ ţýþé… ~~~~]\"],\"editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne\":[\"[Þŕéšéţš šţáý áš çîţéð. Ðûþļîçáţé öñé ţö çĥáñĝé îţš ļáýéŕš. ~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost\":[\"[Å ŵáļļ ţýþé ĥöļðš áţ ɱöšţ \",[\"limit\",\"plural\",{\"one\":[\"#\",\" ļáýéŕ\"],\"other\":[\"#\",\" ļáýéŕš\"]}],\". ~~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.add\":[\"[Åðð ~]\"],\"editor.buildPanel.wallTypeEditorDialog.airSpace\":[\"[Åîŕ šþáçé ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.brickVeneer\":[\"[Ɓŕîçķ ṽéñééŕ ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.drywall\":[\"[Ðŕýŵáļļ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.duplicateWallType\":[\"[Ðûþļîçáţé ŵáļļ ţýþé ~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.editWallType\":[\"[Éðîţ ŵáļļ ţýþé ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming\":[\"[Éṽéŕý ŵáļļ ţýþé ĥáš öñé ƒŕáɱîñĝ çöŕé ~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.fiberCement\":[\"[Ƒîƀéŕ çéɱéñţ ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.finish\":[\"[Ƒîñîšĥ ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.framingCore\":[\"[Ƒŕáɱîñĝ çöŕé ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother\":[\"[\",[\"fullReason\"],\" Ŕéɱöṽé öñé ţö áðð áñöţĥéŕ. ~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.gypsumSheathing\":[\"[Ĝýþšûɱ šĥéáţĥîñĝ ~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.inside\":[\"[Îñšîðé ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.insulation\":[\"[Îñšûļáţîöñ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.lapSiding\":[\"[Ļáþ šîðîñĝ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.layer\":[\"[Ļáýéŕ ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.layerRole\":[\"[Ļáýéŕ ŕöļé ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.lining\":[\"[Ļîñîñĝ ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside\":[\"[Ļîšţ ţĥé ļáýéŕš ƒŕöɱ ţĥé öûţšîðé îñ, áŕöûñð öñé ƒŕáɱîñĝ çöŕé. Ŵáļļš áļŕéáðý ðŕáŵñ ķééþ ţĥéîŕ ļáýéŕš; ŵáļļš ðŕáŵñ ƒŕöɱ ñöŵ öñ ûšé ţĥé šáṽéð ţýþé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA\":[\"[Ļîšţš ûñðéŕ Éẋţéŕîöŕ: îţ ĥáš á çļáððîñĝ šîðé. ~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch\":[\"[Ļîšţš ûñðéŕ Îñţéŕîöŕ: ƀöţĥ ƒáçéš ɱáţçĥ. ~~~~~~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.material\":[\"[Ṁáţéŕîáļ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard\":[\"[Ṁîñéŕáļ ŵööļ ƀöáŕð ~~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.moveIn\":[\"[Ṁöṽé îñ ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.moveOut\":[\"[Ṁöṽé öûţ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.name\":[\"[Ñáɱé ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.newWallType\":[\"[Ñéŵ ŵáļļ ţýþé ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.osb\":[\"[ÖŠƁ ~]\"],\"editor.buildPanel.wallTypeEditorDialog.outside\":[\"[Öûţšîðé ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.plaster\":[\"[Þļášţéŕ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.plywood\":[\"[Þļýŵööð ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.removeLayer\":[\"[Ŕéɱöṽé ļáýéŕ ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.rigidFoam\":[\"[Ŕîĝîð ƒöáɱ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.saveWallType\":[\"[Šáṽé ŵáļļ ţýþé ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.sheathing\":[\"[Šĥéáţĥîñĝ ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.stoneVeneer\":[\"[Šţöñé ṽéñééŕ ~~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.stucco\":[\"[Šţûççö ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"editor.buildPanel.wallTypeEditorDialog.totalThickness\":[\"[Ţöţáļ \",[\"thickness\"],\" ~~]\"],\"editor.buildPanel.wallTypeEditorDialog.wallType\":[\"[Ŵáļļ ţýþé ~~~]\"],\"editor.buildPanel.wallTypes.brick\":[\"[Ɓŕîçķ ~~]\"],\"editor.buildPanel.wallTypes.cladding\":[\"[Çļáððîñĝ ~~~]\"],\"editor.buildPanel.wallTypes.drawingExterior\":[\"[Ðŕáŵîñĝ: Éẋţéŕîöŕ ŵáļļš. ~~~~~~]\"],\"editor.buildPanel.wallTypes.drawingInterior\":[\"[Ðŕáŵîñĝ: Îñţéŕîöŕ ŵáļļš. ~~~~~~]\"],\"editor.buildPanel.wallTypes.drywallPaint\":[\"[Ðŕýŵáļļ þáîñţ ~~~~]\"],\"editor.buildPanel.wallTypes.exterior\":[\"[Éẋţéŕîöŕ ~~~]\"],\"editor.buildPanel.wallTypes.fiberCement\":[\"[Ƒîƀéŕ çéɱéñţ ~~~~]\"],\"editor.buildPanel.wallTypes.forcedExterior\":[\"[\",[\"drawing\"],\" Éṽéŕý ŵáļļ ţáķéš ţĥé éẋţéŕîöŕ ţýþé ûñţîļ ýöû þîçķ á šĥáþé öŕ ļéáṽé ţĥé ţööļ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypes.forcedInterior\":[\"[\",[\"drawing\"],\" Éṽéŕý ŵáļļ ţáķéš ţĥé îñţéŕîöŕ ţýþé ûñţîļ ýöû þîçķ á šĥáþé öŕ ļéáṽé ţĥé ţööļ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypes.hint\":[\"[Ñéŵ ŵáļļš ûšé ţĥé éẋţéŕîöŕ ţýþé áñð çļáððîñĝ; ŵáļļš ðŕáŵñ îñšîðé á ŕööɱ ûšé ţĥé îñţéŕîöŕ ţýþé. Éṽéŕý ðŕýŵáļļ ƒáçé ĝéţš ţĥé þáîñţ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.buildPanel.wallTypes.interior\":[\"[Îñţéŕîöŕ ~~~]\"],\"editor.buildPanel.wallTypes.plainWall\":[\"[Þļáîñ ŵáļļ ~~~]\"],\"editor.buildPanel.wallTypes.siding\":[\"[Šîðîñĝ ~~]\"],\"editor.buildPanel.wallTypes.stone\":[\"[Šţöñé ~~]\"],\"editor.buildPanel.wallTypes.stucco\":[\"[Šţûççö ~~]\"],\"editor.cameraControlsHint\":[\"[Çáɱéŕá çöñţŕöļš ĥîñţ ~~~~~~]\"],\"editor.cameraHint.pan\":[\"[Þáñ ~]\"],\"editor.cameraHint.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.cameraHint.zoom\":[\"[Žööɱ ~~]\"],\"editor.controls.finishSwatch.ariaLabel\":[[\"label\"],\": \",[\"finish\"]],\"editor.controls.finishSwatch.default\":[\"[Ðéƒáûļţ ~~~]\"],\"editor.dismiss\":[\"[Ðîšɱîšš ~~~]\"],\"editor.dismissCameraControlsHint\":[\"[Ðîšɱîšš çáɱéŕá çöñţŕöļš ĥîñţ ~~~~~~~~]\"],\"editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS\":[\"[Çļîçķ ţö éðîţ ţĥé ƒöûñðáţîöñ’š öûţļîñé ~~~~~~~~~~]\"],\"editor.expandSidebar\":[\"[Éẋþáñð šîðéƀáŕ ~~~~]\"],\"editor.floatingActionMenu.axis\":[\"[Åẋîš ~~]\"],\"editor.floatingActionMenu.axis2\":[\"[⌥ áẋîš ~~]\"],\"editor.floatingActionMenu.noEquipment\":[\"[⚠ ñö éǫûîþɱéñţ ~~~~]\"],\"editor.floatingActionMenu.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"editor.floatingActionMenu.registerCount\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" ŕéĝîšţéŕ\"],\"other\":[\"#\",\" ŕéĝîšţéŕš\"]}],\" ~~~~~~]\"],\"editor.floatingActionMenu.runCount\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕûñ\"],\"other\":[[\"number\"],\" ŕûñš\"]}],\" ~~~]\"],\"editor.floatingActionMenu.system\":[\"[Šýšţéɱ ~~]\"],\"editor.floatingLevelSelector.addAFloorUnderTheRoof\":[\"[Åðð á ƒļööŕ ûñðéŕ ţĥé ŕööƒ ~~~~~~~]\"],\"editor.floatingLevelSelector.addLevelAbove\":[\"[Åðð ļéṽéļ áƀöṽé ~~~~]\"],\"editor.floatingLevelSelector.addLevelBelow\":[\"[Åðð ļéṽéļ ƀéļöŵ ~~~~]\"],\"editor.floatingLevelSelector.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.floatingLevelSelector.delete\":[\"[Ðéļéţé ~~]\"],\"editor.floatingLevelSelector.deleteLevel\":[\"[Ðéļéţé ļéṽéļ ~~~~]\"],\"editor.floatingLevelSelector.deleteLevelConfirm\":[\"[Åŕé ýöû šûŕé ýöû ŵáñţ ţö ðéļéţé \",[\"levelName\"],\"? Åļļ ŵáļļš, ƒļööŕš, áñð öƀĵéçţš öñ ţĥîš ļéṽéļ ŵîļļ ƀé þéŕɱáñéñţļý ŕéɱöṽéð. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.dragToReorder\":[\"[Ðŕáĝ ţö ŕéöŕðéŕ ~~~~]\"],\"editor.floatingLevelSelector.duplicateLevel\":[\"[Ðûþļîçáţé ļéṽéļ ~~~~~]\"],\"editor.floatingLevelSelector.duplicateWithOptions\":[\"[Ðûþļîçáţé ŵîţĥ öþţîöñš... ~~~~~~]\"],\"editor.floatingLevelSelector.exitUnitFocus\":[\"[Éẋîţ ûñîţ ƒöçûš ~~~~]\"],\"editor.floatingLevelSelector.focusedUnitHasZonesHere\":[\"[Ƒöçûšéð ûñîţ ĥáš žöñéš ĥéŕé ~~~~~~~]\"],\"editor.floatingLevelSelector.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.floatingLevelSelector.foundationBadgeBasement\":[\"[Ɓášéɱéñţ ~~~]\"],\"editor.floatingLevelSelector.foundationBadgeBasementTitle\":[\"[Ɓášéɱéñţ ŵáļļš çûţ îñţö ţĥé ĝŕöûñð ~~~~~~~~~]\"],\"editor.floatingLevelSelector.foundationBadgeOff\":[\"[Öƒƒ ~]\"],\"editor.floatingLevelSelector.foundationBadgeOffTitle\":[\"[Ñö ƒöûñðáţîöñ ðŕáŵñ: çļîçķ ţö šéţ öñé ûþ ~~~~~~~~~~]\"],\"editor.floatingLevelSelector.foundationBadgePiles\":[\"[Þîļéš ~~]\"],\"editor.floatingLevelSelector.foundationBadgePilesTitle\":[\"[Ĝŕáðé ƀéáɱ öñ þîļéš ~~~~~]\"],\"editor.floatingLevelSelector.foundationBadgeRaised\":[\"[Ŕáîšéð ~~]\"],\"editor.floatingLevelSelector.foundationBadgeRaisedTitle\":[\"[Ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ ~~~~~~~]\"],\"editor.floatingLevelSelector.foundationBadgeSlab\":[\"[Šļáƀ ~~]\"],\"editor.floatingLevelSelector.foundationBadgeSlabTitle\":[\"[Šļáƀ öñ ĝŕáðé ~~~~]\"],\"editor.floatingLevelSelector.groundFloorOverGrade\":[\"[Ţĥé ĝŕöûñð ƒļööŕ šţáñðš \",[\"distance\"],\" öṽéŕ ĝŕáðé. \",[\"action\"],\" ~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundFloorUnderGrade\":[\"[Ţĥé ĝŕöûñð ƒļööŕ šţáñðš \",[\"distance\"],\" ûñðéŕ ĝŕáðé. \",[\"action\"],\" ~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundLineTheLevelsUnderIt\":[\"[Ĝŕöûñð ļîñé: ţĥé ļéṽéļš ûñðéŕ îţ áŕé ƀéļöŵ ĝŕáðé ~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.groundOffset\":[\"[Ĝŕöûñð · \",[\"offset\"],\" ~~]\"],\"editor.floatingLevelSelector.insertLevelHere\":[\"[Îñšéŕţ ļéṽéļ ĥéŕé ~~~~~]\"],\"editor.floatingLevelSelector.levelHeight\":[\"[Ļéṽéļ ĥéîĝĥţ ~~~~]\"],\"editor.floatingLevelSelector.pasteCopiedSelection\":[\"[Þášţé çöþîéð šéļéçţîöñ ~~~~~~]\"],\"editor.floatingLevelSelector.reorderLevelname\":[\"[Ŕéöŕðéŕ \",[\"levelName\"],\" ~~~]\"],\"editor.floatingLevelSelector.seatTheBuildingSoThisFloor\":[\"[Šéáţ ţĥé ƀûîļðîñĝ šö ţĥîš ƒļööŕ šţáñðš áţ ĝŕáðé; ţĥé ƒļööŕš ûñðéŕ îţ áŕé ƀéļöŵ ĝŕáðé ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.floatingLevelSelector.setAsGroundFloor\":[\"[Šéţ áš ĝŕöûñð ƒļööŕ ~~~~~]\"],\"editor.floatingLevelSelector.showTheFoundation\":[\"[Šĥöŵ ţĥé ƒöûñðáţîöñ ~~~~~~]\"],\"editor.floatingLevelSelector.theGroundLevelCannotBeDeleted\":[\"[Ţĥé ĝŕöûñð ļéṽéļ çáññöţ ƀé ðéļéţéð ~~~~~~~~~]\"],\"editor.floatingLevelSelector.thisFloorStandsAtGrade\":[\"[Ţĥîš ƒļööŕ šţáñðš áţ ĝŕáðé ~~~~~~~]\"],\"editor.floatingLevelSelector.unit\":[\"[Ûñîţ ~~]\"],\"editor.floorFootprint.area\":[\"[Ƒļööŕ áŕéá ~~~]\"],\"editor.floorFootprint.room\":[\"[\",[\"room\"],\" ƒļööŕ ~~]\"],\"editor.floorFootprint.shared\":[\"[Šĥáŕéð ƒļööŕ ~~~~]\"],\"editor.helpers.buildingHelper.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.buildingHelper.placeBuilding\":[\"[Þļáçé ƀûîļðîñĝ ~~~~]\"],\"editor.helpers.buildingHelper.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.helpers.contextualHelperPanel.cancelPlacement\":[\"[Çáñçéļ þļáçéɱéñţ ~~~~~]\"],\"editor.helpers.contextualHelperPanel.continuationClickOrPressCTo\":[\"[Çöñţîñûáţîöñ — çļîçķ öŕ þŕéšš Ç ţö çýçļé ~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.continuationLabel\":[\"[Çöñţîñûáţîöñ: \",[\"label\"],\" ~~~~]\"],\"editor.helpers.contextualHelperPanel.dragToDrawFence\":[\"[Ðŕáĝ ţö ðŕáŵ ƒéñçé ~~~~~]\"],\"editor.helpers.contextualHelperPanel.eraseScope\":[\"[Éŕášé: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift\":[\"[Éŕášé šçöþé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.eraseScopeLabel\":[\"[Éŕášé šçöþé: \",[\"scope\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT\":[\"[Ƒéñçé ţýþé — çļîçķ öŕ þŕéšš Ţ ţö šŵîţçĥ ƀéţŵééñ šţŕáîĝĥţ, çûŕṽéð áñð ƒŕééĥáñð ~~~~~~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.fewerShortcuts\":[\"[Ƒéŵéŕ šĥöŕţçûţš ~~~~~]\"],\"editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick\":[\"[Ƒîñîšĥ çûŕṽé (öŕ ðöûƀļé-çļîçķ) ~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.gridLength\":[\"[Ĝŕîð: \",[\"length\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl\":[\"[Ĝŕîð šţéþ — çļîçķ öŕ ţáþ Çţŕļ ţö çýçļé ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.gridStepLength\":[\"[Ĝŕîð šţéþ: \",[\"length\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase\":[\"[Ĥöṽéŕ á þáîñţéð šûŕƒáçé ţö éŕášé ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPaint\":[\"[Ĥöṽéŕ á šûŕƒáçé ţö þáîñţ ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts\":[\"[Ĥöṽéŕ á šûŕƒáçé ţö þîçķ îţš ɱáţéŕîáļ ~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.moreShortcuts\":[\"[Ṁöŕé šĥöŕţçûţš ~~~~]\"],\"editor.helpers.contextualHelperPanel.paintScope\":[\"[Þáîñţ: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift\":[\"[Þáîñţ šçöþé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.paintScopeLabel\":[\"[Þáîñţ šçöþé: \",[\"scope\"],\" ~~~]\"],\"editor.helpers.contextualHelperPanel.pickScope\":[\"[Þîçķ: \",[\"scope\"],\" ~~]\"],\"editor.helpers.contextualHelperPanel.placeGateOnAFence\":[\"[Þļáçé ĝáţé öñ á ƒéñçé ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.placePassageOnAFence\":[\"[Þļáçé þáššáĝé öñ á ƒéñçé ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.selectAMaterialToPaint\":[\"[Šéļéçţ á ɱáţéŕîáļ ţö þáîñţ ~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingAngles\":[\"[Šñáþþîñĝ: Åñĝļéš ~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingGrid\":[\"[Šñáþþîñĝ: Ĝŕîð ~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingLines\":[\"[Šñáþþîñĝ: Ļîñéš ~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift\":[\"[Šñáþþîñĝ ɱöðé — çļîçķ öŕ þŕéšš Šĥîƒţ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.snappingOff\":[\"[Šñáþþîñĝ: Öƒƒ ~~~~]\"],\"editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved\":[\"[Šţŕáîĝĥţ çöñţîñûáţîöñ îš ûñáṽáîļáƀļé ƒöŕ çûŕṽéð öŕ ƒŕééĥáñð ƒéñçéš ~~~~~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightContinuous\":[\"[Šţŕáîĝĥţ: Çöñţîñûöûš ~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress\":[\"[Šţŕáîĝĥţ ƒéñçé çöñţîñûáţîöñ — çļîçķ öŕ þŕéšš Ç ţö ţöĝĝļé ~~~~~~~~~~~~~~]\"],\"editor.helpers.contextualHelperPanel.straightSingle\":[\"[Šţŕáîĝĥţ: Šîñĝļé ~~~~~]\"],\"editor.helpers.helperManager.addPoint\":[\"[Åðð þöîñţ ~~~]\"],\"editor.helpers.helperManager.addPointClickTheFirstTo\":[\"[Åðð þöîñţ · çļîçķ ţĥé ƒîŕšţ ţö çļöšé ~~~~~~~~~]\"],\"editor.helpers.helperManager.brushSize\":[\"[Ɓŕûšĥ šîžé ~~~]\"],\"editor.helpers.helperManager.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.helperManager.cancelPicking\":[\"[Çáñçéļ þîçķîñĝ ~~~~]\"],\"editor.helpers.helperManager.cancelStroke\":[\"[Çáñçéļ šţŕöķé ~~~~]\"],\"editor.helpers.helperManager.done\":[\"[Ðöñé ~~]\"],\"editor.helpers.helperManager.drawTheMezzanine\":[\"[Ðŕáŵ ţĥé ɱéžžáñîñé ~~~~~]\"],\"editor.helpers.helperManager.drawTheOpening\":[\"[Ðŕáŵ ţĥé öþéñîñĝ ~~~~~]\"],\"editor.helpers.helperManager.drawTheTerrace\":[\"[Ðŕáŵ ţĥé ţéŕŕáçé ~~~~~]\"],\"editor.helpers.helperManager.exitSmartMeasure\":[\"[Éẋîţ šɱáŕţ ɱéášûŕé ~~~~~]\"],\"editor.helpers.helperManager.finish\":[\"[Ƒîñîšĥ ~~]\"],\"editor.helpers.helperManager.finishAtTheEdge\":[\"[Ƒîñîšĥ áţ ţĥé éðĝé ~~~~~]\"],\"editor.helpers.helperManager.freeMove\":[\"[Ƒŕéé ɱöṽé ~~~]\"],\"editor.helpers.helperManager.freeMoveSlideDoorsAndWindows\":[\"[Ƒŕéé ɱöṽé · šļîðé ðööŕš áñð ŵîñðöŵš ~~~~~~~~~]\"],\"editor.helpers.helperManager.holdToPickAMaterial\":[\"[Ĥöļð ţö þîçķ á ɱáţéŕîáļ ~~~~~~]\"],\"editor.helpers.helperManager.inspectSurfaceDimensions\":[\"[Îñšþéçţ šûŕƒáçé ðîɱéñšîöñš ~~~~~~~~]\"],\"editor.helpers.helperManager.paintWithThisMaterial\":[\"[Þáîñţ ŵîţĥ ţĥîš ɱáţéŕîáļ ~~~~~~~]\"],\"editor.helpers.helperManager.pickTargetHeight\":[\"[Þîçķ ţáŕĝéţ ĥéîĝĥţ ~~~~~]\"],\"editor.helpers.helperManager.pinMeasurementLens\":[\"[Þîñ ɱéášûŕéɱéñţ ļéñš ~~~~~~]\"],\"editor.helpers.helperManager.place\":[\"[Þļáçé ~~]\"],\"editor.helpers.helperManager.placeInsideTheRoom\":[\"[Þļáçé îñšîðé ţĥé ŕööɱ ~~~~~~]\"],\"editor.helpers.helperManager.pressAnywhereDragToTheOpposite\":[\"[Þŕéšš áñýŵĥéŕé, ðŕáĝ ţö ţĥé öþþöšîţé çöŕñéŕ ~~~~~~~~~~~]\"],\"editor.helpers.helperManager.raiseOrLower\":[\"[Ŕáîšé öŕ ļöŵéŕ ~~~~]\"],\"editor.helpers.helperManager.removeAPaintedPartOrReset\":[\"[Ŕéɱöṽé á þáîñţéð þáŕţ, öŕ ŕéšéţ á šûŕƒáçé ~~~~~~~~~~]\"],\"editor.helpers.helperManager.removeLastPoint\":[\"[Ŕéɱöṽé ļášţ þöîñţ ~~~~~]\"],\"editor.helpers.helperManager.resize\":[\"[Ŕéšîžé ~~]\"],\"editor.helpers.helperManager.rotate45\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.helpers.helperManager.rotateTheSelection45\":[\"[Ŕöţáţé ţĥé šéļéçţîöñ ±45° ~~~~~~]\"],\"editor.helpers.helperManager.setOneCornerThenTheOpposite\":[\"[Šéţ öñé çöŕñéŕ, ţĥéñ ţĥé öþþöšîţé ~~~~~~~~~]\"],\"editor.helpers.itemHelper.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.helpers.itemHelper.forcePlace\":[\"[Ƒöŕçé þļáçé ~~~]\"],\"editor.helpers.itemHelper.place\":[\"[Þļáçé ~~]\"],\"editor.helpers.itemHelper.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.helpers.registeredToolHelper.guidedConstraintsBypassed\":[\"[Ĝûîðéð çöñšţŕáîñţš ƀýþáššéð ~~~~~~~~]\"],\"editor.inspector.field.angle.g10zaw\":[\"[Åñĝļé ~~]\"],\"editor.inspector.field.armLengthM.1jclqgw\":[\"[Åŕɱ ļéñĝţĥ ɱ ~~~]\"],\"editor.inspector.field.attachmentSide.6n9s5v\":[\"[Åţţáçĥɱéñţ šîðé ~~~~~]\"],\"editor.inspector.field.autoHangers.10554ka\":[\"[Åûţö ĥáñĝéŕš ~~~~]\"],\"editor.inspector.field.autoMiterCorners.zxr5lt\":[\"[Åûţö ɱîţéŕ çöŕñéŕš ~~~~~]\"],\"editor.inspector.field.autoSpan.1gas9r6\":[\"[Åûţö šþáñ ~~~]\"],\"editor.inspector.field.backRatio.1ughd99\":[\"[Ɓáçķ ŕáţîö ~~~]\"],\"editor.inspector.field.baseHeight.1hs4zdf\":[\"[Ɓášé ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.baseStyle.24vty5\":[\"[Ɓášé šţýļé ~~~]\"],\"editor.inspector.field.beamHeight.1kq0oh1\":[\"[Ɓéáɱ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.beamWidth.1q1mzbe\":[\"[Ɓéáɱ ŵîðţĥ ~~~]\"],\"editor.inspector.field.bodyShape.arvkuw\":[\"[Ɓöðý šĥáþé ~~~]\"],\"editor.inspector.field.bracketStyle.1vvoptm\":[\"[Ɓŕáçķéţ šţýļé ~~~~]\"],\"editor.inspector.field.branchAngle.v56zme\":[\"[Ɓŕáñçĥ áñĝļé ~~~~]\"],\"editor.inspector.field.cabHeight.1yn34mk\":[\"[Çáƀ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.canopyForm.1czzggp\":[\"[Çáñöþý ƒöŕɱ ~~~]\"],\"editor.inspector.field.cap.s0thej\":[\"[Çáþ ~]\"],\"editor.inspector.field.capOverhang.kxxz9j\":[\"[Çáþ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.capShape.17mbxhu\":[\"[Çáþ šĥáþé ~~~]\"],\"editor.inspector.field.capThickness.u835q9\":[\"[Çáþ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.carcassHeight.1aks0zy\":[\"[Çáŕçášš ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.cleanoutStyle.127zz6z\":[\"[Çļéáñöûţ šţýļé ~~~~]\"],\"editor.inspector.field.collarDiameter.1d5k28r\":[\"[Çöļļáŕ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.collarHeight.1dqwsrf\":[\"[Çöļļáŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.collarShape.1gacv11\":[\"[Çöļļáŕ šĥáþé ~~~~]\"],\"editor.inspector.field.collarWidth.qvc9ik\":[\"[Çöļļáŕ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.columns.1sy73ks\":[\"[Çöļûɱñš ~~~]\"],\"editor.inspector.field.conicalFullCircle.1pgkpz5\":[\"[Çöñîçáļ ƒûļļ çîŕçļé ~~~~~~]\"],\"editor.inspector.field.connectionMode.1nty478\":[\"[Çöññéçţîöñ ɱöðé ~~~~~]\"],\"editor.inspector.field.connectionOffset.2m70s8\":[\"[Çöññéçţîöñ öƒƒšéţ ~~~~~]\"],\"editor.inspector.field.cornerBevel.16iniho\":[\"[Çöŕñéŕ ƀéṽéļ ~~~~]\"],\"editor.inspector.field.coveringType.95yd3k\":[\"[Çöṽéŕîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.cricketHeight.pok31n\":[\"[Çŕîçķéţ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.cricketLength.i2aht8\":[\"[Çŕîçķéţ ļéñĝţĥ ~~~~]\"],\"editor.inspector.field.cricketSide.1xnwil7\":[\"[Çŕîçķéţ šîðé ~~~~]\"],\"editor.inspector.field.cricketStyle.10j0lat\":[\"[Çŕîçķéţ šţýļé ~~~~]\"],\"editor.inspector.field.curb.5xi2ol\":[\"[Çûŕƀ ~~]\"],\"editor.inspector.field.curbHeight.1yn97sg\":[\"[Çûŕƀ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.curveOffset.l4xhvr\":[\"[Çûŕṽé öƒƒšéţ ~~~~]\"],\"editor.inspector.field.damperAngle.h5fxn7\":[\"[Ðáɱþéŕ áñĝļé ~~~~]\"],\"editor.inspector.field.deckThickness.4jmqze\":[\"[Ðéçķ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.defaultLevelId.xmrjzp\":[\"[Ðéƒáûļţ ļéṽéļ îð ~~~~~]\"],\"editor.inspector.field.depth.1lu11u2\":[\"[Ðéþţĥ ~~]\"],\"editor.inspector.field.diameter.s3robe\":[\"[Ðîáɱéţéŕ ~~~]\"],\"editor.inspector.field.diameter2.pm6cl4\":[\"[Ðîáɱéţéŕ2 ~~~]\"],\"editor.inspector.field.doorDurationMs.13nx909\":[\"[Ðööŕ ðûŕáţîöñ ɱš ~~~~~]\"],\"editor.inspector.field.doorHeight.92r9k4\":[\"[Ðööŕ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.doorPanelStyle.2qog4q\":[\"[Ðööŕ þáñéļ šţýļé ~~~~~]\"],\"editor.inspector.field.doorStyle.gerpos\":[\"[Ðööŕ šţýļé ~~~]\"],\"editor.inspector.field.doorWidth.sy732l\":[\"[Ðööŕ ŵîðţĥ ~~~]\"],\"editor.inspector.field.downspoutEnabled.1gn00nv\":[\"[Ðöŵñšþöûţ éñáƀļéð ~~~~~]\"],\"editor.inspector.field.downspoutPosition.fxcsop\":[\"[Ðöŵñšþöûţ þöšîţîöñ ~~~~~~]\"],\"editor.inspector.field.ductMaterial.i8eonq\":[\"[Ðûçţ ɱáţéŕîáļ ~~~~]\"],\"editor.inspector.field.dutchGabletRake.6paydp\":[\"[Ðûţçĥ ĝáƀļéţ ŕáķé ~~~~~]\"],\"editor.inspector.field.dutchHipHeightRatio.1etnwgg\":[\"[Ðûţçĥ ĥîþ ĥéîĝĥţ ŕáţîö ~~~~~~]\"],\"editor.inspector.field.dutchHipWidthRatio.1dopfvb\":[\"[Ðûţçĥ ĥîþ ŵîðţĥ ŕáţîö ~~~~~~]\"],\"editor.inspector.field.dutchTopRakeThickness.1w9khxt\":[\"[Ðûţçĥ ţöþ ŕáķé ţĥîçķñéšš ~~~~~~~]\"],\"editor.inspector.field.dutchWaistLengthRatio.agtoxw\":[\"[Ðûţçĥ ŵáîšţ ļéñĝţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.dwellMs.1lkk0o5\":[\"[Ðŵéļļ ɱš ~~~]\"],\"editor.inspector.field.edgeInset.1obeimx\":[\"[Éðĝé îñšéţ ~~~]\"],\"editor.inspector.field.elevation.4jbp44\":[\"[Éļéṽáţîöñ ~~~]\"],\"editor.inspector.field.endCapLeft.19jel4v\":[\"[Éñð çáþ ļéƒţ ~~~]\"],\"editor.inspector.field.endCapRight.lhse9c\":[\"[Éñð çáþ ŕîĝĥţ ~~~~]\"],\"editor.inspector.field.endCaps.4fsb5b\":[\"[Éñð çáþš ~~~]\"],\"editor.inspector.field.endPost.1guwsk0\":[\"[Éñð þöšţ ~~~]\"],\"editor.inspector.field.equipmentType.1mc2ywt\":[\"[Éǫûîþɱéñţ ţýþé ~~~~]\"],\"editor.inspector.field.fillToFloor.1oppnkf\":[\"[Ƒîļļ ţö ƒļööŕ ~~~~]\"],\"editor.inspector.field.finial.1ertf4s\":[\"[Ƒîñîáļ ~~]\"],\"editor.inspector.field.fittingType.18mz17s\":[\"[Ƒîţţîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.flashingHeight.3e8nn6\":[\"[Ƒļášĥîñĝ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.flashingProjection.hhf0fk\":[\"[Ƒļášĥîñĝ þŕöĵéçţîöñ ~~~~~~]\"],\"editor.inspector.field.flueCount.j3t2ci\":[\"[Ƒļûé çöûñţ ~~~]\"],\"editor.inspector.field.flueDiameter.cjhubg\":[\"[Ƒļûé ðîáɱéţéŕ ~~~~]\"],\"editor.inspector.field.flueHeight.1dhc4y4\":[\"[Ƒļûé ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.flueShape.1ealiy4\":[\"[Ƒļûé šĥáþé ~~~]\"],\"editor.inspector.field.flueSpacing.exhzco\":[\"[Ƒļûé šþáçîñĝ ~~~~]\"],\"editor.inspector.field.footingStyle.e3btg6\":[\"[Ƒööţîñĝ šţýļé ~~~~]\"],\"editor.inspector.field.frameDepth.1934a5n\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"editor.inspector.field.frameThickness.1agxzd8\":[\"[Ƒŕáɱé ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.framingStrategy.3jx9qe\":[\"[Ƒŕáɱîñĝ šţŕáţéĝý ~~~~~]\"],\"editor.inspector.field.fromLevelId.15ieejs\":[\"[Ƒŕöɱ ļéṽéļ îð ~~~~]\"],\"editor.inspector.field.gableOverhang.2z0obw\":[\"[Ĝáƀļé öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.gambrelLowerHeightRatio.wqq7lg\":[\"[Ĝáɱƀŕéļ ļöŵéŕ ĥéîĝĥţ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.gambrelLowerWidthRatio.uvyb1v\":[\"[Ĝáɱƀŕéļ ļöŵéŕ ŵîðţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.gapX.wjxa7\":[\"[Ĝáþ ẋ ~~]\"],\"editor.inspector.field.gapY.mkbl8\":[\"[Ĝáþ ý ~~]\"],\"editor.inspector.field.glassThickness.s97b0x\":[\"[Ĝļášš ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.groundClearance.4os7f0\":[\"[Ĝŕöûñð çļéáŕáñçé ~~~~~]\"],\"editor.inspector.field.guardInfill.16heiig\":[\"[Ĝûáŕð îñƒîļļ ~~~~]\"],\"editor.inspector.field.gutterEnabled.1oxiltf\":[\"[Ĝûţţéŕ éñáƀļéð ~~~~]\"],\"editor.inspector.field.gutterProfile.1ngzqi3\":[\"[Ĝûţţéŕ þŕöƒîļé ~~~~]\"],\"editor.inspector.field.gutterSize.jgavtz\":[\"[Ĝûţţéŕ šîžé ~~~]\"],\"editor.inspector.field.hangerMaxReach.1869epv\":[\"[Ĥáñĝéŕ ɱáẋ ŕéáçĥ ~~~~~]\"],\"editor.inspector.field.hangerSpacing.i2b5d7\":[\"[Ĥáñĝéŕ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.hangerStyle.l7v7gb\":[\"[Ĥáñĝéŕ šţýļé ~~~~]\"],\"editor.inspector.field.height.7puwki\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.inspector.field.height2.7b3jlc\":[\"[Ĥéîĝĥţ2 ~~]\"],\"editor.inspector.field.heightAboveRidge.1k99brk\":[\"[Ĥéîĝĥţ áƀöṽé ŕîðĝé ~~~~~]\"],\"editor.inspector.field.highEdgeHeight.1s7v90t\":[\"[Ĥîĝĥ éðĝé ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.highOverhang.67w55d\":[\"[Ĥîĝĥ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.highSideMode.1rc1r5j\":[\"[Ĥîĝĥ šîðé ɱöðé ~~~~]\"],\"editor.inspector.field.hoodOverhang.1q1hcin\":[\"[Ĥööð öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.infillPlacement.11dxzpo\":[\"[Îñƒîļļ þļáçéɱéñţ ~~~~~]\"],\"editor.inspector.field.inletShape.ypayh2\":[\"[Îñļéţ šĥáþé ~~~]\"],\"editor.inspector.field.innerRadius.1fkmg0j\":[\"[Îññéŕ ŕáðîûš ~~~~]\"],\"editor.inspector.field.insulated.iwziey\":[\"[Îñšûļáţéð ~~~]\"],\"editor.inspector.field.insulationR.663ufx\":[\"[Îñšûļáţîöñ ŕ ~~~~]\"],\"editor.inspector.field.lanternHeight.oz7zpm\":[\"[Ļáñţéŕñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.lanternTopScale.hkmpp2\":[\"[Ļáñţéŕñ ţöþ šçáļé ~~~~~]\"],\"editor.inspector.field.ledgerDepth.7av8k9\":[\"[Ļéðĝéŕ ðéþţĥ ~~~~]\"],\"editor.inspector.field.ledgerHeight.zo2oif\":[\"[Ļéðĝéŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.ledgerVerticalOffset.1athbgt\":[\"[Ļéðĝéŕ ṽéŕţîçáļ öƒƒšéţ ~~~~~~]\"],\"editor.inspector.field.leftEndCondition.1syqlaw\":[\"[Ļéƒţ éñð çöñðîţîöñ ~~~~~]\"],\"editor.inspector.field.leftOverhang.1ydl9ma\":[\"[Ļéƒţ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.length.1kpdcfp\":[\"[Ļéñĝţĥ ~~]\"],\"editor.inspector.field.liquidDiameter.w3pk7c\":[\"[Ļîǫûîð ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.louverCount.lhw2gr\":[\"[Ļöûṽéŕ çöûñţ ~~~~]\"],\"editor.inspector.field.lowBeamInset.1g7yw5l\":[\"[Ļöŵ ƀéáɱ îñšéţ ~~~~]\"],\"editor.inspector.field.lowEdgeHeight.47mae1\":[\"[Ļöŵ éðĝé ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.lowOverhang.t4nz1h\":[\"[Ļöŵ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.mansardSteepHeightRatio.1hbb8rq\":[\"[Ṁáñšáŕð šţééþ ĥéîĝĥţ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.mansardSteepWidthRatio.6xk6vd\":[\"[Ṁáñšáŕð šţééþ ŵîðţĥ ŕáţîö ~~~~~~~]\"],\"editor.inspector.field.matchHostRoofMaterial.s56zwj\":[\"[Ṁáţçĥ ĥöšţ ŕööƒ ɱáţéŕîáļ ~~~~~~~]\"],\"editor.inspector.field.matchHostRoofStructure.bojht3\":[\"[Ṁáţçĥ ĥöšţ ŕööƒ šţŕûçţûŕé ~~~~~~~]\"],\"editor.inspector.field.mount.h80tay\":[\"[Ṁöûñţ ~~]\"],\"editor.inspector.field.mountingType.1xvin1e\":[\"[Ṁöûñţîñĝ ţýþé ~~~~]\"],\"editor.inspector.field.neckHeight.19ym8n1\":[\"[Ñéçķ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.opacity.1pmpfd2\":[\"[Öþáçîţý ~~~]\"],\"editor.inspector.field.openingAngle.brhi7c\":[\"[Öþéñîñĝ áñĝļé ~~~~]\"],\"editor.inspector.field.openingOffset.wpoqt6\":[\"[Öþéñîñĝ öƒƒšéţ ~~~~]\"],\"editor.inspector.field.openingSide.18l2iwe\":[\"[Öþéñîñĝ šîðé ~~~~]\"],\"editor.inspector.field.operationState.1lwrz07\":[\"[Öþéŕáţîöñ šţáţé ~~~~~]\"],\"editor.inspector.field.outletShape.lgpqhx\":[\"[Öûţļéţ šĥáþé ~~~~]\"],\"editor.inspector.field.overhang.b5ljpv\":[\"[Öṽéŕĥáñĝ ~~~]\"],\"editor.inspector.field.panelHeight.1kf31vk\":[\"[Þáñéļ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.panelWidth.946581\":[\"[Þáñéļ ŵîðţĥ ~~~]\"],\"editor.inspector.field.patternAlignment.jd1ht0\":[\"[Þáţţéŕñ áļîĝñɱéñţ ~~~~~]\"],\"editor.inspector.field.patternCount.1r37pve\":[\"[Þáţţéŕñ çöûñţ ~~~~]\"],\"editor.inspector.field.patternDistribution.fmam51\":[\"[Þáţţéŕñ ðîšţŕîƀûţîöñ ~~~~~~]\"],\"editor.inspector.field.patternRemainder.i47p5y\":[\"[Þáţţéŕñ ŕéɱáîñðéŕ ~~~~~]\"],\"editor.inspector.field.picketProfile.1xgzuyc\":[\"[Þîçķéţ þŕöƒîļé ~~~~]\"],\"editor.inspector.field.picketRailCount.atgt6w\":[\"[Þîçķéţ ŕáîļ çöûñţ ~~~~~]\"],\"editor.inspector.field.picketSpacing.p8d98w\":[\"[Þîçķéţ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.picketTop.1tskplu\":[\"[Þîçķéţ ţöþ ~~~]\"],\"editor.inspector.field.picketTopClearance.16eq1k0\":[\"[Þîçķéţ ţöþ çļéáŕáñçé ~~~~~~]\"],\"editor.inspector.field.picketVariation.f7vl1c\":[\"[Þîçķéţ ṽáŕîáţîöñ ~~~~~]\"],\"editor.inspector.field.picketWidth.3fr3rh\":[\"[Þîçķéţ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.pipeMaterial.1ggc2wy\":[\"[Þîþé ɱáţéŕîáļ ~~~~]\"],\"editor.inspector.field.pitch.xe6ekx\":[\"[Þîţçĥ ~~]\"],\"editor.inspector.field.position.1quewx6\":[\"[Þöšîţîöñ ~~~]\"],\"editor.inspector.field.postBracing.crq8nn\":[\"[Þöšţ ƀŕáçîñĝ ~~~~]\"],\"editor.inspector.field.postCap.1xcvxrv\":[\"[Þöšţ çáþ ~~~]\"],\"editor.inspector.field.postCount.22r56c\":[\"[Þöšţ çöûñţ ~~~]\"],\"editor.inspector.field.postDepth.1ko6j1m\":[\"[Þöšţ ðéþţĥ ~~~]\"],\"editor.inspector.field.postInset.le36li\":[\"[Þöšţ îñšéţ ~~~]\"],\"editor.inspector.field.postLayoutMode.16jre62\":[\"[Þöšţ ļáýöûţ ɱöðé ~~~~~]\"],\"editor.inspector.field.postSize.1jodxek\":[\"[Þöšţ šîžé ~~~]\"],\"editor.inspector.field.postSpacing.voppfi\":[\"[Þöšţ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.postThrough.1abgu4a\":[\"[Þöšţ ţĥŕöûĝĥ ~~~~]\"],\"editor.inspector.field.postWidth.rk26fz\":[\"[Þöšţ ŵîðţĥ ~~~]\"],\"editor.inspector.field.profile.1kwlci6\":[\"[Þŕöƒîļé ~~~]\"],\"editor.inspector.field.projection.jhtig0\":[\"[Þŕöĵéçţîöñ ~~~]\"],\"editor.inspector.field.purlinHeight.eo9cjm\":[\"[Þûŕļîñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.purlinSpacing.4gbzb2\":[\"[Þûŕļîñ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.purlinWidth.r6tu3j\":[\"[Þûŕļîñ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.rafterEndInset.14c0wy5\":[\"[Ŕáƒţéŕ éñð îñšéţ ~~~~~]\"],\"editor.inspector.field.rafterHeight.1e1n99w\":[\"[Ŕáƒţéŕ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.rafterSpacing.19subc0\":[\"[Ŕáƒţéŕ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.rafterWidth.eksj4t\":[\"[Ŕáƒţéŕ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.railingHeight.6wf4bm\":[\"[Ŕáîļîñĝ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.railingMode.ov7xpu\":[\"[Ŕáîļîñĝ ɱöðé ~~~~]\"],\"editor.inspector.field.railingStyle.1nyd2u\":[\"[Ŕáîļîñĝ šţýļé ~~~~]\"],\"editor.inspector.field.resizeLock.b2hbr0\":[\"[Ŕéšîžé ļöçķ ~~~]\"],\"editor.inspector.field.returnDiameter.166ozhe\":[\"[Ŕéţûŕñ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.returnHeight.b9sq6y\":[\"[Ŕéţûŕñ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.returnShape.1e3a27i\":[\"[Ŕéţûŕñ šĥáþé ~~~~]\"],\"editor.inspector.field.returnWidth.z9r3h3\":[\"[Ŕéţûŕñ ŵîðţĥ ~~~~]\"],\"editor.inspector.field.rightEndCondition.tjlw3l\":[\"[Ŕîĝĥţ éñð çöñðîţîöñ ~~~~~~]\"],\"editor.inspector.field.rightOverhang.yqbeft\":[\"[Ŕîĝĥţ öṽéŕĥáñĝ ~~~~]\"],\"editor.inspector.field.roofHeight.1uhy6qy\":[\"[Ŕööƒ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.roofStyle.pawqfy\":[\"[Ŕööƒ šţýļé ~~~]\"],\"editor.inspector.field.roofThickness.1xlr3rx\":[\"[Ŕööƒ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.roofType.1m9kv5h\":[\"[Ŕööƒ ţýþé ~~~]\"],\"editor.inspector.field.rotation.ly2rj\":[\"[Ŕöţáţîöñ ~~~]\"],\"editor.inspector.field.rows.1ba1neg\":[\"[Ŕöŵš ~~]\"],\"editor.inspector.field.scale.b6rpxt\":[\"[Šçáļé ~~]\"],\"editor.inspector.field.seamDetail.197za48\":[\"[Šéáɱ ðéţáîļ ~~~]\"],\"editor.inspector.field.segmentType.l667sy\":[\"[Šéĝɱéñţ ţýþé ~~~~]\"],\"editor.inspector.field.shaftStyle.1i5ampa\":[\"[Šĥáƒţ šţýļé ~~~]\"],\"editor.inspector.field.shaftWallThickness.bduslr\":[\"[Šĥáƒţ ŵáļļ ţĥîçķñéšš ~~~~~~]\"],\"editor.inspector.field.shape.iq9t52\":[\"[Šĥáþé ~~]\"],\"editor.inspector.field.shape2.fi0nx8\":[\"[Šĥáþé2 ~~]\"],\"editor.inspector.field.shedHighSide.bzzddg\":[\"[Šĥéð ĥîĝĥ šîðé ~~~~]\"],\"editor.inspector.field.shingleThickness.1hn5wxr\":[\"[Šĥîñĝļé ţĥîçķñéšš ~~~~~]\"],\"editor.inspector.field.shoulderExtent.11j8uiv\":[\"[Šĥöûļðéŕ éẋţéñţ ~~~~~]\"],\"editor.inspector.field.shoulderHeight.xyqn6g\":[\"[Šĥöûļðéŕ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.shoulderStyle.1hkcv2w\":[\"[Šĥöûļðéŕ šţýļé ~~~~]\"],\"editor.inspector.field.showCenterColumn.1uggg45\":[\"[Šĥöŵ çéñţéŕ çöļûɱñ ~~~~~]\"],\"editor.inspector.field.showInfill.33rv7u\":[\"[Šĥöŵ îñƒîļļ ~~~]\"],\"editor.inspector.field.showStepSupports.1kg8h8m\":[\"[Šĥöŵ šţéþ šûþþöŕţš ~~~~~]\"],\"editor.inspector.field.sideFlashing.cv744o\":[\"[Šîðé ƒļášĥîñĝ ~~~~]\"],\"editor.inspector.field.size.1a4x3zw\":[\"[Šîžé ~~]\"],\"editor.inspector.field.skylightType.1a5vj3i\":[\"[Šķýļîĝĥţ ţýþé ~~~~]\"],\"editor.inspector.field.slabOpeningMode.g4f730\":[\"[Šļáƀ öþéñîñĝ ɱöðé ~~~~~]\"],\"editor.inspector.field.slatGap.slmk93\":[\"[Šļáţ ĝáþ ~~~]\"],\"editor.inspector.field.slideDirection.1527q13\":[\"[Šļîðé ðîŕéçţîöñ ~~~~~]\"],\"editor.inspector.field.span.1bmmuw1\":[\"[Šþáñ ~~]\"],\"editor.inspector.field.speed.2w2xwg\":[\"[Šþééð ~~]\"],\"editor.inspector.field.spinSpeed.1ozx746\":[\"[Šþîñ šþééð ~~~]\"],\"editor.inspector.field.stairType.1u05e02\":[\"[Šţáîŕ ţýþé ~~~]\"],\"editor.inspector.field.standoff.4wv2g0\":[\"[Šţáñðöƒƒ ~~~]\"],\"editor.inspector.field.standoffHeight.1qdnff\":[\"[Šţáñðöƒƒ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.startPost.1ood9kv\":[\"[Šţáŕţ þöšţ ~~~]\"],\"editor.inspector.field.stepCount.nzorrw\":[\"[Šţéþ çöûñţ ~~~]\"],\"editor.inspector.field.strapSpacing.1cyt3dc\":[\"[Šţŕáþ šþáçîñĝ ~~~~]\"],\"editor.inspector.field.strapStyle.fh1oy4\":[\"[Šţŕáþ šţýļé ~~~]\"],\"editor.inspector.field.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.inspector.field.suctionDiameter.nutxtj\":[\"[Šûçţîöñ ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.supplyDiameter.h5ldh5\":[\"[Šûþþļý ðîáɱéţéŕ ~~~~~]\"],\"editor.inspector.field.supplyHeight.zy6ru1\":[\"[Šûþþļý ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.supplyShape.k39wnb\":[\"[Šûþþļý šĥáþé ~~~~]\"],\"editor.inspector.field.supplyWidth.g66axi\":[\"[Šûþþļý ŵîðţĥ ~~~~]\"],\"editor.inspector.field.supportOffset.1w4cocr\":[\"[Šûþþöŕţ öƒƒšéţ ~~~~]\"],\"editor.inspector.field.sweepAngle.ucmmlw\":[\"[Šŵééþ áñĝļé ~~~]\"],\"editor.inspector.field.system.13qbhrw\":[\"[Šýšţéɱ ~~]\"],\"editor.inspector.field.terminal.1501knl\":[\"[Ţéŕɱîñáļ ~~~]\"],\"editor.inspector.field.terminalType.1ne8lgb\":[\"[Ţéŕɱîñáļ ţýþé ~~~~]\"],\"editor.inspector.field.thickness.1l8hu45\":[\"[Ţĥîçķñéšš ~~~]\"],\"editor.inspector.field.tiltAngle.1312yw1\":[\"[Ţîļţ áñĝļé ~~~]\"],\"editor.inspector.field.toLevelId.8i5qx7\":[\"[Ţö ļéṽéļ îð ~~~]\"],\"editor.inspector.field.topLandingDepth.ki7gk2\":[\"[Ţöþ ļáñðîñĝ ðéþţĥ ~~~~~]\"],\"editor.inspector.field.topLandingMode.zk0h7e\":[\"[Ţöþ ļáñðîñĝ ɱöðé ~~~~~]\"],\"editor.inspector.field.topRailHeight.fscisv\":[\"[Ţöþ ŕáîļ ĥéîĝĥţ ~~~~]\"],\"editor.inspector.field.transitionMode.f3evgt\":[\"[Ţŕáñšîţîöñ ɱöðé ~~~~~]\"],\"editor.inspector.field.transitionWidth.rtgz3m\":[\"[Ţŕáñšîţîöñ ŵîðţĥ ~~~~~]\"],\"editor.inspector.field.vaneCount.55vtye\":[\"[Ṽáñé çöûñţ ~~~]\"],\"editor.inspector.field.wallHeight.18vmh5c\":[\"[Ŵáļļ ĥéîĝĥţ ~~~]\"],\"editor.inspector.field.wallShell.vqsyhv\":[\"[Ŵáļļ šĥéļļ ~~~]\"],\"editor.inspector.field.wallSkirtHeight.1uea2ad\":[\"[Ŵáļļ šķîŕţ ĥéîĝĥţ ~~~~~]\"],\"editor.inspector.field.wallThickness.17wn4iv\":[\"[Ŵáļļ ţĥîçķñéšš ~~~~]\"],\"editor.inspector.field.width.gfxz9b\":[\"[Ŵîðţĥ ~~]\"],\"editor.inspector.field.width2.1nqawvr\":[\"[Ŵîðţĥ2 ~~]\"],\"editor.inspector.field.withBack.1ay8nus\":[\"[Ŵîţĥ ƀáçķ ~~~]\"],\"editor.inspector.field.withBottom.1x1z1vo\":[\"[Ŵîţĥ ƀöţţöɱ ~~~]\"],\"editor.inspector.field.withSides.uq066x\":[\"[Ŵîţĥ šîðéš ~~~]\"],\"editor.inspector.group.advanced.qwfkor\":[\"[Åðṽáñçéð ~~~]\"],\"editor.inspector.group.air.15jotdj\":[\"[Åîŕ ~]\"],\"editor.inspector.group.appearance.dli9wf\":[\"[Åþþéáŕáñçé ~~~]\"],\"editor.inspector.group.body.ign4wl\":[\"[Ɓöðý ~~]\"],\"editor.inspector.group.cab.o4z1ox\":[\"[Çáƀ ~]\"],\"editor.inspector.group.cabinet.rzvymr\":[\"[Çáƀîñéţ ~~~]\"],\"editor.inspector.group.cap.s0thej\":[\"[Çáþ ~]\"],\"editor.inspector.group.collar.1am2iqo\":[\"[Çöļļáŕ ~~]\"],\"editor.inspector.group.connection.2r1h4p\":[\"[Çöññéçţîöñ ~~~]\"],\"editor.inspector.group.connections.ai4w8u\":[\"[Çöññéçţîöñš ~~~~]\"],\"editor.inspector.group.construction.j6p9kc\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"editor.inspector.group.cricket.6iwg8g\":[\"[Çŕîçķéţ ~~~]\"],\"editor.inspector.group.curb.5xi2ol\":[\"[Çûŕƀ ~~]\"],\"editor.inspector.group.curvePoints.ztdcnn\":[\"[Çûŕṽé þöîñţš ~~~~]\"],\"editor.inspector.group.dimensions.pewa8g\":[\"[Ðîɱéñšîöñš ~~~]\"],\"editor.inspector.group.doors.18w2kpa\":[\"[Ðööŕš ~~]\"],\"editor.inspector.group.dormer.1fjs5tq\":[\"[Ðöŕɱéŕ ~~]\"],\"editor.inspector.group.dormerRoof.u9uy2g\":[\"[Ðöŕɱéŕ ŕööƒ ~~~]\"],\"editor.inspector.group.drainage.7easy0\":[\"[Ðŕáîñáĝé ~~~]\"],\"editor.inspector.group.elevation.4jbp44\":[\"[Éļéṽáţîöñ ~~~]\"],\"editor.inspector.group.endCaps.4fsb5b\":[\"[Éñð çáþš ~~~]\"],\"editor.inspector.group.equipment.1xtq0ev\":[\"[Éǫûîþɱéñţ ~~~]\"],\"editor.inspector.group.face.1531fr0\":[\"[Ƒáçé ~~]\"],\"editor.inspector.group.fitting.171z1r4\":[\"[Ƒîţţîñĝ ~~~]\"],\"editor.inspector.group.flues.nq6pye\":[\"[Ƒļûéš ~~]\"],\"editor.inspector.group.footprint.g8q2pg\":[\"[Ƒööţþŕîñţ ~~~]\"],\"editor.inspector.group.frame.q7q9fq\":[\"[Ƒŕáɱé ~~]\"],\"editor.inspector.group.geometry.a21os1\":[\"[Ĝéöɱéţŕý ~~~]\"],\"editor.inspector.group.grid.d8gwxd\":[\"[Ĝŕîð ~~]\"],\"editor.inspector.group.hangers.agnfzn\":[\"[Ĥáñĝéŕš ~~~]\"],\"editor.inspector.group.hardware.1nygwm1\":[\"[Ĥáŕðŵáŕé ~~~]\"],\"editor.inspector.group.hungWall.1ftr1gz\":[\"[Ĥûñĝ ŵáļļ ~~~]\"],\"editor.inspector.group.insulation.1kyo5e7\":[\"[Îñšûļáţîöñ ~~~]\"],\"editor.inspector.group.lantern.1h7o2xb\":[\"[Ļáñţéŕñ ~~~]\"],\"editor.inspector.group.levels.1s6yaru\":[\"[Ļéṽéļš ~~]\"],\"editor.inspector.group.line.166mv5z\":[\"[Ļîñé ~~]\"],\"editor.inspector.group.lines.12bghr0\":[\"[Ļîñéš ~~]\"],\"editor.inspector.group.motion.1be7sjr\":[\"[Ṁöţîöñ ~~]\"],\"editor.inspector.group.mounting.feutd2\":[\"[Ṁöûñţîñĝ ~~~]\"],\"editor.inspector.group.opening.dfet3\":[\"[Öþéñîñĝ ~~~]\"],\"editor.inspector.group.panelDimensions.b59qe6\":[\"[Þáñéļ ðîɱéñšîöñš ~~~~~]\"],\"editor.inspector.group.patternDistribution.fmam51\":[\"[Þáţţéŕñ ðîšţŕîƀûţîöñ ~~~~~~]\"],\"editor.inspector.group.placement.13lcvts\":[\"[Þļáçéɱéñţ ~~~]\"],\"editor.inspector.group.position.1quewx6\":[\"[Þöšîţîöñ ~~~]\"],\"editor.inspector.group.profile.1kwlci6\":[\"[Þŕöƒîļé ~~~]\"],\"editor.inspector.group.railing.19a5bnr\":[\"[Ŕáîļîñĝ ~~~]\"],\"editor.inspector.group.return.1klrjrz\":[\"[Ŕéţûŕñ ~~]\"],\"editor.inspector.group.settings.ktdb8o\":[\"[Šéţţîñĝš ~~~]\"],\"editor.inspector.group.shaft.mad9cv\":[\"[Šĥáƒţ ~~]\"],\"editor.inspector.group.shape.iq9t52\":[\"[Šĥáþé ~~]\"],\"editor.inspector.group.shoulder.1nyfx25\":[\"[Šĥöûļðéŕ ~~~]\"],\"editor.inspector.group.size.1a4x3zw\":[\"[Šîžé ~~]\"],\"editor.inspector.group.structure.gezdra\":[\"[Šţŕûçţûŕé ~~~]\"],\"editor.inspector.group.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.inspector.group.supply.1oy2zwu\":[\"[Šûþþļý ~~]\"],\"editor.inspector.group.surface.wtamqk\":[\"[Šûŕƒáçé ~~~]\"],\"editor.inspector.group.terminal.1501knl\":[\"[Ţéŕɱîñáļ ~~~]\"],\"editor.inspector.group.topology.18t8dyc\":[\"[Ţöþöļöĝý ~~~]\"],\"editor.inspector.group.transform.1c55ayj\":[\"[Ţŕáñšƒöŕɱ ~~~]\"],\"editor.inspector.group.trap.1qc7vhe\":[\"[Ţŕáþ ~~]\"],\"editor.inspector.group.type.1m2zofh\":[\"[Ţýþé ~~]\"],\"editor.inspector.option.abs.132mz8b\":[\"[Åƀš ~]\"],\"editor.inspector.option.accessPanel.13sptb5\":[\"[Åççéšš þáñéļ ~~~~]\"],\"editor.inspector.option.airHandler.1crnyg3\":[\"[Åîŕ ĥáñðļéŕ ~~~]\"],\"editor.inspector.option.alternating.5jashi\":[\"[Åļţéŕñáţîñĝ ~~~~]\"],\"editor.inspector.option.arched.14188w0\":[\"[Åŕçĥéð ~~]\"],\"editor.inspector.option.auto.540b06\":[\"[Åûţö ~~]\"],\"editor.inspector.option.automatic.r122ro\":[\"[Åûţöɱáţîç ~~~]\"],\"editor.inspector.option.back.1hzmxtu\":[\"[Ɓáçķ ~~]\"],\"editor.inspector.option.balusters.1j3w6ng\":[\"[Ɓáļûšţéŕš ~~~]\"],\"editor.inspector.option.band.160umau\":[\"[Ɓáñð ~~]\"],\"editor.inspector.option.basePlate.17fcg8k\":[\"[Ɓášé þļáţé ~~~]\"],\"editor.inspector.option.boards.u14hig\":[\"[Ɓöáŕðš ~~]\"],\"editor.inspector.option.bookshelf.ls0zqk\":[\"[Ɓööķšĥéļƒ ~~~]\"],\"editor.inspector.option.both.mkyb42\":[\"[Ɓöţĥ ~~]\"],\"editor.inspector.option.bottom.9mpruy\":[\"[Ɓöţţöɱ ~~]\"],\"editor.inspector.option.box.1nbjcuq\":[\"[Ɓöẋ ~]\"],\"editor.inspector.option.break.17spnvc\":[\"[Ɓŕéáķ ~~]\"],\"editor.inspector.option.butterfly.pvv0g4\":[\"[Ɓûţţéŕƒļý ~~~]\"],\"editor.inspector.option.cable.fp50zq\":[\"[Çáƀļé ~~]\"],\"editor.inspector.option.castIron.1q2z4qk\":[\"[Çášţ îŕöñ ~~~]\"],\"editor.inspector.option.ceiling.1t2zaqo\":[\"[Çéîļîñĝ ~~~]\"],\"editor.inspector.option.center.kzdxno\":[\"[Çéñţéŕ ~~]\"],\"editor.inspector.option.centerOpening.bn1rvw\":[\"[Çéñţéŕ öþéñîñĝ ~~~~]\"],\"editor.inspector.option.cleanout.lww8bm\":[\"[Çļéáñöûţ ~~~]\"],\"editor.inspector.option.concretePad.m1zdij\":[\"[Çöñçŕéţé þáð ~~~~]\"],\"editor.inspector.option.condenser.a74nps\":[\"[Çöñðéñšéŕ ~~~]\"],\"editor.inspector.option.corbeled.bovs4v\":[\"[Çöŕƀéļéð ~~~]\"],\"editor.inspector.option.count.1qoi4r8\":[\"[Çöûñţ ~~]\"],\"editor.inspector.option.coupling.111aap2\":[\"[Çöûþļîñĝ ~~~]\"],\"editor.inspector.option.coveringSpecific.1h9fo8w\":[\"[Çöṽéŕîñĝ šþéçîƒîç ~~~~~]\"],\"editor.inspector.option.cross.1elubzv\":[\"[Çŕöšš ~~]\"],\"editor.inspector.option.cubby.ivq4qc\":[\"[Çûƀƀý ~~]\"],\"editor.inspector.option.curved.6noz1s\":[\"[Çûŕṽéð ~~]\"],\"editor.inspector.option.cylinder.eol6vj\":[\"[Çýļîñðéŕ ~~~]\"],\"editor.inspector.option.damper.wgri80\":[\"[Ðáɱþéŕ ~~]\"],\"editor.inspector.option.destination.gfzm1r\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"editor.inspector.option.diffuser.1igj0jh\":[\"[Ðûšéŕ ~~~]\"],\"editor.inspector.option.dogEar.xhtvjt\":[\"[Ðöĝ éáŕ ~~]\"],\"editor.inspector.option.dome.1c87e56\":[\"[Ðöɱé ~~]\"],\"editor.inspector.option.double.13h5354\":[\"[Ðöûƀļé ~~]\"],\"editor.inspector.option.ductBoard.lc3o4t\":[\"[Ðûçţ ƀöáŕð ~~~]\"],\"editor.inspector.option.dutch.1djx429\":[\"[Ðûţçĥ ~~]\"],\"editor.inspector.option.elbow.1flup74\":[\"[Éļƀöŵ ~~]\"],\"editor.inspector.option.end.1llf32i\":[\"[Éñð ~]\"],\"editor.inspector.option.endCap.1p4v4na\":[\"[Éñð çáþ ~~]\"],\"editor.inspector.option.equalFit.11vw52c\":[\"[Éǫûáļ ƒîţ ~~~]\"],\"editor.inspector.option.fixedCount.1o3dznu\":[\"[Ƒîẋéð çöûñţ ~~~]\"],\"editor.inspector.option.fixedSpacing.hl4dr4\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"editor.inspector.option.flat.ly0bei\":[\"[Ƒļáţ ~~]\"],\"editor.inspector.option.flex.ktpamq\":[\"[Ƒļéẋ ~~]\"],\"editor.inspector.option.floating.1oadpln\":[\"[Ƒļöáţîñĝ ~~~]\"],\"editor.inspector.option.floor.q9caf1\":[\"[Ƒļööŕ ~~]\"],\"editor.inspector.option.flush.olwwu5\":[\"[Ƒļûšĥ ~~]\"],\"editor.inspector.option.front.uhugl4\":[\"[Ƒŕöñţ ~~]\"],\"editor.inspector.option.furnace.w0mzpf\":[\"[Ƒûŕñáçé ~~~]\"],\"editor.inspector.option.gable.1ekyx6\":[\"[Ĝáƀļé ~~]\"],\"editor.inspector.option.gambrel.1yo90d9\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"editor.inspector.option.generic.1074oey\":[\"[Ĝéñéŕîç ~~~]\"],\"editor.inspector.option.glass.163tc5n\":[\"[Ĝļášš ~~]\"],\"editor.inspector.option.glassFrame.jp9s3m\":[\"[Ĝļášš ƒŕáɱé ~~~]\"],\"editor.inspector.option.globe.1avkqoy\":[\"[Ĝļöƀé ~~]\"],\"editor.inspector.option.grounded.jwoq73\":[\"[Ĝŕöûñðéð ~~~]\"],\"editor.inspector.option.guard.1m575se\":[\"[Ĝûáŕð ~~]\"],\"editor.inspector.option.halfRound.m5hob0\":[\"[Ĥáļƒ ŕöûñð ~~~]\"],\"editor.inspector.option.hidden.gp48rd\":[\"[Ĥîððéñ ~~]\"],\"editor.inspector.option.hip.r0jnjy\":[\"[Ĥîþ ~]\"],\"editor.inspector.option.horizontal.tyapah\":[\"[Ĥöŕîžöñţáļ ~~~]\"],\"editor.inspector.option.include.1a27vnr\":[\"[Îñçļûðé ~~~]\"],\"editor.inspector.option.independentHighBeam.1tn0zvy\":[\"[Îñðéþéñðéñţ ĥîĝĥ ƀéáɱ ~~~~~~]\"],\"editor.inspector.option.industrial.8sd9u\":[\"[Îñðûšţŕîáļ ~~~]\"],\"editor.inspector.option.inline.1uh05lg\":[\"[Îñļîñé ~~]\"],\"editor.inspector.option.integrated.1xd9lv0\":[\"[Îñţéĝŕáţéð ~~~]\"],\"editor.inspector.option.joined.1x2o8ds\":[\"[Ĵöîñéð ~~]\"],\"editor.inspector.option.kStyle.m8kfwz\":[\"[Ķ šţýļé ~~]\"],\"editor.inspector.option.kickout.vt8s6p\":[\"[Ķîçķöûţ ~~~]\"],\"editor.inspector.option.knee.186hcui\":[\"[Ķñéé ~~]\"],\"editor.inspector.option.landing.tyqarg\":[\"[Ļáñðîñĝ ~~~]\"],\"editor.inspector.option.lantern.1h7o2xb\":[\"[Ļáñţéŕñ ~~~]\"],\"editor.inspector.option.leave.1xf0sbk\":[\"[Ļéáṽé ~~]\"],\"editor.inspector.option.left.14n06mo\":[\"[Ļéƒţ ~~]\"],\"editor.inspector.option.level.i4lhtp\":[\"[Ļéṽéļ ~~]\"],\"editor.inspector.option.lowProfile.1h9lgzg\":[\"[Ļöŵ þŕöƒîļé ~~~]\"],\"editor.inspector.option.mansard.dwzck9\":[\"[Ṁáñšáŕð ~~~]\"],\"editor.inspector.option.manual.1wu7r43\":[\"[Ṁáñûáļ ~~]\"],\"editor.inspector.option.maximumSpacing.n1fzl4\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"editor.inspector.option.metal.1az9fgc\":[\"[Ṁéţáļ ~~]\"],\"editor.inspector.option.metalPanel.1xdz476\":[\"[Ṁéţáļ þáñéļ ~~~]\"],\"editor.inspector.option.minimal.fwmiwg\":[\"[Ṁîñîɱáļ ~~~]\"],\"editor.inspector.option.mono.oszvd6\":[\"[Ṁöñö ~~]\"],\"editor.inspector.option.none.deku7v\":[\"[Ñöñé ~~]\"],\"editor.inspector.option.omit.okcs7w\":[\"[Öɱîţ ~~]\"],\"editor.inspector.option.open.n6hn1l\":[\"[Öþéñ ~~]\"],\"editor.inspector.option.openRack.mvt1x0\":[\"[Öþéñ ŕáçķ ~~~]\"],\"editor.inspector.option.opening.dfet3\":[\"[Öþéñîñĝ ~~~]\"],\"editor.inspector.option.oval.1jiqzxx\":[\"[Öṽáļ ~~]\"],\"editor.inspector.option.picket.zyeoy1\":[\"[Þîçķéţ ~~]\"],\"editor.inspector.option.pointed.effedk\":[\"[Þöîñţéð ~~~]\"],\"editor.inspector.option.postAndRail.lajk2u\":[\"[Þöšţ áñð ŕáîļ ~~~~]\"],\"editor.inspector.option.preserveHighEdge.y6ywf0\":[\"[Þŕéšéŕṽé ĥîĝĥ éðĝé ~~~~~]\"],\"editor.inspector.option.preserveLowEdge.196m5pk\":[\"[Þŕéšéŕṽé ļöŵ éðĝé ~~~~~]\"],\"editor.inspector.option.preservePitch.1uq973r\":[\"[Þŕéšéŕṽé þîţçĥ ~~~~]\"],\"editor.inspector.option.privacy.1th4hrb\":[\"[Þŕîṽáçý ~~~]\"],\"editor.inspector.option.purlins.od124c\":[\"[Þûŕļîñš ~~~]\"],\"editor.inspector.option.pvc.1oc4u2w\":[\"[Þṽç ~]\"],\"editor.inspector.option.pyramid.17ucsqv\":[\"[Þýŕáɱîð ~~~]\"],\"editor.inspector.option.rafters.16tzbby\":[\"[Ŕáƒţéŕš ~~~]\"],\"editor.inspector.option.rail.39zqc9\":[\"[Ŕáîļ ~~]\"],\"editor.inspector.option.raised.4kyx9h\":[\"[Ŕáîšéð ~~]\"],\"editor.inspector.option.rect.tpfvaf\":[\"[Ŕéçţ ~~]\"],\"editor.inspector.option.reducer.18byt67\":[\"[Ŕéðûçéŕ ~~~]\"],\"editor.inspector.option.return.1klrjrz\":[\"[Ŕéţûŕñ ~~]\"],\"editor.inspector.option.returnGrille.r28ncm\":[\"[Ŕéţûŕñ ĝŕîļļé ~~~~]\"],\"editor.inspector.option.right.8humpx\":[\"[Ŕîĝĥţ ~~]\"],\"editor.inspector.option.round.1vx0wyj\":[\"[Ŕöûñð ~~]\"],\"editor.inspector.option.rounded.199nwpm\":[\"[Ŕöûñðéð ~~~]\"],\"editor.inspector.option.sanitaryTee.oro3d4\":[\"[Šáñîţáŕý ţéé ~~~~]\"],\"editor.inspector.option.scalloped.1eltba0\":[\"[Šçáļļöþéð ~~~]\"],\"editor.inspector.option.scoop.qy5zdv\":[\"[Šçööþ ~~]\"],\"editor.inspector.option.segmentedPanel.5r3hwl\":[\"[Šéĝɱéñţéð þáñéļ ~~~~~]\"],\"editor.inspector.option.shed.6t994v\":[\"[Šĥéð ~~]\"],\"editor.inspector.option.sheetMetal.l164lv\":[\"[Šĥééţ ɱéţáļ ~~~]\"],\"editor.inspector.option.shingle.pfmynh\":[\"[Šĥîñĝļé ~~~]\"],\"editor.inspector.option.shingled.1nm9inf\":[\"[Šĥîñĝļéð ~~~]\"],\"editor.inspector.option.simple.12mfu73\":[\"[Šîɱþļé ~~]\"],\"editor.inspector.option.single.1uzxurt\":[\"[Šîñĝļé ~~]\"],\"editor.inspector.option.singleLeft.1atdh02\":[\"[Šîñĝļé ļéƒţ ~~~]\"],\"editor.inspector.option.singleRight.1axbu67\":[\"[Šîñĝļé ŕîĝĥţ ~~~~]\"],\"editor.inspector.option.slantBox.1ta99vy\":[\"[Šļáñţ ƀöẋ ~~~]\"],\"editor.inspector.option.slat.1p9njbz\":[\"[Šļáţ ~~]\"],\"editor.inspector.option.sliding.cd0jox\":[\"[Šļîðîñĝ ~~~]\"],\"editor.inspector.option.slope.1bn2qw\":[\"[Šļöþé ~~]\"],\"editor.inspector.option.sloped.lo4sh0\":[\"[Šļöþéð ~~]\"],\"editor.inspector.option.solid.o4uy6s\":[\"[Šöļîð ~~]\"],\"editor.inspector.option.solidPanel.2yaw62\":[\"[Šöļîð þáñéļ ~~~]\"],\"editor.inspector.option.spiral.91hcl4\":[\"[Šþîŕáļ ~~]\"],\"editor.inspector.option.splash.1uke4cm\":[\"[Šþļášĥ ~~]\"],\"editor.inspector.option.spread.1g1zysu\":[\"[Šþŕéáð ~~]\"],\"editor.inspector.option.square.1xl1lk6\":[\"[Šǫûáŕé ~~]\"],\"editor.inspector.option.stair.1xf86e\":[\"[Šţáîŕ ~~]\"],\"editor.inspector.option.standard.vuco\":[\"[Šţáñðáŕð ~~~]\"],\"editor.inspector.option.start.30xvgf\":[\"[Šţáŕţ ~~]\"],\"editor.inspector.option.step.jtn9kf\":[\"[Šţéþ ~~]\"],\"editor.inspector.option.stepped.naxzh0\":[\"[Šţéþþéð ~~~]\"],\"editor.inspector.option.straight.lxqbfr\":[\"[Šţŕáîĝĥţ ~~~]\"],\"editor.inspector.option.strap.14q0ps9\":[\"[Šţŕáþ ~~]\"],\"editor.inspector.option.supply.1oy2zwu\":[\"[Šûþþļý ~~]\"],\"editor.inspector.option.supplyRegister.1lzdbbn\":[\"[Šûþþļý ŕéĝîšţéŕ ~~~~~]\"],\"editor.inspector.option.tapered.3y5blo\":[\"[Ţáþéŕéð ~~~]\"],\"editor.inspector.option.targetSpacing.14uibc9\":[\"[Ţáŕĝéţ šþáçîñĝ ~~~~]\"],\"editor.inspector.option.tee.7d6tjp\":[\"[Ţéé ~]\"],\"editor.inspector.option.tilted.6n0u5j\":[\"[Ţîļţéð ~~]\"],\"editor.inspector.option.top.2nypcs\":[\"[Ţöþ ~]\"],\"editor.inspector.option.transition.1rziz9e\":[\"[Ţŕáñšîţîöñ ~~~]\"],\"editor.inspector.option.vent.xda5j2\":[\"[Ṽéñţ ~~]\"],\"editor.inspector.option.walkOn.1n5sg9l\":[\"[Ŵáļķ öñ ~~]\"],\"editor.inspector.option.wall.blj585\":[\"[Ŵáļļ ~~]\"],\"editor.inspector.option.wallAbutment.lza941\":[\"[Ŵáļļ áƀûţɱéñţ ~~~~]\"],\"editor.inspector.option.wallLedger.5m8ana\":[\"[Ŵáļļ ļéðĝéŕ ~~~]\"],\"editor.inspector.option.wallShelf.yiozfl\":[\"[Ŵáļļ šĥéļƒ ~~~]\"],\"editor.inspector.option.waste.3xrp79\":[\"[Ŵášţé ~~]\"],\"editor.inspector.option.wye.14vupc8\":[\"[Ŵýé ~]\"],\"editor.layers.collapse\":[\"[Çöļļáþšé ~~~]\"],\"editor.layers.defaultName.ceiling\":[\"[Çéîļîñĝ (\",[\"area\"],\") ~~~]\"],\"editor.layers.defaultName.collection\":[\"[Çöļļéçţîöñ ~~~]\"],\"editor.layers.defaultName.flight\":[\"[Ƒļîĝĥţ (\",[\"width\"],\"×\",[\"length\"],\"ɱ) ~~~]\"],\"editor.layers.defaultName.landing\":[\"[Ļáñðîñĝ (\",[\"width\"],\"×\",[\"length\"],\"ɱ) ~~~]\"],\"editor.layers.defaultName.node\":[\"[Ñöðé ~~]\"],\"editor.layers.defaultName.roof\":[\"[Ŕööƒ (\",[\"count\",\"plural\",{\"one\":[\"#\",\" šéĝɱéñţ\"],\"other\":[\"#\",\" šéĝɱéñţš\"]}],\") ~~~~~~]\"],\"editor.layers.defaultName.roofSegment\":[\"[\",[\"roofType\",\"select\",{\"hip\":[\"Ĥîþ\"],\"gable\":[\"Ĝáƀļé\"],\"shed\":[\"Šĥéð\"],\"gambrel\":[\"Ĝáɱƀŕéļ\"],\"dutch\":[\"Ðûţçĥ\"],\"mansard\":[\"Ṁáñšáŕð\"],\"flat\":[\"Ƒļáţ\"],\"conical\":[\"Çöñîçáļ\"],\"barrel\":[\"Ɓáŕŕéļ\"],\"other\":[\"Ŕööƒ\"]}],\" (\",[\"width\"],\"×\",[\"depth\"],\"ɱ) ~~~~~~~~~~~~~~~~]\"],\"editor.layers.defaultName.room\":[\"[Ŕööɱ (\",[\"area\"],\") ~~]\"],\"editor.layers.defaultName.slab\":[\"[Šļáƀ (\",[\"area\"],\") ~~]\"],\"editor.layers.defaultName.staircase\":[\"[Šţáîŕçášé (\",[\"count\",\"plural\",{\"one\":[\"#\",\" šéĝɱéñţ\"],\"other\":[\"#\",\" šéĝɱéñţš\"]}],\") ~~~~~~~~]\"],\"editor.layers.defaultName.zone\":[\"[Žöñé (\",[\"area\"],\") ~~]\"],\"editor.layers.expand\":[\"[Éẋþáñð ~~]\"],\"editor.layers.levelOptions\":[\"[\",[\"levelName\"],\" öþţîöñš ~~~]\"],\"editor.layers.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.layers.spaceColour\":[\"[\",[\"role\",\"select\",{\"room\":[\"Ŕööɱ çöļöûŕ\"],\"other\":[\"Žöñé çöļöûŕ\"]}],\" ~~~~~~]\"],\"editor.layers.unitColour\":[\"[Ûñîţ çöļöûŕ ~~~]\"],\"editor.levelDuplicateDialog.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromLevel\":[\"[Çĥööšé ŵĥáţ ţö çöþý ƒŕöɱ \",[\"levelName\"],\". ~~~~~~]\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel\":[\"[Çĥööšé ŵĥáţ ţö çöþý ƒŕöɱ ţĥîš ļéṽéļ. ~~~~~~~~~]\"],\"editor.levelDuplicateDialog.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.levelDuplicateDialog.duplicateLevel\":[\"[Ðûþļîçáţé Ļéṽéļ ~~~~~]\"],\"editor.levelDuplicateDialog.everything\":[\"[Éṽéŕýţĥîñĝ ~~~]\"],\"editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout\":[\"[Šţŕûçţûŕé, ƒîñîšĥéš, áñð þļáçéð îţéɱš, ŵîţĥöûţ ĝûîðé ŕéƒéŕéñçéš. ~~~~~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.structureFurniture\":[\"[Šţŕûçţûŕé + ƒûŕñîţûŕé ~~~~~~]\"],\"editor.levelDuplicateDialog.structureMaterials\":[\"[Šţŕûçţûŕé + ɱáţéŕîáļš ~~~~~~]\"],\"editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences\":[\"[Šţŕûçţûŕé, ɱáţéŕîáļš, ƒûŕñîţûŕé, áñð ŕéƒéŕéñçéš. ~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.structureOnly\":[\"[Šţŕûçţûŕé öñļý ~~~~]\"],\"editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd\":[\"[Šţŕûçţûŕé ŵîţĥ ţĥé çûŕŕéñţ ɱáţéŕîáļ áñð ƒîñîšĥ áššîĝñɱéñţš. ~~~~~~~~~~~~~~~~]\"],\"editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd\":[\"[Ŵáļļš, šļáƀš, ŕööƒš, šţáîŕš, ŵîñðöŵš, áñð ðööŕš ŵîţĥöûţ ƒîñîšĥéš. ~~~~~~~~~~~~~~~~]\"],\"editor.levelWallCopyPrompt.copy\":[\"[Çöþý ~~]\"],\"editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel\":[\"[Çöþý éẋţéŕîöŕ ŵáļļš ƒŕöɱ ţĥé ļéṽéļ \",[\"direction\"],\"? ~~~~~~~~~]\"],\"editor.levelWallCopyPrompt.noThanks\":[\"[Ñö ţĥáñķš ~~~]\"],\"editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace\":[\"[\",[\"sourceName\"],\"’š öûţšîðé ŵáļļš, îñ þļáçé, šö ţĥîš ļéṽéļ ļîñéš ûþ ŵîţĥ îţ. ~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.basementExcavation\":[\"[Çûţ \",[\"depth\"],\" îñţö ţĥé ĝŕöûñð öñ ţĥé ĥîĝĥ šîðé, ðûĝ öûţ îñšîðé ţĥé ŵáļļš’ öûţšîðé ƒáçéš; ţĥé ĝŕöûñð ŕöûñð ţĥé ĥöûšé îš ļéƒţ áš îţ ļîéš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.basementTopOverTheGround\":[\"[Ţĥé ƀášéɱéñţ ŵáļļ’š ţöþ šţáñðš \",[\"distance\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð; ţĥé ƒöûñðáţîöñ îš šéţ ƒöŕ \",[\"asked\"],\" öṽéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.basementTopUnderTheGround\":[\"[Ţĥé ƀášéɱéñţ ŵáļļ’š ţöþ šţáñðš \",[\"distance\"],\" ûñðéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð; ţĥé ƒöûñðáţîöñ îš šéţ ƒöŕ \",[\"asked\"],\" öṽéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.crawlSpaceDugOut\":[\"[Çŕáŵļ šþáçé: ðûĝ öûţ ûþ ţö \",[\"dug\"],\" ƒöŕ \",[\"clearance\"],\" ûñðéŕ ţĥé ĵöîšţš (ÎŔÇ Ŕ317.1). ~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.crippleWalls\":[\"[Þášţ \",[\"stem\"],\" öƒ šţéɱ öûţ öƒ ţĥé ĝŕöûñð ţĥé šţéɱ ðŕöþš ţö \",[\"least\"],\" öṽéŕ ĝŕáðé: \",[\"cripples\",\"plural\",{\"one\":[\"#\",\" çŕîþþļé ŵáļļ\"],\"other\":[\"#\",\" çŕîþþļé ŵáļļš\"]}],\" öṽéŕ îţ, ûþ ţö \",[\"tallest\"],\" ţáļļ. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.daylightWalls\":[\"[Ţĥé ĝŕöûñð ƒáļļš \",[\"fall\"],\" áçŕöšš ţĥé ĥöûšé: ţĥé ðöŵñĥîļļ ŵáļļš šţáñð ûþ ţö \",[\"exposed\"],\" öûţ öƒ îţ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.deepFill\":[\"[Ţĥáţ îš \",[\"fill\"],\" öƒ ƒîļļ. Å ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ šûîţš á ƒļööŕ ţĥîš ĥîĝĥ. ~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.deepFillOnASlope\":[\"[Ţĥáţ îš \",[\"fill\"],\" öƒ ƒîļļ öñ ţĥé ļöŵ šîðé. Å ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ šûîţš ţĥîš šļöþé. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.floorOverTheGround\":[\"[Ţĥé ƒļööŕ šţáñðš \",[\"distance\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð; ţĥé ƒöûñðáţîöñ îš šéţ ƒöŕ \",[\"asked\"],\" öṽéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.floorUnderTheGround\":[\"[Ţĥé ƒļööŕ šţáñðš \",[\"distance\"],\" ûñðéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð; ţĥé ƒöûñðáţîöñ îš šéţ ƒöŕ \",[\"asked\"],\" öṽéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.footingSteps\":[\"[Ţĥé ĝŕöûñð ƒáļļš \",[\"fall\"],\" áçŕöšš ţĥé ĥöûšé: ţĥé ƒööţîñĝ šţéþš \",[\"steps\",\"plural\",{\"one\":[\"#\",\" ţîɱé\"],\"other\":[\"#\",\" ţîɱéš\"]}],\" áļöñĝ îţš šţééþéšţ šîðé, \",[\"riser\"],\" á šţéþ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.footingStepsUpTo\":[\"[Ţĥé ĝŕöûñð ƒáļļš \",[\"fall\"],\" áçŕöšš ţĥé ĥöûšé: ţĥé ƒööţîñĝ šţéþš \",[\"steps\",\"plural\",{\"one\":[\"#\",\" ţîɱé\"],\"other\":[\"#\",\" ţîɱéš\"]}],\" áļöñĝ îţš šţééþéšţ šîðé, ûþ ţö \",[\"riser\"],\" á šţéþ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.footingTooNarrow\":[\"[Ţĥé ƒööţîñĝ îš \",[\"width\"],\" ŵîðé: \",[\"storeys\",\"plural\",{\"one\":[\"öñé šţöŕéý ášķš\"],\"other\":[\"#\",\" šţöŕéýš ášķ\"]}],\" \",[\"asked\"],\" öñ 1500 þšƒ šöîļ (ÎŔÇ Ţáƀļé Ŕ403.1(1)). ~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.footingTooShallow\":[\"[Ţĥé ƒööţîñĝ’š ƀöţţöɱ îš \",[\"depth\"],\" ûñðéŕ ĝŕáðé: ķééþ îţ \",[\"least\"],\" áţ ļéášţ, öŕ ţĥé ƒŕöšţ ļîñé (ÎŔÇ Ŕ403.1.4). ~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingCut\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: çûţ \",[\"cut\"],\". ~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingCutOnASlope\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: çûţ \",[\"cut\"],\" öñ ţĥé ĥîĝĥ šîðé. ~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingFilled\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: ƒîļļéð \",[\"fill\"],\". ~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingFilledAndCut\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: ƒîļļéð \",[\"fill\"],\", çûţ \",[\"cut\"],\". ~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingFilledAndCutOnASlope\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: ƒîļļéð ûþ ţö \",[\"fill\"],\" öñ ţĥé ļöŵ šîðé, çûţ \",[\"cut\"],\" öñ ţĥé ĥîĝĥ šîðé. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.gradingFilledOnASlope\":[\"[Ţĥé ĝŕöûñð îš ĥéļð \",[\"least\"],\" ûñðéŕ ţĥé šļáƀ’š ţöþ öñ éṽéŕý šîðé: ƒîļļéð ûþ ţö \",[\"fill\"],\" öñ ţĥé ļöŵ šîðé. ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.groundCutRoundTheStem\":[\"[Ţĥé ĝŕöûñð îš çûţ \",[\"cut\"],\" öñ ţĥé ĥîĝĥ šîðé ţö ĥöļð ţĥé šţéɱ ŵáļļ’š ţöþ \",[\"least\"],\" öṽéŕ îţ (ÎŔÇ Ŕ317.1). Ŕáîšîñĝ ţĥé ƒļööŕ çûţš ļéšš. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.raiseItOnAStemWall\":[\"[Ŕáîšé îţ öñ á šţéɱ ŵáļļ ~~~~~~]\"],\"editor.lib.buildingFoundation.raiseTheFloor\":[\"[Ŕáîšé ţĥé ƒļööŕ ~~~~]\"],\"editor.lib.buildingFoundation.seatBasementWallTop\":[\"[Šéáţ ţĥé ƀášéɱéñţ ŵáļļ’š ţöþ \",[\"asked\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð ~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.seatFloorTop\":[\"[Šéáţ ţĥé ƒļööŕ’š ţöþ \",[\"asked\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð ~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.seatGroundFloor\":[\"[Šéáţ ţĥé ĝŕöûñð ƒļööŕ \",[\"asked\"],\" öṽéŕ ĝŕáðé ~~~~~~~~~]\"],\"editor.lib.buildingFoundation.seatItAt\":[\"[Šéáţ îţ áţ \",[\"asked\"],\" ~~~]\"],\"editor.lib.buildingFoundation.seatRaisedFloorTop\":[\"[Šéáţ ţĥé ŕáîšéð ƒļööŕ’š ţöþ \",[\"asked\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð ~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.seatSlabTop\":[\"[Šéáţ ţĥé šļáƀ’š ţöþ \",[\"asked\"],\" öṽéŕ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð ~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.setIt\":[\"[Šéţ îţ \",[\"height\"],\" ~~]\"],\"editor.lib.buildingFoundation.setItDeep\":[\"[Šéţ îţ \",[\"depth\"],\" ðééþ ~~~]\"],\"editor.lib.buildingFoundation.sillTooNearTheGround\":[\"[Ţĥé šîļļ áñð ţĥé ƒļööŕ ƒŕáɱîñĝ šîţ \",[\"height\"],\" öṽéŕ ţĥé ĝŕöûñð: ķééþ ţĥéɱ \",[\"least\"],\" öƒƒ îţ áţ ļéášţ (ÎŔÇ Ŕ317.1). ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.slabEdgeSteps\":[\"[Ţĥé ĝŕöûñð ƒáļļš \",[\"fall\"],\" áçŕöšš ţĥé ĥöûšé: ţĥé šļáƀ éðĝé šţéþš \",[\"steps\",\"plural\",{\"one\":[\"#\",\" ţîɱé\"],\"other\":[\"#\",\" ţîɱéš\"]}],\" áļöñĝ îţš šţééþéšţ šîðé, \",[\"riser\"],\" á šţéþ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.slabEdgeStepsUpTo\":[\"[Ţĥé ĝŕöûñð ƒáļļš \",[\"fall\"],\" áçŕöšš ţĥé ĥöûšé: ţĥé šļáƀ éðĝé šţéþš \",[\"steps\",\"plural\",{\"one\":[\"#\",\" ţîɱé\"],\"other\":[\"#\",\" ţîɱéš\"]}],\" áļöñĝ îţš šţééþéšţ šîðé, ûþ ţö \",[\"riser\"],\" á šţéþ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.slabEdgeTooShallow\":[\"[Ţĥé šļáƀ éðĝé’š ƀöţţöɱ îš \",[\"depth\"],\" ûñðéŕ ĝŕáðé: ķééþ îţ \",[\"least\"],\" áţ ļéášţ, öŕ ţĥé ƒŕöšţ ļîñé (ÎŔÇ Ŕ403.1.4). ~~~~~~~~~~~~~~~~~~~]\"],\"editor.lib.buildingFoundation.widenItTo\":[\"[Ŵîðéñ îţ ţö \",[\"width\"],\" ~~~]\"],\"editor.lib.contextualHelp.addOrRemoveObjectsFromThe\":[\"[Åðð öŕ ŕéɱöṽé öƀĵéçţš ƒŕöɱ ţĥé šéļéçţîöñ ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.adjustOneSideSCornerRadius\":[\"[Åðĵûšţ öñé šîðé’š çöŕñéŕ ŕáðîûš ~~~~~~~~]\"],\"editor.lib.contextualHelp.clearTheSelectionOrClickOutside\":[\"[Çļéáŕ ţĥé šéļéçţîöñ (öŕ çļîçķ öûţšîðé) ~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickAHandleDotToShow\":[\"[Çļîçķ á ĥáñðļé ðöţ ţö šĥöŵ ɱöṽé áŕŕöŵš ~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickOrDragTheSelectionTo\":[\"[Çļîçķ öŕ ðŕáĝ ţĥé šéļéçţîöñ ţö ɱöṽé îţ áš öñé ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.clickTheHandleDotToShow\":[\"[Çļîçķ ţĥé ĥáñðļé ðöţ ţö šĥöŵ ɱöṽé + ŕöţáţé ĥáñðļéš ~~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.detachTheJointWhileDraggingAn\":[\"[Ðéţáçĥ ţĥé ĵöîñţ ŵĥîļé ðŕáĝĝîñĝ áñ áŕŕöŵ ~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.dragLeftOrRightToRotate\":[\"[Ðŕáĝ ļéƒţ öŕ ŕîĝĥţ ţö ŕöţáţé šéļéçţéð öƀĵéçţ ~~~~~~~~~~~~]\"],\"editor.lib.contextualHelp.dragSelectedMovableObject\":[\"[Ðŕáĝ šéļéçţéð ɱöṽáƀļé öƀĵéçţ ~~~~~~~~]\"],\"editor.lib.contextualHelp.groupSelectionSessionOnly\":[\"[Ĝŕöûþ šéļéçţîöñ (šéššîöñ öñļý) ~~~~~~~~]\"],\"editor.lib.contextualHelp.holdToRotateFreely\":[\"[Ĥöļð ţö ŕöţáţé ƒŕééļý ~~~~~~]\"],\"editor.lib.contextualHelp.rotate45\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.lib.contextualHelp.rotateTheSelection45\":[\"[Ŕöţáţé ţĥé šéļéçţîöñ ±45° ~~~~~~]\"],\"editor.lib.contextualHelp.rotatingFreely\":[\"[Ŕöţáţîñĝ ƒŕééļý (ñö áñĝļé šţéþ) ~~~~~~~~]\"],\"editor.lib.contextualHelp.switchTheRotationAxisYX\":[\"[Šŵîţçĥ ţĥé ŕöţáţîöñ áẋîš (Ý → Ẋ → Ž) ~~~~~~~~]\"],\"editor.lib.contextualHelp.ungroupSessionSelection\":[\"[Ûñĝŕöûþ šéššîöñ šéļéçţîöñ ~~~~~~~]\"],\"editor.lib.continuation.continuous\":[\"[Çöñţîñûöûš ~~~]\"],\"editor.lib.continuation.continuousCanopy\":[\"[Çöñţîñûöûš çáñöþý ~~~~~]\"],\"editor.lib.continuation.continuousRun\":[\"[Çöñţîñûöûš ŕûñ ~~~~]\"],\"editor.lib.continuation.curvedFence\":[\"[Çûŕṽéð ƒéñçé ~~~~]\"],\"editor.lib.continuation.freehandFence\":[\"[Ƒŕééĥáñð ƒéñçé ~~~~]\"],\"editor.lib.continuation.placeMultiple\":[\"[Þļáçé ɱûļţîþļé ~~~~]\"],\"editor.lib.continuation.placeOnce\":[\"[Þļáçé öñçé ~~~]\"],\"editor.lib.continuation.polygonRoom\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.lib.continuation.rectangleRoom\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.lib.continuation.singleCabinet\":[\"[Šîñĝļé çáƀîñéţ ~~~~]\"],\"editor.lib.continuation.singleCanopy\":[\"[Šîñĝļé çáñöþý ~~~~]\"],\"editor.lib.continuation.singleFence\":[\"[Šîñĝļé ƒéñçé ~~~~]\"],\"editor.lib.continuation.walls\":[\"[Ŵáļļš ~~]\"],\"editor.lib.hudTitle.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"editor.lib.hudTitle.divideRoom\":[\"[Ðîṽîðé ŕööɱ ~~~]\"],\"editor.lib.hudTitle.editKind\":[\"[Éðîţ \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.erase\":[\"[Éŕášé ~~]\"],\"editor.lib.hudTitle.levelTerrain\":[\"[Ļéṽéļ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.lowerTerrain\":[\"[Ļöŵéŕ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.lib.hudTitle.moveKind\":[\"[Ṁöṽé \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.moveRoom\":[\"[Ṁöṽé ŕööɱ ~~~]\"],\"editor.lib.hudTitle.moveSelection\":[\"[Ṁöṽé šéļéçţîöñ ~~~~]\"],\"editor.lib.hudTitle.paint\":[\"[Þáîñţ ~~]\"],\"editor.lib.hudTitle.paintARectangle\":[\"[Þáîñţ á ŕéçţáñĝļé ~~~~~]\"],\"editor.lib.hudTitle.paintAShape\":[\"[Þáîñţ á šĥáþé ~~~~]\"],\"editor.lib.hudTitle.pickMaterial\":[\"[Þîçķ ɱáţéŕîáļ ~~~~]\"],\"editor.lib.hudTitle.raiseTerrain\":[\"[Ŕáîšé ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.resize\":[\"[Ŕéšîžé ~~]\"],\"editor.lib.hudTitle.rotate\":[\"[Ŕöţáţé ~~]\"],\"editor.lib.hudTitle.select\":[\"[Šéļéçţ ~~]\"],\"editor.lib.hudTitle.smartMeasure\":[\"[Šɱáŕţ ɱéášûŕé ~~~~]\"],\"editor.lib.hudTitle.smoothTerrain\":[\"[Šɱööţĥ ţéŕŕáîñ ~~~~]\"],\"editor.lib.hudTitle.splitKind\":[\"[Šþļîţ \",[\"kind\"],\" ~~]\"],\"editor.lib.hudTitle.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"editor.lib.toolShortcuts.wallsTooltip\":[\"[\",[\"title\"],\" (\",[\"exterior\"],\" éẋţéŕîöŕ, \",[\"interior\"],\" îñţéŕîöŕ) ~~~~~]\"],\"editor.lib.wallDrawVariant.polygonRoom\":[\"[Þöļýĝöñ ŕööɱ ~~~~]\"],\"editor.lib.wallDrawVariant.rectangleRoom\":[\"[Ŕéçţáñĝļé ŕööɱ ~~~~]\"],\"editor.lib.wallDrawVariant.walls\":[\"[Ŵáļļš ~~]\"],\"editor.mechanismAction\":[\"[\",[\"action\",\"select\",{\"close\":[\"Çļöšé\"],\"open\":[\"Öþéñ\"],\"stop\":[\"Šţöþ\"],\"other\":[\"Þļáý\"]}],\" ~~~~~~]\"],\"editor.nodeActionMenu.curve\":[\"[Çûŕṽé ~~]\"],\"editor.nodeActionMenu.cutOut\":[\"[Çûţ öûţ ~~]\"],\"editor.nodeActionMenu.delete\":[\"[Ðéļéţé ~~]\"],\"editor.nodeActionMenu.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.nodeActionMenu.editMesh\":[\"[Éðîţ ɱéšĥ ~~~]\"],\"editor.nodeActionMenu.findInCatalog\":[\"[Ƒîñð îñ çáţáļöĝ ~~~~]\"],\"editor.nodeActionMenu.groupSelection\":[\"[Ĝŕöûþ šéļéçţîöñ ~~~~~]\"],\"editor.nodeActionMenu.move\":[\"[Ṁöṽé ~~]\"],\"editor.nodeActionMenu.rotateLeft\":[\"[Ŕöţáţé ļéƒţ ~~~]\"],\"editor.nodeActionMenu.rotateRight\":[\"[Ŕöţáţé ŕîĝĥţ ~~~~]\"],\"editor.nodeActionMenu.ungroupSelection\":[\"[Ûñĝŕöûþ šéļéçţîöñ ~~~~~]\"],\"editor.nodeDisplay.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.nodeDisplay.column\":[\"[Çöļûɱñ ~~]\"],\"editor.nodeDisplay.door\":[\"[Ðööŕ ~~]\"],\"editor.nodeDisplay.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"editor.nodeDisplay.fence\":[\"[Ƒéñçé ~~]\"],\"editor.nodeDisplay.guide\":[\"[Ĝûîðé îɱáĝé ~~~]\"],\"editor.nodeDisplay.item\":[\"[Îţéɱ ~~]\"],\"editor.nodeDisplay.roof\":[\"[Ŕööƒ ~~]\"],\"editor.nodeDisplay.roofSegment\":[\"[Ŕööƒ šéĝɱéñţ ~~~~]\"],\"editor.nodeDisplay.room\":[\"[Ŕööɱ ~~]\"],\"editor.nodeDisplay.scan\":[\"[3Ð Šçáñ ~~]\"],\"editor.nodeDisplay.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"editor.nodeDisplay.slab\":[\"[Šļáƀ ~~]\"],\"editor.nodeDisplay.stair\":[\"[Šţáîŕ ~~]\"],\"editor.nodeDisplay.stairSegment\":[\"[Šţáîŕ šéĝɱéñţ ~~~~]\"],\"editor.nodeDisplay.wall\":[\"[Ŵáļļ ~~]\"],\"editor.nodeDisplay.window\":[\"[Ŵîñðöŵ ~~]\"],\"editor.nodeDisplay.zone\":[\"[Žöñé ~~]\"],\"editor.panels.buildingFoundationSection.atTheHighSideOfThe\":[\"[Åţ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð, ţö šçáļé ~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.atTheHighSideOfThe2\":[\"[Åţ ţĥé ĥîĝĥ šîðé öƒ ţĥé ĝŕöûñð, ţö šçáļé; ţĥé ðášĥéð ļîñé îš ţĥé ĝŕáðé ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.auto\":[\"[Åûţö ~~]\"],\"editor.panels.buildingFoundationSection.basementSlab\":[\"[Ɓášéɱéñţ šļáƀ ~~~~]\"],\"editor.panels.buildingFoundationSection.basementWall\":[\"[Ɓášéɱéñţ ŵáļļ ~~~~]\"],\"editor.panels.buildingFoundationSection.basementWallHint\":[\"[Þöûŕéð çöñçŕéţé: 8 îñ ƒöŕ ɱöšţ ƀášéɱéñţš (ÎŔÇ Ţáƀļé Ŕ404.1.2(1)). ~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.basementWallItsOwn\":[\"[Ɓášéɱéñţ ŵáļļ (îţš öŵñ) ~~~~~~]\"],\"editor.panels.buildingFoundationSection.basementWallThickness\":[\"[Ɓášéɱéñţ ŵáļļ \",[\"thickness\"],\" ~~~~]\"],\"editor.panels.buildingFoundationSection.basementWallTopOverGrade\":[\"[Ɓášéɱéñţ ŵáļļ ţöþ öṽéŕ ĝŕáðé ~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.basementWallTopOverGradeHint\":[\"[Ṁéášûŕéð öṽéŕ ţĥé ĥîĝĥéšţ ĝŕáðé áļöñĝ ţĥé ĥöûšé: ţĥé šîļļ áñð ţĥé ƒļööŕ ƒŕáɱîñĝ šîţ öñ îţ, 8 îñ öƒƒ ţĥé ĝŕöûñð áţ ļéášţ (ÎŔÇ Ŕ317.1). Šéţ îţ ĥîĝĥéŕ ƒöŕ á ðáýļîĝĥţ ƀášéɱéñţ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.carpet\":[\"[Çáŕþéţ ~~]\"],\"editor.panels.buildingFoundationSection.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.panels.buildingFoundationSection.ceramicTile\":[\"[Çéŕáɱîç ţîļé ~~~~]\"],\"editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA\":[\"[Çļöšé ţĥé öûţšîðé ŵáļļš îñţö á ļööþ ţö ðŕáŵ ţĥé ƒöûñðáţîöñ ûñðéŕ ţĥéɱ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.crawlSpaceUnderJoists\":[\"[Çŕáŵļ šþáçé ûñðéŕ ĵöîšţš ~~~~~~~]\"],\"editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint\":[\"[Ṁéášûŕéð ƒŕöɱ ţĥé ûñðéŕšîðé öƒ ţĥé ĵöîšţš, ŵĥîçĥ ƀéáŕ öñ ţĥé šîļļ (ÎŔÇ Ŕ317.1: 18 îñ ţö ţĥé ðîŕţ). Ţĥé çŕáŵļ šþáçé îš ðûĝ öûţ ţö îţ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.custom\":[\"[Çûšţöɱ ~~]\"],\"editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor\":[\"[Ðéļéţé ţĥé ƀášéɱéñţ áñð îţ šţáñðš öñ îţš ŕáîšéð ƒļööŕ áĝáîñ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.deleteTheBasementSlab\":[\"[Ðéļéţé ţĥé ƀášéɱéñţ áñð îţ šţáñðš öñ îţš šļáƀ áĝáîñ. ~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.depthDeep\":[\"[\",[\"depth\"],\" ðééþ ~~]\"],\"editor.panels.buildingFoundationSection.depthDown\":[\"[\",[\"depth\"],\" ðöŵñ ~~]\"],\"editor.panels.buildingFoundationSection.depthIJoist\":[\"[\",[\"depth\"],\" Î-ĵöîšţ ~~]\"],\"editor.panels.buildingFoundationSection.drawTheFoundation\":[\"[Ðŕáŵ ţĥé ƒöûñðáţîöñ ~~~~~~]\"],\"editor.panels.buildingFoundationSection.drywall\":[\"[Ðŕýŵáļļ ~~~]\"],\"editor.panels.buildingFoundationSection.drywallCeiling\":[\"[Ðŕýŵáļļ çéîļîñĝ ~~~~~]\"],\"editor.panels.buildingFoundationSection.edgeUnderGrade\":[\"[Éðĝé ûñðéŕ ĝŕáðé ~~~~~]\"],\"editor.panels.buildingFoundationSection.edgeUnderGradeHint\":[\"[ÎŔÇ Ŕ403.1.4: áţ ļéášţ 12 îñ, öŕ ţĥé ƒŕöšţ ļîñé. ~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.edgeWidth\":[\"[Éðĝé ŵîðţĥ ~~~]\"],\"editor.panels.buildingFoundationSection.edgeWidthWide\":[\"[Éðĝé \",[\"width\"],\" ŵîðé ~~~]\"],\"editor.panels.buildingFoundationSection.engineeredWood\":[\"[Éñĝîñééŕéð ŵööð ~~~~~]\"],\"editor.panels.buildingFoundationSection.finish\":[\"[Ƒîñîšĥ ~~]\"],\"editor.panels.buildingFoundationSection.finishFloorOverGrade\":[\"[Ƒîñîšĥ ƒļööŕ öṽéŕ ĝŕáðé ~~~~~~]\"],\"editor.panels.buildingFoundationSection.finishFloorOverGradeHint\":[\"[Ṁéášûŕéð öṽéŕ ţĥé ĥîĝĥéšţ ĝŕáðé ûñðéŕ ţĥé öûţšîðé ŵáļļš. Ţĥé ƒöûñðáţîöñ ĝŕöŵš ðöŵñ ţĥé šļöþé ƒŕöɱ ţĥéŕé. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.floorFraming\":[\"[Ƒļööŕ ƒŕáɱîñĝ ~~~~]\"],\"editor.panels.buildingFoundationSection.floorFramingAndSill\":[\"[Ƒļööŕ ƒŕáɱîñĝ áñð šîļļ \",[\"depth\"],\" ~~~~~~]\"],\"editor.panels.buildingFoundationSection.floorFramingHint\":[\"[Ƒļööŕ ţöþ ðöŵñ ţö ţĥé šţéɱ: ƒļööŕîñĝ, šûƀƒļööŕ, ĵöîšţš, šîļļ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.floorFramingSillIncluded\":[\"[Ƒļööŕ ƒŕáɱîñĝ \",[\"depth\"],\", šîļļ \",[\"sill\"],\" îñçļûðéð ~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.flooring\":[\"[Ƒļööŕîñĝ ~~~]\"],\"editor.panels.buildingFoundationSection.footingThickness\":[\"[Ƒööţîñĝ ţĥîçķñéšš ~~~~~]\"],\"editor.panels.buildingFoundationSection.footingUnderGrade\":[\"[Ƒööţîñĝ ûñðéŕ ĝŕáðé ~~~~~~]\"],\"editor.panels.buildingFoundationSection.footingUnderGradeHint\":[\"[ÎŔÇ Ŕ403.1.4: áţ ļéášţ 12 îñ ûñðéŕ ţĥé ĝŕöûñð ƀéšîðé îţ, öûţšîðé áñð îñ ţĥé ðûĝ-öûţ çŕáŵļ šþáçé; ðééþéŕ ţö ţĥé ƒŕöšţ ļîñé. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.footingWidth\":[\"[Ƒööţîñĝ ŵîðţĥ ~~~~]\"],\"editor.panels.buildingFoundationSection.footingWidthHint\":[\"[Ţáƀļé Ŕ403.1(1), 1500 þšƒ šöîļ: 12, 15, 23 îñ ƒöŕ 1, 2, 3 šţöŕéýš. ~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.footingWidthThickness\":[\"[Ƒööţîñĝ \",[\"width\"],\" × \",[\"thickness\"],\" ~~~]\"],\"editor.panels.buildingFoundationSection.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.panels.buildingFoundationSection.gradeBeamDepth\":[\"[Ĝŕáðé ƀéáɱ ðéþţĥ ~~~~~]\"],\"editor.panels.buildingFoundationSection.gradeBeamOnPiles\":[\"[Ĝŕáðé ƀéáɱ öñ þîļéš ~~~~~]\"],\"editor.panels.buildingFoundationSection.gradeBeamWidth\":[\"[Ĝŕáðé ƀéáɱ ŵîðţĥ ~~~~~]\"],\"editor.panels.buildingFoundationSection.gradeBeamWidthDepth\":[\"[Ĝŕáðé ƀéáɱ \",[\"width\"],\" × \",[\"depth\"],\" ~~~]\"],\"editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder\":[\"[Ĥáļƒ ţĥé éðĝé’š ðéþţĥ ûñðéŕ ţĥé šļáƀ, 6 ţö 12 îñ ~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.hardwood\":[\"[Ĥáŕðŵööð ~~~]\"],\"editor.panels.buildingFoundationSection.heightCrawl\":[\"[\",[\"height\"],\" çŕáŵļ ~~]\"],\"editor.panels.buildingFoundationSection.heightFinishFloor\":[\"[\",[\"height\"],\" ƑƑ ~]\"],\"editor.panels.buildingFoundationSection.heightTop\":[\"[\",[\"height\"],\" ţöþ ~]\"],\"editor.panels.buildingFoundationSection.iJoists\":[\"[Î-ĵöîšţš ~~~]\"],\"editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill\":[\"[Îţš ļáýéŕš ɱáķé \",[\"depth\"],\" ŵîţĥ ţĥé šîļļ; ţĥé ƒŕáɱîñĝ ƒöļļöŵš ţĥéɱ öñçé á ļáýéŕ îš šéţ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe\":[\"[Îţš ŵáļļš áŕé þöûŕéð ûñðéŕ ţĥé ĥöûšé’š öûţšîðé ŵáļļš. ~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.itsWallsAreTheBasementS\":[\"[Îţš ŵáļļš áŕé ţĥé ƀášéɱéñţ’š öŵñ, öñ ţĥéîŕ ƒööţîñĝ. ~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.joists\":[\"[Ĵöîšţš ~~]\"],\"editor.panels.buildingFoundationSection.kind\":[\"[Ķîñð ~~]\"],\"editor.panels.buildingFoundationSection.labelAuto\":[\"[\",[\"label\"],\" (áûţö) ~~]\"],\"editor.panels.buildingFoundationSection.laminate\":[\"[Ļáɱîñáţé ~~~]\"],\"editor.panels.buildingFoundationSection.leastDepthUnderGrade\":[\"[Ļéášţ ðéþţĥ ûñðéŕ ĝŕáðé ~~~~~~]\"],\"editor.panels.buildingFoundationSection.leastDepthUnderGradeHint\":[\"[Ţĥé ƀéáɱ ĝöéš áţ ļéášţ ţĥîš ƒáŕ ûñðéŕ ĝŕáðé (ÎŔÇ Ŕ403.1.4: 12 îñ, öŕ ţĥé ƒŕöšţ ļîñé); á ðééþéŕ ƀéáɱ ĝöéš ƒûŕţĥéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.leastStepRun\":[\"[Ļéášţ šţéþ ŕûñ ~~~~]\"],\"editor.panels.buildingFoundationSection.leastStepRunHint\":[\"[ÎŔÇ Ŕ403.1.5: ţĥé ţöþ öƒ á ƒööţîñĝ îš ļéṽéļ; îţ šţéþš ŵĥéŕé ţĥé ĝŕöûñð ƒáļļš. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.lumber\":[\"[Ļûɱƀéŕ ~~]\"],\"editor.panels.buildingFoundationSection.nameTheMaterial\":[\"[Ñáɱé ţĥé ɱáţéŕîáļ ~~~~~]\"],\"editor.panels.buildingFoundationSection.off\":[\"[Öƒƒ ~]\"],\"editor.panels.buildingFoundationSection.osb\":[\"[ÖŠƁ ~]\"],\"editor.panels.buildingFoundationSection.osbSubfloor\":[\"[ÖŠƁ šûƀƒļööŕ ~~~~]\"],\"editor.panels.buildingFoundationSection.panel\":[\"[Þáñéļ ~~]\"],\"editor.panels.buildingFoundationSection.pileDiameter\":[\"[Þîļé ðîáɱéţéŕ ~~~~]\"],\"editor.panels.buildingFoundationSection.pileDiameterHint\":[\"[Þîļéš áŕé šîžéð ƀý ţĥé éñĝîñééŕ. ~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.pileDiameterSpacingDeep\":[\"[Þîļé \",[\"diameter\"],\" @ \",[\"spacing\"],\", \",[\"length\"],\" ðééþ ~~~]\"],\"editor.panels.buildingFoundationSection.pileLength\":[\"[Þîļé ļéñĝţĥ ~~~]\"],\"editor.panels.buildingFoundationSection.pileSpacing\":[\"[Þîļé šþáçîñĝ ~~~~]\"],\"editor.panels.buildingFoundationSection.plaster\":[\"[Þļášţéŕ ~~~]\"],\"editor.panels.buildingFoundationSection.plasterCeiling\":[\"[Þļášţéŕ çéîļîñĝ ~~~~~]\"],\"editor.panels.buildingFoundationSection.plywood\":[\"[Þļýŵööð ~~~]\"],\"editor.panels.buildingFoundationSection.plywoodSubfloor\":[\"[Þļýŵööð šûƀƒļööŕ ~~~~~]\"],\"editor.panels.buildingFoundationSection.preset\":[\"[Þŕéšéţ ~~]\"],\"editor.panels.buildingFoundationSection.raisedFloor\":[\"[Ŕáîšéð ƒļööŕ ~~~~]\"],\"editor.panels.buildingFoundationSection.sectionBasement\":[\"[Ƒöûñðáţîöñ šéçţîöñ: ƀášéɱéñţ, îţš ŵáļļ'š ţöþ \",[\"top\"],\" öṽéŕ ĝŕáðé, îţš ƒļööŕ \",[\"floor\"],\" ûñðéŕ îţ ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.sectionRaisedFloor\":[\"[Ƒöûñðáţîöñ šéçţîöñ: ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ, ƒîñîšĥ ƒļööŕ \",[\"floor\"],\" öṽéŕ ĝŕáðé, çŕáŵļ šþáçé \",[\"crawl\"],\" ûñðéŕ ţĥé ĵöîšţš ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles\":[\"[Ƒöûñðáţîöñ šéçţîöñ: ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ öñ þîļéš, ƒîñîšĥ ƒļööŕ \",[\"floor\"],\" öṽéŕ ĝŕáðé, çŕáŵļ šþáçé \",[\"crawl\"],\" ûñðéŕ ţĥé ĵöîšţš ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.sectionSlabOnGrade\":[\"[Ƒöûñðáţîöñ šéçţîöñ: šļáƀ öñ ĝŕáðé, ƒîñîšĥ ƒļööŕ \",[\"floor\"],\" öṽéŕ ĝŕáðé ~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles\":[\"[Ƒöûñðáţîöñ šéçţîöñ: šļáƀ öñ ĝŕáðé öñ þîļéš, ƒîñîšĥ ƒļööŕ \",[\"floor\"],\" öṽéŕ ĝŕáðé ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.sill\":[\"[Šîļļ ~~]\"],\"editor.panels.buildingFoundationSection.size\":[\"[Šîžé ~~]\"],\"editor.panels.buildingFoundationSection.sizeJoists\":[\"[\",[\"size\"],\" ĵöîšţš ~~]\"],\"editor.panels.buildingFoundationSection.slab\":[\"[Šļáƀ ~~]\"],\"editor.panels.buildingFoundationSection.slabHint\":[\"[ÎŔÇ Ŕ506.1 ášķš 3½ îñ áţ ļéášţ. ~~~~~~]\"],\"editor.panels.buildingFoundationSection.slabOnGrade\":[\"[Šļáƀ öñ ĝŕáðé ~~~~]\"],\"editor.panels.buildingFoundationSection.slabThickness\":[\"[Šļáƀ \",[\"thickness\"],\" ~~]\"],\"editor.panels.buildingFoundationSection.slabTopOverGrade\":[\"[Šļáƀ ţöþ öṽéŕ ĝŕáðé ~~~~~]\"],\"editor.panels.buildingFoundationSection.slabTopOverGradeHint\":[\"[Ţĥé ĝŕöûñð ŕöûñð ţĥé šļáƀ îš ĝŕáðéð ţö ţĥîš öñ éṽéŕý šîðé: ƒîļļéð ŵĥéŕé îţ ƒáļļš áŵáý, çûţ ŵĥéŕé îţ ŕîšéš (ÎŔÇ Ŕ317.1: 8 îñ). ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"editor.panels.buildingFoundationSection.spacingOnCentre\":[\"[\",[\"spacing\"],\" ö.ç. ~]\"],\"editor.panels.buildingFoundationSection.stemTopOverGrade\":[\"[Šţéɱ ţöþ öṽéŕ ĝŕáðé ~~~~~]\"],\"editor.panels.buildingFoundationSection.stemTopOverGradeHint\":[\"[ÎŔÇ Ŕ317.1 ķééþš ŵööð ƒŕáɱîñĝ 8 îñ öƒƒ ţĥé ĝŕöûñð. ~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.stemWall\":[\"[Šţéɱ ŵáļļ ~~~]\"],\"editor.panels.buildingFoundationSection.stemWallAndFooting\":[\"[Šţéɱ ŵáļļ áñð ƒööţîñĝ ~~~~~~]\"],\"editor.panels.buildingFoundationSection.stemWallHint\":[\"[Ţáƀļé Ŕ404.1.2: 6 îñ ƒöŕ öñé šţöŕéý, 8 îñ ƒöŕ ɱöŕé. ~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.stemWallThickness\":[\"[Šţéɱ ŵáļļ \",[\"thickness\"],\" ~~~]\"],\"editor.panels.buildingFoundationSection.stepDownTheSlope\":[\"[Šţéþ ðöŵñ ţĥé šļöþé ~~~~~]\"],\"editor.panels.buildingFoundationSection.stepHeight\":[\"[Šţéþ ĥéîĝĥţ ~~~]\"],\"editor.panels.buildingFoundationSection.subfloorSheathing\":[\"[Šûƀƒļööŕ šĥéáţĥîñĝ ~~~~~~]\"],\"editor.panels.buildingFoundationSection.tallestStemBeforeStepping\":[\"[Ţáļļéšţ šţéɱ ƀéƒöŕé šţéþþîñĝ ~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint\":[\"[Å šîðé ŵĥöšé šţéɱ ŵöûļð šţáñð ţáļļéŕ öûţ öƒ ţĥé ĝŕöûñð šţéþš ðöŵñ ţĥé šļöþé: îţš šţéɱ ðŕöþš ţö îţš ţöþ öṽéŕ ĝŕáðé, á çŕîþþļé ŵáļļ öṽéŕ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.taper\":[\"[Ţáþéŕ ~~]\"],\"editor.panels.buildingFoundationSection.taperAt45\":[\"[Ţáþéŕ \",[\"taper\"],\" áţ 45° ~~~]\"],\"editor.panels.buildingFoundationSection.taperHint\":[\"[Ţĥé éðĝé’š îñšîðé ƒáçé šţáñðš þļûɱƀ ƒŕöɱ îţš ƀöţţöɱ, ţĥéñ ţáþéŕš îñ áţ 45° ţö ţĥé šļáƀ. Åûţö ŕûñš îţ ĥáļƒ ţĥé éðĝé’š ðéþţĥ ûñðéŕ ţĥé šļáƀ, 6 ţö 12 îñ, šö á ðééþéŕ éðĝé ĝŕöŵš þļûɱƀ, ñöţ ŵîðéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.theHouseStandsOnABasement\":[\"[Ţĥé ĥöûšé šţáñðš öñ á ƀášéɱéñţ: îţš ŵáļļš áŕé ţĥé ƒöûñðáţîöñ ŵáļļš, šö öñļý ţĥéîŕ ƒööţîñĝ îš ðŕáŵñ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement\":[\"[Ţĥé ĥöûšé šţáñðš öñ îţš ƀášéɱéñţ, çûţ îñţö ţĥé ĝŕöûñð: ţĥé ĝŕöûñð ŕöûñð îţ îš ļéƒţ áš îţ ļîéš. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.buildingFoundationSection.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"editor.panels.buildingFoundationSection.titleMaterial\":[\"[\",[\"title\"],\" ɱáţéŕîáļ ~~~]\"],\"editor.panels.buildingFoundationSection.turnedDownEdge\":[\"[Ţûŕñéð-ðöŵñ éðĝé ~~~~~]\"],\"editor.panels.buildingFoundationSection.vinylPlank\":[\"[Ṽîñýļ þļáñķ ~~~]\"],\"editor.panels.buildingFoundationSection.woodPlank\":[\"[Ŵööð þļáñķ ~~~]\"],\"editor.panels.buildingFoundationSection.woodPlankCeiling\":[\"[Ŵööð þļáñķ çéîļîñĝ ~~~~~]\"],\"editor.panels.collections.collectionsPopover.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.panels.collections.collectionsPopover.collectionName\":[\"[Çöļļéçţîöñ ñáɱé… ~~~~~]\"],\"editor.panels.collections.collectionsPopover.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.panels.collections.collectionsPopover.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.collections.collectionsPopover.deleteName\":[\"[Ðéļéţé \\\"\",[\"name\"],\"\\\"? ~~]\"],\"editor.panels.collections.collectionsPopover.new\":[\"[Ñéŵ ~]\"],\"editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo\":[\"[Ñö çöļļéçţîöñš ýéţ. Çŕéáţé öñé ţö ĝŕöûþ éļéɱéñţš ţöĝéţĥéŕ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.collections.collectionsPopover.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.panels.deckBoardsFields.alongHouse\":[\"[Åļöñĝ ĥöûšé ~~~]\"],\"editor.panels.deckBoardsFields.boards\":[\"[Ɓöáŕðš ~~]\"],\"editor.panels.deckBoardsFields.boardsSummary\":[\"[\",[\"label\"],\" ƀöáŕðš \",[\"thickness\"],\" × \",[\"width\"],\" îñ, \",[\"gap\"],\" îñ áþáŕţ, \",[\"direction\",\"select\",{\"parallel\":[\"áļöñĝ ţĥé ĥöûšé, öñ ĵöîšţš šǫûáŕé ţö îţ\"],\"diagonal\":[\"áţ 45° ţö ţĥé ĥöûšé\"],\"other\":[\"öûţ ƒŕöɱ ţĥé ĥöûšé, öñ ĵöîšţš áļöñĝ îţ, çáŕŕîéð ƀý ƀéáɱš šǫûáŕé ţö ţĥé ĥöûšé\"]}],\"; ĵöîšţš \",[\"spacing\"],\" îñ öñ çéñţŕé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.deckBoardsFields.deckBoards\":[\"[Ðéçķ ƀöáŕðš ~~~]\"],\"editor.panels.deckBoardsFields.diagonal\":[\"[Ðîáĝöñáļ ~~~]\"],\"editor.panels.deckBoardsFields.gap\":[\"[Ĝáþ ~]\"],\"editor.panels.deckBoardsFields.outFromHouse\":[\"[Öûţ ƒŕöɱ ĥöûšé ~~~~]\"],\"editor.panels.deckBoardsFields.run\":[\"[Ŕûñ ~]\"],\"editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe\":[\"[Å ţĥîçķéŕ ƒļööŕ ĝŕöŵš ûþŵáŕð: ţĥé ŕööɱš ƀéļöŵ ķééþ ţĥéîŕ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.buildUpFollows\":[\"[Îţš ļáýéŕš ɱáķé \",[\"thickness\"],\"; ţĥé ţĥîçķñéšš ƒöļļöŵš ţĥéɱ öñ ţĥé ƒîŕšţ çĥáñĝé. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.edgeFinish\":[\"[Éðĝé ƒîñîšĥ ~~~]\"],\"editor.panels.floorFoundationPanel.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.panels.floorFoundationPanel.floorBuildUp\":[\"[Ƒļööŕ ƀûîļð-ûþ ~~~~]\"],\"editor.panels.floorFoundationPanel.floorPlate\":[\"[Ƒļööŕ þļáţé ~~~]\"],\"editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn\":[\"[Ƒļööŕ þļáţé \",[\"thicknessLabel\"],\" ţĥîçķ, ŕéšţîñĝ öñ ţĥé ŵáļļš ƀéļöŵ ~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.floorThicknessValue\":[\"[Ƒļööŕ ţĥîçķñéšš \",[\"thickness\"],\" ~~~~~]\"],\"editor.panels.floorFoundationPanel.floorTopAbove\":[\"[Ƒļööŕ ţöþ +\",[\"height\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.floorTopValue\":[\"[Ƒļööŕ ţöþ \",[\"height\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.foundationFinish\":[\"[Ƒöûñðáţîöñ ƒîñîšĥ ~~~~~]\"],\"editor.panels.floorFoundationPanel.foundationHeight\":[\"[Ƒöûñðáţîöñ ĥéîĝĥţ ~~~~~]\"],\"editor.panels.floorFoundationPanel.kicker\":[\"[Ƒļööŕ þļáţé ~~~]\"],\"editor.panels.floorFoundationPanel.kickerLevel\":[\"[Ƒļööŕ þļáţé · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.kickerRestsOn\":[\"[Ƒļööŕ þļáţé · \",[\"level\"],\" · ŕéšţš öñ \",[\"below\"],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.lowerTogether\":[\"[\",[\"count\",\"plural\",{\"2\":[\"Ļöŵéŕ ƀöţĥ\"],\"other\":[\"Ļöŵéŕ áļļ \",[\"number\"]]}],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.notToScale\":[\"[Ñöţ ţö šçáļé ~~~]\"],\"editor.panels.floorFoundationPanel.onTheGround\":[\"[Öñ ţĥé ĝŕöûñð ~~~~]\"],\"editor.panels.floorFoundationPanel.raiseTogether\":[\"[\",[\"count\",\"plural\",{\"2\":[\"Ŕáîšé ƀöţĥ\"],\"other\":[\"Ŕáîšé áļļ \",[\"number\"]]}],\" ~~~~~~]\"],\"editor.panels.floorFoundationPanel.raisedOnAFoundation\":[\"[Ŕáîšéð öñ á ƒöûñðáţîöñ ~~~~~~]\"],\"editor.panels.floorFoundationPanel.section.floorPlate\":[\"[Ƒļööŕ þļáţé · \",[\"thickness\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.section.floorTop\":[\"[Ƒļööŕ ţöþ ~~~]\"],\"editor.panels.floorFoundationPanel.section.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.panels.floorFoundationPanel.section.foundationKind\":[\"[Ƒöûñðáţîöñ · \",[\"foundation\"],\" ~~~]\"],\"editor.panels.floorFoundationPanel.section.ground\":[\"[Ĝŕöûñð ~~]\"],\"editor.panels.floorFoundationPanel.section.layer\":[[\"layer\"],\" · \",[\"size\"]],\"editor.panels.floorFoundationPanel.section.slab\":[\"[Šļáƀ · \",[\"thickness\"],\" ~~]\"],\"editor.panels.floorFoundationPanel.section.wallsBelow\":[\"[Ŵáļļš ƀéļöŵ ~~~]\"],\"editor.panels.floorFoundationPanel.sectionLayerSize\":[[\"layer\"],\" \",[\"size\"]],\"editor.panels.floorFoundationPanel.sectionLayers\":[\"[Ƒļööŕ þļáţé \",[\"thickness\"],\" ţĥîçķ: \",[\"layers\"],\"; ŕéšţîñĝ öñ ţĥé ŵáļļš ƀéļöŵ ~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.sectionOnGround\":[\"[Šļáƀ \",[\"thickness\"],\" ţĥîçķ öñ ţĥé ĝŕöûñð, ƒļööŕ ţöþ \",[\"top\"],\" áƀöṽé ţĥé ĝŕöûñð ~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.sectionRaised\":[\"[Šļáƀ \",[\"thickness\"],\" ţĥîçķ öñ á \",[\"foundation\"],\" ƒöûñðáţîöñ, ƒļööŕ ţöþ \",[\"top\"],\" áƀöṽé ţĥé ĝŕöûñð ~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.slab\":[\"[Šļáƀ ~~]\"],\"editor.panels.floorFoundationPanel.slabThickness\":[\"[Šļáƀ ţĥîçķñéšš ~~~~]\"],\"editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll\":[\"[Ţĥé ƒļööŕ'š öûţšîðé éðĝé, áļļ áŕöûñð ţĥé ƒööţþŕîñţ. ~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround\":[\"[Ţĥé ƒöûñðáţîöñ šţáñðš öñ ţĥé ĝŕöûñð; ţĥé šļáƀ šîţš öñ îţ. ~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation\":[\"[Ţĥé šļáƀ šîţš öñ ţĥé ƒöûñðáţîöñ (öŕ ţĥé ĝŕöûñð): á ţĥîçķéŕ šļáƀ ŕáîšéš ţĥé ƒļööŕ ţöþ áñð éṽéŕýţĥîñĝ öñ îţ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.floorFoundationPanel.underTheFloor\":[\"[Ûñðéŕ ţĥé ƒļööŕ ~~~~]\"],\"editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt\":[\"[Å ĥáţçĥ îñ ţĥé ɱéžžáñîñé: îţ çûţš öñļý ţĥé ɱéžžáñîñé’š ƒļööŕ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow\":[\"[Åļšö çûţ ţĥé çéîļîñĝ ƀéļöŵ ~~~~~~~]\"],\"editor.panels.floorOpeningPanel.alsoCutTheFloorAbove\":[\"[Åļšö çûţ ţĥé ƒļööŕ áƀöṽé ~~~~~~]\"],\"editor.panels.floorOpeningPanel.areaDragCorners\":[\"[\",[\"area\"],\" · Ðŕáĝ îţš çöŕñéŕš ţö çĥáñĝé ţĥé šĥáþé. ~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.areaOwned\":[\"[\",[\"area\"],\" · Šĥáþé ƒöļļöŵš îţš öŵñéŕ. ~~~~~~]\"],\"editor.panels.floorOpeningPanel.areaStairResize\":[\"[\",[\"area\"],\" · Ðŕáĝ îţš šîðéš öŕ çöŕñéŕš ţö ŕéšîžé îţ; îţ ɱöṽéš ŵîţĥ ţĥé šţáîŕ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.floorOpeningPanel.done\":[\"[Ðöñé ~~]\"],\"editor.panels.floorOpeningPanel.kicker\":[\"[Öþéñîñĝ ~~~]\"],\"editor.panels.floorOpeningPanel.kickerLevel\":[\"[Öþéñîñĝ · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.kickerRoom\":[\"[Öþéñîñĝ · îñ \",[\"room\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.kickerRoomLevel\":[\"[Öþéñîñĝ · îñ \",[\"room\"],\" · \",[\"level\"],\" ~~~]\"],\"editor.panels.floorOpeningPanel.oneVoidWith\":[\"[Öñ \",[\"level\"],\", áš öñé ṽöîð ŵîţĥ ţĥîš öþéñîñĝ. ~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.ownedByElevator\":[\"[Öŵñéð ƀý Éļéṽáţöŕ ~~~~~]\"],\"editor.panels.floorOpeningPanel.ownedByStaircase\":[\"[Öŵñéð ƀý Šţáîŕçášé ~~~~~]\"],\"editor.panels.floorOpeningPanel.room\":[\"[Ŕööɱ ~~]\"],\"editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis\":[\"[Ţĥéŕé îš ñö ƒļööŕ áƀöṽé ţĥîš šţöŕéý. ~~~~~~~~~]\"],\"editor.panels.floorOpeningPanel.thereIsNoStoreyBelow\":[\"[Ţĥéŕé îš ñö šţöŕéý ƀéļöŵ. ~~~~~~]\"],\"editor.panels.floorOpeningPanel.title\":[\"[\",[\"source\",\"select\",{\"stair\":[\"Šţáîŕ öþéñîñĝ\"],\"elevator\":[\"Éļéṽáţöŕ öþéñîñĝ\"],\"ceiling\":[\"Çéîļîñĝ öþéñîñĝ\"],\"other\":[\"Ƒļööŕ öþéñîñĝ\"]}],\" ~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.aRaisedFloorOnAStem\":[\"[Å ŕáîšéð ƒļööŕ öñ á šţéɱ ŵáļļ öƒ îţš öŵñ. ~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.aSlabOfItsOwn\":[\"[Å šļáƀ öƒ îţš öŵñ (á þöŕçĥ, á þáţîö, á ĝáŕáĝé þáð), ţĥé ĝŕöûñð ĥéļð 8 îñ ûñðéŕ îţš ţöþ áļļ ŕöûñð. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.clickItsCornersClickTheFirst\":[\"[Çļîçķ îţš çöŕñéŕš; çļîçķ ţĥé ƒîŕšţ áĝáîñ, öŕ þŕéšš Éñţéŕ, ţö çļöšé îţ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA\":[\"[Çļöšé ţĥé öûţšîðé ŵáļļš îñţö á ļööþ ƒîŕšţ, öŕ ðŕáŵ öñé ~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.deleteThisOutline\":[\"[Ðéļéţé ţĥîš öûţļîñé ~~~~~~]\"],\"editor.panels.foundationViewPanel.draw\":[\"[Ðŕáŵ ~~]\"],\"editor.panels.foundationViewPanel.editItMyself\":[\"[Éðîţ îţ ɱýšéļƒ ~~~~]\"],\"editor.panels.foundationViewPanel.followTheWalls\":[\"[Ƒöļļöŵ ţĥé ŵáļļš ~~~~~]\"],\"editor.panels.foundationViewPanel.footingsDrawn\":[\"[\",[\"footings\",\"plural\",{\"one\":[\"#\",\" ƒööţîñĝ\"],\"other\":[\"#\",\" ƒööţîñĝš\"]}],\" ðŕáŵñ: çļîçķ öñé îñ ţĥé þļáñ ţö šîžé öŕ ɱöṽé îţ. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.panels.foundationViewPanel.houseFoundation\":[\"[Ĥöûšé ƒöûñðáţîöñ ~~~~~]\"],\"editor.panels.foundationViewPanel.howFarItsTopSitsUnder\":[\"[Ĥöŵ ƒáŕ îţš ţöþ šîţš ûñðéŕ ţĥé ĥöûšé’š ƒļööŕ: á þöŕçĥ šļáƀ šţéþš ðöŵñ ƒŕöɱ ţĥé ðööŕ ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd\":[\"[Îţš öŵñ: ðŕáĝ îţš çöŕñéŕš áñð šîðéš îñ ţĥé þļáñ öŕ îñ 3Ð. Ṁöṽîñĝ ţĥé ŵáļļš ļéáṽéš îţ ƀé. ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.outline\":[\"[Öûţļîñé ~~~]\"],\"editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors\":[\"[Öûţļîñéš öƒ šļáƀš áñð ŕáîšéð ƒļööŕš (þöŕçĥéš, þáţîöš, á ĝáŕáĝé þáð), áñð ƒööţîñĝš ûñðéŕ îñţéŕîöŕ ƀéáŕîñĝ ŵáļļš áñð þöšţš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.raisedFloor\":[\"[Ŕáîšéð ƒļööŕ ~~~~]\"],\"editor.panels.foundationViewPanel.reTraceFromWalls\":[\"[Ŕé-ţŕáçé ƒŕöɱ ŵáļļš ~~~~~]\"],\"editor.panels.foundationViewPanel.setTheOutlineBackToThe\":[\"[Šéţ ţĥé öûţļîñé ƀáçķ ţö ţĥé öûţšîðé ƒáçéš öƒ ţĥé ŵáļļš, ķééþîñĝ îţ ýöûŕ öŵñ ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.slab\":[\"[Šļáƀ ~~]\"],\"editor.panels.foundationViewPanel.theHouses\":[\"[Ţĥé ĥöûšé’š ~~~]\"],\"editor.panels.foundationViewPanel.theHousesOwnFoundation\":[\"[Ţĥé ĥöûšé’š öŵñ ƒöûñðáţîöñ, þöûŕéð áš ţĥé ĥöûšé’š šéţţîñĝš ƀéļöŵ šáý, îñ þļáçé öƒ ţĥé ŵáļļš’ öûţļîñé. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.foundationViewPanel.thisOutline\":[\"[Ţĥîš öûţļîñé ~~~~]\"],\"editor.panels.foundationViewPanel.topUnderTheFloor\":[\"[Ţöþ ûñðéŕ ţĥé ƒļööŕ ~~~~~]\"],\"editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt\":[\"[Ţŕáçéð ƒŕöɱ ţĥé öûţšîðé ŵáļļš: îţ ɱöṽéš ŵĥéñ ţĥéý ðö. ~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.alreadyJoined\":[\"[Öñé öƒ ţĥéɱ áļŕéáðý ŵŕáþš áñöţĥéŕ çöŕñéŕ. Ûñŵŕáþ îţ ƒîŕšţ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.between\":[\"[\",[\"kind\",\"select\",{\"item\":[\"Ţĥéŕé îš áñ îţéɱ ƀéţŵééñ ţĥéɱ.\"],\"door\":[\"Ţĥéŕé îš á ðööŕ ƀéţŵééñ ţĥéɱ.\"],\"window\":[\"Ţĥéŕé îš á ŵîñðöŵ ƀéţŵééñ ţĥéɱ.\"],\"shelf\":[\"Ţĥéŕé îš á šĥéļƒ ƀéţŵééñ ţĥéɱ.\"],\"other\":[\"Ţĥéŕé îš šöɱéţĥîñĝ ƀéţŵééñ ţĥéɱ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.cannotMerge\":[\"[Ţĥéšé ŵîñðöŵš çáññöţ ƀé ɱéŕĝéð. ~~~~~~~~]\"],\"editor.panels.mergeRefusal.cornerAngle\":[\"[Ţĥéîŕ ŵáļļš ɱééţ áţ ţöö šĥáŕþ öŕ ţöö ƒļáţ áñ áñĝļé ƒöŕ á çöŕñéŕ ŵîñðöŵ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.heightExceedsWall\":[\"[Ţĥé ŵîñðöŵ šéļéçţéð ƒîŕšţ îš ţáļļéŕ ţĥáñ ţĥé öţĥéŕ ŵáļļ. Šéļéçţ ţĥé öţĥéŕ öñé ƒîŕšţ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notAdjacent\":[\"[Ţĥéý áŕé ţöö ƒáŕ áþáŕţ. Ŵîñðöŵš ɱéŕĝé ŵîţĥ áţ ɱöšţ \",[\"reach\"],\" ƀéţŵééñ ţĥéɱ. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notAtCorner\":[\"[Öñé öƒ ţĥéɱ îš ţöö ƒáŕ ƒŕöɱ ţĥé çöŕñéŕ. Å ŵîñðöŵ ĵöîñš öñé ŵîţĥîñ \",[\"reach\"],\" öƒ îţ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.notOnWall\":[\"[Å ŕööƒ öŕ ðöŕɱéŕ ŵîñðöŵ çáññöţ ĵöîñ áñöţĥéŕ ŵîñðöŵ. ~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.overlap\":[\"[\",[\"kind\",\"select\",{\"item\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ áñ îţéɱ.\"],\"door\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á ðööŕ.\"],\"window\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á ŵîñðöŵ.\"],\"shelf\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ á šĥéļƒ.\"],\"other\":[\"Šļîðîñĝ ţĥéɱ ţö ţĥé çöŕñéŕ ŵöûļð öṽéŕļáþ šöɱéţĥîñĝ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.mergeRefusal.wallsNotMeeting\":[\"[Ţĥéîŕ ŵáļļš ðö ñöţ ɱééţ áţ á çöŕñéŕ. ~~~~~~~~~]\"],\"editor.panels.mixed\":[\"[Ṁîẋéð ~~]\"],\"editor.panels.mobilePanelSheet.close\":[\"[Çļöšé ~~]\"],\"editor.panels.mobileSelectionBar.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.mobileSelectionBar.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.panels.mobileSelectionBar.editProperties\":[\"[Éðîţ þŕöþéŕţîéš ~~~~~]\"],\"editor.panels.mobileSelectionBar.editResolvedlabel\":[\"[Éðîţ \",[\"resolvedLabel\"],\" ~~]\"],\"editor.panels.mobileSelectionBar.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.multiHeightMode.currentlyCurrentlabel\":[\"[Çûŕŕéñţļý \",[\"currentLabel\"],\" ~~~]\"],\"editor.panels.multiHeightMode.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"editor.panels.multiHeightMode.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"editor.panels.multiHeightMode.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.multiHeightMode.top\":[\"[Ţöþ ~]\"],\"editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects\":[\"[\",[\"label\"],\" (šéššîöñ öñļý). Þļáîñ çļîçķ ŕéšéļéçţš áļļ ɱéɱƀéŕš. Ñöţ šáṽéð ŵîţĥ ţĥé þŕöĵéçţ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.multiSelectionPanel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.panels.multiSelectionPanel.group\":[\"[Ĝŕöûþ ~~]\"],\"editor.panels.multiSelectionPanel.groupCtrlCmdG\":[\"[Ĝŕöûþ (Çţŕļ/Çɱð+Ĝ) ~~~~]\"],\"editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects\":[\"[\",[\"label\"],\" (šéššîöñ öñļý). Þļáîñ çļîçķ ŕéšéļéçţš áļļ ɱéɱƀéŕš. Ñöţ šáṽéð ŵîţĥ ţĥé þŕöĵéçţ. ~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.lengthSelected\":[\"[\",[\"length\"],\" šéļéçţéð ~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindows\":[\"[Ṁéŕĝé ŵîñðöŵš ~~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow\":[\"[Ṁéŕĝé ŵîñðöŵš îñţö á çöŕñéŕ ŵîñðöŵ: ƀöţĥ ţáķé ţĥé ĥéîĝĥţ, šîļļ áñð ţýþé öƒ ţĥé öñé ýöû šéļéçţéð ƒîŕšţ ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth\":[\"[Ṁéŕĝé ŵîñðöŵš îñţö öñé áçŕöšš ƀöţĥ: îţ ķééþš ţĥé ţýþé áñð šţýļé öƒ ţĥé öñé ýöû šéļéçţéð ƒîŕšţ ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.selectBuilding\":[\"[Šéļéçţ ƀûîļðîñĝ ~~~~~]\"],\"editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove\":[\"[Šéļéçţ ţĥé ŵĥöļé ƀûîļðîñĝ ţö ɱöṽé áļļ îţš ļéṽéļš ~~~~~~~~~~~~]\"],\"editor.panels.multiSelectionPanel.ungroup\":[\"[Ûñĝŕöûþ ~~~]\"],\"editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG\":[\"[Ûñĝŕöûþ (Çţŕļ/Çɱð+Šĥîƒţ+Ĝ) ~~~~~~]\"],\"editor.panels.multiSelectionPanel.unwrapTheCorner\":[\"[Ûñŵŕáþ ţĥé çöŕñéŕ ~~~~~]\"],\"editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay\":[\"[Ûñŵŕáþ ţĥé çöŕñéŕ: ƀöţĥ ŵîñðöŵš šţáý, éáçĥ ƒŕáɱéð áţ ţĥé çöŕñéŕ ~~~~~~~~~~~~~~~~]\"],\"editor.panels.panelManager.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"editor.panels.panelManager.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"editor.panels.panelWrapper.back\":[\"[Ɓáçķ ~~]\"],\"editor.panels.panelWrapper.close\":[\"[Çļöšé ~~]\"],\"editor.panels.panelWrapper.closeTitle\":[\"[Çļöšé \",[\"title\"],\" ~~]\"],\"editor.panels.panelWrapper.collapsePanel\":[\"[Çöļļáþšé þáñéļ ~~~~]\"],\"editor.panels.panelWrapper.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.panels.panelWrapper.expandPanel\":[\"[Éẋþáñð þáñéļ ~~~~]\"],\"editor.panels.panelWrapper.manageCollections\":[\"[Ṁáñáĝé çöļļéçţîöñš… ~~~~~~]\"],\"editor.panels.panelWrapper.openTitle\":[\"[Öþéñ \",[\"title\"],\" ~~]\"],\"editor.panels.panelWrapper.reset\":[\"[Ŕéšéţ ~~]\"],\"editor.panels.panelWrapper.titleHitAnErrorAndWas\":[\"[“\",[\"title\"],\"” ĥîţ áñ éŕŕöŕ áñð ŵáš ûñļöáðéð ƒöŕ ţĥîš šéššîöñ. ~~~~~~~~~~~~]\"],\"editor.panels.parametricFieldControl.mixed\":[\"[Ṁîẋéð ~~]\"],\"editor.panels.parametricInspector.actions\":[\"[Åçţîöñš ~~~]\"],\"editor.panels.parametricInspector.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.parametricInspector.deleteWithContents\":[\"[Ðéļéţé ŵîţĥ çöñţéñţš ~~~~~~]\"],\"editor.panels.parametricInspector.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.referencePanel.cancel\":[\"[Çáñçéļ ~~]\"],\"editor.panels.referencePanel.capture\":[\"[Çáþţûŕé ~~~]\"],\"editor.panels.referencePanel.center\":[\"[Çéñţéŕ ~~]\"],\"editor.panels.referencePanel.clearScale\":[\"[Çļéáŕ Šçáļé ~~~]\"],\"editor.panels.referencePanel.clickBothEndsOfAKnown\":[\"[Çļîçķ ƀöţĥ éñðš öƒ á ķñöŵñ ðîšţáñçé öñ ţĥé þļáñ, ţĥéñ ţýþé îţš ŕéáļ ļéñĝţĥ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.panels.referencePanel.drawALineOverAKnown\":[\"[Ðŕáŵ á ļîñé öṽéŕ á ķñöŵñ ðîɱéñšîöñ öñ ţĥé þļáñ, ţĥéñ ţýþé îţš ŕéáļ ļéñĝţĥ ţö šçáļé ţĥé îɱáĝé éẋáçţļý. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.editScale\":[\"[Éðîţ Šçáļé ~~~]\"],\"editor.panels.referencePanel.guideImage\":[\"[Ĝûîðé Îɱáĝé ~~~]\"],\"editor.panels.referencePanel.hide\":[\"[Ĥîðé ~~]\"],\"editor.panels.referencePanel.hideScale\":[\"[Ĥîðé Šçáļé ~~~]\"],\"editor.panels.referencePanel.image\":[\"[Îɱáĝé ~~]\"],\"editor.panels.referencePanel.lock\":[\"[Ļöçķ ~~]\"],\"editor.panels.referencePanel.move\":[\"[Ṁöṽé ~~]\"],\"editor.panels.referencePanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage\":[\"[Öṽéŕļáý îɱáĝé ûñáṽáîļáƀļé. Ŕéþļáçé ţĥé îɱáĝé ţö ŕéšţöŕé îţ. ~~~~~~~~~~~~~~~]\"],\"editor.panels.referencePanel.pos\":[\"[þöš ~]\"],\"editor.panels.referencePanel.position\":[\"[Þöšîţîöñ ~~~]\"],\"editor.panels.referencePanel.quickActions\":[\"[Ǫûîçķ Åçţîöñš ~~~~]\"],\"editor.panels.referencePanel.referenceScale\":[\"[Ŕéƒéŕéñçé Šçáļé ~~~~~]\"],\"editor.panels.referencePanel.replace\":[\"[Ŕéþļáçé ~~~]\"],\"editor.panels.referencePanel.replacing\":[\"[Ŕéþļáçîñĝ... ~~~]\"],\"editor.panels.referencePanel.resetImageScale\":[\"[Ŕéšéţ Îɱáĝé Šçáļé ~~~~~]\"],\"editor.panels.referencePanel.resetRotation\":[\"[Ŕéšéţ Ŕöţáţîöñ ~~~~]\"],\"editor.panels.referencePanel.rot\":[\"[ŕöţ ~]\"],\"editor.panels.referencePanel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"editor.panels.referencePanel.scale\":[\"[šçáļé ~~]\"],\"editor.panels.referencePanel.scaleOpacity\":[\"[Šçáļé & Öþáçîţý ~~~~]\"],\"editor.panels.referencePanel.scaled\":[\"[Šçáļéð · \",[\"scale\"],\" ~~]\"],\"editor.panels.referencePanel.scaledHidden\":[\"[Šçáļéð (ĥîððéñ) · \",[\"scale\"],\" ~~~~]\"],\"editor.panels.referencePanel.setScale\":[\"[Šéţ Šçáļé ~~~]\"],\"editor.panels.referencePanel.show\":[\"[Šĥöŵ ~~]\"],\"editor.panels.referencePanel.showScale\":[\"[Šĥöŵ Šçáļé ~~~]\"],\"editor.panels.referencePanel.uncalibrated\":[\"[Ûñçáļîƀŕáţéð ~~~~]\"],\"editor.panels.referencePanel.unlock\":[\"[Ûñļöçķ ~~]\"],\"editor.panels.referencePanel.xyz\":[\"[ẊÝŽ ~]\"],\"editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness\":[\"[Å ðŕáŵñ šļáƀ: šéţ îţš ţĥîçķñéšš áñð ƒîñîšĥéš ĥéŕé. Îţ ĥáš ñö ƒööţþŕîñţ ĥéîĝĥţ öŕ ƒöûñðáţîöñ. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.addMezzanine\":[\"[+ Åðð ɱéžžáñîñé ~~~~]\"],\"editor.panels.roomConstructionRows.atFloorLevel\":[\"[Åţ ƒļööŕ ļéṽéļ ~~~~]\"],\"editor.panels.roomConstructionRows.beam\":[\"[Ɓéáɱ ~~]\"],\"editor.panels.roomConstructionRows.beamSize\":[\"[Ɓéáɱ šîžé ~~~]\"],\"editor.panels.roomConstructionRows.builtOn\":[\"[Ɓûîļţ öñ ~~~]\"],\"editor.panels.roomConstructionRows.builtWith\":[\"[Ɓûîļţ ŵîţĥ ~~~]\"],\"editor.panels.roomConstructionRows.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.panels.roomConstructionRows.ceilingAt\":[\"[Åţ \",[\"height\"],\" ~]\"],\"editor.panels.roomConstructionRows.closeWithWalls\":[\"[Çļöšé ŵîţĥ ŵáļļš ~~~~~]\"],\"editor.panels.roomConstructionRows.drawnFloor\":[\"[\",[\"ambiguous\",\"select\",{\"yes\":[\"Ƒļööŕ îš šéṽéŕáļ ðŕáŵñ šļáƀš, ɱöšţļý \",[\"name\"]],\"other\":[\"Ƒļööŕ îš á ðŕáŵñ šļáƀ: \",[\"name\"]]}],[\"shared\",\"plural\",{\"0\":[],\"one\":[\" (šĥáŕéð ŵîţĥ \",[\"sharedCount\"],\" ŕööɱ)\"],\"other\":[\" (šĥáŕéð ŵîţĥ \",[\"sharedCount\"],\" ŕööɱš)\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.drawnFloorSwitchHint\":[\"[Ţĥîš ƒļööŕ îš á ðŕáŵñ šļáƀ: šéļéçţ îţ ţö éðîţ öŕ ðéļéţé îţ. ~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.drawnSlab\":[\"[Ðŕáŵñ šļáƀ ~~~]\"],\"editor.panels.roomConstructionRows.drawnSlabAt\":[\"[Ðŕáŵñ šļáƀ áţ \",[\"height\"],\" ~~~~]\"],\"editor.panels.roomConstructionRows.editShape\":[\"[Éðîţ šĥáþé ~~~]\"],\"editor.panels.roomConstructionRows.flatAtWallHeight\":[\"[Ƒļáţ, áţ ŵáļļ ĥéîĝĥţ ~~~~~]\"],\"editor.panels.roomConstructionRows.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.panels.roomConstructionRows.floorDown\":[\"[\",[\"distance\"],\" ðöŵñ ~~]\"],\"editor.panels.roomConstructionRows.floorThickness\":[\"[Ƒļööŕ ţĥîçķñéšš ~~~~~]\"],\"editor.panels.roomConstructionRows.floorUp\":[\"[\",[\"distance\"],\" ûþ ~]\"],\"editor.panels.roomConstructionRows.framingAutoHint\":[\"[\",[\"posts\",\"plural\",{\"one\":[[\"postCount\"],\" þöšţ\"],\"other\":[[\"postCount\"],\" þöšţš\"]}],\" áñð \",[\"beams\",\"plural\",{\"one\":[[\"beamCount\"],\" ƀéáɱ\"],\"other\":[[\"beamCount\"],\" ƀéáɱš\"]}],\", á þöšţ öñ éáçĥ öûţšîðé çöŕñéŕ, ķéþţ ŵîţĥ \",[\"room\",\"select\",{\"deck\":[\"ţĥé ðéçķ\"],\"patio\":[\"ţĥé þáţîö\"],\"other\":[\"ţĥé þöŕçĥ\"]}],\". ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.framingAutomatic\":[\"[Åûţöɱáţîç ~~~]\"],\"editor.panels.roomConstructionRows.framingManual\":[\"[Ṁáñûáļ ~~]\"],\"editor.panels.roomConstructionRows.framingManualHint\":[\"[Ķéþţ áš ýöû éðîţéð ţĥéɱ. Åûţöɱáţîç ŕéƀûîļðš ţĥéɱ ƒŕöɱ ţĥéšé šéţţîñĝš. ~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.framingOff\":[\"[Öƒƒ ~]\"],\"editor.panels.roomConstructionRows.framingOffHint\":[\"[Ñöñé. Åûţöɱáţîç þûţš á þöšţ öñ éáçĥ öûţšîðé çöŕñéŕ áñð á ƀéáɱ öñ ţĥéɱ, ţö çáŕŕý á ŕööƒ öṽéŕ \",[\"room\",\"select\",{\"deck\":[\"ţĥé ðéçķ\"],\"patio\":[\"ţĥé þáţîö\"],\"other\":[\"ţĥé þöŕçĥ\"]}],\". ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.handDrawnCeiling\":[\"[Ĥáñð-ðŕáŵñ çéîļîñĝ ~~~~~]\"],\"editor.panels.roomConstructionRows.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.roomConstructionRows.includeFloor\":[\"[Îñçļûðé ƒļööŕ ~~~~]\"],\"editor.panels.roomConstructionRows.includePart\":[\"[\",[\"part\",\"select\",{\"floor\":[\"Îñçļûðé ƒļööŕ\"],\"walls\":[\"Îñçļûðé ŵáļļš\"],\"other\":[\"Îñçļûðé çéîļîñĝ\"]}],\" ~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.includeWalls\":[\"[Îñçļûðé ŵáļļš ~~~~]\"],\"editor.panels.roomConstructionRows.keepOutsideDimensions\":[\"[Ķééþ öûţšîðé ðîɱéñšîöñš ~~~~~~~]\"],\"editor.panels.roomConstructionRows.lowered\":[\"[Ļöŵéŕéð \",[\"lift\"],\" ~~~]\"],\"editor.panels.roomConstructionRows.newFloor\":[\"[+ Ñéŵ ƒļööŕ ~~~]\"],\"editor.panels.roomConstructionRows.noFloor\":[\"[Ñö ƒļööŕ ~~~]\"],\"editor.panels.roomConstructionRows.notIncluded\":[\"[Ñöţ îñçļûðéð ~~~~]\"],\"editor.panels.roomConstructionRows.nothingToChange\":[\"[Ñöţĥîñĝ ţö çĥáñĝé. ~~~~~]\"],\"editor.panels.roomConstructionRows.onTheFoundation\":[\"[Öñ ţĥé ƒöûñðáţîöñ ~~~~~]\"],\"editor.panels.roomConstructionRows.onTheGround\":[\"[Öñ ţĥé ĝŕöûñð ~~~~]\"],\"editor.panels.roomConstructionRows.onTheWallsBelow\":[\"[Öñ ţĥé ŵáļļš ƀéļöŵ ~~~~~]\"],\"editor.panels.roomConstructionRows.openSides\":[\"[\",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" öþéñ šîðé\"],\"other\":[[\"openSideCount\"],\" öþéñ šîðéš\"]}],\" ~~~~~~]\"],\"editor.panels.roomConstructionRows.partialCovered\":[\"[Þáŕţîáļ · \",[\"coverage\",\"number\",\"percent\"],\" çöṽéŕéð ~~~~~]\"],\"editor.panels.roomConstructionRows.partialWalls\":[\"[Þáŕţîáļ · \",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" ŵáļļ\"],\"other\":[[\"wallCount\"],\" ŵáļļš\"]}],\", \",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" öþéñ šîðé\"],\"other\":[[\"openSideCount\"],\" öþéñ šîðéš\"]}],\" ~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.postSize\":[\"[Þöšţ šîžé ~~~]\"],\"editor.panels.roomConstructionRows.postSpacingMax\":[\"[Þöšţ šþáçîñĝ (ɱáẋ) ~~~~~]\"],\"editor.panels.roomConstructionRows.postsAndBeam\":[\"[Þöšţš áñð ƀéáɱ ~~~~]\"],\"editor.panels.roomConstructionRows.raised\":[\"[Ŕáîšéð \",[\"lift\"],\" ~~]\"],\"editor.panels.roomConstructionRows.raisedFloor\":[\"[Ŕáîšéð ƒļööŕ ~~~~]\"],\"editor.panels.roomConstructionRows.replace\":[\"[Ŕéþļáçé ~~~]\"],\"editor.panels.roomConstructionRows.roomType\":[\"[Ŕööɱ ţýþé ~~~]\"],\"editor.panels.roomConstructionRows.roomTypeDeck\":[\"[Ðéçķ ~~]\"],\"editor.panels.roomConstructionRows.roomTypeDeckHint\":[\"[Öûţðööŕš: ðéçķ ƀöáŕðš öñ á ŵööð ƒŕáɱé áñð þöšţš, 1 îñ ðöŵñ ƒŕöɱ ţĥé ĥöûšé; ñö çéîļîñĝ. ~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.roomTypePatio\":[\"[Þáţîö ~~]\"],\"editor.panels.roomConstructionRows.roomTypePatioHint\":[\"[Öûţðööŕš áñð öþéñ: á çöñçŕéţé šļáƀ 1½ îñ ðöŵñ ƒŕöɱ ţĥé ĥöûšé, ñö çéîļîñĝ. ~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.roomTypePorch\":[\"[Þöŕçĥ ~~]\"],\"editor.panels.roomConstructionRows.roomTypePorchHint\":[\"[Öûţðööŕš ûñðéŕ á çéîļîñĝ: á çöñçŕéţé šļáƀ 1½ îñ ðöŵñ ƒŕöɱ ţĥé ĥöûšé, çéîļîñĝ áţ ţĥé ĥöûšé’š. ~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.roomTypeRoom\":[\"[Ŕööɱ ~~]\"],\"editor.panels.roomConstructionRows.roomTypeRoomHint\":[\"[Åñ öŕðîñáŕý ŕööɱ îñšîðé ţĥé ĥöûšé. ~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.select\":[\"[Šéļéçţ ~~]\"],\"editor.panels.roomConstructionRows.shape\":[\"[Šĥáþé ~~]\"],\"editor.panels.roomConstructionRows.sharedWallsNote\":[\"[Ţûŕñîñĝ ŵáļļš öƒƒ ķééþš ţĥé öñéš šĥáŕéð ŵîţĥ \",[\"rooms\"],\". ~~~~~~~~~~~~]\"],\"editor.panels.roomConstructionRows.sitsOnPreset\":[\"[Šîţš öñ: \",[\"name\"],\" · \",[\"preset\"],\" ~~]\"],\"editor.panels.roomConstructionRows.slab\":[\"[Šļáƀ ~~]\"],\"editor.panels.roomConstructionRows.theNextRoom\":[\"[ţĥé ñéẋţ ŕööɱ ~~~~]\"],\"editor.panels.roomConstructionRows.useExisting\":[\"[Ûšé éẋîšţîñĝ ~~~~]\"],\"editor.panels.roomConstructionRows.wallCount\":[\"[\",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" ŵáļļ\"],\"other\":[[\"wallCount\"],\" ŵáļļš\"]}],\" ~~~]\"],\"editor.panels.roomConstructionRows.walls\":[\"[Ŵáļļš ~~]\"],\"editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding\":[\"[Ŵáļļš ţĥîçķéñ îñŵáŕð, šö ţĥé ƀûîļðîñĝ'š öûţéŕ šîžé šţáýš ţĥé šáɱé. ~~~~~~~~~~~~~~~~]\"],\"editor.panels.roomOpeningsField.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"editor.panels.roomOpeningsField.done\":[\"[Ðöñé ~~]\"],\"editor.panels.roomOpeningsField.editFloor\":[\"[Éðîţ ƒļööŕ ~~~]\"],\"editor.panels.roomOpeningsField.elevatorOpening\":[\"[Éļéṽáţöŕ öþéñîñĝ ~~~~~]\"],\"editor.panels.roomOpeningsField.noOpeningsYetCutOneFor\":[\"[Ñö öþéñîñĝš ýéţ. Çûţ öñé ƒöŕ á šţáîŕ ṽöîð öŕ á ĥáţçĥ. ~~~~~~~~~~~~]\"],\"editor.panels.roomOpeningsField.openingNumber\":[\"[Öþéñîñĝ \",[\"number\"],\" ~~~]\"],\"editor.panels.roomOpeningsField.openings\":[\"[\",[\"count\",\"plural\",{\"0\":[\"Ñö öþéñîñĝš\"],\"one\":[[\"number\"],\" öþéñîñĝ\"],\"other\":[[\"number\"],\" öþéñîñĝš\"]}],\" ~~~~~~~~]\"],\"editor.panels.roomOpeningsField.stairOpening\":[\"[Šţáîŕ öþéñîñĝ ~~~~]\"],\"editor.panels.roomPanel.addStairs\":[\"[Åðð šţáîŕš ~~~]\"],\"editor.panels.roomPanel.addToAUnit\":[\"[Åðð ţö á ûñîţ ~~~]\"],\"editor.panels.roomPanel.area\":[\"[Åŕéá ~~]\"],\"editor.panels.roomPanel.browse\":[\"[Ɓŕöŵšé ~~]\"],\"editor.panels.roomPanel.builtWith\":[\"[Ɓûîļţ ŵîţĥ ~~~]\"],\"editor.panels.roomPanel.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"editor.panels.roomPanel.deleteRoom\":[\"[Ðéļéţé ŕööɱ ~~~]\"],\"editor.panels.roomPanel.editShape\":[\"[Éðîţ šĥáþé ~~~]\"],\"editor.panels.roomPanel.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.panels.roomPanel.headroom\":[\"[\",[\"height\"],\" ĥéáðŕööɱ ~~~]\"],\"editor.panels.roomPanel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"editor.panels.roomPanel.hideCode\":[\"[Ĥîðé çöðé ~~~]\"],\"editor.panels.roomPanel.kickerLevel\":[\"[Ŕööɱ · \",[\"level\"],\" ~~]\"],\"editor.panels.roomPanel.kickerMezzanineIn\":[\"[Ṁéžžáñîñé · îñ \",[\"room\"],\" ~~~~]\"],\"editor.panels.roomPanel.kickerNotEnclosed\":[\"[Ŕööɱ · ñöţ éñçļöšéð ~~~~~]\"],\"editor.panels.roomPanel.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"editor.panels.roomPanel.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.panels.roomPanel.mezzanineName\":[\"[Ṁéžžáñîñé ñáɱé ~~~~]\"],\"editor.panels.roomPanel.mezzanineNames\":[\"[Ṁéžžáñîñé ñáɱéš ~~~~~]\"],\"editor.panels.roomPanel.newUnit\":[\"[Ñéŵ ûñîţ ~~~]\"],\"editor.panels.roomPanel.none\":[\"[Ñöñé ~~]\"],\"editor.panels.roomPanel.notEnoughHeadroom\":[\"[Ñöţ éñöûĝĥ ĥéáðŕööɱ ~~~~~~]\"],\"editor.panels.roomPanel.openRoomArea\":[\"[\",[\"area\"],\" · Çļöšé îţš ŵáļļš ţö ƀûîļð îţš ƒļööŕ áñð çéîļîñĝ. ~~~~~~~~~~~~]\"],\"editor.panels.roomPanel.openingCounts\":[\"[\",[\"doors\",\"plural\",{\"one\":[[\"doorCount\"],\" ðööŕ\"],\"other\":[[\"doorCount\"],\" ðööŕš\"]}],\" · \",[\"windows\",\"plural\",{\"one\":[[\"windowCount\"],\" ŵîñðöŵ\"],\"other\":[[\"windowCount\"],\" ŵîñðöŵš\"]}],\" ~~~~~~~]\"],\"editor.panels.roomPanel.overTheMezzanine\":[\"[Öṽéŕ ţĥé ɱéžžáñîñé ~~~~~]\"],\"editor.panels.roomPanel.perimeter\":[\"[Þéŕîɱéţéŕ ~~~]\"],\"editor.panels.roomPanel.pick\":[\"[Þîçķ ~~]\"],\"editor.panels.roomPanel.pickingItAddsANumber\":[\"[Þîçķîñĝ îţ áððš á ñûɱƀéŕ ~~~~~~]\"],\"editor.panels.roomPanel.remove\":[\"[Ŕéɱöṽé ~~]\"],\"editor.panels.roomPanel.removeFromUnit\":[\"[Ŕéɱöṽé ƒŕöɱ ûñîţ ~~~~~]\"],\"editor.panels.roomPanel.removeFromUnitNamed\":[\"[Ŕéɱöṽé ƒŕöɱ \",[\"unit\"],\" ~~~]\"],\"editor.panels.roomPanel.rename\":[\"[Ŕéñáɱé ~~]\"],\"editor.panels.roomPanel.renameNamed\":[\"[\",[\"name\"],\" · Ŕéñáɱé ~~]\"],\"editor.panels.roomPanel.revert\":[\"[Ŕéṽéŕţ ~~]\"],\"editor.panels.roomPanel.room\":[\"[Ŕööɱ ~~]\"],\"editor.panels.roomPanel.roomColourUsedInThePlan\":[\"[Ŕööɱ çöļöûŕ — ûšéð îñ ţĥé þļáñ áñð ļáƀéļš ~~~~~~~~~~]\"],\"editor.panels.roomPanel.roomName\":[\"[Ŕööɱ ñáɱé ~~~]\"],\"editor.panels.roomPanel.roomNames\":[\"[Ŕööɱ ñáɱéš ~~~]\"],\"editor.panels.roomPanel.scriptedFinish\":[\"[Šçŕîþţéð ƒîñîšĥ ~~~~~]\"],\"editor.panels.roomPanel.shape\":[\"[Šĥáþé ~~]\"],\"editor.panels.roomPanel.showMezzanineNames\":[\"[Šĥöŵ ɱéžžáñîñé ñáɱéš ~~~~~~]\"],\"editor.panels.roomPanel.showRoomNames\":[\"[Šĥöŵ ŕööɱ ñáɱéš ~~~~]\"],\"editor.panels.roomPanel.stairs\":[\"[Šţáîŕš ~~]\"],\"editor.panels.roomPanel.unit\":[\"[Ûñîţ ~~]\"],\"editor.panels.roomPanel.use\":[\"[Ûšé ~]\"],\"editor.panels.roomPanel.useTyped\":[[\"use\"],\" “\",[\"name\"],\"”\"],\"editor.panels.roomPanel.usedOnThisLevel\":[\"[\",[\"used\"],\" öñ ţĥîš ļéṽéļ ~~~~]\"],\"editor.panels.roomPanel.viewCode\":[\"[Ṽîéŵ çöðé ~~~]\"],\"editor.panels.scriptUnreadable\":[\"[Ţĥé šçŕîþţ çöûļð ñöţ ƀé ŕéáð. ~~~~~~~]\"],\"editor.panels.shapeChoice.polygon\":[\"[Þöļýĝöñ ~~~]\"],\"editor.panels.shapeChoice.rectangle\":[\"[Ŕéçţáñĝļé ~~~]\"],\"editor.panels.wallRegionList.deleteRegion\":[\"[Ðéļéţé ŕéĝîöñ ~~~~]\"],\"editor.panels.wallRegionList.paintRegions\":[\"[Þáîñţ ŕéĝîöñš ~~~~]\"],\"editor.proceduralItems.inspector.adjustDesign\":[\"[Åðĵûšţ ðéšîĝñ ~~~~]\"],\"editor.proceduralItems.inspector.back\":[\"[Ɓáçķ ~~]\"],\"editor.proceduralItems.inspector.editParts\":[\"[Éðîţ þáŕţš ~~~]\"],\"editor.proceduralItems.inspector.invalidAttachment\":[\"[Îñṽáļîð áţţáçĥɱéñţ ~~~~~~]\"],\"editor.proceduralItems.inspector.invalidEdit\":[\"[Îñṽáļîð éðîţ ~~~~]\"],\"editor.proceduralItems.inspector.invalidPlacement\":[\"[Îñṽáļîð þļáçéɱéñţ ~~~~~]\"],\"editor.proceduralItems.inspector.invalidPosition\":[\"[Îñṽáļîð þöšîţîöñ ~~~~~]\"],\"editor.proceduralItems.inspector.invalidReset\":[\"[Îñṽáļîð ŕéšéţ ~~~~]\"],\"editor.proceduralItems.inspector.pos\":[\"[þöš ~]\"],\"editor.proceduralItems.inspector.position\":[\"[Þöšîţîöñ ~~~]\"],\"editor.proceduralItems.inspector.resetThisPart\":[\"[Ŕéšéţ ţĥîš þáŕţ ~~~~]\"],\"editor.reloadEditor\":[\"[Ŕéļöáð éðîţöŕ ~~~~]\"],\"editor.riserDiagramPanel.close\":[\"[Çļöšé ŕîšéŕ ðîáĝŕáɱ ~~~~~~]\"],\"editor.riserDiagramPanel.dwvPlumbingIsometric\":[\"[ÐŴṼ þļûɱƀîñĝ îšöɱéţŕîç ~~~~~~]\"],\"editor.riserDiagramPanel.dwvRiserDiagram\":[\"[ÐŴṼ ŕîšéŕ ðîáĝŕáɱ ~~~~~]\"],\"editor.riserDiagramPanel.noDrainWasteOrVentPipes\":[\"[Ñö ðŕáîñ, ŵášţé, öŕ ṽéñţ þîþéš ýéţ. Ðŕáŵ þļûɱƀîñĝ ţö šéé ţĥé ŕîšéŕ ðîáĝŕáɱ. ~~~~~~~~~~~~~~~~~~]\"],\"editor.riserDiagramPanel.riserDiagram\":[\"[Ŕîšéŕ Ðîáĝŕáɱ ~~~~]\"],\"editor.riserDiagramPanel.trap\":[\"[\",[\"diameter\"],\"\\\" Þ-ţŕáþ ~~]\"],\"editor.riserDiagramPanel.vent\":[\"[Ṽéñţ ~~]\"],\"editor.riserDiagramPanel.ventTermination\":[\"[\",[\"diameter\"],\"\\\" ṼŢŔ ~]\"],\"editor.riserDiagramPanel.waste\":[\"[Ŵášţé ~~]\"],\"editor.room.builtOn.above\":[\"[\",[\"current\"],\" · \",[\"distance\"],\" áƀöṽé \",[\"shared\"],\" ~~]\"],\"editor.room.builtOn.below\":[\"[\",[\"current\"],\" · \",[\"distance\"],\" ƀéļöŵ \",[\"shared\"],\" ~~]\"],\"editor.room.builtOn.levelWith\":[\"[\",[\"current\"],\" · ļéṽéļ ŵîţĥ \",[\"shared\"],\" ~~~]\"],\"editor.roomHandles.cannotReadInput\":[\"[Çáññöţ ŕéáð \\\"\",[\"input\"],\"\\\" ~~~]\"],\"editor.roomHandles.dimensionValue\":[\"[Ðîɱéñšîöñ ṽáļûé ~~~~~]\"],\"editor.roomHandles.floor\":[\"[Ƒļööŕ ~~]\"],\"editor.roomHandles.typeASizeToMoveThe\":[\"[Ţýþé á šîžé ţö ɱöṽé ţĥé ŵáļļ ~~~~~~~]\"],\"editor.roomLabel.room\":[\"[Ŕööɱ ~~]\"],\"editor.roomLabel.zone\":[\"[Žöñé ~~]\"],\"editor.roomName.attic\":[\"[Åţţîç ~~]\"],\"editor.roomName.balcony\":[\"[Ɓáļçöñý ~~~]\"],\"editor.roomName.basement\":[\"[Ɓášéɱéñţ ~~~]\"],\"editor.roomName.bathroom\":[\"[Ɓáţĥŕööɱ ~~~]\"],\"editor.roomName.bedroom\":[\"[Ɓéðŕööɱ ~~~]\"],\"editor.roomName.boilerRoom\":[\"[Ɓöîļéŕ ŕööɱ ~~~]\"],\"editor.roomName.carport\":[\"[Çáŕþöŕţ ~~~]\"],\"editor.roomName.cellar\":[\"[Çéļļáŕ ~~]\"],\"editor.roomName.closet\":[\"[Çļöšéţ ~~]\"],\"editor.roomName.corridor\":[\"[Çöŕŕîðöŕ ~~~]\"],\"editor.roomName.courtyard\":[\"[Çöûŕţýáŕð ~~~]\"],\"editor.roomName.deck\":[\"[Ðéçķ ~~]\"],\"editor.roomName.den\":[\"[Ðéñ ~]\"],\"editor.roomName.diningRoom\":[\"[Ðîñîñĝ ŕööɱ ~~~]\"],\"editor.roomName.dressingRoom\":[\"[Ðŕéššîñĝ ŕööɱ ~~~~]\"],\"editor.roomName.enSuite\":[\"[Éñ-šûîţé ~~~]\"],\"editor.roomName.entrance\":[\"[Éñţŕáñçé ~~~]\"],\"editor.roomName.familyRoom\":[\"[Ƒáɱîļý ŕööɱ ~~~]\"],\"editor.roomName.gallery\":[\"[Ĝáļļéŕý ~~~]\"],\"editor.roomName.gamesRoom\":[\"[Ĝáɱéš ŕööɱ ~~~]\"],\"editor.roomName.garage\":[\"[Ĝáŕáĝé ~~]\"],\"editor.roomName.garden\":[\"[Ĝáŕðéñ ~~]\"],\"editor.roomName.gardenShed\":[\"[Ĝáŕðéñ šĥéð ~~~]\"],\"editor.roomName.guestRoom\":[\"[Ĝûéšţ ŕööɱ ~~~]\"],\"editor.roomName.gym\":[\"[Ĝýɱ ~]\"],\"editor.roomName.hallway\":[\"[Ĥáļļŵáý ~~~]\"],\"editor.roomName.homeCinema\":[\"[Ĥöɱé çîñéɱá ~~~]\"],\"editor.roomName.kidsRoom\":[\"[Ķîðš' ŕööɱ ~~~]\"],\"editor.roomName.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"editor.roomName.kitchenette\":[\"[Ķîţçĥéñéţţé ~~~~]\"],\"editor.roomName.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"editor.roomName.laundry\":[\"[Ļáûñðŕý ~~~]\"],\"editor.roomName.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.roomName.livingRoom\":[\"[Ļîṽîñĝ ŕööɱ ~~~]\"],\"editor.roomName.loft\":[\"[Ļöƒţ ~~]\"],\"editor.roomName.lounge\":[\"[Ļöûñĝé ~~]\"],\"editor.roomName.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.roomName.mudroom\":[\"[Ṁûðŕööɱ ~~~]\"],\"editor.roomName.nursery\":[\"[Ñûŕšéŕý ~~~]\"],\"editor.roomName.office\":[\"[Öƒƒîçé ~~]\"],\"editor.roomName.pantry\":[\"[Þáñţŕý ~~]\"],\"editor.roomName.patio\":[\"[Þáţîö ~~]\"],\"editor.roomName.playroom\":[\"[Þļáýŕööɱ ~~~]\"],\"editor.roomName.poolArea\":[\"[Þööļ áŕéá ~~~]\"],\"editor.roomName.porch\":[\"[Þöŕçĥ ~~]\"],\"editor.roomName.powderRoom\":[\"[Þöŵðéŕ ŕööɱ ~~~]\"],\"editor.roomName.primaryBedroom\":[\"[Þŕîɱáŕý ƀéðŕööɱ ~~~~~]\"],\"editor.roomName.readingNook\":[\"[Ŕéáðîñĝ ñööķ ~~~~]\"],\"editor.roomName.section.bathrooms\":[\"[Ɓáţĥŕööɱš ~~~]\"],\"editor.roomName.section.circulation\":[\"[Çîŕçûļáţîöñ ~~~~]\"],\"editor.roomName.section.living\":[\"[Ļîṽîñĝ ~~]\"],\"editor.roomName.section.mezzanine\":[\"[Ṁéžžáñîñé ~~~]\"],\"editor.roomName.section.outdoor\":[\"[Öûţðööŕ ~~~]\"],\"editor.roomName.section.sleeping\":[\"[Šļééþîñĝ ~~~]\"],\"editor.roomName.section.utility\":[\"[Ûţîļîţý & šţöŕáĝé ~~~~~]\"],\"editor.roomName.section.work\":[\"[Ŵöŕķ & ĥöƀƀîéš ~~~~]\"],\"editor.roomName.showerRoom\":[\"[Šĥöŵéŕ ŕööɱ ~~~]\"],\"editor.roomName.stairwell\":[\"[Šţáîŕŵéļļ ~~~]\"],\"editor.roomName.storage\":[\"[Šţöŕáĝé ~~~]\"],\"editor.roomName.studio\":[\"[Šţûðîö ~~]\"],\"editor.roomName.study\":[\"[Šţûðý ~~]\"],\"editor.roomName.sunroom\":[\"[Šûñŕööɱ ~~~]\"],\"editor.roomName.terrace\":[\"[Ţéŕŕáçé ~~~]\"],\"editor.roomName.toilet\":[\"[Ţöîļéţ ~~]\"],\"editor.roomName.utilityRoom\":[\"[Ûţîļîţý ŕööɱ ~~~~]\"],\"editor.roomName.veranda\":[\"[Ṽéŕáñðá ~~~]\"],\"editor.roomName.walkInCloset\":[\"[Ŵáļķ-îñ çļöšéţ ~~~~]\"],\"editor.roomName.workshop\":[\"[Ŵöŕķšĥöþ ~~~]\"],\"editor.sidebar.iconRail.settings\":[\"[Šéţţîñĝš ~~~]\"],\"editor.sidebar.iconRail.site\":[\"[Šîţé ~~]\"],\"editor.sidebar.panels.itemsPanel.all\":[\"[Åļļ ~]\"],\"editor.sidebar.panels.itemsPanel.community\":[\"[Çöɱɱûñîţý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.all\":[\"[Åļļ ~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.community\":[\"[Çöɱɱûñîţý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.mine\":[\"[Ṁîñé ~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch\":[\"[Ñö ŕéšûļţš ƒöŕ “\",[\"search\"],\"” ~~~~]\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.search\":[\"[Šéáŕçĥ... ~~]\"],\"editor.sidebar.panels.itemsPanel.library\":[\"[Ļîƀŕáŕý ~~~]\"],\"editor.sidebar.panels.itemsPanel.mine\":[\"[Ṁîñé ~~]\"],\"editor.sidebar.panels.itemsPanel.noResultsForSearch\":[\"[Ñö ŕéšûļţš ƒöŕ “\",[\"search\"],\"” ~~~~]\"],\"editor.sidebar.panels.itemsPanel.search\":[\"[Šéáŕçĥ... ~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools\":[\"[Ɓûîļð Ţööļš ~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera\":[\"[Çáɱéŕá ~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation\":[\"[Ðîŕéçţ Ṁáñîþûļáţîöñ ~~~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools\":[\"[Ðŕáŵîñĝ Ţööļš ~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation\":[\"[Éðîţöŕ Ñáṽîĝáţîöñ ~~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement\":[\"[Îţéɱ Þļáçéɱéñţ ~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts\":[\"[Ķéýƀöáŕð šĥöŕţçûţš ~~~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory\":[\"[Ṁöðéš & Ĥîšţöŕý ~~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints\":[\"[Šĥöŕţçûţš áŕé çöñţéẋţ-áŵáŕé. Ĝûîðéð çöñšţŕáîñţš áŕé éñáƀļéð ƀý ðéƒáûļţ; ĥöļð Šĥîƒţ ðûŕîñĝ áñ áçţîṽé ĝéšţûŕé ţö ƀûîļð ƒŕééļý. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.addLevel\":[\"[Åðð ļéṽéļ ~~~]\"],\"editor.sidebar.panels.sitePanel.addPoint\":[\"[Åðð þöîñţ ~~~]\"],\"editor.sidebar.panels.sitePanel.area\":[\"[Åŕéá: ~~]\"],\"editor.sidebar.panels.sitePanel.baseElevation\":[\"[Ɓášé éļéṽáţîöñ ~~~~]\"],\"editor.sidebar.panels.sitePanel.building\":[\"[Ɓûîļðîñĝ ~~~]\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel\":[\"[Åðð ñéŵ ļéṽéļ ~~~~]\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.building\":[\"[Ɓûîļðîñĝ ~~~]\"],\"editor.sidebar.panels.sitePanel.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.capture\":[\"[Çáþţûŕé ~~~]\"],\"editor.sidebar.panels.sitePanel.clearSelection\":[\"[Çļéáŕ šéļéçţîöñ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo\":[\"[Çļöšé ŵáļļš áŕöûñð á šþáçé ţö ɱáķé á ŕööɱ. ~~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour\":[\"[Çöļļéçţîöñ çöļöûŕ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collections\":[\"[Çöļļéçţîöñš ~~~~]\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection\":[\"[Ðéļéţé çöļļéçţîöñ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.sidebar.panels.sitePanel.deleteLevel\":[\"[Ðéļéţé ļéṽéļ ~~~~]\"],\"editor.sidebar.panels.sitePanel.drawAZone\":[\"[Ðŕáŵ á žöñé ~~~]\"],\"editor.sidebar.panels.sitePanel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateLevel\":[\"[Ðûþļîçáţé ļéṽéļ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateLevelWithOptions\":[\"[Ðûþļîçáţé ļéṽéļ ŵîţĥ öþţîöñš ~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.duplicateWithOptions\":[\"[Ðûþļîçáţé ŵîţĥ öþţîöñš... ~~~~~~]\"],\"editor.sidebar.panels.sitePanel.elements\":[\"[Éļéɱéñţš ~~~]\"],\"editor.sidebar.panels.sitePanel.guide\":[\"[Ĝûîðé ~~]\"],\"editor.sidebar.panels.sitePanel.hide\":[\"[Ĥîðé ~~]\"],\"editor.sidebar.panels.sitePanel.level\":[\"[Ļéṽéļ ~~]\"],\"editor.sidebar.panels.sitePanel.newUnit\":[\"[Ñéŵ ûñîţ ~~~]\"],\"editor.sidebar.panels.sitePanel.noBuildingsYet\":[\"[Ñö ƀûîļðîñĝš ýéţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.noElementsOnThisLevel\":[\"[Ñö éļéɱéñţš öñ ţĥîš ļéṽéļ ~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.noLevelsYet\":[\"[Ñö ļéṽéļš ýéţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.objectsSelected\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" öƀĵéçţ šéļéçţéð\"],\"other\":[\"#\",\" öƀĵéçţš šéļéçţéð\"]}],\" ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.otherZones\":[\"[Öţĥéŕ žöñéš ~~~]\"],\"editor.sidebar.panels.sitePanel.perimeter\":[\"[Þéŕîɱéţéŕ: ~~~]\"],\"editor.sidebar.panels.sitePanel.propertyLine\":[\"[Þŕöþéŕţý Ļîñé ~~~~]\"],\"editor.sidebar.panels.sitePanel.rooms\":[\"[Ŕööɱš ~~]\"],\"editor.sidebar.panels.sitePanel.selectALevelToViewContent\":[\"[Šéļéçţ á ļéṽéļ ţö ṽîéŵ çöñţéñţ ~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.show\":[\"[Šĥöŵ ~~]\"],\"editor.sidebar.panels.sitePanel.site\":[\"[Šîţé ~~]\"],\"editor.sidebar.panels.sitePanel.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted\":[\"[Ţĥé ĝŕöûñð ļéṽéļ çáññöţ ƀé ðéļéţéð ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.hide\":[\"[Ĥîðé ~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.show\":[\"[Šĥöŵ ~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit\":[\"[Ðéļéţé ûñîţ ~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.focused\":[\"[Ƒöçûšéð ~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit\":[\"[Ŕéɱöṽé ƒŕöɱ ûñîţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.room\":[\"[Ŕööɱ ~~]\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.zone\":[\"[Žöñé ~~]\"],\"editor.sidebar.panels.sitePanel.units\":[\"[Ûñîţš ~~]\"],\"editor.sidebar.panels.sitePanel.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.sitePanel.uploadScanFloorplan\":[\"[Ûþļöáð šçáñ/ƒļööŕþļáñ ~~~~~~]\"],\"editor.sidebar.panels.sitePanel.uploadingProgress\":[\"[\",[\"assetType\",\"select\",{\"guide\":[\"Ûþļöáðîñĝ ĝûîðé... \",[\"progress\"],\"%\"],\"other\":[\"Ûþļöáðîñĝ šçáñ... \",[\"progress\"],\"%\"]}],\" ~~~~~~~~~]\"],\"editor.sidebar.panels.sitePanel.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname\":[\"[Îñ \",[\"unitName\"],\" ~]\"],\"editor.sidebar.panels.zonePanel.actions\":[\"[Åçţîöñš ~~~]\"],\"editor.sidebar.panels.zonePanel.addOne\":[\"[Åðð öñé ~~]\"],\"editor.sidebar.panels.zonePanel.cameraSnapshot\":[\"[Çáɱéŕá šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.zonePanel.clearSnapshot\":[\"[Çļéáŕ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.zonePanel.delete\":[\"[Ðéļéţé ~~]\"],\"editor.sidebar.panels.zonePanel.deleteWithContents\":[\"[Ðéļéţé ŵîţĥ çöñţéñţš ~~~~~~]\"],\"editor.sidebar.panels.zonePanel.noZonesOnThisLevel\":[\"[Ñö žöñéš öñ ţĥîš ļéṽéļ. ~~~~~~]\"],\"editor.sidebar.panels.zonePanel.saveToCatalog\":[\"[Šáṽé ţö çáţáļöĝ ~~~~]\"],\"editor.sidebar.panels.zonePanel.selectALevelToViewAnd\":[\"[Šéļéçţ á ļéṽéļ ţö ṽîéŵ áñð çŕéáţé žöñéš ~~~~~~~~~~]\"],\"editor.sidebar.panels.zonePanel.takeSnapshot\":[\"[Ţáķé šñáþšĥöţ ~~~~]\"],\"editor.sidebar.panels.zonePanel.updateSnapshot\":[\"[Ûþðáţé šñáþšĥöţ ~~~~~]\"],\"editor.sidebar.panels.zonePanel.viewSnapshot\":[\"[Ṽîéŵ šñáþšĥöţ ~~~~]\"],\"editor.sidebar.usePluginPanels.labelPluginCrashed\":[\"[\\\"\",[\"label\"],\"\\\" þļûĝîñ çŕášĥéð ~~~~]\"],\"editor.sidebar.usePluginPanels.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"editor.sidebar.usePluginPanels.plugins\":[\"[Þļûĝîñš ~~~]\"],\"editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd\":[\"[Ţĥîš þáñéļ ĥîţ áñ éŕŕöŕ áñð ŵáš ûñļöáðéð ƒöŕ ţĥîš šéššîöñ. Ţĥé ŕéšţ öƒ ţĥé éðîţöŕ îš ûñჃéçţéð — ŕéļöáð ţö ţŕý áĝáîñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"editor.snapTarget.ceiling\":[\"[Çéîļîñĝ áţţáçĥɱéñţ ~~~~~~]\"],\"editor.snapTarget.roof\":[\"[Ŕööƒ áţţáçĥɱéñţ ~~~~~]\"],\"editor.snapTarget.wall\":[\"[Ŵáļļ áţţáçĥɱéñţ ~~~~~]\"],\"editor.theEditorSceneFailedToRender\":[\"[Ţĥé éðîţöŕ šçéñé ƒáîļéð ţö ŕéñðéŕ ~~~~~~~~~]\"],\"editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i\":[\"[Å çûŕţáîñ ŵáļļ ţáķéš ñö çöŕñéŕ ŵîñðöŵ ~~~~~~~~~~]\"],\"editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5\":[\"[Å çûŕṽéð ŵáļļ ţáķéš ñö çöŕñéŕ ŵîñðöŵ ~~~~~~~~~]\"],\"editor.toolHint.addACornerClickTheFirst.o4r67t\":[\"[Åðð á çöŕñéŕ · çļîçķ ţĥé ƒîŕšţ ţö çļöšé ~~~~~~~~~]\"],\"editor.toolHint.addAWallEachOneStarts.4zkf12\":[\"[Åðð á ŵáļļ · éáçĥ öñé šţáŕţš ŵĥéŕé ţĥé ļášţ éñðéð ~~~~~~~~~~~~]\"],\"editor.toolHint.anotherOpeningIsInTheWay.11l9dd0\":[\"[Åñöţĥéŕ öþéñîñĝ îš îñ ţĥé ŵáý áţ ţĥé çöŕñéŕ ~~~~~~~~~~~]\"],\"editor.toolHint.autoHangers.10554ka\":[\"[Åûţö ĥáñĝéŕš ~~~~]\"],\"editor.toolHint.autoHangersClickOrPressH.rwa3ka\":[\"[Åûţö ĥáñĝéŕš - çļîçķ öŕ þŕéšš Ĥ ţö ţöĝĝļé ~~~~~~~~~~]\"],\"editor.toolHint.autoHangersOff.7yx01x\":[\"[Åûţö ĥáñĝéŕš: Öƒƒ ~~~~~]\"],\"editor.toolHint.autoHangersOn.10b1umh\":[\"[Åûţö ĥáñĝéŕš: Öñ ~~~~]\"],\"editor.toolHint.bypassSnapping.w5lmdv\":[\"[Ɓýþášš šñáþþîñĝ ~~~~~]\"],\"editor.toolHint.cancel.ew9em3\":[\"[Çáñçéļ ~~]\"],\"editor.toolHint.cancelMove.lvp31c\":[\"[Çáñçéļ ɱöṽé ~~~]\"],\"editor.toolHint.cancelRunExit.zqjf3h\":[\"[Çáñçéļ ŕûñ / éẋîţ ~~~~]\"],\"editor.toolHint.cancelStartPoint.1xbq2lh\":[\"[Çáñçéļ šţáŕţ þöîñţ ~~~~~]\"],\"editor.toolHint.closeTheOutline.f6cvzi\":[\"[Çļöšé ţĥé öûţļîñé ~~~~~]\"],\"editor.toolHint.cornerWindow.1avkzpo\":[\"[Çöŕñéŕ ŵîñðöŵ ~~~~]\"],\"editor.toolHint.cuts.1y0axps\":[\"[Çûţš ~~]\"],\"editor.toolHint.cutsValue.ouzhvb\":[\"[Çûţš: \",[\"value\"],\" ~~]\"],\"editor.toolHint.cutsValueEven.1ngsz3\":[\"[Çûţš: \",[\"value\"],\", éṽéñ ~~~]\"],\"editor.toolHint.cycleMonoGableButterfly.1poga3m\":[\"[Çýçļé ɱöñö / ĝáƀļé / ƀûţţéŕƒļý ~~~~~~~]\"],\"editor.toolHint.cycleSnappingMode.1ct0gn6\":[\"[Çýçļé šñáþþîñĝ ɱöðé ~~~~~~]\"],\"editor.toolHint.doorStyleClickOrPressL.33pe65\":[\"[Ðööŕ šţýļé — çļîçķ öŕ þŕéšš Ļ ţö çýçļé ~~~~~~~~~]\"],\"editor.toolHint.doorTypeClickOrPressO.1hhh8gt\":[\"[Ðööŕ ţýþé — çļîçķ öŕ þŕéšš Ö ţö çýçļé ~~~~~~~~~]\"],\"editor.toolHint.dropDownspoutFromOutlet.1k4h1m4\":[\"[Ðŕöþ ðöŵñšþöûţ ƒŕöɱ öûţļéţ ~~~~~~~]\"],\"editor.toolHint.ductDiameterDownUp.1wdz5vq\":[\"[Ðûçţ ðîáɱéţéŕ ðöŵñ / ûþ ~~~~~~]\"],\"editor.toolHint.endRunCancel.59dh2w\":[\"[Éñð ŕûñ / çáñçéļ ~~~~]\"],\"editor.toolHint.endTheRun.1c1raig\":[\"[Éñð ţĥé ŕûñ ~~~]\"],\"editor.toolHint.exit.engmg5\":[\"[Éẋîţ ~~]\"],\"editor.toolHint.exitDrawing.9noxyb\":[\"[Éẋîţ ðŕáŵîñĝ ~~~~]\"],\"editor.toolHint.finishAndContinue.1gpfsks\":[\"[Ƒîñîšĥ áñð çöñţîñûé ~~~~~~]\"],\"editor.toolHint.finishCeiling.1c1lfel\":[\"[Ƒîñîšĥ çéîļîñĝ ~~~~]\"],\"editor.toolHint.finishGridAxis.1wvrfhr\":[\"[Ƒîñîšĥ ĝŕîð áẋîš ~~~~~]\"],\"editor.toolHint.finishMeasurement.1e4gv16\":[\"[Ƒîñîšĥ ɱéášûŕéɱéñţ ~~~~~~]\"],\"editor.toolHint.finishMultiPointWitnesses.8wzskz\":[\"[Ƒîñîšĥ ɱûļţî-þöîñţ ŵîţñéššéš ~~~~~~~~]\"],\"editor.toolHint.finishSlab.xmnm8e\":[\"[Ƒîñîšĥ šļáƀ ~~~]\"],\"editor.toolHint.finishTheRun.ayliww\":[\"[Ƒîñîšĥ ţĥé ŕûñ ~~~~]\"],\"editor.toolHint.flipSide.172xku9\":[\"[Ƒļîþ šîðé ~~~]\"],\"editor.toolHint.followTraceALineset.mgquwe\":[\"[Ƒöļļöŵ: ţŕáçé á ļîñéšéţ ~~~~~~]\"],\"editor.toolHint.forcePlace.1hq1nr\":[\"[Ƒöŕçé þļáçé ~~~]\"],\"editor.toolHint.freeAngle.1ptblho\":[\"[Ƒŕéé áñĝļé ~~~]\"],\"editor.toolHint.freePlace.1s1uusw\":[\"[Ƒŕéé þļáçé ~~~]\"],\"editor.toolHint.freePlacement.1nztdac\":[\"[Ƒŕéé þļáçéɱéñţ ~~~~]\"],\"editor.toolHint.goVerticalClickToPlace.dg8g6m\":[\"[Ĝö ṽéŕţîçáļ ↕, çļîçķ ţö þļáçé ~~~~~~~]\"],\"editor.toolHint.highlightOutlet.65hoq8\":[\"[Ĥîĝĥļîĝĥţ öûţļéţ ~~~~~]\"],\"editor.toolHint.joinAsACornerWindow.jvdp4j\":[\"[Ĵöîñ áš á çöŕñéŕ ŵîñðöŵ ~~~~~~]\"],\"editor.toolHint.key.altDrag.ai3wn9\":[\"[Åļţ + ðŕáĝ ~~~]\"],\"editor.toolHint.key.backspace.qy6qx8\":[\"[Ɓáçķšþáçé ~~~]\"],\"editor.toolHint.key.click.m8sun\":[\"[Çļîçķ ~~]\"],\"editor.toolHint.key.clickAgain.190ws9j\":[\"[Çļîçķ áĝáîñ ~~~]\"],\"editor.toolHint.key.command.1j2mkia\":[\"[Çöɱɱáñð ~~~]\"],\"editor.toolHint.key.control.10fbl8e\":[\"[Çöñţŕöļ ~~~]\"],\"editor.toolHint.key.delete.oay2cq\":[\"[Ðéļéţé ~~]\"],\"editor.toolHint.key.doubleClick.vlma6o\":[\"[Ðöûƀļé çļîçķ ~~~~]\"],\"editor.toolHint.key.drag.8pwtpf\":[\"[Ðŕáĝ ~~]\"],\"editor.toolHint.key.enter.1ctpui5\":[\"[Éñţéŕ ~~]\"],\"editor.toolHint.key.hover.6xw79p\":[\"[Ĥöṽéŕ ~~]\"],\"editor.toolHint.key.hoverADuctEnd.19axr3b\":[\"[Ĥöṽéŕ á ðûçţ éñð ~~~~]\"],\"editor.toolHint.key.hoverAPipeEnd.1jbum5f\":[\"[Ĥöṽéŕ á þîþé éñð ~~~~]\"],\"editor.toolHint.key.hoverGutter.1dwcsno\":[\"[Ĥöṽéŕ ĝûţţéŕ ~~~~]\"],\"editor.toolHint.key.leftClick.s4gwfc\":[\"[Ļéƒţ çļîçķ ~~~]\"],\"editor.toolHint.key.middleClick.1jbaan4\":[\"[Ṁîððļé çļîçķ ~~~~]\"],\"editor.toolHint.key.rShiftR.13bnobh\":[\"[Ŕ / Šĥîƒţ+Ŕ ~~~]\"],\"editor.toolHint.key.rightClick.1ovld0h\":[\"[Ŕîĝĥţ çļîçķ ~~~]\"],\"editor.toolHint.key.room.1f3jkpm\":[\"[Ŕööɱ ~~]\"],\"editor.toolHint.key.scroll.ai6wr0\":[\"[Šçŕöļļ ~~]\"],\"editor.toolHint.key.scrollWheel.18j0ix7\":[\"[Šçŕöļļ ŵĥééļ ~~~~]\"],\"editor.toolHint.key.space.1hrh82t\":[\"[Šþáçé ~~]\"],\"editor.toolHint.key.tab.1xsal0s\":[\"[Ţáƀ ~]\"],\"editor.toolHint.mountFloorCeilingWall.32dxpv\":[\"[Ṁöûñţ: ƒļööŕ / çéîļîñĝ / ŵáļļ ~~~~~~~]\"],\"editor.toolHint.noRoomForTheMatchingWindow.1755948\":[\"[Ñö ŕööɱ ƒöŕ ţĥé ɱáţçĥîñĝ ŵîñðöŵ öñ ţĥé öţĥéŕ ŵáļļ ~~~~~~~~~~~~]\"],\"editor.toolHint.noWallMeetsHere.19n99yc\":[\"[Ñö ŵáļļ ɱééţš ĥéŕé ~~~~~]\"],\"editor.toolHint.notARectangleDrawThisRoof.100pa5g\":[\"[Ñöţ á ŕéçţáñĝļé: ðŕáŵ ţĥîš ŕööƒ îñšţéáð ~~~~~~~~~~]\"],\"editor.toolHint.numberOfCutsScrollOrClick.1kqe84b\":[\"[Ñûɱƀéŕ öƒ çûţš — šçŕöļļ öŕ çļîçķ ţö çĥáñĝé ~~~~~~~~~~]\"],\"editor.toolHint.pickWitnessPoint.k3hgh1\":[\"[Þîçķ ŵîţñéšš þöîñţ ~~~~~]\"],\"editor.toolHint.pipeSizeDownUp.wp04og\":[\"[Þîþé šîžé ðöŵñ / ûþ ~~~~~]\"],\"editor.toolHint.placeACornerOfTheFoundation.1o1ze9j\":[\"[Þļáçé á çöŕñéŕ öƒ ţĥé ƒöûñðáţîöñ ~~~~~~~~~]\"],\"editor.toolHint.placeAndContinue.qfwkrq\":[\"[Þļáçé áñð çöñţîñûé ~~~~~]\"],\"editor.toolHint.placeBlock.1aylwrx\":[\"[Þļáçé ƀļöçķ ~~~]\"],\"editor.toolHint.placeBoxVentOnRoof.kf88e5\":[\"[Þļáçé ƀöẋ ṽéñţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeCabinet.b0ouyk\":[\"[Þļáçé çáƀîñéţ ~~~~]\"],\"editor.toolHint.placeCanopyOrSetTheNext.12l7zf8\":[\"[Þļáçé çáñöþý öŕ šéţ ţĥé ñéẋţ ŕûñ þöîñţ ~~~~~~~~~~]\"],\"editor.toolHint.placeChimneyOnRoof.19dbqu\":[\"[Þļáçé çĥîɱñéý öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeColumn.1w2vq86\":[\"[Þļáçé çöļûɱñ ~~~~]\"],\"editor.toolHint.placeCupolaOnRoof.ofx3bn\":[\"[Þļáçé çûþöļá öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj\":[\"[Þļáçé ðîɱéñšîöñ ļîñé ŵĥéñ ñééðéð ~~~~~~~~~]\"],\"editor.toolHint.placeDoorOnWall.1jwadf\":[\"[Þļáçé ðööŕ öñ ŵáļļ ~~~~~]\"],\"editor.toolHint.placeDormerOnRoof.1w518a6\":[\"[Þļáçé ðöŕɱéŕ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeElevator.1f65zva\":[\"[Þļáçé éļéṽáţöŕ ~~~~]\"],\"editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn\":[\"[Þļáçé éýéƀŕöŵ ṽéñţ öñ ŕööƒ ~~~~~~~]\"],\"editor.toolHint.placeFitting.1pmx3vn\":[\"[Þļáçé ƒîţţîñĝ ~~~~]\"],\"editor.toolHint.placeGutterOnRoofEave.llwzf9\":[\"[Þļáçé ĝûţţéŕ öñ ŕööƒ éáṽé ~~~~~~~]\"],\"editor.toolHint.placeItLockedTo45.16e4csk\":[\"[Þļáçé îţ (ļöçķéð ţö 45°) ~~~~~]\"],\"editor.toolHint.placeItem.teynf\":[\"[Þļáçé îţéɱ ~~~]\"],\"editor.toolHint.placeMeasurementPoint.bsatli\":[\"[Þļáçé ɱéášûŕéɱéñţ þöîñţ ~~~~~~~]\"],\"editor.toolHint.placeOnAFence.1dptstr\":[\"[Þļáçé öñ á ƒéñçé ~~~~]\"],\"editor.toolHint.placeRegister.1uebycd\":[\"[Þļáçé ŕéĝîšţéŕ ~~~~]\"],\"editor.toolHint.placeRidgeVentOnRoof.2tpd8r\":[\"[Þļáçé ŕîðĝé ṽéñţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeShelf.y4l3iw\":[\"[Þļáçé šĥéļƒ ~~~]\"],\"editor.toolHint.placeSkylightOnRoof.e215pe\":[\"[Þļáçé šķýļîĝĥţ öñ ŕööƒ ~~~~~~]\"],\"editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn\":[\"[Þļáçé šöļáŕ þáñéļ áŕŕáý öñ ŕööƒ ~~~~~~~~]\"],\"editor.toolHint.placeSpawnPoint.205o25\":[\"[Þļáçé šþáŵñ þöîñţ ~~~~~]\"],\"editor.toolHint.placeStairs.zwxxu2\":[\"[Þļáçé šţáîŕš ~~~~]\"],\"editor.toolHint.placeTrap.1gxemn3\":[\"[Þļáçé ţŕáþ ~~~]\"],\"editor.toolHint.placeTurbineVentOnRoof.1iqeluf\":[\"[Þļáçé ţûŕƀîñé ṽéñţ öñ ŕööƒ ~~~~~~~]\"],\"editor.toolHint.placeUnit.8zkx4q\":[\"[Þļáçé ûñîţ ~~~]\"],\"editor.toolHint.placeWindowOnWall.1k3qnfb\":[\"[Þļáçé ŵîñðöŵ öñ ŵáļļ ~~~~~~]\"],\"editor.toolHint.placeWithoutTheCornerSnap.1nifqew\":[\"[Þļáçé ŵîţĥöûţ ţĥé çöŕñéŕ šñáþ ~~~~~~~~]\"],\"editor.toolHint.placement.13lcvts\":[\"[Þļáçéɱéñţ ~~~]\"],\"editor.toolHint.placementAuto.1caa52d\":[\"[Þļáçéɱéñţ: Åûţö ~~~~]\"],\"editor.toolHint.placementGround.1ruz145\":[\"[Þļáçéɱéñţ: Ĝŕöûñð ~~~~~]\"],\"editor.toolHint.placementRoof.renkw8\":[\"[Þļáçéɱéñţ: Ŕööƒ ~~~~]\"],\"editor.toolHint.placementSurfaceClickOrPressP.w56h19\":[\"[Þļáçéɱéñţ šûŕƒáçé - çļîçķ öŕ þŕéšš Þ ţö çýçļé ~~~~~~~~~~~]\"],\"editor.toolHint.placementType.1mdpl60\":[\"[Þļáçéɱéñţ ţýþé ~~~~]\"],\"editor.toolHint.placementTypeClickOrPressI.10cgxh2\":[\"[Þļáçéɱéñţ ţýþé — çļîçķ öŕ þŕéšš Î ţö ţöĝĝļé ~~~~~~~~~~~]\"],\"editor.toolHint.pointInsideARoomPorchOr.58w64l\":[\"[Þöîñţ îñšîðé á ŕööɱ, þöŕçĥ öŕ ðéçķ ~~~~~~~~~]\"],\"editor.toolHint.removeLastPoint.2dmdcx\":[\"[Ŕéɱöṽé ļášţ þöîñţ ~~~~~]\"],\"editor.toolHint.removeLastWitness.1b6vvzw\":[\"[Ŕéɱöṽé ļášţ ŵîţñéšš ~~~~~~]\"],\"editor.toolHint.ridgeDirection.5m1369\":[\"[Ŕîðĝé ðîŕéçţîöñ ~~~~~]\"],\"editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk\":[\"[Ŕîðĝé ðîŕéçţîöñ - çļîçķ öŕ þŕéšš Ŕ ţö ţûŕñ îţ 90° ~~~~~~~~~~~]\"],\"editor.toolHint.ridgeLongSide.ew0euv\":[\"[Ŕîðĝé: Ļöñĝ šîðé ~~~~]\"],\"editor.toolHint.ridgeShortSide.gye8pb\":[\"[Ŕîðĝé: Šĥöŕţ šîðé ~~~~~]\"],\"editor.toolHint.rotate.1thqa0a\":[\"[Ŕöţáţé ~~]\"],\"editor.toolHint.rotate45.9zq68u\":[\"[Ŕöţáţé ±45° ~~]\"],\"editor.toolHint.rotate45FloorCeiling.m9t57d\":[\"[Ŕöţáţé ±45° (ƒļööŕ / çéîļîñĝ) ~~~~~~]\"],\"editor.toolHint.rotateGhost15.rsa52c\":[\"[Ŕöţáţé ĝĥöšţ ±15° ~~~~]\"],\"editor.toolHint.rotateOrFlipTheRunSide.t5ior7\":[\"[Ŕöţáţé öŕ ƒļîþ ţĥé ŕûñ šîðé ~~~~~~~]\"],\"editor.toolHint.rotateSpawnPoint.1opcuu7\":[\"[Ŕöţáţé šþáŵñ þöîñţ ~~~~~]\"],\"editor.toolHint.roundRectangularOval.e4nh6x\":[\"[Ŕöûñð / ŕéçţáñĝûļáŕ / öṽáļ ~~~~~~]\"],\"editor.toolHint.setBeamStartEndTheNext.di3bzj\":[\"[Šéţ ƀéáɱ šţáŕţ / éñð; ţĥé ñéẋţ ƀéáɱ šţáŕţš ţĥéŕé ~~~~~~~~~~~~]\"],\"editor.toolHint.setFenceStartEnd.1phbwsq\":[\"[Šéţ ƒéñçé šţáŕţ / éñð ~~~~~]\"],\"editor.toolHint.setFootingStartEndOrPlace.nnv1av\":[\"[Šéţ ƒööţîñĝ šţáŕţ / éñð, öŕ þļáçé á þáð ~~~~~~~~~]\"],\"editor.toolHint.setOneCornerThenTheOpposite.8tsl49\":[\"[Šéţ öñé çöŕñéŕ, ţĥéñ ţĥé öþþöšîţé ~~~~~~~~~]\"],\"editor.toolHint.setRoofFootprint.p50n0o\":[\"[Šéţ ŕööƒ ƒööţþŕîñţ ~~~~~]\"],\"editor.toolHint.slopeLevel.ftqp67\":[\"[Šļöþé / ļéṽéļ ~~~]\"],\"editor.toolHint.snapOntoTheRun.86yhmp\":[\"[Šñáþ öñţö ţĥé ŕûñ ~~~~~]\"],\"editor.toolHint.splitAtTheMarks.x5tpdd\":[\"[Šþļîţ áţ ţĥé ɱáŕķš ~~~~~]\"],\"editor.toolHint.startGridAxis.k92yza\":[\"[Šţáŕţ ĝŕîð áẋîš ~~~~]\"],\"editor.toolHint.startLineset.23q8dh\":[\"[Šţáŕţ ļîñéšéţ ~~~~]\"],\"editor.toolHint.startLiquidLine.1rlxsgd\":[\"[Šţáŕţ ļîǫûîð ļîñé ~~~~~]\"],\"editor.toolHint.startRun.r6fc5u\":[\"[Šţáŕţ ŕûñ ~~~]\"],\"editor.toolHint.startSegment.rolym6\":[\"[Šţáŕţ šéĝɱéñţ ~~~~]\"],\"editor.toolHint.stepBackOrCancel.zbfdkn\":[\"[Šţéþ ƀáçķ öŕ çáñçéļ ~~~~~]\"],\"editor.toolHint.style.nf3vcm\":[\"[Šţýļé ~~]\"],\"editor.toolHint.styleBarn.k9n1x\":[\"[Šţýļé: Ɓáŕñ ~~~]\"],\"editor.toolHint.styleCasement.ziklec\":[\"[Šţýļé: Çášéɱéñţ ~~~~]\"],\"editor.toolHint.styleCraftsman.52e6a9\":[\"[Šţýļé: Çŕáƒţšɱáñ ~~~~~]\"],\"editor.toolHint.styleDoubleHung.1lg8pkg\":[\"[Šţýļé: Ðöûƀļé-ĥûñĝ ~~~~~]\"],\"editor.toolHint.styleFrench.1ue3j64\":[\"[Šţýļé: Ƒŕéñçĥ ~~~~]\"],\"editor.toolHint.styleGlass.qudnb4\":[\"[Šţýļé: Ĝļášš ~~~]\"],\"editor.toolHint.styleGrid.1vuq4po\":[\"[Šţýļé: Ĝŕîð ~~~]\"],\"editor.toolHint.styleHalfLouvered.13b1few\":[\"[Šţýļé: Ĥáļƒ-ļöûṽéŕéð ~~~~~~]\"],\"editor.toolHint.styleHorizontalBands.12ethxk\":[\"[Šţýļé: Ĥöŕîžöñţáļ ƀáñðš ~~~~~~]\"],\"editor.toolHint.styleModern.1fh8hhn\":[\"[Šţýļé: Ṁöðéŕñ ~~~~]\"],\"editor.toolHint.stylePanel.922jze\":[\"[Šţýļé: Þáñéļ ~~~]\"],\"editor.toolHint.stylePaneledGlass.17a17en\":[\"[Šţýļé: Þáñéļéð ĝļášš ~~~~~~]\"],\"editor.toolHint.styleShaker.49z17k\":[\"[Šţýļé: Šĥáķéŕ ~~~~]\"],\"editor.toolHint.styleSingle.6f3cm0\":[\"[Šţýļé: Šîñĝļé ~~~~]\"],\"editor.toolHint.styleSixPanel.u49laj\":[\"[Šţýļé: Šîẋ-þáñéļ ~~~~]\"],\"editor.toolHint.styleSliding.q81s0i\":[\"[Šţýļé: Šļîðîñĝ ~~~~]\"],\"editor.toolHint.styleTallGrid.1eotg3x\":[\"[Šţýļé: Ţáļļ ĝŕîð ~~~~]\"],\"editor.toolHint.styleTransom.kb9bh6\":[\"[Šţýļé: Ţŕáñšöɱ ~~~~]\"],\"editor.toolHint.styleTripleHung.pxnqi3\":[\"[Šţýļé: Ţŕîþļé-ĥûñĝ ~~~~~]\"],\"editor.toolHint.styleWideGrid.12n27et\":[\"[Šţýļé: Ŵîðé ĝŕîð ~~~~]\"],\"editor.toolHint.switchRotationAxisYXZ.15lohm6\":[\"[Šŵîţçĥ ŕöţáţîöñ áẋîš (Ý → Ẋ → Ž) ~~~~~~~]\"],\"editor.toolHint.theWallsMeetAtTooSharp.kh3wi3\":[\"[Ţĥé ŵáļļš ɱééţ áţ ţöö šĥáŕþ öŕ ţöö ƒļáţ áñ áñĝļé ~~~~~~~~~~~~]\"],\"editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4\":[\"[Ţĥé ŵîñðöŵ ŵáîţîñĝ ţĥéŕé îš ţáļļéŕ ţĥáñ ţĥîš ŵáļļ ~~~~~~~~~~~~~]\"],\"editor.toolHint.threeOrMoreWallsMeetHere.rgifif\":[\"[Ţĥŕéé öŕ ɱöŕé ŵáļļš ɱééţ ĥéŕé ~~~~~~~~]\"],\"editor.toolHint.traceCeilingOutline.1b639wv\":[\"[Ţŕáçé çéîļîñĝ öûţļîñé ~~~~~~]\"],\"editor.toolHint.traceSlabOutline.z0j2lc\":[\"[Ţŕáçé šļáƀ öûţļîñé ~~~~~]\"],\"editor.toolHint.type.1m2zofh\":[\"[Ţýþé ~~]\"],\"editor.toolHint.typeAwning.1cj59al\":[\"[Ţýþé: Åŵñîñĝ ~~~]\"],\"editor.toolHint.typeBarn.sbnygq\":[\"[Ţýþé: Ɓáŕñ ~~~]\"],\"editor.toolHint.typeBay.1200hc1\":[\"[Ţýþé: Ɓáý ~~~]\"],\"editor.toolHint.typeBow.14s2t9p\":[\"[Ţýþé: Ɓöŵ ~~~]\"],\"editor.toolHint.typeCabinet.1r68je5\":[\"[Ţýþé: Çáƀîñéţ ~~~~]\"],\"editor.toolHint.typeCasement.176uo9j\":[\"[Ţýþé: Çášéɱéñţ ~~~~]\"],\"editor.toolHint.typeDouble.af7w4i\":[\"[Ţýþé: Ðöûƀļé ~~~]\"],\"editor.toolHint.typeDoubleHung.t482fu\":[\"[Ţýþé: Ðöûƀļé Ĥûñĝ ~~~~~]\"],\"editor.toolHint.typeFixed.v1cjez\":[\"[Ţýþé: Ƒîẋéð ~~~]\"],\"editor.toolHint.typeFolding.sxxyng\":[\"[Ţýþé: Ƒöļðîñĝ ~~~~]\"],\"editor.toolHint.typeFrench.1y57z1b\":[\"[Ţýþé: Ƒŕéñçĥ ~~~]\"],\"editor.toolHint.typeHinged.1xrcfju\":[\"[Ţýþé: Ĥîñĝéð ~~~]\"],\"editor.toolHint.typeIsland.ufr3ce\":[\"[Ţýþé: Κļáñð ~~~]\"],\"editor.toolHint.typeLouvered.oxl1cz\":[\"[Ţýþé: Ļöûṽéŕéð ~~~~]\"],\"editor.toolHint.typePocket.hc65x9\":[\"[Ţýþé: Þöçķéţ ~~~]\"],\"editor.toolHint.typeSingleHung.p53pyl\":[\"[Ţýþé: Šîñĝļé Ĥûñĝ ~~~~~]\"],\"editor.toolHint.typeSliding.nwmr3f\":[\"[Ţýþé: Šļîðîñĝ ~~~~]\"],\"editor.toolHint.verticalStackClickToPlace.ijcn4q\":[\"[Ṽéŕţîçáļ šţáçķ ↕, çļîçķ ţö þļáçé ~~~~~~~~]\"],\"editor.toolHint.wasteVent.1hvi3vj\":[\"[Ŵášţé / ṽéñţ ~~~]\"],\"editor.toolHint.windowStyleClickOrPressL.ah7qm9\":[\"[Ŵîñðöŵ šţýļé — çļîçķ öŕ þŕéšš Ļ ţö çýçļé ~~~~~~~~~~]\"],\"editor.toolHint.windowTypeClickOrPressO.wv4x35\":[\"[Ŵîñðöŵ ţýþé — çļîçķ öŕ þŕéšš Ö ţö çýçļé ~~~~~~~~~]\"],\"editor.tools.distribution.distributionRunTool.lengthField\":[\"[Ļéñĝţĥ: \",[\"field\"],\" ɱ ~~~]\"],\"editor.tools.distribution.distributionRunTool.tabDirection\":[\"[Ţáƀ: ðîŕéçţîöñ ~~~~]\"],\"editor.tools.distribution.distributionRunTool.typeALength\":[\"[ţýþé á ļéñĝţĥ ~~~~]\"],\"editor.views.plugins\":[\"[Þļûĝîñš ~~~]\"],\"editor.views.view2d\":[\"[2Ð ~]\"],\"editor.views.view3d\":[\"[3Ð ~]\"],\"editor.views.viewBar.auto\":[\"[Åûţö ~~]\"],\"editor.views.viewBar.browsePlugins\":[\"[Ɓŕöŵšé þļûĝîñš ~~~~]\"],\"editor.views.viewBar.closeLabel\":[\"[Çļöšé \",[\"label\"],\" ~~]\"],\"editor.views.viewBar.closeSplit\":[\"[Çļöšé šþļîţ (\\\\) ~~~]\"],\"editor.views.viewBar.ground\":[\"[Ĝŕöûñð ~~]\"],\"editor.views.viewBar.groundSolid\":[\"[\",[\"standing\",\"select\",{\"partly\":[\"Þáŕţļý ƀéļöŵ ĝŕáðé · ĝŕöûñð šöļîð\"],\"below\":[\"Ɓéļöŵ ĝŕáðé · ĝŕöûñð šöļîð\"],\"other\":[\"Ĝŕöûñð šöļîð\"]}],\" ~~~~~~~~~~~~~~~~~~]\"],\"editor.views.viewBar.groundVeiled\":[\"[\",[\"standing\",\"select\",{\"partly\":[\"Þáŕţļý ƀéļöŵ ĝŕáðé · ĝŕöûñð ṽéîļéð\"],\"below\":[\"Ɓéļöŵ ĝŕáðé · ĝŕöûñð ṽéîļéð\"],\"other\":[\"Ĝŕöûñð ṽéîļéð\"]}],\" ~~~~~~~~~~~~~~~~~~~]\"],\"editor.views.viewBar.keepTheGroundSolid\":[\"[Ķééþ ţĥé ĝŕöûñð šöļîð ~~~~~~]\"],\"editor.views.viewBar.moreViews\":[\"[Ṁöŕé ṽîéŵš ~~~]\"],\"editor.views.viewBar.paneViews\":[\"[Þáñé \",[\"pane\"],\" ṽîéŵš ~~~]\"],\"editor.views.viewBar.pinLabel\":[\"[Þîñ \",[\"label\"],\" ~]\"],\"editor.views.viewBar.pinToTheViewBar\":[\"[Þîñ ţö ţĥé ṽîéŵ ƀáŕ ~~~~~]\"],\"editor.views.viewBar.solid\":[\"[Šöļîð ~~]\"],\"editor.views.viewBar.split\":[\"[Šþļîţ (\\\\) ~~]\"],\"editor.views.viewBar.split2\":[\"[Šþļîţ ~~]\"],\"editor.views.viewBar.swapPanes\":[\"[Šŵáþ þáñéš ~~~]\"],\"editor.views.viewBar.unpinFromTheViewBar\":[\"[Ûñþîñ ƒŕöɱ ţĥé ṽîéŵ ƀáŕ ~~~~~~]\"],\"editor.views.viewBar.unpinLabel\":[\"[Ûñþîñ \",[\"label\"],\" ~~]\"],\"editor.views.viewBar.veilTheGroundOverALevel\":[\"[Ṽéîļ ţĥé ĝŕöûñð öṽéŕ á ļéṽéļ ƀéļöŵ ĝŕáðé ~~~~~~~~~~]\"],\"editor.views.viewBar.views\":[\"[Ṽîéŵš ~~]\"],\"editor.youCanRetryTheSceneOr\":[\"[Ýöû çáñ ŕéţŕý ţĥé šçéñé öŕ ŕéţûŕñ ĥöɱé ŵîţĥöûţ ŕéļöáðîñĝ ţĥé ŵĥöļé áþþ šĥéļļ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.beam.description\":[\"[Å ƒŕáɱîñĝ ƀéáɱ ðŕáŵñ éñð ţö éñð: á þöŕçĥ ĥéáðéŕ, á ðéçķ ƀéáɱ, á ĝîŕðéŕ. ~~~~~~~~~~~~~~~~]\"],\"node.beam.label\":[\"[Ɓéáɱ ~~]\"],\"node.beam.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.beam.panel.addPosts\":[\"[Åðð þöšţš ~~~]\"],\"node.beam.panel.beam\":[\"[Ɓéáɱ ~~]\"],\"node.beam.panel.beamSize\":[\"[Ɓéáɱ \",[\"size\"],\" ~~]\"],\"node.beam.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.beam.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.beam.panel.dropped\":[\"[Ðŕöþþéð ~~~]\"],\"node.beam.panel.flush\":[\"[Ƒļûšĥ ~~]\"],\"node.beam.panel.followTheCeilingPlane\":[\"[Ƒöļļöŵ ţĥé çéîļîñĝ þļáñé ~~~~~~~]\"],\"node.beam.panel.lengthLongItsUnderside\":[\"[\",[\"length\"],\" ļöñĝ; îţš ûñðéŕšîðé \",[\"underside\"],\" öṽéŕ ţĥé ƒļööŕ, ţĥé çéîļîñĝ þļáñé \",[\"plane\"],\". ~~~~~~~~~~~~~]\"],\"node.beam.panel.material\":[\"[Ṁáţéŕîáļ ~~~]\"],\"node.beam.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.beam.panel.n1Ply\":[\"[1 þļý ~]\"],\"node.beam.panel.placement\":[\"[Þļáçéɱéñţ ~~~]\"],\"node.beam.panel.pliesPlies\":[\"[\",[\"plies\"],\" þļîéš ~~]\"],\"node.beam.panel.size\":[\"[Šîžé ~~]\"],\"node.beam.panel.top\":[\"[Ţöþ ~]\"],\"node.beam.panel.topElevation\":[\"[Ţöþ éļéṽáţîöñ ~~~~]\"],\"node.beam.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.block.description\":[\"[Å ţöþöļöĝý-ƀáçķéð šöļîð éðîţéð ðîŕéçţļý îñ ţĥé çáñṽáš. ~~~~~~~~~~~~~~]\"],\"node.block.label\":[\"[Ɓļöçķ ~~]\"],\"node.boxVent.description\":[\"[Šɱáļļ ļöûṽéŕéð éẋĥáûšţ ṽéñţ ţĥáţ šîţš öñ á ŕööƒ šļöþé. ~~~~~~~~~~~~~~]\"],\"node.boxVent.label\":[\"[Ɓöẋ Ṽéñţ ~~~]\"],\"node.boxVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.boxVent.panel.baseFlange\":[\"[Ɓášé Ƒļáñĝé ~~~]\"],\"node.boxVent.panel.baseHeight\":[\"[Ɓášé Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.baseInset\":[\"[Ɓášé Îñšéţ ~~~]\"],\"node.boxVent.panel.box\":[\"[Ɓöẋ ~]\"],\"node.boxVent.panel.boxVent\":[\"[Ɓöẋ Ṽéñţ ~~~]\"],\"node.boxVent.panel.cap\":[\"[Çáþ ~]\"],\"node.boxVent.panel.capHeight\":[\"[Çáþ Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.cornerBevel\":[\"[Çöŕñéŕ Ɓéṽéļ ~~~~]\"],\"node.boxVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.boxVent.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.boxVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.boxVent.panel.dome\":[\"[Ðöɱé ~~]\"],\"node.boxVent.panel.domeCurvature\":[\"[Ðöɱé Çûŕṽáţûŕé ~~~~]\"],\"node.boxVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.boxVent.panel.gapHeight\":[\"[Ĝáþ Ĥéîĝĥţ ~~~]\"],\"node.boxVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.boxVent.panel.hoodOverhang\":[\"[Ĥööð Öṽéŕĥáñĝ ~~~~]\"],\"node.boxVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.boxVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.boxVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.boxVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.boxVent.panel.topTaper\":[\"[Ţöþ Ţáþéŕ ~~~]\"],\"node.boxVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.building.description\":[\"[Å ƀûîļðîñĝ çöñţáîñéŕ ĥöļðîñĝ öñé öŕ ɱöŕé ļéṽéļš. ~~~~~~~~~~~~]\"],\"node.building.label\":[\"[Ɓûîļðîñĝ ~~~]\"],\"node.cabinet.compartmentCard.baskets\":[\"[Ɓášķéţš ~~~]\"],\"node.cabinet.compartmentCard.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.cabinet.compartmentCard.bottomFreezer\":[\"[Ɓöţţöɱ Ƒŕééžéŕ ~~~~]\"],\"node.cabinet.compartmentCard.bowls\":[\"[Ɓöŵļš ~~]\"],\"node.cabinet.compartmentCard.burnersOn\":[\"[Ɓûŕñéŕš öñ ~~~]\"],\"node.cabinet.compartmentCard.chimney\":[\"[Çĥîɱñéý ~~~]\"],\"node.cabinet.compartmentCard.decrease\":[\"[Ðéçŕéášé \",[\"label\"],\" ~~~]\"],\"node.cabinet.compartmentCard.door\":[\"[Ðööŕ ~~]\"],\"node.cabinet.compartmentCard.double\":[\"[Ðöûƀļé ~~]\"],\"node.cabinet.compartmentCard.drawer\":[\"[Ðŕáŵéŕ ~~]\"],\"node.cabinet.compartmentCard.drawers\":[\"[Ðŕáŵéŕš ~~~]\"],\"node.cabinet.compartmentCard.flipHinge\":[\"[Ƒļîþ ĥîñĝé ~~~]\"],\"node.cabinet.compartmentCard.flipTheDoorHingeToThe\":[\"[Ƒļîþ ţĥé ðööŕ ĥîñĝé ţö ţĥé öþþöšîţé šîðé ~~~~~~~~~~]\"],\"node.cabinet.compartmentCard.fridge\":[\"[Ƒŕîðĝé ~~]\"],\"node.cabinet.compartmentCard.gas\":[\"[Ĝáš ~]\"],\"node.cabinet.compartmentCard.glass\":[\"[Ĝļášš ~~]\"],\"node.cabinet.compartmentCard.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cabinet.compartmentCard.hob\":[\"[Ĥöƀ ~]\"],\"node.cabinet.compartmentCard.increase\":[\"[Îñçŕéášé \",[\"label\"],\" ~~~]\"],\"node.cabinet.compartmentCard.induction\":[\"[Îñðûçţîöñ ~~~]\"],\"node.cabinet.compartmentCard.layout\":[\"[Ļáýöûţ ~~]\"],\"node.cabinet.compartmentCard.left\":[\"[Ļéƒţ ~~]\"],\"node.cabinet.compartmentCard.micro\":[\"[Ṁîçŕö ~~]\"],\"node.cabinet.compartmentCard.moveDown\":[\"[Ṁöṽé ðöŵñ ~~~]\"],\"node.cabinet.compartmentCard.moveUp\":[\"[Ṁöṽé ûþ ~~]\"],\"node.cabinet.compartmentCard.oven\":[\"[Öṽéñ ~~]\"],\"node.cabinet.compartmentCard.pullout\":[\"[Þûļļöûţ ~~~]\"],\"node.cabinet.compartmentCard.rack\":[\"[Ŕáçķ ~~]\"],\"node.cabinet.compartmentCard.removeCompartment\":[\"[Ŕéɱöṽé çöɱþáŕţɱéñţ ~~~~~~]\"],\"node.cabinet.compartmentCard.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.cabinet.compartmentCard.shelf\":[\"[Šĥéļƒ ~~]\"],\"node.cabinet.compartmentCard.shelves\":[\"[Šĥéļṽéš ~~~]\"],\"node.cabinet.compartmentCard.shelvesInside\":[\"[Šĥéļṽéš îñšîðé ~~~~]\"],\"node.cabinet.compartmentCard.single\":[\"[Šîñĝļé ~~]\"],\"node.cabinet.compartmentCard.sink\":[\"[Šîñķ ~~]\"],\"node.cabinet.compartmentCard.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.compartmentCard.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.cabinet.compartmentCard.top\":[\"[Ţöþ ~]\"],\"node.cabinet.compartmentCard.topFreezer\":[\"[Ţöþ Ƒŕééžéŕ ~~~]\"],\"node.cabinet.compartmentCard.topGrate\":[\"[Ţöþ ĝŕáţé ~~~]\"],\"node.cabinet.compartmentCard.tray\":[\"[Ţŕáý ~~]\"],\"node.cabinet.compartmentCard.type\":[\"[Ţýþé ~~]\"],\"node.cabinet.compartmentCard.washer\":[\"[Ŵášĥéŕ ~~]\"],\"node.cabinet.compartmentCard.wire\":[\"[Ŵîŕé ~~]\"],\"node.cabinet.description\":[\"[Å çöñƒîĝûŕáƀļé þáŕáɱéţŕîç ƀášé çáƀîñéţ. ~~~~~~~~~~~]\"],\"node.cabinet.essentialControls.bottomFreezer\":[\"[Ɓöţţöɱ ƒŕééžéŕ ~~~~]\"],\"node.cabinet.essentialControls.cooktopType\":[\"[Çööķţöþ ţýþé ~~~~]\"],\"node.cabinet.essentialControls.doorDirection\":[\"[Ðööŕ ðîŕéçţîöñ ~~~~]\"],\"node.cabinet.essentialControls.double\":[\"[Ðöûƀļé ~~]\"],\"node.cabinet.essentialControls.fridgeType\":[\"[Ƒŕîðĝé ţýþé ~~~]\"],\"node.cabinet.essentialControls.gas\":[\"[Ĝáš ~]\"],\"node.cabinet.essentialControls.glass\":[\"[Ĝļášš ~~]\"],\"node.cabinet.essentialControls.induction\":[\"[Îñðûçţîöñ ~~~]\"],\"node.cabinet.essentialControls.itemOptions\":[\"[Îţéɱ öþţîöñš ~~~~]\"],\"node.cabinet.essentialControls.leftHinge\":[\"[Ļéƒţ ĥîñĝé ~~~]\"],\"node.cabinet.essentialControls.rightHinge\":[\"[Ŕîĝĥţ ĥîñĝé ~~~]\"],\"node.cabinet.essentialControls.single\":[\"[Šîñĝļé ~~]\"],\"node.cabinet.essentialControls.sinkLayout\":[\"[Šîñķ ļáýöûţ ~~~]\"],\"node.cabinet.essentialControls.topFreezer\":[\"[Ţöþ ƒŕééžéŕ ~~~]\"],\"node.cabinet.kitchenCollection.chooseLabel\":[\"[Çĥööšé \",[\"label\"],\" ~~]\"],\"node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge\":[\"[Ñö ķîţçĥéñ îţéɱš ɱáţçĥ. Ţŕý “ƒŕîðĝé”, “šîñķ”, öŕ “çáƀîñéţ”. ~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenCollection.searchFridgeSinkCabinets\":[\"[Šéáŕçĥ ƒŕîðĝé, šîñķ, çáƀîñéţš… ~~~~~~~~]\"],\"node.cabinet.kitchenCollection.searchKitchenItems\":[\"[Šéáŕçĥ ķîţçĥéñ îţéɱš ~~~~~~]\"],\"node.cabinet.kitchenGroup.appliances\":[\"[Åþþļîáñçéš & ƒîẋţûŕéš ~~~~~~]\"],\"node.cabinet.kitchenGroup.cabinets\":[\"[Çáƀîñéţš ~~~]\"],\"node.cabinet.kitchenModuleActions.addBeside\":[\"[Åðð ƀéšîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.addBesideSelectedItem\":[\"[Åðð ƀéšîðé šéļéçţéð îţéɱ ~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.addLabel\":[\"[Åðð \",[\"label\"],\" ~]\"],\"node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis\":[\"[Åñ ûþþéŕ çáƀîñéţ áţţáçĥéð ţö ţĥîš îţéɱ ŵîļļ ƀé ŕéɱöṽéð ţö ɱáķé ŕööɱ. ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.cabinet\":[\"[Çáƀîñéţ ~~~]\"],\"node.cabinet.kitchenModuleActions.cancel\":[\"[Çáñçéļ ~~]\"],\"node.cabinet.kitchenModuleActions.cannotFit\":[\"[Ţĥîš \",[\"width\"],\" îţéɱ çáññöţ ƒîţ ŵîţĥîñ ţĥé çûŕŕéñţ ŕûñ áñð îţš ŵáļļ öŕ çöŕñéŕ ļîɱîţš. Ṁöṽé áñ éñð çáƀîñéţ öŕ ƒŕéé šþáçé, ţĥéñ ţŕý áĝáîñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.insertsBeside\":[\"[\",[\"side\",\"select\",{\"left\":[\"Îñšéŕţš öñ ţĥé ļéƒţ öƒ ţĥé šéļéçţéð îţéɱ.\"],\"other\":[\"Îñšéŕţš öñ ţĥé ŕîĝĥţ öƒ ţĥé šéļéçţéð îţéɱ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.kitchenChanged\":[\"[Ţĥé ķîţçĥéñ çĥáñĝéð. Šéļéçţ áñ îţéɱ áĝáîñ ţö ŕéƒŕéšĥ ţĥé þŕéṽîéŵ. ~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs\":[\"[Ķîţçĥéñ ŕûñ þŕéṽîéŵ, ĥîĝĥļîĝĥţéð îţéɱ îš ţĥé þŕöþöšéð çĥáñĝé ~~~~~~~~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.leftSide\":[\"[Ļéƒţ šîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.moved\":[\"[\",[\"name\"],\": ɱöṽéš \",[\"distance\"],\" ~~]\"],\"node.cabinet.kitchenModuleActions.previewItem\":[[\"name\"],\": \",[\"width\"]],\"node.cabinet.kitchenModuleActions.replaceSelectedItem\":[\"[Ŕéþļáçé šéļéçţéð îţéɱ ~~~~~~]\"],\"node.cabinet.kitchenModuleActions.replaceWith\":[\"[Ŕéþļáçé ŵîţĥ… ~~~~]\"],\"node.cabinet.kitchenModuleActions.replaceWithLabel\":[\"[Ŕéþļáçé ŵîţĥ \",[\"label\"],\" ~~~~]\"],\"node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem\":[\"[Ŕéþļáçéš öñļý ţĥé šéļéçţéð îţéɱ. ~~~~~~~~~]\"],\"node.cabinet.kitchenModuleActions.resized\":[[\"name\"],\": \",[\"before\"],\" → \",[\"after\"]],\"node.cabinet.kitchenModuleActions.rightSide\":[\"[Ŕîĝĥţ šîðé ~~~]\"],\"node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen\":[\"[Ûñáƀļé ţö ûþðáţé ţĥîš ķîţçĥéñ. ~~~~~~~~]\"],\"node.cabinet.label\":[\"[Ṁöðûļáŕ Çáƀîñéţ ~~~~~]\"],\"node.cabinet.panel.aboveCeiling\":[\"[Ƒîñîšĥéð ĥéîĝĥţ éẋţéñðš \",[\"overflow\"],\" ɱɱ áƀöṽé ţĥé çéîļîñĝ. ~~~~~~~~~~~~]\"],\"node.cabinet.panel.addChimney\":[\"[Åðð çĥîɱñéý ~~~]\"],\"node.cabinet.panel.addCompartment\":[\"[Åðð çöɱþáŕţɱéñţ ~~~~~]\"],\"node.cabinet.panel.addWallCabinet\":[\"[Åðð ŵáļļ çáƀîñéţ ~~~~~]\"],\"node.cabinet.panel.advanced\":[\"[Åðṽáñçéð ~~~]\"],\"node.cabinet.panel.appliance\":[\"[Åþþļîáñçé ~~~]\"],\"node.cabinet.panel.applianceFront\":[\"[Åþþļîáñçé ƒŕöñţ ~~~~~]\"],\"node.cabinet.panel.auto\":[\"[Åûţö ~~]\"],\"node.cabinet.panel.bar\":[\"[Ɓáŕ ~]\"],\"node.cabinet.panel.baseCabinet\":[\"[Ɓášé Çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview\":[\"[Çáƀîñéţ çöñšţŕûçţîöñ, çöɱþáŕţɱéñţš, áñð öþéñîñĝ þŕéṽîéŵ. ~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.cabinetPanel\":[\"[Çáƀîñéţ þáñéļ ~~~~]\"],\"node.cabinet.panel.cabinetType\":[\"[Çáƀîñéţ Ţýþé ~~~~]\"],\"node.cabinet.panel.center\":[\"[Çéñţéŕ ~~]\"],\"node.cabinet.panel.close\":[\"[Çļöšé ~~]\"],\"node.cabinet.panel.closeCabinet\":[\"[Çļöšé çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.commonWallCabinetHeightsUseThe\":[\"[Çöɱɱöñ ŵáļļ-çáƀîñéţ ĥéîĝĥţš. Ûšé ţĥé šļîðéŕ ƀéļöŵ ƒöŕ á çûšţöɱ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.compartments\":[\"[Çöɱþáŕţɱéñţš ~~~~]\"],\"node.cabinet.panel.cutout\":[\"[Çûţöûţ ~~]\"],\"node.cabinet.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cabinet.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.cabinet.panel.editKitchenRun\":[\"[Éðîţ ķîţçĥéñ ŕûñ ~~~~~]\"],\"node.cabinet.panel.fillToCeiling\":[\"[Ƒîļļ ţö çéîļîñĝ ~~~~]\"],\"node.cabinet.panel.finish\":[\"[Ƒîñîšĥ ~~]\"],\"node.cabinet.panel.frontConstruction\":[\"[Ƒŕöñţ çöñšţŕûçţîöñ ~~~~~~]\"],\"node.cabinet.panel.handles\":[\"[Ĥáñðļéš ~~~]\"],\"node.cabinet.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cabinet.panel.heightPreset\":[\"[Ĥéîĝĥţ þŕéšéţ ~~~~]\"],\"node.cabinet.panel.hole\":[\"[Ĥöļé ~~]\"],\"node.cabinet.panel.inset\":[\"[Îñšéţ ~~]\"],\"node.cabinet.panel.kitchen\":[\"[Ķîţçĥéñ ~~~]\"],\"node.cabinet.panel.kitchenRun\":[\"[Ķîţçĥéñ ŕûñ ~~~]\"],\"node.cabinet.panel.knob\":[\"[Ķñöƀ ~~]\"],\"node.cabinet.panel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.cabinet.panel.none\":[\"[Ñöñé ~~]\"],\"node.cabinet.panel.open\":[\"[Öþéñ ~~]\"],\"node.cabinet.panel.openAnimation\":[\"[Öþéñ Åñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.openCabinet\":[\"[Öþéñ çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.overlay\":[\"[Öṽéŕļáý ~~~]\"],\"node.cabinet.panel.panelReady\":[\"[Þáñéļ-ŕéáðý ~~~]\"],\"node.cabinet.panel.planningChecks\":[\"[Þļáññîñĝ çĥéçķš ~~~~~]\"],\"node.cabinet.panel.play\":[\"[Þļáý ~~]\"],\"node.cabinet.panel.playAnimation\":[\"[Þļáý áñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cabinet.panel.raisedArch\":[\"[Ŕáîšéð Åŕçĥ ~~~]\"],\"node.cabinet.panel.removeWallCabinet\":[\"[Ŕéɱöṽé ŵáļļ çáƀîñéţ ~~~~~~]\"],\"node.cabinet.panel.replaceWith\":[\"[Ŕéþļáçé ŵîţĥ… ~~~~]\"],\"node.cabinet.panel.revealGap\":[\"[Ŕéṽéáļ ĝáþ ~~~]\"],\"node.cabinet.panel.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.cabinet.panel.sharedCountertopPlinthAndFinishes\":[\"[Šĥáŕéð çöûñţéŕţöþ, þļîñţĥ, áñð ƒîñîšĥéš ~~~~~~~~~~]\"],\"node.cabinet.panel.slab\":[\"[Šļáƀ ~~]\"],\"node.cabinet.panel.solidMatchingFrontBetweenTheCabinet\":[\"[Šöļîð ɱáţçĥîñĝ ƒŕöñţ ƀéţŵééñ ţĥé çáƀîñéţ áñð ŵáļļ. Çöûñţéŕţöþ áñð ţöé-ķîçķ çöñţîñûé áçŕöšš ţĥîš ƒîļļéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.standardWidth\":[\"[Šţáñðáŕð ŵîðţĥ ~~~~]\"],\"node.cabinet.panel.stop\":[\"[Šţöþ ~~]\"],\"node.cabinet.panel.stopAnimation\":[\"[Šţöþ áñîɱáţîöñ ~~~~]\"],\"node.cabinet.panel.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.panel.tallCabinet\":[\"[Ţáļļ Çáƀîñéţ ~~~~]\"],\"node.cabinet.panel.top\":[\"[Ţöþ ~]\"],\"node.cabinet.panel.topCabinet\":[\"[Ţöþ Çáƀîñéţ ~~~]\"],\"node.cabinet.panel.topCeiling\":[\"[Ţöþ / Çéîļîñĝ ~~~]\"],\"node.cabinet.panel.topDepth\":[\"[Ţöþ ðéþţĥ ~~~]\"],\"node.cabinet.panel.topHeight\":[\"[Ţöþ ĥéîĝĥţ ~~~]\"],\"node.cabinet.panel.trimSoffit\":[\"[Ţŕîɱ / Šöƒƒîţ ~~~]\"],\"node.cabinet.panel.usesTheCabinetFrontStyleReveal\":[\"[Ûšéš ţĥé çáƀîñéţ ƒŕöñţ šţýļé, ŕéṽéáļ, ĥáñðļé, áñð ƒŕöñţ ɱáţéŕîáļ šéţţîñĝš ƀéļöŵ. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.panel.wallFiller\":[\"[Ŵáļļ ƒîļļéŕ ~~~]\"],\"node.cabinet.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.cabinet.preset.baseCabinet\":[\"[Ɓášé çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.dishwasher\":[\"[Ðîšĥŵášĥéŕ ~~~]\"],\"node.cabinet.preset.drawerCabinet\":[\"[Ðŕáŵéŕ çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.fridge\":[\"[Ƒŕîðĝé ~~]\"],\"node.cabinet.preset.gasCooktop\":[\"[Ĝáš çööķţöþ ~~~]\"],\"node.cabinet.preset.inductionCooktop\":[\"[Îñðûçţîöñ çööķţöþ ~~~~~]\"],\"node.cabinet.preset.ovenTower\":[\"[Öṽéñ Ţöŵéŕ ~~~]\"],\"node.cabinet.preset.sinkCabinet\":[\"[Šîñķ çáƀîñéţ ~~~~]\"],\"node.cabinet.preset.tallPantry\":[\"[Ţáļļ Þáñţŕý ~~~]\"],\"node.cabinet.preset.wallCabinet\":[\"[Ŵáļļ çáƀîñéţ ~~~~]\"],\"node.cabinet.runPanel.addLeft\":[\"[Åðð ļéƒţ ~~~]\"],\"node.cabinet.runPanel.addRight\":[\"[Åðð ŕîĝĥţ ~~~]\"],\"node.cabinet.runPanel.alignCabinetsHandles\":[\"[Åļîĝñ çáƀîñéţš & ĥáñðļéš ~~~~~~]\"],\"node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop\":[\"[Åþþļîéš ðéþţĥ, çáŕçášš, þļîñţĥ, áñð çöûñţéŕţöþ ţĥîçķñéšš ţö ţĥîš ŕûñ. ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayCreate\":[\"[\",[\"count\",\"plural\",{\"one\":[\"Çŕéáţé \",[\"number\"],\" ɱöðûļé çöþý\"],\"other\":[\"Çŕéáţé \",[\"number\"],\" ɱöðûļé çöþîéš\"]}],\" ~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayCreateNamed\":[\"[\",[\"count\",\"plural\",{\"one\":[\"Çŕéáţé \",[\"number\"],\" \",[\"name\"],\" çöþý\"],\"other\":[\"Çŕéáţé \",[\"number\"],\" \",[\"name\"],\" çöþîéš\"]}],\" ~~~~~~~]\"],\"node.cabinet.runPanel.arrayInvalidOptions\":[\"[Çĥööšé á çöþý çöûñţ ƒŕöɱ 1 ţö 20 áñð šþáçîñĝ ƒŕöɱ 0 ţö 2 ɱ ~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayNoRoom\":[\"[Ţĥéŕé îš ñöţ éñöûĝĥ ŕööɱ îñ ţĥîš ŕûñ ƒöŕ ţĥé ŕéǫûéšţéð áŕŕáý ~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.arrayNoSource\":[\"[Çĥööšé á šţáñðáŕð öŕ áþþļîáñçé ɱöðûļé áš ţĥé šöûŕçé ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.auto\":[\"[Åûţö ~~]\"],\"node.cabinet.runPanel.back\":[\"[Ɓáçķ ~~]\"],\"node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping\":[\"[Ɓáļáñçéš šţáñðáŕð ƀášé çáƀîñéţš ŵĥîļé ķééþîñĝ áþþļîáñçé áñð çöŕñéŕ-ƒîļļéŕ ŵîðţĥš ƒîẋéð. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.bar\":[\"[Ɓáŕ ~]\"],\"node.cabinet.runPanel.barCounter\":[\"[Ɓáŕ çöûñţéŕ ~~~]\"],\"node.cabinet.runPanel.barDepth\":[\"[Ɓáŕ ðéþţĥ ~~~]\"],\"node.cabinet.runPanel.barHeight\":[\"[Ɓáŕ ĥéîĝĥţ ~~~]\"],\"node.cabinet.runPanel.carcassHeight\":[\"[Çáŕçášš ĥéîĝĥţ ~~~~]\"],\"node.cabinet.runPanel.center\":[\"[Çéñţéŕ ~~]\"],\"node.cabinet.runPanel.commonWallCabinetHeightsUseThe\":[\"[Çöɱɱöñ ŵáļļ-çáƀîñéţ ĥéîĝĥţš. Ûšé ţĥé šļîðéŕ ƀéļöŵ ƒöŕ á çûšţöɱ ĥéîĝĥţ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.copies\":[\"[Çöþîéš ~~]\"],\"node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure\":[\"[Çöþîéš îñçļûðé ţĥé šöûŕçé çáƀîñéţ šţŕûçţûŕé áñð áñý áţţáçĥéð ŵáļļ çáƀîñéţ. Éẋîšţîñĝ ɱöðûļéš šţáý ƒîẋéð; ţĥé ŕéǫûéšţéð áŕŕáý ɱûšţ ƒîţ îñ ţĥé áṽáîļáƀļé ŕûñ šþáçé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.cornerFillersCannotBeUsedAs\":[\"[Çöŕñéŕ ƒîļļéŕš çáññöţ ƀé ûšéð áš áŕŕáý šöûŕçéš ~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.countertopDepth\":[\"[Çöûñţéŕţöþ ðéþţĥ ~~~~~]\"],\"node.cabinet.runPanel.countertopHeight\":[\"[Çöûñţéŕţöþ ĥéîĝĥţ ~~~~~]\"],\"node.cabinet.runPanel.createArray\":[\"[Çŕéáţé áŕŕáý ~~~~]\"],\"node.cabinet.runPanel.cutout\":[\"[Çûţöûţ ~~]\"],\"node.cabinet.runPanel.deleteModule\":[\"[Ðéļéţé \",[\"name\"],\" ~~]\"],\"node.cabinet.runPanel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cabinet.runPanel.direction\":[\"[Ðîŕéçţîöñ ~~~]\"],\"node.cabinet.runPanel.duplicateAlongRun\":[\"[Ðûþļîçáţé áļöñĝ ŕûñ ~~~~~~]\"],\"node.cabinet.runPanel.equalizeAll\":[\"[Éǫûáļîžé áļļ ŕéšîžéáƀļé šţáñðáŕð ƀášé çáƀîñéţš îñ ţĥîš ŕûñ ~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeAlreadyEqual\":[\"[Ţĥé ŕéšîžéáƀļé çáƀîñéţ ŵîðţĥš áŕé áļŕéáðý éǫûáļ ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeNeedsTwo\":[\"[Åţ ļéášţ ţŵö šţáñðáŕð ƀášé çáƀîñéţš áŕé ŕéǫûîŕéð ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeNoFit\":[\"[Ţĥé áṽáîļáƀļé ŕûñ ŵîðţĥ çáññöţ šáţý ţĥé çáƀîñéţ ŵîðţĥ ļîɱîţš ~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.equalizeWidths\":[\"[Éǫûáļîžé ŵîðţĥš ~~~~~]\"],\"node.cabinet.runPanel.fillerPlanSide\":[\"[\",[\"side\",\"select\",{\"left\":[\"Ļéƒţ: \",[\"width\"],\" ɱɱ\"],\"other\":[\"Ŕîĝĥţ: \",[\"width\"],\" ɱɱ\"]}],\" ~~~~]\"],\"node.cabinet.runPanel.finishAlign\":[\"[Ƒîñîšĥ & áļîĝñ ~~~~]\"],\"node.cabinet.runPanel.finishedBack\":[\"[Ƒîñîšĥéð ƀáçķ ~~~~]\"],\"node.cabinet.runPanel.finishedExposedEnds\":[\"[Ƒîñîšĥéð éẋþöšéð éñðš ~~~~~~]\"],\"node.cabinet.runPanel.fitWallFillers\":[\"[Ƒîţ ŵáļļ ƒîļļéŕš ~~~~~]\"],\"node.cabinet.runPanel.fits50150MmGapsAt\":[\"[Ƒîţš 50–150 ɱɱ ĝáþš áţ ŕûñ éñðš ƀéšîðé þéŕþéñðîçûļáŕ ŵáļļš. ~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.fronts\":[\"[Ƒŕöñţš ~~]\"],\"node.cabinet.runPanel.handles\":[\"[Ĥáñðļéš ~~~]\"],\"node.cabinet.runPanel.heightPreset\":[\"[Ĥéîĝĥţ þŕéšéţ ~~~~]\"],\"node.cabinet.runPanel.hole\":[\"[Ĥöļé ~~]\"],\"node.cabinet.runPanel.inset\":[\"[Îñšéţ ~~]\"],\"node.cabinet.runPanel.islandBar\":[\"[Κļáñð & Ɓáŕ ~~~]\"],\"node.cabinet.runPanel.kitchenRun\":[\"[Ķîţçĥéñ ŕûñ ~~~]\"],\"node.cabinet.runPanel.knob\":[\"[Ķñöƀ ~~]\"],\"node.cabinet.runPanel.left\":[\"[Ļéƒţ ~~]\"],\"node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd\":[\"[Ṁáţçĥ ƀášé ĥéîĝĥţš, ƀáçķš, ŕéṽéáļš áñð ĥáñðļéš. Åļîĝñ ûþþéŕ çáƀîñéţš; þŕéšéŕṽé ţáļļ áþþļîáñçéš áñð çûšţöɱ çöɱþáŕţɱéñţš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.metricProfile\":[\"[Ṁéţŕîç · 600 ɱɱ ~~~]\"],\"node.cabinet.runPanel.moduleNumber\":[\"[Ṁöðûļé \",[\"number\"],\" ~~]\"],\"node.cabinet.runPanel.modules\":[\"[Ṁöðûļéš ~~~]\"],\"node.cabinet.runPanel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.cabinet.runPanel.noEligibleModule\":[\"[Ñö éļîĝîƀļé ɱöðûļé ~~~~~]\"],\"node.cabinet.runPanel.none\":[\"[Ñöñé ~~]\"],\"node.cabinet.runPanel.overlay\":[\"[Öṽéŕļáý ~~~]\"],\"node.cabinet.runPanel.plinthHeight\":[\"[Þļîñţĥ ĥéîĝĥţ ~~~~]\"],\"node.cabinet.runPanel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cabinet.runPanel.raisedArch\":[\"[Ŕáîšéð Åŕçĥ ~~~]\"],\"node.cabinet.runPanel.revealGap\":[\"[Ŕéṽéáļ ĝáþ ~~~]\"],\"node.cabinet.runPanel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.cabinet.runPanel.seatingOverhang\":[\"[Šéáţîñĝ öṽéŕĥáñĝ ~~~~~]\"],\"node.cabinet.runPanel.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.cabinet.runPanel.sharedPlinthCountertop\":[\"[Šĥáŕéð Þļîñţĥ & Çöûñţéŕţöþ ~~~~~~~]\"],\"node.cabinet.runPanel.showCountertop\":[\"[Šĥöŵ çöûñţéŕţöþ ~~~~~]\"],\"node.cabinet.runPanel.showPlinth\":[\"[Šĥöŵ þļîñţĥ ~~~]\"],\"node.cabinet.runPanel.slab\":[\"[Šļáƀ ~~]\"],\"node.cabinet.runPanel.sourceModule\":[\"[Šöûŕçé ɱöðûļé ~~~~]\"],\"node.cabinet.runPanel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.cabinet.runPanel.standardDimensions\":[\"[Šţáñðáŕð ðîɱéñšîöñš ~~~~~~]\"],\"node.cabinet.runPanel.style\":[\"[Šţýļé ~~]\"],\"node.cabinet.runPanel.summaryCompartment\":[\"[\",[\"type\",\"select\",{\"shelf\":[\"Šĥéļƒ\"],\"drawer\":[\"Ðŕáŵéŕ\"],\"door\":[\"Ðööŕ\"],\"oven\":[\"Öṽéñ\"],\"microwave\":[\"Ṁîçŕöŵáṽé\"],\"dishwasher\":[\"Ðîšĥŵášĥéŕ\"],\"sink\":[\"Šîñķ\"],\"cooktop_gas\":[\"Ĝáš çööķţöþ\"],\"cooktop_induction\":[\"Îñðûçţîöñ çööķţöþ\"],\"pull_out_pantry\":[\"Þûļļ-öûţ þáñţŕý\"],\"fridge_single\":[\"Ƒŕîðĝé\"],\"fridge_double\":[\"Ðöûƀļé ƒŕîðĝé\"],\"fridge_top_freezer\":[\"Ţöþ-ƒŕééžéŕ ƒŕîðĝé\"],\"fridge_bottom_freezer\":[\"Ɓöţţöɱ-ƒŕééžéŕ ƒŕîðĝé\"],\"hood_pyramid\":[\"Þýŕáɱîð ĥööð\"],\"hood_curved_glass\":[\"Çûŕṽéð ĝļášš ĥööð\"],\"other\":[[\"raw\"]]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.cabinet.runPanel.summaryCompartments\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" çöɱþáŕţɱéñţ\"],\"other\":[[\"number\"],\" çöɱþáŕţɱéñţš\"]}],\" ~~~~~~~]\"],\"node.cabinet.runPanel.summaryEmpty\":[\"[Éɱþţý ~~]\"],\"node.cabinet.runPanel.summaryTall\":[\"[Ţáļļ çáƀîñéţ ~~~~]\"],\"node.cabinet.runPanel.top\":[\"[Ţöþ ~]\"],\"node.cabinet.runPanel.unableToFinish\":[\"[Ûñáƀļé ţö ƒîñîšĥ ţĥîš ķîţçĥéñ ŕûñ. ~~~~~~~~~]\"],\"node.cabinet.runPanel.usProfile\":[\"[ÛŠ · 24 îñ ~~]\"],\"node.cabinet.runPanel.useAsArraySource\":[\"[Ûšé \",[\"name\"],\" áš áŕŕáý šöûŕçé ~~~~~]\"],\"node.cabinet.runPanel.useThisModuleAsTheArray\":[\"[Ûšé ţĥîš ɱöðûļé áš ţĥé áŕŕáý šöûŕçé ~~~~~~~~~]\"],\"node.cabinet.runPanel.waterfallEnds\":[\"[Ŵáţéŕƒáļļ éñðš ~~~~]\"],\"node.cabinetModule.description\":[\"[Åñ éðîţáƀļé ɱöðûļé îñšîðé á ɱöðûļáŕ çáƀîñéţ ŕûñ. ~~~~~~~~~~~~]\"],\"node.cabinetModule.label\":[\"[Çáƀîñéţ Ṁöðûļé ~~~~]\"],\"node.ceiling.buildUpSection.bottomChord\":[\"[Ɓöţţöɱ çĥöŕð ~~~~]\"],\"node.ceiling.buildUpSection.ceilingJoists\":[\"[Çéîļîñĝ ĵöîšţš ~~~~]\"],\"node.ceiling.buildUpSection.decking\":[\"[Ðéçķîñĝ ~~~]\"],\"node.ceiling.buildUpSection.finish\":[\"[Ƒîñîšĥ ~~]\"],\"node.ceiling.buildUpSection.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.ceiling.buildUpSection.joists\":[\"[Ĵöîšţš ~~]\"],\"node.ceiling.buildUpSection.layers\":[\"[Ļáýéŕš ~~]\"],\"node.ceiling.buildUpSection.none\":[\"[Ñöñé ~~]\"],\"node.ceiling.buildUpSection.preset\":[\"[Þŕéšéţ ~~]\"],\"node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside\":[\"[Ţĥé ĥéîĝĥţ îš ţĥé ƒîñîšĥéð ûñðéŕšîðé; ţĥé ļáýéŕš ŕîšé ƒŕöɱ îţ. ~~~~~~~~~~~~~~~]\"],\"node.ceiling.buildUpSection.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.ceiling.buildUpSection.trussBottomChords\":[\"[Ţŕûšš ƀöţţöɱ çĥöŕðš ~~~~~~]\"],\"node.ceiling.description\":[\"[Å þöļýĝöñ-ƀöûñðéð çéîļîñĝ šûŕƒáçé ţĥáţ ĥöšţš çéîļîñĝ-ɱöûñţéð îţéɱš. ~~~~~~~~~~~~~~~~~~]\"],\"node.ceiling.label\":[\"[Çéîļîñĝ ~~~]\"],\"node.ceiling.panel.addHole\":[\"[Åðð Ĥöļé ~~~]\"],\"node.ceiling.panel.area\":[\"[Åŕéá ~~]\"],\"node.ceiling.panel.auto\":[\"[Åûţö ~~]\"],\"node.ceiling.panel.ceiling\":[\"[Çéîļîñĝ ~~~]\"],\"node.ceiling.panel.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"node.ceiling.panel.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"node.ceiling.panel.done\":[\"[Ðöñé ~~]\"],\"node.ceiling.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.ceiling.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.ceiling.panel.high30m\":[\"[Ĥîĝĥ (3.0ɱ) ~~]\"],\"node.ceiling.panel.high90\":[\"[Ĥîĝĥ (9'0\\\") ~~]\"],\"node.ceiling.panel.holes\":[\"[Ĥöļéš ~~]\"],\"node.ceiling.panel.info\":[\"[Îñƒö ~~]\"],\"node.ceiling.panel.limitedByLevel\":[\"[Ļîɱîţéð ƀý ţĥé ļéṽéļ ţö \",[\"height\"],\" — ŕáîšé ţĥé ļéṽéļ ĥéîĝĥţ ƒöŕ á ţáļļéŕ çéîļîñĝ. ~~~~~~~~~~~~~~~~~]\"],\"node.ceiling.panel.low24m\":[\"[Ļöŵ (2.4ɱ) ~~]\"],\"node.ceiling.panel.low80\":[\"[Ļöŵ (8'0\\\") ~]\"],\"node.ceiling.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.ceiling.panel.noHoles\":[\"[Ñö ĥöļéš ~~~]\"],\"node.ceiling.panel.standard25m\":[\"[Šţáñðáŕð (2.5ɱ) ~~~]\"],\"node.ceiling.panel.standard86\":[\"[Šţáñðáŕð (8'6\\\") ~~~]\"],\"node.ceiling.panel.tallerThanLevel\":[\"[Ţáļļéŕ ţĥáñ ţĥîš ļéṽéļ (\",[\"available\"],\" áṽáîļáƀļé). Ŕáîšé ţĥé ļéṽéļ ĥéîĝĥţ ƒîŕšţ. ~~~~~~~~~~~~~~~~]\"],\"node.chimney.description\":[\"[Ṽéŕţîçáļ ɱášöñŕý šţáçķ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~]\"],\"node.chimney.label\":[\"[Çĥîɱñéý ~~~]\"],\"node.chimney.panel.aboveRidge\":[\"[Åƀöṽé Ŕîðĝé ~~~]\"],\"node.chimney.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.chimney.panel.back\":[\"[Ɓáçķ ~~]\"],\"node.chimney.panel.bands\":[\"[Ɓáñðš ~~]\"],\"node.chimney.panel.cap\":[\"[Çáþ ~]\"],\"node.chimney.panel.chimney\":[\"[Çĥîɱñéý ~~~]\"],\"node.chimney.panel.chimneyType\":[\"[Çĥîɱñéý Ţýþé ~~~~]\"],\"node.chimney.panel.corbeled\":[\"[Çöŕƀéļéð ~~~]\"],\"node.chimney.panel.cornerBevel\":[\"[Çöŕñéŕ Ɓéṽéļ ~~~~]\"],\"node.chimney.panel.count\":[\"[Çöûñţ ~~]\"],\"node.chimney.panel.cricket\":[\"[Çŕîçķéţ ~~~]\"],\"node.chimney.panel.cutoutOffset\":[\"[Çûţöûţ Öƒƒšéţ ~~~~]\"],\"node.chimney.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.chimney.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.chimney.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.chimney.panel.double\":[\"[Ðöûƀļé ~~]\"],\"node.chimney.panel.extent\":[\"[Éẋţéñţ ~~]\"],\"node.chimney.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.chimney.panel.flues\":[\"[Ƒļûéš ~~]\"],\"node.chimney.panel.footprint\":[\"[Ƒööţþŕîñţ ~~~]\"],\"node.chimney.panel.front\":[\"[Ƒŕöñţ ~~]\"],\"node.chimney.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.chimney.panel.hollowDepth\":[\"[Ĥöļļöŵ Ðéþţĥ ~~~~]\"],\"node.chimney.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.chimney.panel.none\":[\"[Ñöñé ~~]\"],\"node.chimney.panel.offset\":[\"[Öƒƒšéţ ~~]\"],\"node.chimney.panel.overhang\":[\"[Öṽéŕĥáñĝ ~~~]\"],\"node.chimney.panel.panels\":[\"[Þáñéļš ~~]\"],\"node.chimney.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.chimney.panel.rectangular\":[\"[Ŕéçţáñĝûļáŕ ~~~~]\"],\"node.chimney.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.chimney.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.chimney.panel.shoulder\":[\"[Šĥöûļðéŕ ~~~]\"],\"node.chimney.panel.sideMargin\":[\"[Šîðé Ṁáŕĝîñ ~~~]\"],\"node.chimney.panel.simple\":[\"[Šîɱþļé ~~]\"],\"node.chimney.panel.single\":[\"[Šîñĝļé ~~]\"],\"node.chimney.panel.sloped\":[\"[Šļöþéð ~~]\"],\"node.chimney.panel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.chimney.panel.square\":[\"[Šǫûáŕé ~~]\"],\"node.chimney.panel.stepped\":[\"[Šţéþþéð ~~~]\"],\"node.chimney.panel.style\":[\"[Šţýļé ~~]\"],\"node.chimney.panel.tapered\":[\"[Ţáþéŕéð ~~~]\"],\"node.chimney.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.chimney.panel.topOffset\":[\"[Ţöþ Öƒƒšéţ ~~~]\"],\"node.chimney.panel.wallThickness\":[\"[Ŵáļļ Ţĥîçķñéšš ~~~~]\"],\"node.chimney.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.chimney.preset.brick\":[\"[Ɓŕîçķ ~~]\"],\"node.chimney.preset.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.chimney.preset.round\":[\"[Ŕöûñð ~~]\"],\"node.column.description\":[\"[Å þáŕáɱéţŕîç çöļûɱñ ŵîţĥ çöñƒîĝûŕáƀļé çŕöšš-šéçţîöñ, ƀášé, áñð çáþîţáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.column.label\":[\"[Çöļûɱñ ~~]\"],\"node.column.panel.aFrame\":[\"[Å-Ƒŕáɱé ~~]\"],\"node.column.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.column.panel.applyPreset\":[\"[Åþþļý þŕéšéţ... ~~~~]\"],\"node.column.panel.applyProportion\":[\"[Åþþļý þŕöþöŕţîöñ... ~~~~~]\"],\"node.column.panel.bottomDepth\":[\"[Ɓöţţöɱ Ðéþţĥ ~~~~]\"],\"node.column.panel.bottomHeight\":[\"[Ɓöţţöɱ Ĥéîĝĥţ ~~~~]\"],\"node.column.panel.bottomSpread\":[\"[Ɓöţţöɱ Šþŕéáð ~~~~]\"],\"node.column.panel.bottomStepSpread\":[\"[Ɓöţţöɱ Šţéþ Šþŕéáð ~~~~~]\"],\"node.column.panel.bottomTiers\":[\"[Ɓöţţöɱ Ţîéŕš ~~~~]\"],\"node.column.panel.bottomWidth\":[\"[Ɓöţţöɱ Ŵîðţĥ ~~~~]\"],\"node.column.panel.boxFrame\":[\"[Ɓöẋ Ƒŕáɱé ~~~]\"],\"node.column.panel.braceDepth\":[\"[Ɓŕáçé Ðéþţĥ ~~~]\"],\"node.column.panel.braceWidth\":[\"[Ɓŕáçé Ŵîðţĥ ~~~]\"],\"node.column.panel.bulge\":[\"[Ɓûļĝé ~~]\"],\"node.column.panel.bulged\":[\"[Ɓûļĝéð ~~]\"],\"node.column.panel.column\":[\"[Çöļûɱñ ~~]\"],\"node.column.panel.connectorPlates\":[\"[Çöññéçţöŕ Þļáţéš ~~~~~]\"],\"node.column.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.column.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.column.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.column.panel.edgeSoftness\":[\"[Éðĝé Šöƒţñéšš ~~~~]\"],\"node.column.panel.endWidth\":[\"[Éñð Ŵîðţĥ ~~~]\"],\"node.column.panel.ends\":[\"[Éñðš ~~]\"],\"node.column.panel.forkSpread\":[\"[Ƒöŕķ Šþŕéáð ~~~]\"],\"node.column.panel.heavy\":[\"[Ĥéáṽý ~~]\"],\"node.column.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.column.panel.heightAndFootprintAreControlledBy\":[\"[Ĥéîĝĥţ áñð ƒööţþŕîñţ áŕé çöñţŕöļļéð ƀý ţĥé ļéáñ-ţö éẋţéñšîöñ. Ŕöţáţé öŕ çĥáñĝé ţĥé šûþþöŕţ šţýļé ĥéŕé; ŕéšîžé ƒŕöɱ ţĥé þáŕéñţ ļéáñ-ţö. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.column.panel.hourglass\":[\"[Ĥöûŕĝļášš ~~~]\"],\"node.column.panel.kBrace\":[\"[Ķ Ɓŕáçé ~~]\"],\"node.column.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.column.panel.neckWidth\":[\"[Ñéçķ Ŵîðţĥ ~~~]\"],\"node.column.panel.noBottom\":[\"[Ñö Ɓöţţöɱ ~~~]\"],\"node.column.panel.noTop\":[\"[Ñö Ţöþ ~~]\"],\"node.column.panel.plinthThickness\":[\"[Þļîñţĥ Ţĥîçķñéšš ~~~~~]\"],\"node.column.panel.portalFrame\":[\"[Þöŕţáļ Ƒŕáɱé ~~~~]\"],\"node.column.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.column.panel.rectangular\":[\"[Ŕéçţáñĝûļáŕ ~~~~]\"],\"node.column.panel.ringPairs\":[\"[Ŕîñĝ Þáîŕš ~~~]\"],\"node.column.panel.ringSpread\":[\"[Ŕîñĝ Šþŕéáð ~~~]\"],\"node.column.panel.ringThickness\":[\"[Ŕîñĝ Ţĥîçķñéšš ~~~~]\"],\"node.column.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.column.panel.roundBandWidth\":[\"[Ŕöûñð Ɓáñð Ŵîðţĥ ~~~~~]\"],\"node.column.panel.roundedBottom\":[\"[Ŕöûñðéð Ɓöţţöɱ ~~~~]\"],\"node.column.panel.roundedTop\":[\"[Ŕöûñðéð Ţöþ ~~~]\"],\"node.column.panel.segmentTwist\":[\"[Šéĝɱéñţ Ţŵîšţ ~~~~]\"],\"node.column.panel.shaft\":[\"[Šĥáƒţ ~~]\"],\"node.column.panel.shaftCornerRadius\":[\"[Šĥáƒţ Çöŕñéŕ Ŕáðîûš ~~~~~~]\"],\"node.column.panel.shaftWidth\":[\"[Šĥáƒţ Ŵîðţĥ ~~~]\"],\"node.column.panel.shape\":[\"[Šĥáþé ~~]\"],\"node.column.panel.shortStout\":[\"[Šĥöŕţ / Šţöûţ ~~~]\"],\"node.column.panel.simpleBlockBottom\":[\"[Šîɱþļé Ɓļöçķ Ɓöţţöɱ ~~~~~~]\"],\"node.column.panel.simpleTop\":[\"[Šîɱþļé Ţöþ ~~~]\"],\"node.column.panel.singleStrut\":[\"[Šîñĝļé Šţŕûţ ~~~~]\"],\"node.column.panel.slender\":[\"[Šļéñðéŕ ~~~]\"],\"node.column.panel.square\":[\"[Šǫûáŕé ~~]\"],\"node.column.panel.squarePlinthBottom\":[\"[Šǫûáŕé Þļîñţĥ Ɓöţţöɱ ~~~~~~]\"],\"node.column.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.column.panel.steppedBottom\":[\"[Šţéþþéð Ɓöţţöɱ ~~~~]\"],\"node.column.panel.steppedTop\":[\"[Šţéþþéð Ţöþ ~~~]\"],\"node.column.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.column.panel.taper\":[\"[Ţáþéŕ ~~]\"],\"node.column.panel.tapered\":[\"[Ţáþéŕéð ~~~]\"],\"node.column.panel.topDepth\":[\"[Ţöþ Ðéþţĥ ~~~]\"],\"node.column.panel.topHeight\":[\"[Ţöþ Ĥéîĝĥţ ~~~]\"],\"node.column.panel.topSpread\":[\"[Ţöþ Šþŕéáð ~~~]\"],\"node.column.panel.topStepSpread\":[\"[Ţöþ Šţéþ Šþŕéáð ~~~~]\"],\"node.column.panel.topTiers\":[\"[Ţöþ Ţîéŕš ~~~]\"],\"node.column.panel.topWidth\":[\"[Ţöþ Ŵîðţĥ ~~~]\"],\"node.column.panel.transform\":[\"[Ţŕáñšƒöŕɱ ~~~]\"],\"node.column.panel.trestle\":[\"[Ţŕéšţļé ~~~]\"],\"node.column.panel.tripod\":[\"[Ţŕîþöð ~~]\"],\"node.column.panel.twistSegments\":[\"[Ţŵîšţ Šéĝɱéñţš ~~~~]\"],\"node.column.panel.vSupport\":[\"[Ṽ Šûþþöŕţ ~~~]\"],\"node.column.panel.vertical\":[\"[Ṽéŕţîçáļ ~~~]\"],\"node.column.panel.waist\":[\"[Ŵáîšţ ~~]\"],\"node.column.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.column.panel.xBrace\":[\"[Ẋ Ɓŕáçé ~~]\"],\"node.column.panel.ySupport\":[\"[Ý Šûþþöŕţ ~~~]\"],\"node.column.panel.yaw\":[\"[Ýáŵ ~]\"],\"node.column.postSection.aSizeDeckPostStandsNo\":[\"[Å \",[\"size\"],\" ðéçķ þöšţ šţáñðš ñö ţáļļéŕ ţĥáñ \",[\"limit\"],\" (ÎŔÇ Ţáƀļé Ŕ507.4, ƀý šþéçîéš áñð ţĥé áŕéá îţ çáŕŕîéš): çĥéçķ îţ, öŕ ûšé ţĥé ñéẋţ šîžé ûþ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.column.postSection.custom\":[\"[Çûšţöɱ ~~]\"],\"node.column.postSection.fitToTheBeamOverIt\":[\"[Ƒîţ ţö ţĥé ƀéáɱ öṽéŕ îţ (\",[\"height\"],\") ~~~~~~]\"],\"node.column.postSection.fitToTheCeilingPlane\":[\"[Ƒîţ ţö ţĥé çéîļîñĝ þļáñé (\",[\"height\"],\") ~~~~~~]\"],\"node.column.postSection.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.column.postSection.keepItsShapeAndStyleIt\":[\"[Ķééþ îţš šĥáþé áñð šţýļé îţ áš á çöļûɱñ ~~~~~~~~~~]\"],\"node.column.postSection.makeItAColumn\":[\"[Ṁáķé îţ á çöļûɱñ ~~~~]\"],\"node.column.postSection.post\":[\"[Þöšţ ~~]\"],\"node.column.postSection.treated\":[\"[Ţŕéáţéð ~~~]\"],\"node.column.postSection.untreated\":[\"[Ûñţŕéáţéð ~~~]\"],\"node.column.preset.aFrameSupport\":[\"[Å-Ƒŕáɱé Šûþþöŕţ ~~~~]\"],\"node.column.preset.basicPillar\":[\"[Šţŕáîĝĥţ Ŕöûñð ~~~~]\"],\"node.column.preset.boxFrameSupport\":[\"[Ɓöẋ Ƒŕáɱé ~~~]\"],\"node.column.preset.bulgedPillar\":[\"[Šöƒţ Ɓûļĝéð ~~~]\"],\"node.column.preset.hourglassPillar\":[\"[Ĥöûŕĝļášš ~~~]\"],\"node.column.preset.kBraceSupport\":[\"[Ķ Ɓŕáçé ~~]\"],\"node.column.preset.portalFrameSupport\":[\"[Þöŕţáļ Ƒŕáɱé ~~~~]\"],\"node.column.preset.singleStrutSupport\":[\"[Šîñĝļé Šţŕûţ ~~~~]\"],\"node.column.preset.squarePillar\":[\"[Šǫûáŕé Ɓļöçķ ~~~~]\"],\"node.column.preset.taperedPillar\":[\"[Ţáþéŕéð Ŕöûñð ~~~~]\"],\"node.column.preset.trestleSupport\":[\"[Ţŕéšţļé Ƒŕáɱé ~~~~]\"],\"node.column.preset.tripodSupport\":[\"[Ţŕîþöð Šûþþöŕţ ~~~~]\"],\"node.column.preset.vFrameSupport\":[\"[Ṽ Šûþþöŕţ ~~~]\"],\"node.column.preset.xBraceSupport\":[\"[Ẋ Ɓŕáçé ~~]\"],\"node.column.preset.yFrameSupport\":[\"[Ý Šûþþöŕţ ~~~]\"],\"node.constructionDimension.description\":[\"[Åššöçîáţîṽé ļîñéáŕ, çûŕṽéð, çîŕçûļáŕ, áñĝûļáŕ, öŕ çööŕðîñáţé þļáñ ðîɱéñšîöñ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.constructionDimension.label\":[\"[Çöñšţŕûçţîöñ Ðîɱéñšîöñ ~~~~~~~]\"],\"node.cornerRadius.bottomLeft\":[\"[Ɓöţţöɱ Ļéƒţ ~~~]\"],\"node.cornerRadius.bottomRight\":[\"[Ɓöţţöɱ Ŕîĝĥţ ~~~~]\"],\"node.cornerRadius.topLeft\":[\"[Ţöþ Ļéƒţ ~~~]\"],\"node.cornerRadius.topRight\":[\"[Ţöþ Ŕîĝĥţ ~~~]\"],\"node.cupola.description\":[\"[Ļöûṽéŕéð ŕööƒ ļáñţéŕñ ŵîţĥ á ðöɱé öŕ þýŕáɱîð çáþ áñð öþţîöñáļ ƒîñîáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.cupola.label\":[\"[Çûþöļá ~~]\"],\"node.cupola.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.cupola.panel.cupola\":[\"[Çûþöļá ~~]\"],\"node.cupola.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.cupola.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.cupola.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.cupola.panel.dome\":[\"[Ðöɱé ~~]\"],\"node.cupola.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.cupola.panel.finial\":[\"[Ƒîñîáļ ~~]\"],\"node.cupola.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.cupola.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.cupola.panel.noFinial\":[\"[Ñö Ƒîñîáļ ~~~]\"],\"node.cupola.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.cupola.panel.pyramid\":[\"[Þýŕáɱîð ~~~]\"],\"node.cupola.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.cupola.panel.style\":[\"[Šţýļé ~~]\"],\"node.cupola.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.cutout.autoElevator\":[\"[Åûţö éļéṽáţöŕ çûţöûţ ~~~~~~]\"],\"node.cutout.autoStair\":[\"[Åûţö šţáîŕ çûţöûţ ~~~~~]\"],\"node.cutout.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.cutout.room\":[\"[Ŕööɱ çûţöûţ ~~~]\"],\"node.door.actions.swapHingeSide\":[\"[Šŵáþ ĥîñĝé šîðé ~~~~]\"],\"node.door.actions.swapSwingSide\":[\"[Šŵáþ šŵîñĝ šîðé ~~~~]\"],\"node.door.description\":[\"[Å ðööŕ çûţ îñţö á ŵáļļ. Åñîɱáţéð öþéñ/çļöšé šţáţé. ~~~~~~~~~~~~]\"],\"node.door.label\":[\"[Ðööŕ ~~]\"],\"node.door.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.door.panel.addSegment\":[\"[+ Åðð Šéĝɱéñţ ~~~]\"],\"node.door.panel.all\":[\"[Åļļ ~]\"],\"node.door.panel.arch\":[\"[Åŕçĥ ~~]\"],\"node.door.panel.archHeight\":[\"[Åŕçĥ Ĥéîĝĥţ ~~~]\"],\"node.door.panel.auto\":[\"[Åûţö ~~]\"],\"node.door.panel.autoFollowsTheWall\":[\"[Åûţö ƒöļļöŵš ţĥé ŵáļļ ~~~~~~]\"],\"node.door.panel.barHeight\":[\"[Ɓáŕ Ĥéîĝĥţ ~~~]\"],\"node.door.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.door.panel.contentPadding\":[\"[Çöñţéñţ Þáððîñĝ ~~~~~]\"],\"node.door.panel.cornerRadius\":[\"[Çöŕñéŕ Ŕáðîûš ~~~~]\"],\"node.door.panel.customTheSegmentsMatchNoStyle\":[\"[Çûšţöɱ: ţĥé šéĝɱéñţš ɱáţçĥ ñö šţýļé. ~~~~~~~~~]\"],\"node.door.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.door.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.door.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.door.panel.direction\":[\"[Ðîŕéçţîöñ ~~~]\"],\"node.door.panel.divider\":[\"[Ðîṽîðéŕ ~~~]\"],\"node.door.panel.door\":[\"[Ðööŕ ~~]\"],\"node.door.panel.doorCloser\":[\"[Ðööŕ Çļöšéŕ ~~~]\"],\"node.door.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.door.panel.empty\":[\"[Éɱþţý ~~]\"],\"node.door.panel.enableHandle\":[\"[Éñáƀļé Ĥáñðļé ~~~~]\"],\"node.door.panel.enableThreshold\":[\"[Éñáƀļé Ţĥŕéšĥöļð ~~~~~]\"],\"node.door.panel.flipSide\":[\"[Ƒļîþ Šîðé ~~~]\"],\"node.door.panel.fold\":[\"[Ƒöļð ~~]\"],\"node.door.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.door.panel.frameDepthAuto\":[\"[Ðéþţĥ (áûţö) ~~~]\"],\"node.door.panel.garage\":[\"[Ĝáŕáĝé ~~]\"],\"node.door.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.door.panel.handle\":[\"[Ĥáñðļé ~~]\"],\"node.door.panel.handleSide\":[\"[Ĥáñðļé Šîðé ~~~]\"],\"node.door.panel.hardware\":[\"[Ĥáŕðŵáŕé ~~~]\"],\"node.door.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.door.panel.hingesSide\":[\"[Ĥîñĝéš Šîðé ~~~]\"],\"node.door.panel.horizontal\":[\"[Ĥöŕîžöñţáļ ~~~]\"],\"node.door.panel.individual\":[\"[Îñðîṽîðûáļ ~~~]\"],\"node.door.panel.inset\":[\"[Îñšéţ ~~]\"],\"node.door.panel.inward\":[\"[Îñŵáŕð ~~]\"],\"node.door.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.door.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.door.panel.open\":[\"[Öþéñ ~~]\"],\"node.door.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.door.panel.openingShape\":[\"[Öþéñîñĝ Šĥáþé ~~~~]\"],\"node.door.panel.outward\":[\"[Öûţŵáŕð ~~~]\"],\"node.door.panel.panel\":[\"[Þáñéļ ~~]\"],\"node.door.panel.panels\":[\"[Þáñéļš ~~]\"],\"node.door.panel.panicBar\":[\"[Þáñîç Ɓáŕ ~~~]\"],\"node.door.panel.pocket\":[\"[Þöçķéţ ~~]\"],\"node.door.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.door.panel.rail\":[\"[Ŕáîļ ~~]\"],\"node.door.panel.rect\":[\"[Ŕéçţ ~~]\"],\"node.door.panel.remove\":[\"[- Ŕéɱöṽé ~~]\"],\"node.door.panel.revealRadius\":[\"[Ŕéṽéáļ Ŕáðîûš ~~~~]\"],\"node.door.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.door.panel.rounded\":[\"[Ŕöûñðéð ~~~]\"],\"node.door.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.door.panel.sizeIsLimitedToTheWall\":[\"[Šîžé îš ļîɱîţéð ţö ţĥé ŵáļļ, îñçļûðîñĝ çļéáŕáñçé ƒöŕ ţĥé öþéñîñĝ ƒŕáɱé. ~~~~~~~~~~~~~~~~~~]\"],\"node.door.panel.slide\":[\"[Šļîðé ~~]\"],\"node.door.panel.style\":[\"[Šţýļé ~~]\"],\"node.door.panel.swing\":[\"[Šŵîñĝ ~~]\"],\"node.door.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.door.panel.threshold\":[\"[Ţĥŕéšĥöļð ~~~]\"],\"node.door.panel.topShape\":[\"[Ţöþ Šĥáþé ~~~]\"],\"node.door.panel.type\":[\"[Ţýþé ~~]\"],\"node.door.panel.vertical\":[\"[Ṽéŕţîçáļ ~~~]\"],\"node.door.panel.wall\":[\"[ŵáļļ ~~]\"],\"node.door.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.door.style.barn\":[\"[Ɓáŕñ ~~]\"],\"node.door.style.craftsman\":[\"[Çŕáƒţšɱáñ ~~~]\"],\"node.door.style.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.door.style.glass\":[\"[Ĝļášš ~~]\"],\"node.door.style.halfLouvered\":[\"[Ĥáļƒ-ļöûṽéŕéð ~~~~]\"],\"node.door.style.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.door.style.panel\":[\"[Þáñéļ ~~]\"],\"node.door.style.paneledGlass\":[\"[Þáñéļéð ĝļášš ~~~~]\"],\"node.door.style.shaker\":[\"[Šĥáķéŕ ~~]\"],\"node.door.style.sixPanel\":[\"[Šîẋ-þáñéļ ~~~]\"],\"node.door.type.barn\":[\"[Ɓáŕñ ~~]\"],\"node.door.type.double\":[\"[Ðöûƀļé ~~]\"],\"node.door.type.folding\":[\"[Ƒöļðîñĝ ~~~]\"],\"node.door.type.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.door.type.garageRollup\":[\"[Ŕöļļ-ûþ ~~]\"],\"node.door.type.garageSectional\":[\"[Šéçţîöñáļ ~~~]\"],\"node.door.type.garageTiltup\":[\"[Ţîļţ-ûþ ~~]\"],\"node.door.type.hinged\":[\"[Ĥîñĝéð ~~]\"],\"node.door.type.pocket\":[\"[Þöçķéţ ~~]\"],\"node.door.type.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.dormer.description\":[\"[Ĥöûšé-šĥáþéð þŕöţŕûšîöñ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~]\"],\"node.dormer.label\":[\"[Ðöŕɱéŕ ~~]\"],\"node.dormer.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.dormer.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.dormer.panel.dormer\":[\"[Ðöŕɱéŕ ~~]\"],\"node.dormer.panel.dutch\":[\"[Ðûţçĥ ~~]\"],\"node.dormer.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.dormer.panel.gable\":[\"[Ĝáƀļé ~~]\"],\"node.dormer.panel.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"node.dormer.panel.hip\":[\"[Ĥîþ ~]\"],\"node.dormer.panel.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"node.dormer.panel.pitchDirection\":[\"[Þîţçĥ Ðîŕéçţîöñ ~~~~~]\"],\"node.dormer.panel.pitchRise\":[\"[Þîţçĥ Ŕîšé ~~~]\"],\"node.dormer.panel.riseBack\":[\"[Ŕîšé Ɓáçķ ~~~]\"],\"node.dormer.panel.riseFront\":[\"[Ŕîšé Ƒŕöñţ ~~~]\"],\"node.dormer.panel.roofHeight\":[\"[Ŕööƒ Ĥéîĝĥţ ~~~]\"],\"node.dormer.panel.roofType\":[\"[Ŕööƒ Ţýþé ~~~]\"],\"node.dormer.panel.section\":[\"[Šéçţîöñ ~~~]\"],\"node.dormer.panel.shed\":[\"[Šĥéð ~~]\"],\"node.dormer.panel.wallHeight\":[\"[Ŵáļļ Ĥéîĝĥţ ~~~]\"],\"node.dormer.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.dormer.panel.windows\":[\"[Ŵîñðöŵš ~~~]\"],\"node.dormer.panelActionsSection.actions\":[\"[Åçţîöñš ~~~]\"],\"node.dormer.panelActionsSection.delete\":[\"[Ðéļéţé ~~]\"],\"node.dormer.panelActionsSection.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.dormer.panelActionsSection.move\":[\"[Ṁöṽé ~~]\"],\"node.dormer.panelPositionSection.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.dormer.panelPositionSection.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.dormer.panelWindowsSection.addWindow\":[\"[Åðð Ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.editNamed\":[\"[Éðîţ \",[\"name\"],\" ~~]\"],\"node.dormer.panelWindowsSection.editWindow\":[\"[Éðîţ ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd\":[\"[Îñçŕéášé ţĥé ðöŕɱéŕ ŵîðţĥ ţö áðð áñöţĥéŕ ŵîñðöŵ. ~~~~~~~~~~~~]\"],\"node.dormer.panelWindowsSection.move\":[\"[Ṁöṽé ~~]\"],\"node.dormer.panelWindowsSection.moveNamed\":[\"[Ṁöṽé \",[\"name\"],\" ~~]\"],\"node.dormer.panelWindowsSection.moveWindow\":[\"[Ṁöṽé ŵîñðöŵ ~~~]\"],\"node.dormer.panelWindowsSection.noWindows\":[\"[Ñö ŵîñðöŵš ~~~]\"],\"node.dormer.panelWindowsSection.windowNumber\":[\"[Ŵîñðöŵ \",[\"number\"],\" ~~]\"],\"node.dormer.panelWindowsSection.windowSummary\":[\"[\",[\"face\",\"select\",{\"back\":[\"Ɓáçķ\"],\"left\":[\"Ļéƒţ\"],\"right\":[\"Ŕîĝĥţ\"],\"other\":[\"Ƒŕöñţ\"]}],\" · \",[\"width\"],\" × \",[\"height\"],\" ɱ ~~~~~~]\"],\"node.dormer.panelWindowsSection.windowsLength\":[\"[Ŵîñðöŵš (\",[\"length\"],\") ~~~]\"],\"node.downspout.description\":[\"[Ṽéŕţîçáļ ðŕöþ þîþé ƒŕöɱ á ĝûţţéŕ öûţļéţ ţö ţĥé ĝŕöûñð. ~~~~~~~~~~~~~~]\"],\"node.downspout.inspectorEditors.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.downspout.label\":[\"[Ðöŵñšþöûţ ~~~]\"],\"node.ductFitting.description\":[\"[Éļƀöŵ, ţéé, ŕéðûçéŕ, öŕ šǫûáŕé-ţö-ŕöûñð ţŕáñšîţîöñ çöññéçţîñĝ ðûçţ ŕûñš. ~~~~~~~~~~~~~~~~~~]\"],\"node.ductFitting.inspectorEditors.swapWH\":[\"[Šŵáþ Ŵ/Ĥ ~~]\"],\"node.ductFitting.inspectorEditors.swapWidthAndHeight\":[\"[Šŵáþ ŵîðţĥ áñð ĥéîĝĥţ ~~~~~~]\"],\"node.ductFitting.label\":[\"[Ðûçţ Ƒîţţîñĝ ~~~~]\"],\"node.ductFitting.tool.axis\":[\"[Åẋîš ~~]\"],\"node.ductFitting.tool.axis2\":[\"[⌥ áẋîš ~~]\"],\"node.ductFitting.tool.hoverADuctFaceThatFits\":[\"[Ĥöṽéŕ á ðûçţ ƒáçé ţĥáţ ƒîţš ţĥé ðööŕ ~~~~~~~~~]\"],\"node.ductFitting.tool.placeAccessDoor\":[\"[Þļáçé áççéšš ðööŕ ~~~~~]\"],\"node.ductFitting.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.ductSegment.description\":[\"[ĤṼÅÇ ðûçţ ŕûñ — þöļýļîñé öƒ ŕöûñð, ŕéçţ, öŕ ƒļáţ-öṽáļ šéçţîöñš. ~~~~~~~~~~~~~~~]\"],\"node.ductSegment.label\":[\"[Ðûçţ ~~]\"],\"node.ductTerminal.description\":[\"[Ðûçţ ţéŕɱîñáļ — šûþþļý ŕéĝîšţéŕ, çéîļîñĝ ðûšéŕ, öŕ ŕéţûŕñ ĝŕîļļé. Ðûçţ ŕûñš éñð áţ îţš çöļļáŕ. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.ductTerminal.label\":[\"[Ŕéĝîšţéŕ ~~~]\"],\"node.ductTerminal.tool.mSurface\":[\"[Ṁ šûŕƒáçé ~~~]\"],\"node.ductTerminal.tool.mount\":[\"[\",[\"mount\",\"select\",{\"floor\":[\"Ṁöûñţ Ƒļööŕ\"],\"ceiling\":[\"Ṁöûñţ Çéîļîñĝ\"],\"wall\":[\"Ṁöûñţ Ŵáļļ\"],\"other\":[\"Ṁöûñţ \",[\"label\"]]}],\" ~~~~~~~~~~~]\"],\"node.ductTerminal.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.ductTerminal.tool.snappedToDuct\":[\"[Šñáþþéð ţö ðûçţ ~~~~]\"],\"node.elevator.description\":[\"[Å ɱûļţî-ļéṽéļ éļéṽáţöŕ šĥáƒţ ŵîţĥ çöñƒîĝûŕáƀļé öþéñîñĝš þéŕ ļéṽéļ. ~~~~~~~~~~~~~~~~~]\"],\"node.elevator.label\":[\"[Éļéṽáţöŕ ~~~]\"],\"node.elevator.panel.access\":[\"[Åççéšš ~~]\"],\"node.elevator.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.elevator.panel.cab\":[\"[Çáƀ ~]\"],\"node.elevator.panel.cabHeight\":[\"[Çáƀ Ĥéîĝĥţ ~~~]\"],\"node.elevator.panel.centerOpening\":[\"[Çéñţéŕ öþéñîñĝ ~~~~]\"],\"node.elevator.panel.defaultFloor\":[\"[Ðéƒáûļţ Ƒļööŕ ~~~~]\"],\"node.elevator.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.elevator.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.elevator.panel.destination\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"node.elevator.panel.disabled\":[\"[Ðîšáƀļéð ~~~]\"],\"node.elevator.panel.doorHeight\":[\"[Ðööŕ Ĥéîĝĥţ ~~~]\"],\"node.elevator.panel.doorTime\":[\"[Ðööŕ Ţîɱé ~~~]\"],\"node.elevator.panel.doorType\":[\"[Ðööŕ Ţýþé ~~~]\"],\"node.elevator.panel.doorWidth\":[\"[Ðööŕ Ŵîðţĥ ~~~]\"],\"node.elevator.panel.doors\":[\"[Ðööŕš ~~]\"],\"node.elevator.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.elevator.panel.dwell\":[\"[Ðŵéļļ ~~]\"],\"node.elevator.panel.elevator\":[\"[Éļéṽáţöŕ ~~~]\"],\"node.elevator.panel.from\":[\"[Ƒŕöɱ ~~]\"],\"node.elevator.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.elevator.panel.glassFrame\":[\"[Ĝļášš ƒŕáɱé ~~~]\"],\"node.elevator.panel.motion\":[\"[Ṁöţîöñ ~~]\"],\"node.elevator.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.elevator.panel.openingStyle\":[\"[Öþéñîñĝ Šţýļé ~~~~]\"],\"node.elevator.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.elevator.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.elevator.panel.segmentedPanel\":[\"[Šéĝɱéñţéð þáñéļ ~~~~~]\"],\"node.elevator.panel.service\":[\"[Šéŕṽîçé ~~~]\"],\"node.elevator.panel.shaft\":[\"[Šĥáƒţ ~~]\"],\"node.elevator.panel.shaftDepth\":[\"[Šĥáƒţ Ðéþţĥ ~~~]\"],\"node.elevator.panel.shaftStyle\":[\"[Šĥáƒţ Šţýļé ~~~]\"],\"node.elevator.panel.shaftWidth\":[\"[Šĥáƒţ Ŵîðţĥ ~~~]\"],\"node.elevator.panel.singleLeft\":[\"[Šîñĝļé ļéƒţ ~~~]\"],\"node.elevator.panel.singleRight\":[\"[Šîñĝļé ŕîĝĥţ ~~~~]\"],\"node.elevator.panel.solid\":[\"[Šöļîð ~~]\"],\"node.elevator.panel.solidPanel\":[\"[Šöļîð þáñéļ ~~~]\"],\"node.elevator.panel.speed\":[\"[Šþééð ~~]\"],\"node.elevator.panel.stopStoporder\":[\"[Šţöþ \",[\"stopOrder\"],\" ~~]\"],\"node.elevator.panel.to\":[\"[Ţö ~]\"],\"node.elevator.panel.wallThickness\":[\"[Ŵáļļ Ţĥîçķñéšš ~~~~]\"],\"node.elevator.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.elevator.panel.yaw\":[\"[Ýáŵ ~]\"],\"node.eyebrowVent.description\":[\"[Ļöŵ çûŕṽéð ļéñš-šĥáþéð ŕööƒ ṽéñţ ŵîţĥ á ļöûṽéŕéð ƒŕöñţ. ~~~~~~~~~~~~~~]\"],\"node.eyebrowVent.label\":[\"[Éýéƀŕöŵ Ṽéñţ ~~~~]\"],\"node.eyebrowVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.eyebrowVent.panel.backHeight\":[\"[Ɓáçķ ĥéîĝĥţ ~~~]\"],\"node.eyebrowVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.eyebrowVent.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.eyebrowVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.eyebrowVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.eyebrowVent.panel.eyebrowVent\":[\"[Éýéƀŕöŵ Ṽéñţ ~~~~]\"],\"node.eyebrowVent.panel.halfRound\":[\"[Ĥáļƒ-ŕöûñð ~~~]\"],\"node.eyebrowVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.eyebrowVent.panel.louvers\":[\"[Ļöûṽéŕš ~~~]\"],\"node.eyebrowVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.eyebrowVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.eyebrowVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.eyebrowVent.panel.scoop\":[\"[Šçööþ ~~]\"],\"node.eyebrowVent.panel.slantBox\":[\"[Šļáñţ-ƀöẋ ~~~]\"],\"node.eyebrowVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.eyebrowVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.fence.description\":[\"[Å šţŕáîĝĥţ öŕ çûŕṽéð ƒéñçé šéĝɱéñţ ŵîţĥ çöñƒîĝûŕáƀļé þöšţš áñð îñƒîļļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.addPointAfter\":[\"[Åðð þöîñţ áƒţéŕ ~~~~]\"],\"node.fence.inspectorEditors.chooseASlabOrShapedSurface\":[\"[Çĥööšé á šļáƀ öŕ šĥáþéð šûŕƒáçé öñ ţĥîš ļéṽéļ. ~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.chooseASurface\":[\"[Çĥööšé á šûŕƒáçé ~~~~~]\"],\"node.fence.inspectorEditors.corner\":[\"[Çöŕñéŕ ~~]\"],\"node.fence.inspectorEditors.curve\":[\"[Çûŕṽé ~~]\"],\"node.fence.inspectorEditors.curved\":[\"[Çûŕṽéð ~~]\"],\"node.fence.inspectorEditors.distributionInfo\":[\"[\",[\"style\",\"select\",{\"picket\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš þîçķéţš îñ éáçĥ ƀáý ƀéţŵééñ þöšţš.\"],\"horizontal\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš þöšţš áļöñĝ ţĥé ƒûļļ ƒéñçé.\"],\"other\":[\"Ðîšţŕîƀûţîöñ çöñţŕöļš îñƒîļļ þîéçéš ƀéţŵééñ ţĥé ţŵö éñð þöšţš.\"]}],\" Šþáçîñĝ îš ɱéášûŕéð ƀéţŵééñ çéñţéŕš. Ţĥé áṽáîļáƀļé ŵîðţĥ ļîɱîţš ţĥé çöûñţ šö þîéçéš ðö ñöţ öṽéŕļáþ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.editPath\":[\"[Éðîţ þáţĥ ~~~]\"],\"node.fence.inspectorEditors.extendPath\":[\"[Éẋţéñð þáţĥ ~~~]\"],\"node.fence.inspectorEditors.followHighestSurface\":[\"[Ƒöļļöŵ ĥîĝĥéšţ šûŕƒáçé ~~~~~~]\"],\"node.fence.inspectorEditors.followSelectedSurface\":[\"[Ƒöļļöŵ šéļéçţéð šûŕƒáçé ~~~~~~~]\"],\"node.fence.inspectorEditors.holdStartingHeight\":[\"[Ĥöļð šţáŕţîñĝ ĥéîĝĥţ ~~~~~~]\"],\"node.fence.inspectorEditors.join\":[\"[Ĵöîñ ~~]\"],\"node.fence.inspectorEditors.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.fence.inspectorEditors.pathSummary\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" þöîñţ\"],\"other\":[[\"number\"],\" þöîñţš\"]}],\" · \",[\"length\"],\" ɱ ~~~~]\"],\"node.fence.inspectorEditors.picketRailProjection\":[\"[Þîçķéţ ŕáîļ þŕöĵéçţîöñ ~~~~~~]\"],\"node.fence.inspectorEditors.point\":[\"[Þöîñţ ~~]\"],\"node.fence.inspectorEditors.removePoint\":[\"[Ŕéɱöṽé þöîñţ ~~~~]\"],\"node.fence.inspectorEditors.setEachSpanToStraightOr\":[\"[Šéţ éáçĥ šþáñ ţö šţŕáîĝĥţ öŕ çûŕṽéð, ţĥéñ ðŕáĝ îţš þöîñţš áñð çûŕṽé ĥáñðļéš îñ éîţĥéŕ ṽîéŵ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.fence.inspectorEditors.smooth\":[\"[Šɱööţĥ ~~]\"],\"node.fence.inspectorEditors.spanToPointNumber\":[\"[Šþáñ ţö þöîñţ \",[\"number\"],\" ~~~~]\"],\"node.fence.inspectorEditors.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.fence.inspectorEditors.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.fence.inspectorEditors.surfaceBehavior\":[\"[Šûŕƒáçé ƀéĥáṽîöŕ ~~~~~]\"],\"node.fence.label\":[\"[Ƒéñçé ~~]\"],\"node.fence.moveEndpointTool.detachEndpoint\":[\"[Ðéţáçĥ éñðþöîñţ ~~~~~]\"],\"node.fence.moveEndpointTool.dragEndpoint\":[\"[Ðŕáĝ éñðþöîñţ ~~~~]\"],\"node.fenceFeature.actions.close\":[\"[Çļöšé ~~]\"],\"node.fenceFeature.actions.open\":[\"[Öþéñ ~~]\"],\"node.fenceFeature.inspector.boardWidth\":[\"[Ɓöáŕð ŵîðţĥ ~~~]\"],\"node.fenceFeature.inspector.bracing\":[\"[Ɓŕáçîñĝ ~~~]\"],\"node.fenceFeature.inspector.distanceFromStart\":[\"[Ðîšţáñçé ƒŕöɱ šţáŕţ ~~~~~~]\"],\"node.fenceFeature.inspector.fenceUnavailable\":[\"[Ƒéñçé ûñáṽáîļáƀļé. ~~~~~]\"],\"node.fenceFeature.inspector.frameWidth\":[\"[Ƒŕáɱé ŵîðţĥ ~~~]\"],\"node.fenceFeature.inspector.gateHeight\":[\"[Ĝáţé ĥéîĝĥţ ~~~]\"],\"node.fenceFeature.inspector.gateStyle\":[\"[Ĝáţé šţýļé ~~~]\"],\"node.fenceFeature.inspector.groundClearance\":[\"[Ĝŕöûñð çļéáŕáñçé ~~~~~]\"],\"node.fenceFeature.inspector.hingeSide\":[\"[Ĥîñĝé šîðé ~~~]\"],\"node.fenceFeature.inspector.hingesAndLatch\":[\"[Ĥîñĝéš áñð ļáţçĥ ~~~~~]\"],\"node.fenceFeature.inspector.horizontalBoards\":[\"[Ĥöŕîžöñţáļ ƀöáŕðš ~~~~~]\"],\"node.fenceFeature.inspector.infillSpacing\":[\"[Îñƒîļļ šþáçîñĝ ~~~~]\"],\"node.fenceFeature.inspector.jambPosts\":[\"[Ĵáɱƀ þöšţš ~~~]\"],\"node.fenceFeature.inspector.keepTheOpeningInsideTheFence\":[\"[Ķééþ ţĥé öþéñîñĝ îñšîðé ţĥé ƒéñçé áñð çļéáŕ öƒ öţĥéŕ ĝáţéš öŕ þáššáĝéš. ~~~~~~~~~~~~~~~~~~]\"],\"node.fenceFeature.inspector.leafThickness\":[\"[Ļéრţĥîçķñéšš ~~~~]\"],\"node.fenceFeature.inspector.leaves\":[\"[Ļéáṽéš ~~]\"],\"node.fenceFeature.inspector.leftLeafProportion\":[\"[Ļéƒţ ļéრþŕöþöŕţîöñ ~~~~~~]\"],\"node.fenceFeature.inspector.matchFenceHeight\":[\"[Ṁáţçĥ ƒéñçé ĥéîĝĥţ ~~~~~]\"],\"node.fenceFeature.inspector.matchFenceStyle\":[\"[Ṁáţçĥ ƒéñçé šţýļé ~~~~~]\"],\"node.fenceFeature.inspector.openRails\":[\"[Öþéñ ŕáîļš ~~~]\"],\"node.fenceFeature.inspector.openingAngle\":[\"[Öþéñîñĝ áñĝļé ~~~~]\"],\"node.fenceFeature.inspector.openingWidth\":[\"[Öþéñîñĝ ŵîðţĥ ~~~~]\"],\"node.fenceFeature.inspector.option.cross\":[\"[Çŕöšš ~~]\"],\"node.fenceFeature.inspector.option.diagonal\":[\"[Ðîáĝöñáļ ~~~]\"],\"node.fenceFeature.inspector.option.doubleGate\":[\"[Ðöûƀļé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.option.inward\":[\"[Îñŵáŕð ~~]\"],\"node.fenceFeature.inspector.option.left\":[\"[Ļéƒţ ~~]\"],\"node.fenceFeature.inspector.option.none\":[\"[Ñöñé ~~]\"],\"node.fenceFeature.inspector.option.outward\":[\"[Öûţŵáŕð ~~~]\"],\"node.fenceFeature.inspector.option.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.fenceFeature.inspector.option.singleGate\":[\"[Šîñĝļé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.picket\":[\"[Þîçķéţ ~~]\"],\"node.fenceFeature.inspector.postHeight\":[\"[Þöšţ ĥéîĝĥţ ~~~]\"],\"node.fenceFeature.inspector.removeGate\":[\"[Ŕéɱöṽé ĝáţé ~~~]\"],\"node.fenceFeature.inspector.removeOpening\":[\"[Ŕéɱöṽé öþéñîñĝ ~~~~]\"],\"node.fenceFeature.inspector.solidPrivacy\":[\"[Šöļîð þŕîṽáçý ~~~~]\"],\"node.fenceFeature.inspector.swingDirection\":[\"[Šŵîñĝ ðîŕéçţîöñ ~~~~~]\"],\"node.fenceFeature.inspector.verticalSlats\":[\"[Ṽéŕţîçáļ šļáţš ~~~~]\"],\"node.fenceGate.description\":[\"[Å ĝáţé ĥöšţéð ƀý á ƒéñçé. ~~~~~~]\"],\"node.fenceGate.label\":[\"[Ĝáţé ~~]\"],\"node.fenceOpening.description\":[\"[Åñ îñţéñţîöñáļ öþéñîñĝ îñ á ƒéñçé. ~~~~~~~~~]\"],\"node.fenceOpening.label\":[\"[Öþéñ Þáššáĝé ~~~~]\"],\"node.floorOpening.label\":[\"[Ƒļööŕ öþéñîñĝ ~~~~]\"],\"node.footing.description\":[\"[Å çöñçŕéţé šţŕîþ öŕ þáð ƒööţîñĝ þöûŕéð ûñðéŕ ţĥé ļöŵéšţ ƒļööŕ. ~~~~~~~~~~~~~~~~]\"],\"node.footing.label\":[\"[Ƒööţîñĝ ~~~]\"],\"node.footing.panel.aGradeBeamOnPilesAs\":[\"[Å ĝŕáðé ƀéáɱ öñ þîļéš, áš ţĥé ƀûîļðîñĝ îš. ~~~~~~~~~~]\"],\"node.footing.panel.aPierCarriesTheFloor\":[\"[Å þîéŕ çáŕŕîéš ţĥé ƒļööŕ. ~~~~~~]\"],\"node.footing.panel.aPileCapOnOnePile\":[\"[Å þîļé çáþ öñ öñé þîļé, áš ţĥé ƀûîļðîñĝ îš. ~~~~~~~~~~]\"],\"node.footing.panel.aStemWallCarriesTheFloor\":[\"[Å šţéɱ ŵáļļ çáŕŕîéš ţĥé ƒļööŕ. ~~~~~~~~]\"],\"node.footing.panel.aThickenedSlabUnderTheFloor\":[\"[Å ţĥîçķéñéð šļáƀ ûñðéŕ ţĥé ƒļööŕ šļáƀ. ~~~~~~~~~~]\"],\"node.footing.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.footing.panel.atLeast\":[\"[Åţ ļéášţ ~~~]\"],\"node.footing.panel.bottom\":[\"[, ƀöţţöɱ ~~]\"],\"node.footing.panel.clearTheDepth\":[\"[Çļéáŕ ţĥé ðéþţĥ ~~~~]\"],\"node.footing.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.footing.panel.depthUnderGrade\":[\"[Ðéþţĥ ûñðéŕ ĝŕáðé ~~~~~]\"],\"node.footing.panel.depthUnderGradeBuildingS\":[\"[Ðéþţĥ ûñðéŕ ĝŕáðé (ƀûîļðîñĝ'š) ~~~~~~~~]\"],\"node.footing.panel.footing\":[\"[Ƒööţîñĝ ~~~]\"],\"node.footing.panel.ircR4031AsksAFooting\":[\"[ÎŔÇ Ŕ403.1 ášķš á ƒööţîñĝ áţ ļéášţ 12 îñ ŵîðé áñð 6 îñ ţĥîçķ. ~~~~~~~~~~~~]\"],\"node.footing.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.footing.panel.pad\":[\"[Þáð ~]\"],\"node.footing.panel.strip\":[\"[Šţŕîþ ~~]\"],\"node.footing.panel.theFootingSThicknessUnderThe\":[\"[ţĥé ƒööţîñĝ’š ţĥîçķñéšš ûñðéŕ ţĥé ƒļööŕ ŕéáçĥéš ţĥîš ƒáŕ. ~~~~~~~~~~~~~~~]\"],\"node.footing.panel.theGradeBeamReachesThisFar\":[\"[ţĥé ĝŕáðé ƀéáɱ ŕéáçĥéš ţĥîš ƒáŕ. ~~~~~~~~]\"],\"node.footing.panel.theSlabAndTheFootingS\":[\"[ţĥé šļáƀ áñð ţĥé ƒööţîñĝ’š ţĥîçķñéšš ŕéáçĥ ţĥîš ƒáŕ. ~~~~~~~~~~~~~]\"],\"node.footing.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.footing.panel.top\":[\"[Ţöþ ~]\"],\"node.footing.panel.useTheBuildingSDepth\":[\"[Ûšé ţĥé ƀûîļðîñĝ'š ðéþţĥ ~~~~~~]\"],\"node.footing.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.foundation.description\":[\"[Ţĥé öûţļîñé ţĥé ƒöûñðáţîöñ îš þöûŕéð ţö: ðŕáŵñ, öŕ ţŕáçéð ƒŕöɱ ţĥé ŵáļļš áñð šţŕéţçĥéð. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.foundation.label\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"node.guide.description\":[\"[Å ɱéášûŕéɱéñţ / ŕéƒéŕéñçé áññöţáţîöñ (ļîñéáŕ, áŕéá, öŕ áŕç). ~~~~~~~~~~~~~~]\"],\"node.guide.label\":[\"[Ĝûîðé ~~]\"],\"node.gutter.description\":[\"[Ŕáîñ-ŵáţéŕ çĥáññéļ ŕûññîñĝ áļöñĝ ţĥé éáṽé öƒ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~~]\"],\"node.gutter.downspoutsPanel.addDownspout\":[\"[Åðð Ðöŵñšþöûţ ~~~~]\"],\"node.gutter.downspoutsPanel.downspoutNumber\":[\"[Ðöŵñšþöûţ \",[\"number\"],\" ~~~]\"],\"node.gutter.downspoutsPanel.downspouts\":[\"[Ðöŵñšþöûţš ~~~]\"],\"node.gutter.downspoutsPanel.removeDownspout\":[\"[Ŕéɱöṽé ðöŵñšþöûţ ~~~~~]\"],\"node.gutter.label\":[\"[Ĝûţţéŕ ~~]\"],\"node.hole.manual\":[\"[Ṁáñûáļ ~~]\"],\"node.holeEditing\":[\"[Ĥöļé \",[\"number\"],\" (Éðîţîñĝ) ~~~~]\"],\"node.holeNumber\":[\"[Ĥöļé \",[\"number\"],\" ~~]\"],\"node.holeSummary\":[\"[\",[\"area\"],\" \",[\"unit\"],\" · \",[\"count\",\"plural\",{\"one\":[[\"points\"],\" þţ\"],\"other\":[[\"points\"],\" þţš\"]}],\" · \",[\"source\"],\" ~~]\"],\"node.hvacEquipment.description\":[\"[Ƒûŕñáçé, áîŕ ĥáñðļéŕ, öŕ çöñðéñšéŕ — ðûçţ ŕûñš çöññéçţ ţö îţš šûþþļý/ŕéţûŕñ çöļļáŕš. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.hvacEquipment.label\":[\"[ĤṼÅÇ Ûñîţ ~~~]\"],\"node.hvacEquipment.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.importedMesh.description\":[\"[Ĝéöɱéţŕý þŕéšéŕṽéð ƒŕöɱ áñ îɱþöŕţéð ɱöðéļ ŵĥéñ ñö ñáţîṽé Þášçáļ šĥáþé éẋîšţš. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.importedMesh.label\":[\"[Îɱþöŕţéð Ṁéšĥ ~~~~]\"],\"node.item.authoredParams.couldnTRebuildWithTheseValues\":[\"[Çöûļðñ'ţ ŕéƀûîļð ŵîţĥ ţĥéšé ṽáļûéš. ~~~~~~~~~]\"],\"node.item.authoredParams.parameters\":[\"[Þáŕáɱéţéŕš ~~~]\"],\"node.item.authoredParams.rebuilding\":[\"[Ŕéƀûîļðîñĝ… ~~~]\"],\"node.item.authoredParams.rebuildingInBlender\":[\"[Ŕéƀûîļðîñĝ îñ Ɓļéñðéŕ… ~~~~~~]\"],\"node.item.authoredParams.resetParam\":[\"[Ŕéšéţ \",[\"param\"],\" ~~]\"],\"node.item.authoredParams.resetParameters\":[\"[Ŕéšéţ þáŕáɱéţéŕš ~~~~~]\"],\"node.item.authoredParams.resetToDefault\":[\"[Ŕéšéţ ţö ðéƒáûļţ ~~~~~]\"],\"node.item.description\":[\"[Å çáţáļöĝ-ƀáçķéð îţéɱ (ƒûŕñîţûŕé, ƒîẋţûŕéš, ðéçöŕáţîöñš). ~~~~~~~~~~~~~~]\"],\"node.item.label\":[\"[Îţéɱ ~~]\"],\"node.item.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.item.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.item.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.item.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.item.panel.info\":[\"[Îñƒö ~~]\"],\"node.item.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.item.panel.pos\":[\"[þöš ~]\"],\"node.item.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.item.panel.rot\":[\"[ŕöţ ~]\"],\"node.item.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.item.panel.scale\":[\"[Šçáļé ~~]\"],\"node.item.panel.scale2\":[\"[šçáļé ~~]\"],\"node.item.panel.uniformScale\":[\"[Ûñîƒöŕɱ Šçáļé ~~~~]\"],\"node.item.panel.xyz\":[\"[ẊÝŽ ~]\"],\"node.leanToExtension.description\":[\"[Åñ áţţáçĥéð ɱöñö-þîţçĥ öŕ ƒŕééšţáñðîñĝ ɱöñö, ĝáƀļé, öŕ ƀûţţéŕƒļý çáñöþý ŵîţĥ ɱáñáĝéð šţŕûçţûŕé áñð ðŕáîñáĝé. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.leanToExtension.label\":[\"[Çáñöþý ~~]\"],\"node.level.description\":[\"[Å šîñĝļé ƒļööŕ öƒ á ƀûîļðîñĝ, ĥöļðîñĝ ŵáļļš / šļáƀš / çéîļîñĝš / îţéɱš. ~~~~~~~~~~~~~~~~]\"],\"node.level.label\":[\"[Ļéṽéļ ~~]\"],\"node.lineset.description\":[\"[Ŕéƒŕîĝéŕáñţ ļîñéšéţ — çöþþéŕ šûçţîöñ + ļîǫûîð þáîŕ ĵöîñîñĝ á çöñðéñšéŕ ţö ţĥé îñðööŕ çöîļ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.lineset.label\":[\"[Ļîñéšéţ ~~~]\"],\"node.liquidLine.description\":[\"[Šţáñðáļöñé ŕéƒŕîĝéŕáñţ ļîǫûîð ļîñé — á ţĥîñ ƀáŕé-çöþþéŕ ŕûñ; Ƒöļļöŵ ɱöðé ţŕáçéš áñ éẋîšţîñĝ ļîñéšéţ. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.liquidLine.label\":[\"[Ļîǫûîð Ļîñé ~~~]\"],\"node.liquidLine.tool.clickToTraceThisLinesetRun\":[\"[Çļîçķ ţö ţŕáçé ţĥîš ļîñéšéţ ŕûñ ~~~~~~~~]\"],\"node.liquidLine.tool.followHoverALineset\":[\"[Ƒöļļöŵ: ĥöṽéŕ á ļîñéšéţ ~~~~~~]\"],\"node.measurement.description\":[\"[Å þéŕšîšţéñţ ðîšţáñçé, áñĝļé, áŕéá, þéŕîɱéţéŕ, öŕ ṽöļûɱé áññöţáţîöñ. ~~~~~~~~~~~~~~~~~]\"],\"node.measurement.label\":[\"[Ṁéášûŕéɱéñţ ~~~~]\"],\"node.numberedName\":[[\"kind\"],\" \",[\"number\"]],\"node.panel.currently\":[\"[Çûŕŕéñţļý \",[\"value\"],\" ~~~]\"],\"node.pipeFitting.description\":[\"[ÐŴṼ ĵöîñţ — éļƀöŵ ƀéñð, 45° ŵýé, öŕ šáñîţáŕý ţéé. ~~~~~~~~~~]\"],\"node.pipeFitting.label\":[\"[Þîþé Ƒîţţîñĝ ~~~~]\"],\"node.pipeFitting.tool.axis\":[\"[Åẋîš ~~]\"],\"node.pipeFitting.tool.axis2\":[\"[⌥ áẋîš ~~]\"],\"node.pipeFitting.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.pipeSegment.description\":[\"[Ðŕáîñ / ŵášţé / ṽéñţ þîþé ŕûñ — ðŕáŵ ļéṽéļ öŕ ţöĝĝļé á ¼″ þéŕ ƒööţ ƒáļļ ŵîţĥ Š. ~~~~~~~~~~~~~~~~~]\"],\"node.pipeSegment.label\":[\"[ÐŴṼ Þîþé ~~~]\"],\"node.pipeTrap.description\":[\"[ÐŴṼ Þ-ţŕáþ — ŵáţéŕ šéáļ öñ ţĥé ŵášţé ļîñé. Ţĥé ţŕáþ áŕɱ ŕûñš ţö ţĥé ṽéñţ. ~~~~~~~~~~~~~~~~~]\"],\"node.pipeTrap.label\":[\"[Ţŕáþ ~~]\"],\"node.pipeTrap.tool.diameterTrap\":[\"[\",[\"diameter\"],\"\\\" Ţŕáþ ~~]\"],\"node.pipeTrap.tool.rTRotate\":[\"[Ŕ/Ţ ŕöţáţé ~~~]\"],\"node.proceduralItem.description\":[\"[Éẋþéŕîɱéñţáļ éðîţáƀļé ŕéçîþé ~~~~~~~~]\"],\"node.proceduralItem.label\":[\"[Þŕöçéðûŕáļ îţéɱ ~~~~~]\"],\"node.ridgeVent.description\":[\"[Ṽéñţîļáţîöñ šţŕîþ ŕûññîñĝ áļöñĝ ţĥé ŕîðĝé öƒ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~~]\"],\"node.ridgeVent.label\":[\"[Ŕîðĝé Ṽéñţ ~~~]\"],\"node.ridgeVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.ridgeVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.ridgeVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.ridgeVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.ridgeVent.panel.endCaps\":[\"[Éñð Çáþš ~~~]\"],\"node.ridgeVent.panel.flanged\":[\"[Ƒļáñĝéð ~~~]\"],\"node.ridgeVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.ridgeVent.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.ridgeVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.ridgeVent.panel.open\":[\"[Öþéñ ~~]\"],\"node.ridgeVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.ridgeVent.panel.ridgeVent\":[\"[Ŕîðĝé Ṽéñţ ~~~]\"],\"node.ridgeVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.ridgeVent.panel.shingled\":[\"[Šĥîñĝļéð ~~~]\"],\"node.ridgeVent.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.ridgeVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.ridgeVent.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.roof.barrelPanel.archBays\":[\"[Åŕçĥ ƀáýš ~~~]\"],\"node.roof.barrelPanel.barrelArch\":[\"[Ɓáŕŕéļ áŕçĥ ~~~]\"],\"node.roof.barrelPanel.flatPanesFollowACircularArch\":[\"[Ƒļáţ þáñéš ƒöļļöŵ á çîŕçûļáŕ áŕçĥ. Ŕîšé îš ɱéášûŕéð ƒŕöɱ ţĥé éáṽéš. ~~~~~~~~~~~~~~~~~]\"],\"node.roof.barrelPanel.rise\":[\"[Ŕîšé ~~]\"],\"node.roof.description\":[\"[Å þîţçĥéð / ĥîþ / ĝáƀļé ŕööƒ çöɱþöšéð öƒ öñé öŕ ɱöŕé šéĝɱéñţš. ~~~~~~~~~~~~~~~]\"],\"node.roof.glazing.face.archBay\":[\"[Åŕçĥ ƀáý \",[\"number\"],\" ~~~]\"],\"node.roof.glazing.face.back\":[\"[Ɓáçķ šļöþé ~~~]\"],\"node.roof.glazing.face.endLeft\":[\"[Ļéƒţ éñð ~~~]\"],\"node.roof.glazing.face.endRight\":[\"[Ŕîĝĥţ éñð ~~~]\"],\"node.roof.glazing.face.front\":[\"[Ƒŕöñţ šļöþé ~~~]\"],\"node.roof.glazing.face.slope\":[\"[Šĥéð šļöþé ~~~]\"],\"node.roof.glazingControls.alignment\":[\"[Åļîĝñɱéñţ ~~~]\"],\"node.roof.glazingControls.center\":[\"[Çéñţéŕ ~~]\"],\"node.roof.glazingControls.chooseALayoutToEditThe\":[\"[Çĥööšé á ļáýöûţ ţö éðîţ ţĥé ĝŕîð. ~~~~~~~~]\"],\"node.roof.glazingControls.empty\":[\"[Éɱþţý ~~]\"],\"node.roof.glazingControls.end\":[\"[Éñð ~]\"],\"node.roof.glazingControls.fixedSpacing\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"node.roof.glazingControls.glass\":[\"[Ĝļášš ~~]\"],\"node.roof.glazingControls.layout\":[\"[Ļáýöûţ ~~]\"],\"node.roof.glazingControls.maxSpacing\":[\"[Ṁáẋ šþáçîñĝ ~~~]\"],\"node.roof.glazingControls.maximumSpacing\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"node.roof.glazingControls.mixed\":[\"[Ṁîẋéð ~~]\"],\"node.roof.glazingControls.panelCount\":[\"[Þáñéļ çöûñţ ~~~]\"],\"node.roof.glazingControls.panels\":[\"[Þáñéļš ~~]\"],\"node.roof.glazingControls.solid\":[\"[Šöļîð ~~]\"],\"node.roof.glazingControls.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.roof.glazingControls.start\":[\"[Šţáŕţ ~~]\"],\"node.roof.glazingControls.titleAlignment\":[\"[\",[\"title\"],\" áļîĝñɱéñţ ~~~]\"],\"node.roof.glazingPanel.changesApplyToAllLengthRoof\":[\"[Çĥáñĝéš áþþļý ţö áļļ \",[\"length\"],\" ŕööƒ šéĝɱéñţš. ~~~~~~~~~]\"],\"node.roof.glazingPanel.chooseOneRoofSystemToEdit\":[\"[Çĥööšé öñé ŕööƒ šýšţéɱ ţö éðîţ šéţţîñĝš ƒöŕ áļļ šéļéçţéð šéĝɱéñţš. ~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.closeTheRoofEnds\":[\"[Çļöšé ţĥé ŕööƒ éñðš ~~~~~]\"],\"node.roof.glazingPanel.defaultPanelInfill\":[\"[Ðéƒáûļţ þáñéļ îñƒîļļ ~~~~~~]\"],\"node.roof.glazingPanel.dividesEachRoofFaceIntoColumns\":[\"[Ðîṽîðéš éáçĥ ŕööƒ ƒáçé îñţö çöļûɱñš. ~~~~~~~~~]\"],\"node.roof.glazingPanel.dividesTheLengthOfEachArch\":[\"[Ðîṽîðéš ţĥé ļéñĝţĥ öƒ éáçĥ áŕçĥ ƀáý îñţö çöļûɱñš. ~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.endInfillFillsTheSpaceAbove\":[\"[Éñð îñƒîļļ ƒîļļš ţĥé šþáçé áƀöṽé ţĥé ŵáļļš. Éðîţ ŕööɱ ŵáļļš áñð çéîļîñĝš šéþáŕáţéļý. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.frameColor\":[\"[Ƒŕáɱé çöļöŕ ~~~]\"],\"node.roof.glazingPanel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.roof.glazingPanel.frameWidth\":[\"[Ƒŕáɱé ŵîðţĥ ~~~]\"],\"node.roof.glazingPanel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.roof.glazingPanel.glass\":[\"[Ĝļášš ~~]\"],\"node.roof.glazingPanel.glassTint\":[\"[Ĝļášš ţîñţ ~~~]\"],\"node.roof.glazingPanel.glazed\":[\"[Ĝļážéð ~~]\"],\"node.roof.glazingPanel.glazingIsAvailableForGableStraight\":[\"[Ĝļážîñĝ îš áṽáîļáƀļé ƒöŕ ĝáƀļé, šţŕáîĝĥţ šĥéð, áñð ƀáŕŕéļ ŕööƒš. ~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.gridAcrossTheRoof\":[\"[Ĝŕîð áçŕöšš ţĥé ŕööƒ ~~~~~~]\"],\"node.roof.glazingPanel.gridAlongTheLength\":[\"[Ĝŕîð áļöñĝ ţĥé ļéñĝţĥ ~~~~~~]\"],\"node.roof.glazingPanel.gridAlongTheSlope\":[\"[Ĝŕîð áļöñĝ ţĥé šļöþé ~~~~~~]\"],\"node.roof.glazingPanel.gridWithinEachArchBay\":[\"[Ĝŕîð ŵîţĥîñ éáçĥ áŕçĥ ƀáý ~~~~~~~]\"],\"node.roof.glazingPanel.infill\":[\"[Îñƒîļļ ~~]\"],\"node.roof.glazingPanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"node.roof.glazingPanel.paneThickness\":[\"[Þáñé ţĥîçķñéšš ~~~~]\"],\"node.roof.glazingPanel.roofSystem\":[\"[Ŕööƒ šýšţéɱ ~~~]\"],\"node.roof.glazingPanel.roughness\":[\"[Ŕöûĝĥñéšš ~~~]\"],\"node.roof.glazingPanel.spacingIsMeasuredOnTheSlope\":[\"[Šþáçîñĝ îš ɱéášûŕéð öñ ţĥé šļöþé, ñöţ îţš ĥöŕîžöñţáļ þŕöĵéçţîöñ. ~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.roof.glazingPanel.subdividesEachFlatArchBayArch\":[\"[Šûƀðîṽîðéš éáçĥ ƒļáţ áŕçĥ ƀáý. Åŕçĥ ƀáýš çöñţŕöļš ţĥé ðîṽîšîöñš áŕöûñð ţĥé çûŕṽé. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.glazingPanelEditor.chooseAPanelToOverrideIts\":[\"[Çĥööšé á þáñéļ ţö öṽéŕŕîðé îţš ðéƒáûļţ îñƒîļļ. ~~~~~~~~~~~~]\"],\"node.roof.glazingPanelEditor.column2\":[\"[Çöļûɱñ ~~]\"],\"node.roof.glazingPanelEditor.custom\":[\"[Çûšţöɱ ~~]\"],\"node.roof.glazingPanelEditor.default\":[\"[Ðéƒáûļţ ~~~]\"],\"node.roof.glazingPanelEditor.grid\":[\"[\",[\"columns\",\"plural\",{\"one\":[[\"columnCount\"],\" çöļûɱñ\"],\"other\":[[\"columnCount\"],\" çöļûɱñš\"]}],\" · \",[\"rows\",\"plural\",{\"one\":[[\"rowCount\"],\" ŕöŵ\"],\"other\":[[\"rowCount\"],\" ŕöŵš\"]}],\" ~~~~~~]\"],\"node.roof.glazingPanelEditor.individualPanels\":[\"[Îñðîṽîðûáļ þáñéļš ~~~~~]\"],\"node.roof.glazingPanelEditor.panelInfill\":[\"[Þáñéļ îñƒîļļ ~~~~]\"],\"node.roof.glazingPanelEditor.resetToDefault\":[\"[Ŕéšéţ ţö ðéƒáûļţ ~~~~~]\"],\"node.roof.glazingPanelEditor.roofFace\":[\"[Ŕööƒ ƒáçé ~~~]\"],\"node.roof.glazingPanelEditor.row2\":[\"[Ŕöŵ ~]\"],\"node.roof.glazingPanelEditor.selectedPanelInfill\":[\"[Šéļéçţéð þáñéļ îñƒîļļ ~~~~~~]\"],\"node.roof.label\":[\"[Ŕööƒ ~~]\"],\"node.roof.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.roof.panel.addChimney\":[\"[Åðð Çĥîɱñéý ~~~]\"],\"node.roof.panel.addCupola\":[\"[Åðð Çûþöļá ~~~]\"],\"node.roof.panel.addDormer\":[\"[Åðð Ðöŕɱéŕ ~~~]\"],\"node.roof.panel.addEyebrowVent\":[\"[Åðð Éýéƀŕöŵ Ṽéñţ ~~~~~]\"],\"node.roof.panel.addGutter\":[\"[Åðð Ĝûţţéŕ ~~~]\"],\"node.roof.panel.addSkylight\":[\"[Åðð Šķýļîĝĥţ ~~~~]\"],\"node.roof.panel.addSolarPanel\":[\"[Åðð Šöļáŕ Þáñéļ ~~~~]\"],\"node.roof.panel.addVent\":[\"[Åðð Ṽéñţ ~~~]\"],\"node.roof.panel.box\":[\"[Ɓöẋ ~]\"],\"node.roof.panel.boxVent\":[\"[ƀöẋ ṽéñţ ~~~]\"],\"node.roof.panel.chimney\":[\"[çĥîɱñéý ~~~]\"],\"node.roof.panel.custom\":[\"[Çûšţöɱ ~~]\"],\"node.roof.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.roof.panel.dormer\":[\"[ðöŕɱéŕ ~~]\"],\"node.roof.panel.drawSegment\":[\"[Ðŕáŵ Šéĝɱéñţ ~~~~]\"],\"node.roof.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.roof.panel.eaveOverhang\":[\"[Éáṽé öṽéŕĥáñĝ ~~~~]\"],\"node.roof.panel.eaves\":[\"[Éáṽéš ~~]\"],\"node.roof.panel.elements\":[\"[Éļéɱéñţš ~~~]\"],\"node.roof.panel.followsWalls\":[\"[Ƒöļļöŵš ŵáļļš ~~~~]\"],\"node.roof.panel.gableOverhang\":[\"[Ĝáƀļé öṽéŕĥáñĝ ~~~~]\"],\"node.roof.panel.gutter\":[\"[ĝûţţéŕ ~~]\"],\"node.roof.panel.level\":[\"[Ļéṽéļ ~~]\"],\"node.roof.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.roof.panel.moveToLevel\":[\"[Ṁöṽé ţö \",[\"level\"],\" ~~]\"],\"node.roof.panel.moveToTheRoofLevel\":[\"[Ṁöṽé ţö ţĥé ŕööƒ ļéṽéļ ~~~~~~]\"],\"node.roof.panel.onStoreyWalls\":[\"[Öñ \",[\"storey\"],\" ŵîţĥ îţš ŵáļļš, šö îţ ĥîðéš ţĥáţ ƒļööŕ'š þļáñ îñ šöļö áñð éẋþļöðéð ṽîéŵš.\",[\"roofs\",\"plural\",{\"0\":[],\"1\":[],\"other\":[\" Ţĥé ƒļööŕ'š \",[\"roofCount\"],\" ŕööƒš ɱöṽé ţöĝéţĥéŕ.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.roof.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.roof.panel.ridge\":[\"[Ŕîðĝé ~~]\"],\"node.roof.panel.ridgeVent\":[\"[ŕîðĝé ṽéñţ ~~~]\"],\"node.roof.panel.roof\":[\"[Ŕööƒ ~~]\"],\"node.roof.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.roof.panel.segmentType\":[\"[\",[\"roofType\",\"select\",{\"hip\":[\"Ĥîþ\"],\"gable\":[\"Ĝáƀļé\"],\"shed\":[\"Šĥéð\"],\"gambrel\":[\"Ĝáɱƀŕéļ\"],\"dutch\":[\"Ðûţçĥ\"],\"mansard\":[\"Ṁáñšáŕð\"],\"flat\":[\"Ƒļáţ\"],\"conical\":[\"Çöñîçáļ\"],\"barrel\":[\"Ɓáŕŕéļ\"],\"other\":[\"Ŕööƒ\"]}],\" ~~~~~~~~~~~~~~~~]\"],\"node.roof.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.roof.panel.skylight\":[\"[šķýļîĝĥţ ~~~]\"],\"node.roof.panel.solarPanel\":[\"[šöļáŕ þáñéļ ~~~]\"],\"node.roof.panel.turbine\":[\"[Ţûŕƀîñé ~~~]\"],\"node.roof.panel.turbineVent\":[\"[ţûŕƀîñé ṽéñţ ~~~~]\"],\"node.roofSegment.buildUpSection.applyToAllPlanesOfThis\":[\"[Åþþļý ţö áļļ þļáñéš öƒ ţĥîš ŕööƒ ~~~~~~~~]\"],\"node.roofSegment.buildUpSection.architecturalShingle\":[\"[Åŕçĥîţéçţûŕáļ šĥîñĝļé ~~~~~~]\"],\"node.roofSegment.buildUpSection.asphalt3Tab\":[\"[Åšþĥáļţ 3-ţáƀ ~~~]\"],\"node.roofSegment.buildUpSection.clayTile\":[\"[Çļáý ţîļé ~~~]\"],\"node.roofSegment.buildUpSection.concreteTile\":[\"[Çöñçŕéţé ţîļé ~~~~]\"],\"node.roofSegment.buildUpSection.covering\":[\"[Çöṽéŕîñĝ ~~~]\"],\"node.roofSegment.buildUpSection.deck\":[\"[Ðéçķ ~~]\"],\"node.roofSegment.buildUpSection.editLayers\":[\"[Éðîţ ļáýéŕš ~~~]\"],\"node.roofSegment.buildUpSection.finishName\":[\"[Ƒîñîšĥ ñáɱé ~~~]\"],\"node.roofSegment.buildUpSection.iceAndWaterShield\":[\"[Îçé áñð ŵáţéŕ šĥîéļð ~~~~~~]\"],\"node.roofSegment.buildUpSection.insulationName\":[\"[Îñšûļáţîöñ ñáɱé ~~~~~]\"],\"node.roofSegment.buildUpSection.interiorFinish\":[\"[Îñţéŕîöŕ ƒîñîšĥ ~~~~~]\"],\"node.roofSegment.buildUpSection.layers\":[\"[Ļáýéŕš ~~]\"],\"node.roofSegment.buildUpSection.n15Felt\":[\"[#15 ƒéļţ ~~]\"],\"node.roofSegment.buildUpSection.n30Felt\":[\"[#30 ƒéļţ ~~]\"],\"node.roofSegment.buildUpSection.none\":[\"[Ñöñé ~~]\"],\"node.roofSegment.buildUpSection.preset\":[\"[Þŕéšéţ ~~]\"],\"node.roofSegment.buildUpSection.rafters\":[\"[Ŕáƒţéŕš ~~~]\"],\"node.roofSegment.buildUpSection.roofing\":[\"[Ŕööƒîñĝ ~~~]\"],\"node.roofSegment.buildUpSection.roofingName\":[\"[Ŕööƒîñĝ ñáɱé ~~~~]\"],\"node.roofSegment.buildUpSection.sheathing\":[\"[Šĥéáţĥîñĝ ~~~]\"],\"node.roofSegment.buildUpSection.sheathingName\":[\"[Šĥéáţĥîñĝ ñáɱé ~~~~]\"],\"node.roofSegment.buildUpSection.slate\":[\"[Šļáţé ~~]\"],\"node.roofSegment.buildUpSection.standingSeamMetal\":[\"[Šţáñðîñĝ-šéáɱ ɱéţáļ ~~~~~~]\"],\"node.roofSegment.buildUpSection.synthetic\":[\"[Šýñţĥéţîç ~~~]\"],\"node.roofSegment.buildUpSection.topChord\":[\"[Ţöþ çĥöŕð ~~~]\"],\"node.roofSegment.buildUpSection.trusses\":[\"[Ţŕûššéš ~~~]\"],\"node.roofSegment.buildUpSection.underlayment\":[\"[Ûñðéŕļáýɱéñţ ~~~~]\"],\"node.roofSegment.buildUpSection.underlaymentName\":[\"[Ûñðéŕļáýɱéñţ ñáɱé ~~~~~]\"],\"node.roofSegment.buildUpSection.woodShake\":[\"[Ŵööð šĥáķé ~~~]\"],\"node.roofSegment.description\":[\"[Å šîñĝļé þîţçĥéð þļáñé öƒ á þáŕéñţ ŕööƒ. ~~~~~~~~~~]\"],\"node.roofSegment.label\":[\"[Ŕööƒ Šéĝɱéñţ ~~~~]\"],\"node.roofSegment.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.roofSegment.panel.angle\":[\"[Åñĝļé ~~]\"],\"node.roofSegment.panel.arc\":[\"[Åŕç ~]\"],\"node.roofSegment.panel.autoGutters\":[\"[Åûţö ĝûţţéŕš ~~~~]\"],\"node.roofSegment.panel.autoRidgeVent\":[\"[Åûţö ŕîðĝé ṽéñţ ~~~~]\"],\"node.roofSegment.panel.barrel\":[\"[Ɓáŕŕéļ ~~]\"],\"node.roofSegment.panel.clippedVersion\":[\"[Çļîþþéð ṽéŕšîöñ ~~~~~]\"],\"node.roofSegment.panel.conical\":[\"[Çöñîçáļ ~~~]\"],\"node.roofSegment.panel.conicalShape\":[\"[Çöñîçáļ Šĥáþé ~~~~]\"],\"node.roofSegment.panel.deckThick\":[\"[Ðéçķ Ţĥîçķ. ~~~]\"],\"node.roofSegment.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.roofSegment.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.roofSegment.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.roofSegment.panel.doneEditing\":[\"[Ðöñé éðîţîñĝ ~~~~]\"],\"node.roofSegment.panel.drainage\":[\"[Ðŕáîñáĝé ~~~]\"],\"node.roofSegment.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.roofSegment.panel.dutch\":[\"[Ðûţçĥ ~~]\"],\"node.roofSegment.panel.eaveCut\":[\"[Éáṽé çûţ ~~~]\"],\"node.roofSegment.panel.eaveOverhang\":[\"[Éáṽé öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.editFootprint\":[\"[Éðîţ ƒööţþŕîñţ ~~~~]\"],\"node.roofSegment.panel.endOverhang\":[\"[Éñð öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.fascia\":[\"[Ƒášçîá ~~]\"],\"node.roofSegment.panel.fasciaHeight\":[\"[Ƒášçîá ĥéîĝĥţ ~~~~]\"],\"node.roofSegment.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.roofSegment.panel.footprint\":[\"[Ƒööţþŕîñţ ~~~]\"],\"node.roofSegment.panel.gable\":[\"[Ĝáƀļé ~~]\"],\"node.roofSegment.panel.gableOverhang\":[\"[Ĝáƀļé öṽéŕĥáñĝ ~~~~]\"],\"node.roofSegment.panel.gambrel\":[\"[Ĝáɱƀŕéļ ~~~]\"],\"node.roofSegment.panel.hip\":[\"[Ĥîþ ~]\"],\"node.roofSegment.panel.kinkDepth\":[\"[Ķîñķ Ðéþţĥ ~~~]\"],\"node.roofSegment.panel.kinkHeight\":[\"[Ķîñķ Ĥéîĝĥţ ~~~]\"],\"node.roofSegment.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.roofSegment.panel.mansard\":[\"[Ṁáñšáŕð ~~~]\"],\"node.roofSegment.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.roofSegment.panel.pitch\":[\"[Þîţçĥ ~~]\"],\"node.roofSegment.panel.plumb\":[\"[Þļûɱƀ ~~]\"],\"node.roofSegment.panel.plumbLevel\":[\"[Þļûɱƀ + ļéṽéļ ~~~]\"],\"node.roofSegment.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.roofSegment.panel.reset\":[\"[Ŕéšéţ ~~]\"],\"node.roofSegment.panel.roofSegment\":[\"[Ŕööƒ Šéĝɱéñţ ~~~~]\"],\"node.roofSegment.panel.roofType\":[\"[Ŕööƒ Ţýþé ~~~]\"],\"node.roofSegment.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.roofSegment.panel.shape\":[\"[Šĥáþé ~~]\"],\"node.roofSegment.panel.shed\":[\"[Šĥéð ~~]\"],\"node.roofSegment.panel.shingleThick\":[\"[Šĥîñĝļé Ţĥîçķ. ~~~~]\"],\"node.roofSegment.panel.span\":[\"[Šþáñ ~~]\"],\"node.roofSegment.panel.square\":[\"[Šǫûáŕé ~~]\"],\"node.roofSegment.panel.startAngle\":[\"[Šţáŕţ Åñĝļé ~~~]\"],\"node.roofSegment.panel.structure\":[\"[Šţŕûçţûŕé ~~~]\"],\"node.roofSegment.panel.topRakeLength\":[\"[Ţöþ Ŕáķé Ļéñĝţĥ ~~~~]\"],\"node.roofSegment.panel.topRakeThick\":[\"[Ţöþ Ŕáķé Ţĥîçķ. ~~~~]\"],\"node.roofSegment.panel.trim\":[\"[Ţŕîɱ ~~]\"],\"node.roofSegment.panel.waistHeight\":[\"[Ŵáîšţ Ĥéîĝĥţ ~~~~]\"],\"node.roofSegment.panel.waistLength\":[\"[Ŵáîšţ Ļéñĝţĥ ~~~~]\"],\"node.roofSegment.panel.waistWidth\":[\"[Ŵáîšţ Ŵîðţĥ ~~~]\"],\"node.roofSegment.panel.wall\":[\"[Ŵáļļ ~~]\"],\"node.roofSegment.panel.wallHeight\":[\"[Ŵáļļ Ĥéîĝĥţ ~~~]\"],\"node.roofSegment.panel.wallThick\":[\"[Ŵáļļ Ţĥîçķ. ~~~]\"],\"node.roofSegment.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.scan.description\":[\"[Å çáþţûŕéð šéššîöñ ŵîţĥ öþţîöñáļ ɱéšĥ, ɱöţîöñ, ɱéðîá, áñð šéñšöŕ ðáţá. ~~~~~~~~~~~~~~~~~]\"],\"node.scan.label\":[\"[Çáþţûŕé ~~~]\"],\"node.segmentNumber\":[\"[Šéĝɱéñţ \",[\"number\"],\" ~~~]\"],\"node.separator.label\":[\"[Šéþáŕáţöŕ ~~~]\"],\"node.shared.buildUpControls.batt\":[\"[Ɓáţţ ~~]\"],\"node.shared.buildUpControls.blown\":[\"[Ɓļöŵñ ~~]\"],\"node.shared.buildUpControls.drywall\":[\"[Ðŕýŵáļļ ~~~]\"],\"node.shared.buildUpControls.drywallThickness\":[\"[Ðŕýŵáļļ ţĥîçķñéšš ~~~~~]\"],\"node.shared.buildUpControls.insulation\":[\"[Îñšûļáţîöñ ~~~]\"],\"node.shared.buildUpControls.insulationThickness\":[\"[Îñšûļáţîöñ ţĥîçķñéšš ~~~~~~]\"],\"node.shared.buildUpControls.labelThickness\":[\"[\",[\"label\"],\" ţĥîçķñéšš ~~~]\"],\"node.shared.buildUpControls.none\":[\"[Ñöñé ~~]\"],\"node.shared.buildUpControls.osb\":[\"[ÖŠƁ ~]\"],\"node.shared.buildUpControls.plaster\":[\"[Þļášţéŕ ~~~]\"],\"node.shared.buildUpControls.plywood\":[\"[Þļýŵööð ~~~]\"],\"node.shared.buildUpControls.rigidBoard\":[\"[Ŕîĝîð ƀöáŕð ~~~]\"],\"node.shared.buildUpControls.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.shared.buildUpControls.spacingOC\":[\"[\",[\"spacing\"],\" ö.ç. ~]\"],\"node.shared.buildUpControls.woodPlank\":[\"[Ŵööð þļáñķ ~~~]\"],\"node.shared.itemInteractionActions.turnLightOff\":[\"[Ţûŕñ ļîĝĥţ öƒƒ ~~~~]\"],\"node.shared.itemInteractionActions.turnLightOn\":[\"[Ţûŕñ ļîĝĥţ öñ ~~~~]\"],\"node.shared.openingCasingSection.casing\":[\"[Çášîñĝ ~~]\"],\"node.shared.openingCasingSection.width\":[\"[Ŵîðţĥ ~~]\"],\"node.shared.runHangerInspector.automaticSupport\":[\"[Åûţöɱáţîç šûþþöŕţ ~~~~~]\"],\"node.shared.runHangerInspector.hangerOnSegment\":[\"[Ĥáñĝéŕ \",[\"number\"],\" · šéĝɱéñţ \",[\"segment\"],\" ~~~~]\"],\"node.shared.runHangerInspector.individualHangers\":[\"[Îñðîṽîðûáļ ĥáñĝéŕš ~~~~~~]\"],\"node.shared.runHangerInspector.missingSupport\":[\"[Ṁîššîñĝ šûþþöŕţ ~~~~~]\"],\"node.shared.runHangerInspector.noSupportWithinReachMoveThe\":[\"[Ñö šûþþöŕţ ŵîţĥîñ ŕéáçĥ. Ṁöṽé ţĥé ĥáñĝéŕ öŕ çĥööšé áñöţĥéŕ ĥöšţ. ~~~~~~~~~~~~~~~~]\"],\"node.shared.runHangerInspector.positionLabel\":[\"[Ĥáñĝéŕ \",[\"number\"],\" þöšîţîöñ îñ ɱéţŕéš ~~~~~~~]\"],\"node.shared.runHangerInspector.positionM\":[\"[Þöšîţîöñ (ɱ) ~~~]\"],\"node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath\":[\"[Þöšîţîöñš áŕé ɱéášûŕéð áļöñĝ éáçĥ þáţĥ šéĝɱéñţ. Çĥáñĝîñĝ šþáçîñĝ ŕéĝéñéŕáţéš ţĥé ñûɱƀéŕéð šļöţš. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.shared.runHangerInspector.revealSupport\":[\"[Ŕéṽéáļ šûþþöŕţ ~~~~]\"],\"node.shared.runHangerInspector.skip\":[\"[Šķîþ ~~]\"],\"node.shared.runHangerInspector.supportLabel\":[\"[Ĥáñĝéŕ \",[\"number\"],\" šûþþöŕţ ~~~~]\"],\"node.shared.systemCheckPanel.aboutTheseChecks\":[\"[Åƀöûţ ţĥéšé çĥéçķš ~~~~~]\"],\"node.shared.systemCheckPanel.allFindings\":[\"[Åļļ ƒîñðîñĝš ~~~~]\"],\"node.shared.systemCheckPanel.allSystems\":[\"[Åļļ šýšţéɱš ~~~]\"],\"node.shared.systemCheckPanel.connections\":[\"[Çöññéçţîöñš ~~~~]\"],\"node.shared.systemCheckPanel.drainage\":[\"[Ðŕáîñáĝé ~~~]\"],\"node.shared.systemCheckPanel.error\":[\"[Éŕŕöŕ ~~]\"],\"node.shared.systemCheckPanel.errors\":[\"[Éŕŕöŕš ~~]\"],\"node.shared.systemCheckPanel.filterSystemChecks\":[\"[Ƒîļţéŕ šýšţéɱ çĥéçķš ~~~~~~]\"],\"node.shared.systemCheckPanel.hangers\":[\"[Ĥáñĝéŕš ~~~]\"],\"node.shared.systemCheckPanel.intersections\":[\"[Îñţéŕšéçţîöñš ~~~~]\"],\"node.shared.systemCheckPanel.locate\":[\"[Ļöçáţé \",[\"name\"],\" (\",[\"id\"],\") ~~]\"],\"node.shared.systemCheckPanel.missingItem\":[\"[Ṁîššîñĝ îţéɱ ~~~~]\"],\"node.shared.systemCheckPanel.noFindings\":[\"[Ñö ƒîñðîñĝš ~~~]\"],\"node.shared.systemCheckPanel.noFindingsInThisCategory\":[\"[Ñö ƒîñðîñĝš îñ ţĥîš çáţéĝöŕý ~~~~~~~~]\"],\"node.shared.systemCheckPanel.openEnds\":[\"[Öþéñ éñðš ~~~]\"],\"node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay\":[\"[Öþéñ éñðš áñð šéþáŕáţé ƀŕáñçĥéš ɱáý ƀé îñţéñţîöñáļ. Îñţéŕšéçţîöñ çĥéçķš ûšé ƀöûñðîñĝ ƀöẋéš; îñšþéçţ öþéñîñĝš áñð çļéáŕáñçéš. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.shared.systemCheckPanel.selectedConnectedSystem\":[\"[Šéļéçţéð çöññéçţéð šýšţéɱ ~~~~~~~]\"],\"node.shared.systemCheckPanel.selectedRuns\":[\"[Šéļéçţéð: \",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕûñ\"],\"other\":[[\"number\"],\" ŕûñš\"]}],\" · \",[\"length\"],\" ɱ ~~~~~]\"],\"node.shared.systemCheckPanel.separateBranches\":[\"[Šéþáŕáţé ƀŕáñçĥéš ~~~~~]\"],\"node.shared.systemCheckPanel.showAllFindings\":[\"[Šĥöŵ áļļ ƒîñðîñĝš ~~~~~]\"],\"node.shared.systemCheckPanel.systemChecks\":[\"[Šýšţéɱ çĥéçķš ~~~~]\"],\"node.shared.systemCheckPanel.systemFinding\":[\"[Šýšţéɱ ƒîñðîñĝ ~~~~]\"],\"node.shared.systemCheckPanel.systemFindings\":[\"[Šýšţéɱ ƒîñðîñĝš ~~~~~]\"],\"node.shared.systemCheckPanel.warning\":[\"[Ŵáŕñîñĝ ~~~]\"],\"node.shared.systemCheckPanel.warnings\":[\"[Ŵáŕñîñĝš ~~~]\"],\"node.shelf.description\":[\"[Å çöñƒîĝûŕáƀļé šĥéļṽîñĝ ûñîţ. Îţéɱš ĥöšţ öñ éáçĥ ŕöŵ. ~~~~~~~~~~~~~]\"],\"node.shelf.label\":[\"[Šĥéļƒ ~~]\"],\"node.site.description\":[\"[Ţĥé ţöþ-ļéṽéļ çöñţáîñéŕ ĥöļðîñĝ ƀûîļðîñĝš, žöñéš, áñð ţĥé þŕöþéŕţý ƀöûñðáŕý. ~~~~~~~~~~~~~~~~~~~]\"],\"node.site.label\":[\"[Šîţé ~~]\"],\"node.site.panel.address\":[\"[Åððŕéšš ~~~]\"],\"node.site.panel.apn\":[\"[ÅÞÑ ~]\"],\"node.site.panel.autoMostNorthFacingEdge\":[\"[Åûţö — ɱöšţ ñöŕţĥ-ƒáçîñĝ éðĝé ~~~~~~~]\"],\"node.site.panel.contourIntervalSitePlanAnd3d\":[\"[Çöñţöûŕ îñţéŕṽáļ (šîţé þļáñ áñð 3Ð) ~~~~~~~~~]\"],\"node.site.panel.county\":[\"[Çöûñţý ~~]\"],\"node.site.panel.eGRs60\":[\"[é.ĝ. ŔŠ-60 ~~]\"],\"node.site.panel.eGTampaLdc27156\":[\"[é.ĝ. Ţáɱþá ĻÐÇ §27-156 ~~~]\"],\"node.site.panel.every12In\":[\"[éṽéŕý 12 îñ ~~~]\"],\"node.site.panel.every24In\":[\"[éṽéŕý 24 îñ ~~~]\"],\"node.site.panel.every6In\":[\"[éṽéŕý 6 îñ ~~~]\"],\"node.site.panel.findParcel\":[\"[Ƒîñð þáŕçéļ ~~~]\"],\"node.site.panel.finding\":[\"[Ƒîñðîñĝ… ~~~]\"],\"node.site.panel.frontStreetEdge\":[\"[Ƒŕöñţ (šţŕééţ) éðĝé ~~~~~]\"],\"node.site.panel.lookingUpTheParcel\":[\"[Ļööķîñĝ ûþ ţĥé þáŕçéļ áñð ţĥé šţŕééţš áŕöûñð îţ… ~~~~~~~~~~~~]\"],\"node.site.panel.lotArea\":[\"[Ļöţ áŕéá ~~~]\"],\"node.site.panel.n1200WCassStTampaFl\":[\"[1200 Ŵ Çášš Šţ, Ţáɱþá, ƑĻ ~~~~~]\"],\"node.site.panel.noSurveyedLinesContoursAreDrawn\":[\"[Ñö šûŕṽéýéð ļîñéš — çöñţöûŕš áŕé ðŕáŵñ ƒŕöɱ ţĥé šçûļþţéð ĝŕöûñð. ~~~~~~~~~~~~~~~~]\"],\"node.site.panel.none\":[\"[ñöñé ~~]\"],\"node.site.panel.northRotation\":[\"[Ñöŕţĥ ŕöţáţîöñ ~~~~]\"],\"node.site.panel.orientation\":[\"[Öŕîéñţáţîöñ ~~~~]\"],\"node.site.panel.parcel\":[\"[Þáŕçéļ ~~]\"],\"node.site.panel.parcelLookupFailed\":[\"[Þáŕçéļ ļööķûþ ƒáîļéð. ~~~~~~]\"],\"node.site.panel.projectLook\":[\"[Þŕöĵéçţ ļööķ ~~~~]\"],\"node.site.panel.saveCurrentLook\":[\"[Šáṽé çûŕŕéñţ ļööķ ~~~~~]\"],\"node.site.panel.saveTheCurrentLightingAndDisplay\":[\"[Šáṽé ţĥé çûŕŕéñţ ļîĝĥţîñĝ áñð ðîšþļáý áš ţĥîš þŕöĵéçţ'š ðéƒáûļţ. Þéŕšöñáļ ðîšþļáý çĥáñĝéš šţáý îñ ţĥîš ƀŕöŵšéŕ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.site.panel.setbacks\":[\"[Šéţƀáçķš ~~~]\"],\"node.site.panel.showGround\":[\"[Šĥöŵ ĝŕöûñð ~~~]\"],\"node.site.panel.showTheContourLinesIn3d\":[\"[Šĥöŵ ţĥé çöñţöûŕ ļîñéš îñ 3Ð ~~~~~~~]\"],\"node.site.panel.side\":[\"[šîðé ~~]\"],\"node.site.panel.site\":[\"[Šîţé ~~]\"],\"node.site.panel.source\":[\"[Šöûŕçé ~~]\"],\"node.site.panel.sourceCitation\":[\"[Šöûŕçé / çîţáţîöñ ~~~~~]\"],\"node.site.panel.streetAddress\":[\"[Šţŕééţ áððŕéšš ~~~~]\"],\"node.site.panel.survey\":[\"[šûŕṽéý ~~]\"],\"node.site.panel.surveyedContours\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"lineCount\"],\" šûŕṽéýéð ļîñé\"],\"other\":[[\"lineCount\"],\" šûŕṽéýéð ļîñéš\"]}],\" áţ \",[\"interval\"],\" ƒţ (\",[\"datum\"],\") — \",[\"source\"],\" ~~~~~~~~~]\"],\"node.site.panel.terrain\":[\"[Ţéŕŕáîñ ~~~]\"],\"node.site.panel.typeAnAddressFirst\":[\"[Ţýþé áñ áððŕéšš ƒîŕšţ. ~~~~~~]\"],\"node.site.panel.useProjectLook\":[\"[Ûšé þŕöĵéçţ ļööķ ~~~~~]\"],\"node.site.panel.zone\":[\"[Žöñé ~~]\"],\"node.site.setback.front\":[\"[Ƒŕöñţ ~~]\"],\"node.site.setback.leftOverride\":[\"[Ļéƒţ (öṽéŕŕîðé) ~~~~]\"],\"node.site.setback.rear\":[\"[Ŕéáŕ ~~]\"],\"node.site.setback.rightOverride\":[\"[Ŕîĝĥţ (öṽéŕŕîðé) ~~~~]\"],\"node.site.setback.side\":[\"[Šîðé ~~]\"],\"node.skylight.description\":[\"[Ƒŕáɱéð ĝļášš öþéñîñĝ öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~]\"],\"node.skylight.label\":[\"[Šķýļîĝĥţ ~~~]\"],\"node.skylight.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.skylight.panel.alongX\":[\"[Åļöñĝ Ẋ ~~]\"],\"node.skylight.panel.alongZ\":[\"[Åļöñĝ Ž ~~]\"],\"node.skylight.panel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.skylight.panel.curb\":[\"[Çûŕƀ ~~]\"],\"node.skylight.panel.cutoutOffset\":[\"[Çûţöûţ Öƒƒšéţ ~~~~]\"],\"node.skylight.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.skylight.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.skylight.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.skylight.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.skylight.panel.glassThickness\":[\"[Ĝļášš Ţĥîçķñéšš ~~~~~]\"],\"node.skylight.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.skylight.panel.lanternHeight\":[\"[Ļáñţéŕñ Ĥéîĝĥţ ~~~~]\"],\"node.skylight.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.skylight.panel.motor\":[\"[Ṁöţöŕ ~~]\"],\"node.skylight.panel.motorHousing\":[\"[Ṁöţöŕ Ĥöûšîñĝ ~~~~]\"],\"node.skylight.panel.no\":[\"[Ñö ~]\"],\"node.skylight.panel.noMotor\":[\"[Ñö Ṁöţöŕ ~~~]\"],\"node.skylight.panel.open\":[\"[Öþéñ ~~]\"],\"node.skylight.panel.openingAngle\":[\"[Öþéñîñĝ Åñĝļé ~~~~]\"],\"node.skylight.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.skylight.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.skylight.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.skylight.panel.skylight\":[\"[Šķýļîĝĥţ ~~~]\"],\"node.skylight.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.skylight.panel.top\":[\"[Ţöþ ~]\"],\"node.skylight.panel.topScale\":[\"[Ţöþ Šçáļé ~~~]\"],\"node.skylight.panel.trackWidth\":[\"[Ţŕáçķ Ŵîðţĥ ~~~]\"],\"node.skylight.panel.type\":[\"[Ţýþé ~~]\"],\"node.skylight.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.skylight.panel.yes\":[\"[Ýéš ~]\"],\"node.skylight.type.flat\":[\"[Ƒļáţ ŕööƒ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.lantern\":[\"[Ŕööƒ ļáñţéŕñ ~~~~]\"],\"node.skylight.type.opening\":[\"[Öþéñîñĝ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.sliding\":[\"[Šļîðîñĝ šķýļîĝĥţ ~~~~~]\"],\"node.skylight.type.walkOn\":[\"[Ŵáļķ-öñ ŕööƒļîĝĥţ ~~~~~]\"],\"node.slab.description\":[\"[Å þöļýĝöñ-ƀöûñðéð ƒļööŕ šûŕƒáçé ţĥáţ ĥöšţš îţéɱš öñ ţöþ. ~~~~~~~~~~~~~~]\"],\"node.slab.label\":[\"[Šļáƀ ~~]\"],\"node.slab.panel.addHole\":[\"[Åðð Ĥöļé ~~~]\"],\"node.slab.panel.area\":[\"[Åŕéá ~~]\"],\"node.slab.panel.auto\":[\"[Åûţö ~~]\"],\"node.slab.panel.base\":[\"[Ɓášé ~~]\"],\"node.slab.panel.cutHatch\":[\"[Çûţ ĥáţçĥ ~~~]\"],\"node.slab.panel.cutOpening\":[\"[Çûţ öþéñîñĝ ~~~]\"],\"node.slab.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.slab.panel.done\":[\"[Ðöñé ~~]\"],\"node.slab.panel.elevation\":[\"[Éļéṽáţîöñ ~~~]\"],\"node.slab.panel.extendsThePerimeterDownToTerrain\":[\"[Éẋţéñðš ţĥé þéŕîɱéţéŕ ðöŵñ ţö ţéŕŕáîñ. Ţĥé ƒļáţ šûŕƒáçé, ƀášé, áñð ţĥîçķñéšš šţáý ûñçĥáñĝéð. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.slab.panel.fixed\":[\"[Ƒîẋéð ~~]\"],\"node.slab.panel.floor\":[\"[Ƒļööŕ ~~]\"],\"node.slab.panel.followsTerrain\":[\"[Ƒöļļöŵš ţéŕŕáîñ ~~~~~]\"],\"node.slab.panel.foundation\":[\"[Ƒöûñðáţîöñ ~~~]\"],\"node.slab.panel.holes\":[\"[Ĥöļéš ~~]\"],\"node.slab.panel.info\":[\"[Îñƒö ~~]\"],\"node.slab.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.slab.panel.noHoles\":[\"[Ñö ĥöļéš ~~~]\"],\"node.slab.panel.rim\":[\"[Ŕîɱ ~]\"],\"node.slab.panel.slab\":[\"[Šļáƀ ~~]\"],\"node.slab.panel.standard2\":[\"[Šţáñðáŕð (2\\\") ~~~]\"],\"node.slab.panel.standard5cm\":[\"[Šţáñðáŕð (5çɱ) ~~~]\"],\"node.slab.panel.sunken15cm\":[\"[Šûñķéñ (15çɱ) ~~~]\"],\"node.slab.panel.sunken6\":[\"[Šûñķéñ (6\\\") ~~]\"],\"node.slab.panel.surface\":[\"[Šûŕƒáçé ~~~]\"],\"node.slab.panel.thick15cm\":[\"[Ţĥîçķ (15çɱ) ~~~]\"],\"node.slab.panel.thick6\":[\"[Ţĥîçķ (6\\\") ~~]\"],\"node.slab.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.slab.panel.thin1\":[\"[Ţĥîñ (1\\\") ~~]\"],\"node.slab.panel.thin2cm\":[\"[Ţĥîñ (2çɱ) ~~]\"],\"node.solarPanel.description\":[\"[Ĝŕîð öƒ þĥöţöṽöļţáîç þáñéļš ɱöûñţéð öñ á ŕööƒ šéĝɱéñţ. ~~~~~~~~~~~~~~]\"],\"node.solarPanel.label\":[\"[Šöļáŕ Þáñéļ ~~~]\"],\"node.solarPanel.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.solarPanel.panel.array\":[\"[Åŕŕáý ~~]\"],\"node.solarPanel.panel.autoFitToRoof\":[\"[Åûţö-ƒîţ ţö ŕööƒ ~~~~]\"],\"node.solarPanel.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.solarPanel.panel.customDimensionsDonTMatchAny\":[\"[Çûšţöɱ — ðîɱéñšîöñš ðöñ'ţ ɱáţçĥ áñý þŕéšéţ ~~~~~~~~~~~]\"],\"node.solarPanel.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.solarPanel.panel.flipOrientation\":[\"[Ƒļîþ öŕîéñţáţîöñ ~~~~~]\"],\"node.solarPanel.panel.flush\":[\"[Ƒļûšĥ ~~]\"],\"node.solarPanel.panel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.solarPanel.panel.frameThickness\":[\"[Ƒŕáɱé ţĥîçķñéšš ~~~~~]\"],\"node.solarPanel.panel.gapX\":[\"[Ĝáþ Ẋ ~~]\"],\"node.solarPanel.panel.gapY\":[\"[Ĝáþ Ý ~~]\"],\"node.solarPanel.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.solarPanel.panel.mounting\":[\"[Ṁöûñţîñĝ ~~~]\"],\"node.solarPanel.panel.panel\":[\"[Þáñéļ ~~]\"],\"node.solarPanel.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.solarPanel.panel.rows\":[\"[Ŕöŵš ~~]\"],\"node.solarPanel.panel.setbacksTooLarge\":[\"[Šéţƀáçķš ţöö ļáŕĝé ţö ƒîţ á þáñéļ. ~~~~~~~~~]\"],\"node.solarPanel.panel.solarPanel\":[\"[Šöļáŕ Þáñéļ ~~~]\"],\"node.solarPanel.panel.standoff\":[\"[Šţáñðöƒƒ ~~~]\"],\"node.solarPanel.panel.tiltAngle\":[\"[Ţîļţ áñĝļé ~~~]\"],\"node.solarPanel.panel.tilted\":[\"[Ţîļţéð ~~]\"],\"node.solarPanel.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.solarPanel.preset.compact\":[\"[Çöɱþáçţ ~~~]\"],\"node.solarPanel.preset.frameless\":[\"[Ƒŕáɱéļéšš ~~~]\"],\"node.solarPanel.preset.residential\":[\"[Ŕéšîðéñţîáļ ~~~~]\"],\"node.solarPanel.preset.residentialLarge\":[\"[Ŕéšîðéñţîáļ Ļáŕĝé ~~~~~]\"],\"node.spawn.description\":[\"[Þļáýéŕ öŕ çáɱéŕá öŕîĝîñ ŵîţĥîñ á ļéṽéļ. Öñé þéŕ ļéṽéļ. ~~~~~~~~~~~~~]\"],\"node.spawn.label\":[\"[Šþáŵñ Þöîñţ ~~~]\"],\"node.stair.constructionControls.body\":[\"[Ɓöðý ~~]\"],\"node.stair.constructionControls.centerStringer\":[\"[Çéñţéŕ šţŕîñĝéŕ ~~~~~]\"],\"node.stair.constructionControls.closedRisers\":[\"[Çļöšéð ŕîšéŕš ~~~~]\"],\"node.stair.constructionControls.fromStructure\":[\"[Ƒŕöɱ šţŕûçţûŕé ~~~~]\"],\"node.stair.constructionControls.inherit\":[\"[Îñĥéŕîţ ~~~]\"],\"node.stair.constructionControls.nosing\":[\"[Ñöšîñĝ ~~]\"],\"node.stair.constructionControls.open\":[\"[Öþéñ ~~]\"],\"node.stair.constructionControls.original\":[\"[Öŕîĝîñáļ ~~~]\"],\"node.stair.constructionControls.riserThickness\":[\"[Ŕîšéŕ ţĥîçķñéšš ~~~~~]\"],\"node.stair.constructionControls.sideStringers\":[\"[Šîðé šţŕîñĝéŕš ~~~~]\"],\"node.stair.constructionControls.solid\":[\"[Šöļîð ~~]\"],\"node.stair.constructionControls.stringerDepth\":[\"[Šţŕîñĝéŕ ðéþţĥ ~~~~]\"],\"node.stair.constructionControls.stringerWidth\":[\"[Šţŕîñĝéŕ ŵîðţĥ ~~~~]\"],\"node.stair.constructionControls.treadFinish\":[\"[Ţŕéáð ƒîñîšĥ ~~~~]\"],\"node.stair.constructionControls.treadThickness\":[\"[Ţŕéáð ţĥîçķñéšš ~~~~~]\"],\"node.stair.constructionControls.waist\":[\"[Ŵáîšţ ~~]\"],\"node.stair.constructionControls.waistThickness\":[\"[Ŵáîšţ ţĥîçķñéšš ~~~~~]\"],\"node.stair.description\":[\"[Å šţáîŕ çöɱþöšéð öƒ öñé öŕ ɱöŕé ƒļîĝĥţš ŵîţĥ çöñƒîĝûŕáƀļé ţŕéáðš, ŕîšéŕš, ŕáîļîñĝš. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.arrivalMismatch\":[\"[Ţĥé ƒļîĝĥţ çĥáîñ ðöéš ñöţ ŕéáçĥ ţĥé ðéçļáŕéð ðéšţîñáţîöñ. ~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.comfortTarget\":[\"[Ţŵö ŕîšéŕš þļûš ĝöîñĝ îš öûţšîðé ţĥé 0.60–0.65 ɱ ðéšîĝñ ŕáñĝé. ~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.goingTarget\":[\"[Ĝöîñĝ îš ƀéļöŵ ţĥé ðéšîĝñ ţáŕĝéţ. ~~~~~~~~~]\"],\"node.stair.diagnostic.headroomTarget\":[\"[Öṽéŕĥéáð šûŕƒáçéš ļéáṽé ļéšš ţĥáñ ţĥé ţáŕĝéţ ĥéáðŕööɱ. ~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.headroomUnresolved\":[\"[Ðéţáîļéð ĥéáðŕööɱ çöûļð ñöţ ƀé éṽáļûáţéð: çĥéçķ ŕîšéŕ çöûñţš áñð ţĥé šûŕƒáçé ǫûéŕý ƀûðĝéţ. ~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.invalidFlight\":[\"[Ƒļîĝĥţ ðîɱéñšîöñš áñð îñţéĝéŕ ŕîšéŕ çöûñţ ɱûšţ ƀé þöšîţîṽé. ~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.invalidRise\":[\"[Ţĥé ðéšţîñáţîöñ ɱûšţ ƀé áƀöṽé ţĥé šţáîŕ ƀášé. ~~~~~~~~~~~~]\"],\"node.stair.diagnostic.landingOverlap\":[\"[Ļáñðîñĝ ðéþţĥ éẋçééðš á ƒûļļ çîŕçļé áţ ţĥé ŵáļķîñĝ ļîñé; ţĥé ṽîšîƀļé ļáñðîñĝ îš ļîɱîţéð ţö öñé çîŕçļé. ~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.narrowWinderEnd\":[\"[Ţĥé ñáŕŕöŵ éñð öƒ á ŵîñðéŕ ţŕéáð îš ƀéļöŵ 0.10 ɱ; çĥéçķ ţĥé îñţéñðéð šţáîŕ ûšé. ~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.nonuniformRisers\":[\"[Ƒļîĝĥţ ŕîšéŕ ĥéîĝĥţš ðéŕ. Ûšé ûñîƒöŕɱ-ŕîšéŕ ŕéþáîŕ öñļý îƒ ţĥîš îš á þŕöþöšéð ðéšîĝñ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.raisedLanding\":[\"[Å ļáñðîñĝ ĥáš á šţöŕéð ŕîšé; îñšþéçţ îţš ţŕáñšîţîöñ ƀéƒöŕé ŕéþáîŕ. ~~~~~~~~~~~~~~~~~]\"],\"node.stair.diagnostic.riserTarget\":[\"[Ŕîšéŕ ĥéîĝĥţ éẋçééðš ţĥé ðéšîĝñ ţáŕĝéţ. ~~~~~~~~~~]\"],\"node.stair.label\":[\"[Šţáîŕ ~~]\"],\"node.stair.layout.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stair.layout.turn\":[\"[\",[\"layout\"],\" ţûŕñ ~~]\"],\"node.stair.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.stair.panel.addFlight\":[\"[Åðð ƒļîĝĥţ ~~~]\"],\"node.stair.panel.addLanding\":[\"[Åðð ļáñðîñĝ ~~~]\"],\"node.stair.panel.applyLabelLayout\":[\"[Åþþļý \",[\"label\"],\" ļáýöûţ ~~~~]\"],\"node.stair.panel.autoCutout\":[\"[Åûţö Çûţöûţ ~~~]\"],\"node.stair.panel.balusters\":[\"[Ɓáļûšţéŕš ~~~]\"],\"node.stair.panel.boards\":[\"[Ɓöáŕðš ~~]\"],\"node.stair.panel.both\":[\"[Ɓöţĥ ~~]\"],\"node.stair.panel.cable\":[\"[Çáƀļé ~~]\"],\"node.stair.panel.centerColumn\":[\"[Çéñţéŕ Çöļûɱñ ~~~~]\"],\"node.stair.panel.clockwise\":[\"[Çļöçķŵîšé ~~~]\"],\"node.stair.panel.connectedTo\":[\"[Çöññéçţéð ţö \",[\"surface\"],\". Ṁöṽé ţĥé šţáîŕ ţö çöññéçţ ţö áñöţĥéŕ éðĝé. ~~~~~~~~~~~~~~]\"],\"node.stair.panel.connectedToSurface\":[\"[Çöññéçţéð ţö á šûŕƒáçé. Ṁöṽé ţĥé šţáîŕ ţö çöññéçţ ţö áñöţĥéŕ éðĝé. ~~~~~~~~~~~~~~~~]\"],\"node.stair.panel.construction\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"node.stair.panel.counterclockwise\":[\"[Çöûñţéŕçļöçķŵîšé ~~~~~]\"],\"node.stair.panel.curved\":[\"[Çûŕṽéð ~~]\"],\"node.stair.panel.customRise\":[\"[Çûšţöɱ ŕîšé ~~~]\"],\"node.stair.panel.deck\":[\"[Ðéçķ ~~]\"],\"node.stair.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.stair.panel.designTargets\":[\"[Ðéšîĝñ ţáŕĝéţš ~~~~]\"],\"node.stair.panel.destination\":[\"[Ðéšţîñáţîöñ ~~~~]\"],\"node.stair.panel.dragNearADeckPatioConcrete\":[\"[Ðŕáĝ ñéáŕ á ðéçķ, þáţîö, çöñçŕéţé šļáƀ, öŕ ļáñðîñĝ éðĝé ţö ƒîţ ţĥé šţáîŕ. ~~~~~~~~~~~~~~~~~]\"],\"node.stair.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.stair.panel.equalAngle\":[\"[Éǫûáļ áñĝļé ~~~]\"],\"node.stair.panel.equalGoing\":[\"[Éǫûáļ ĝöîñĝ ~~~]\"],\"node.stair.panel.fitRisersAndGoing\":[\"[Ƒîţ ŕîšéŕš áñð ĝöîñĝ ~~~~~~]\"],\"node.stair.panel.fitToFloor\":[\"[Ƒîţ Ţö Ƒļööŕ ~~~]\"],\"node.stair.panel.fitUniformRisers\":[\"[Ƒîţ ûñîƒöŕɱ ŕîšéŕš ~~~~~]\"],\"node.stair.panel.flightMeasurements\":[\"[Ƒļîĝĥţ \",[\"number\"],\": \",[\"count\",\"plural\",{\"one\":[[\"riserCount\"],\" ŕîšéŕ\"],\"other\":[[\"riserCount\"],\" ŕîšéŕš\"]}],\" · Ŕ \",[\"riser\"],\" · Ţ \",[\"going\"],\" · \",[\"slope\"],\" ~~~~~~]\"],\"node.stair.panel.floorsCeilingsAndStairsOnly\":[\"[Ƒļööŕš, çéîļîñĝš áñð šţáîŕš öñļý. ~~~~~~~~~]\"],\"node.stair.panel.followsDeck\":[\"[Ƒöļļöŵš ðéçķ ~~~~]\"],\"node.stair.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.stair.panel.fromLevel\":[\"[Ƒŕöɱ Ļéṽéļ ~~~]\"],\"node.stair.panel.geometry\":[\"[Ĝéöɱéţŕý ~~~]\"],\"node.stair.panel.glass\":[\"[Ĝļášš ~~]\"],\"node.stair.panel.headroom\":[\"[Ĥéáðŕööɱ: ~~~]\"],\"node.stair.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.stair.panel.innerGap\":[\"[Îññéŕ ĝáþ ~~~]\"],\"node.stair.panel.innerRadius\":[\"[Îññéŕ Ŕáðîûš ~~~~]\"],\"node.stair.panel.integrated\":[\"[Îñţéĝŕáţéð ~~~]\"],\"node.stair.panel.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"node.stair.panel.layout\":[\"[Ļáýöûţ ~~]\"],\"node.stair.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.stair.panel.leftTurn\":[\"[Ļéƒţ ţûŕñ ~~~]\"],\"node.stair.panel.measurements\":[\"[Ṁéášûŕéɱéñţš ~~~~]\"],\"node.stair.panel.metal\":[\"[Ṁéţáļ ~~]\"],\"node.stair.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.stair.panel.movingThisStairKeepsItsCurrent\":[\"[Ṁöṽîñĝ ţĥîš šţáîŕ ķééþš îţš çûŕŕéñţ ŕîšé, šţéþš, áñð ŕûñ. ~~~~~~~~~~~~~~]\"],\"node.stair.panel.noOverheadSurface\":[\"[Ñö öṽéŕĥéáð šûŕƒáçé ~~~~~~]\"],\"node.stair.panel.none\":[\"[Ñöñé ~~]\"],\"node.stair.panel.notEvaluated\":[\"[Ñöţ éṽáļûáţéð ~~~~]\"],\"node.stair.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.stair.panel.openingOffset\":[\"[Öþéñîñĝ Öƒƒšéţ ~~~~]\"],\"node.stair.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.stair.panel.postRail\":[\"[Þöšţ & ŕáîļ ~~~]\"],\"node.stair.panel.railing\":[\"[Ŕáîļîñĝ ~~~]\"],\"node.stair.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stair.panel.rightTurn\":[\"[Ŕîĝĥţ ţûŕñ ~~~]\"],\"node.stair.panel.rise\":[\"[Ŕîšé ~~]\"],\"node.stair.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.stair.panel.segmentType\":[\"[\",[\"type\",\"select\",{\"landing\":[\"Ļáñðîñĝ\"],\"other\":[\"Šţáîŕ\"]}],\" ~~~~]\"],\"node.stair.panel.segments\":[\"[Šéĝɱéñţš ~~~]\"],\"node.stair.panel.sizing\":[\"[Šîžîñĝ ~~]\"],\"node.stair.panel.snapToNearbySurfaces\":[\"[Šñáþ ţö ñéáŕƀý šûŕƒáçéš ~~~~~~]\"],\"node.stair.panel.spiral\":[\"[Šþîŕáļ ~~]\"],\"node.stair.panel.staircase\":[\"[Šţáîŕçášé ~~~]\"],\"node.stair.panel.stepSupports\":[\"[Šţéþ Šûþþöŕţš ~~~~]\"],\"node.stair.panel.steps\":[\"[Šţéþš ~~]\"],\"node.stair.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stair.panel.style\":[\"[Šţýļé ~~]\"],\"node.stair.panel.surfaceConnection\":[\"[Šûŕƒáçé çöññéçţîöñ ~~~~~~]\"],\"node.stair.panel.sweep\":[\"[Šŵééþ ~~]\"],\"node.stair.panel.targetGoing\":[\"[Ţáŕĝéţ ĝöîñĝ ~~~~]\"],\"node.stair.panel.targetHeadroom\":[\"[Ţáŕĝéţ ĥéáðŕööɱ ~~~~~]\"],\"node.stair.panel.targetMaxRiser\":[\"[Ţáŕĝéţ ɱáẋ ŕîšéŕ ~~~~~]\"],\"node.stair.panel.targetMinimumGoing\":[\"[Ţáŕĝéţ ɱîñîɱûɱ ĝöîñĝ ~~~~~~]\"],\"node.stair.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.stair.panel.to\":[\"[Ţö ~]\"],\"node.stair.panel.topLanding\":[\"[Ţöþ Ļáñðîñĝ ~~~]\"],\"node.stair.panel.type\":[\"[Ţýþé ~~]\"],\"node.stair.panel.unavailable\":[\"[Ûñáṽáîļáƀļé ~~~~]\"],\"node.stair.panel.walkingLineOffset\":[\"[Ŵáļķîñĝ ļîñé öƒƒšéţ ~~~~~~]\"],\"node.stair.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.stair.panel.winder\":[\"[Ŵîñðéŕ ~~]\"],\"node.stair.railingControls.both\":[\"[Ɓöţĥ ~~]\"],\"node.stair.railingControls.bottomExtension\":[\"[Ɓöţţöɱ šļöþéð éẋţéñšîöñ ~~~~~~~]\"],\"node.stair.railingControls.continuous\":[\"[Çöñţîñûöûš ~~~]\"],\"node.stair.railingControls.floor\":[\"[Ƒļööŕ ~~]\"],\"node.stair.railingControls.handrailDiameter\":[\"[Ĥáñðŕáîļ ðîáɱéţéŕ ~~~~~]\"],\"node.stair.railingControls.handrailHeight\":[\"[Ĥáñðŕáîļ ĥéîĝĥţ ~~~~~]\"],\"node.stair.railingControls.handrailInset\":[\"[Ĥáñðŕáîļ îñšéţ ~~~~]\"],\"node.stair.railingControls.left\":[\"[Ļéƒţ ~~]\"],\"node.stair.railingControls.noReturn\":[\"[Ñö ŕéţûŕñ ~~~]\"],\"node.stair.railingControls.none\":[\"[Ñöñé ~~]\"],\"node.stair.railingControls.originalLayout\":[\"[Öŕîĝîñáļ ļáýöûţ ~~~~~]\"],\"node.stair.railingControls.postFacing\":[\"[Þöšţ-ƒáçîñĝ ~~~]\"],\"node.stair.railingControls.returnLength\":[\"[Ŕéţûŕñ ļéñĝţĥ ~~~~]\"],\"node.stair.railingControls.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stair.railingControls.separateHandrail\":[\"[Šéþáŕáţé ĥáñðŕáîļ ~~~~~]\"],\"node.stair.railingControls.setTheLengthToReachThe\":[\"[Šéţ ţĥé ļéñĝţĥ ţö ŕéáçĥ ţĥé ŵáļļ öŕ þöšţ. ~~~~~~~~~~]\"],\"node.stair.railingControls.topExtension\":[\"[Ţöþ ĥöŕîžöñţáļ éẋţéñšîöñ ~~~~~~~]\"],\"node.stair.railingControls.wallFacing\":[\"[Ŵáļļ-ƒáçîñĝ ~~~]\"],\"node.stair.structureControls.aSolidPouredFlightOnA\":[\"[Å šöļîð þöûŕéð ƒļîĝĥţ öñ á ƒööţîñĝ; ŕîšéŕš çļöšéð, ñö šţŕîñĝéŕš. ~~~~~~~~~~~~~~~~]\"],\"node.stair.structureControls.autoAutocount\":[\"[Åûţö (\",[\"autoCount\"],\") ~~]\"],\"node.stair.structureControls.autoClosed\":[\"[Åûţö (çļöšéð) ~~~]\"],\"node.stair.structureControls.autoOpen\":[\"[Åûţö (öþéñ) ~~~]\"],\"node.stair.structureControls.autoPressureTreated\":[\"[Åûţö (þŕéššûŕé-ţŕéáţéð) ~~~~~~]\"],\"node.stair.structureControls.autoTreads\":[\"[Åûţö (\",[\"treads\"],\") ~~]\"],\"node.stair.structureControls.autoUntreated\":[\"[Åûţö (ûñţŕéáţéð) ~~~~]\"],\"node.stair.structureControls.closed1x8Boards\":[\"[Çļöšéð (1ẋ8 ƀöáŕðš) ~~~~]\"],\"node.stair.structureControls.concrete\":[\"[Çöñçŕéţé ~~~]\"],\"node.stair.structureControls.countSteelChannels\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šţééļ çĥáññéļ\"],\"other\":[\"#\",\" šţééļ çĥáññéļš\"]}],\" ~~~~~~~~]\"],\"node.stair.structureControls.countSteelChannelsAtSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šţééļ çĥáññéļ áţ \",[\"spacing\"],\" ö.ç.\"],\"other\":[\"#\",\" šţééļ çĥáññéļš áţ \",[\"spacing\"],\" ö.ç.\"]}],\" ~~~~~~~~~~]\"],\"node.stair.structureControls.countStringers\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šţŕîñĝéŕ\"],\"other\":[\"#\",\" šţŕîñĝéŕš\"]}],\" ~~~~~~]\"],\"node.stair.structureControls.countStringersAtSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[\"#\",\" šţŕîñĝéŕ áţ \",[\"spacing\"],\" ö.ç.\"],\"other\":[\"#\",\" šţŕîñĝéŕš áţ \",[\"spacing\"],\" ö.ç.\"]}],\" ~~~~~~~~]\"],\"node.stair.structureControls.cut\":[\"[Çûţ ~]\"],\"node.stair.structureControls.flightLayout\":[\"[Ƒļîĝĥţ \",[\"number\"],\": \",[\"layout\"],\" ~~]\"],\"node.stair.structureControls.housedSides\":[\"[Ĥöûšéð šîðéš ~~~~]\"],\"node.stair.structureControls.inchesInOC\":[\"[\",[\"inches\"],\" îñ ö.ç. ~~]\"],\"node.stair.structureControls.layoutAuto\":[\"[\",[\"layout\"],\" (áûţö) ~~]\"],\"node.stair.structureControls.lumber\":[\"[Ļûɱƀéŕ ~~]\"],\"node.stair.structureControls.maxSpacing\":[\"[Ṁáẋ šþáçîñĝ ~~~]\"],\"node.stair.structureControls.nosing\":[\"[Ñöšîñĝ ~~]\"],\"node.stair.structureControls.oneStock\":[\"[Öñé \",[\"stock\"],\" ~]\"],\"node.stair.structureControls.open\":[\"[Öþéñ ~~]\"],\"node.stair.structureControls.originalBody\":[\"[Öŕîĝîñáļ ƀöðý ~~~~]\"],\"node.stair.structureControls.plywoodFinish\":[\"[Þļýŵööð + ƒîñîšĥ ~~~~]\"],\"node.stair.structureControls.pressureTreated\":[\"[Þŕéššûŕé-ţŕéáţéð ~~~~~]\"],\"node.stair.structureControls.risers\":[\"[Ŕîšéŕš ~~]\"],\"node.stair.structureControls.steelStringers\":[\"[Šţééļ šţŕîñĝéŕš ~~~~~]\"],\"node.stair.structureControls.stringerCount\":[\"[Šţŕîñĝéŕ çöûñţ ~~~~]\"],\"node.stair.structureControls.stringerType\":[\"[Šţŕîñĝéŕ ţýþé ~~~~]\"],\"node.stair.structureControls.stringers\":[\"[Šţŕîñĝéŕš ~~~]\"],\"node.stair.structureControls.structure\":[\"[Šţŕûçţûŕé ~~~]\"],\"node.stair.structureControls.treads\":[\"[Ţŕéáðš ~~]\"],\"node.stair.structureControls.twoStock\":[\"[Ţŵö \",[\"stock\"],\" ~]\"],\"node.stair.structureControls.untreated\":[\"[Ûñţŕéáţéð ~~~]\"],\"node.stair.structureControls.woodStringers\":[\"[Ŵööð šţŕîñĝéŕš ~~~~]\"],\"node.stairSegment.description\":[\"[Å šîñĝļé ƒļîĝĥţ öƒ á þáŕéñţ šţáîŕ. ~~~~~~~~~]\"],\"node.stairSegment.label\":[\"[Šţáîŕ Šéĝɱéñţ ~~~~]\"],\"node.stairSegment.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.stairSegment.panel.attachment\":[\"[Åţţáçĥɱéñţ ~~~]\"],\"node.stairSegment.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.stairSegment.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.stairSegment.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.stairSegment.panel.editingSwitchesTheStairToCustom\":[\"[Éðîţîñĝ šŵîţçĥéš ţĥé šţáîŕ ţö Çûšţöɱ ŕîšé ~~~~~~~~~~~]\"],\"node.stairSegment.panel.equalAngle\":[\"[Éǫûáļ áñĝļé ~~~]\"],\"node.stairSegment.panel.equalGoing\":[\"[Éǫûáļ ĝöîñĝ ~~~]\"],\"node.stairSegment.panel.fillToFloor\":[\"[Ƒîļļ ţö ƒļööŕ ~~~~]\"],\"node.stairSegment.panel.flight\":[\"[Ƒļîĝĥţ ~~]\"],\"node.stairSegment.panel.flightShape\":[\"[Ƒļîĝĥţ šĥáþé ~~~~]\"],\"node.stairSegment.panel.front\":[\"[Ƒŕöñţ ~~]\"],\"node.stairSegment.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.stairSegment.panel.innerGap\":[\"[Îññéŕ ĝáþ ~~~]\"],\"node.stairSegment.panel.landing\":[\"[Ļáñðîñĝ ~~~]\"],\"node.stairSegment.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.stairSegment.panel.leftTurn\":[\"[Ļéƒţ ţûŕñ ~~~]\"],\"node.stairSegment.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.stairSegment.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.stairSegment.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.stairSegment.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.stairSegment.panel.rightTurn\":[\"[Ŕîĝĥţ ţûŕñ ~~~]\"],\"node.stairSegment.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.stairSegment.panel.stairSegment\":[\"[Šţáîŕ Šéĝɱéñţ ~~~~]\"],\"node.stairSegment.panel.steps\":[\"[Šţéþš ~~]\"],\"node.stairSegment.panel.straight\":[\"[Šţŕáîĝĥţ ~~~]\"],\"node.stairSegment.panel.structure\":[\"[Šţŕûçţûŕé ~~~]\"],\"node.stairSegment.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.stairSegment.panel.type\":[\"[Ţýþé ~~]\"],\"node.stairSegment.panel.walkingLineOffset\":[\"[Ŵáļķîñĝ ļîñé öƒƒšéţ ~~~~~~]\"],\"node.stairSegment.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.stairSegment.panel.winder\":[\"[Ŵîñðéŕ ~~]\"],\"node.structuralGrid.description\":[\"[Þéŕšîšţéñţ çöñšţŕûçţîöñ ĝŕîð áẋîš ŵîţĥ îðéñţîƒîçáţîöñ ƀûƀƀļéš. ~~~~~~~~~~~~~~~~~]\"],\"node.structuralGrid.label\":[\"[Šţŕûçţûŕáļ Ĝŕîð ~~~~~]\"],\"node.turbineVent.description\":[\"[Ŵîñð-ðŕîṽéñ šþîññîñĝ ŵĥîŕļýƀîŕð éẋĥáûšţ ṽéñţ ƒöŕ á ŕööƒ šļöþé. ~~~~~~~~~~~~~~~~]\"],\"node.turbineVent.label\":[\"[Ţûŕƀîñé Ṽéñţ ~~~~]\"],\"node.turbineVent.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.turbineVent.panel.cylinder\":[\"[Çýļîñðéŕ ~~~]\"],\"node.turbineVent.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.turbineVent.panel.diameter\":[\"[Ðîáɱéţéŕ ~~~]\"],\"node.turbineVent.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.turbineVent.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.turbineVent.panel.globe\":[\"[Ĝļöƀé ~~]\"],\"node.turbineVent.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.turbineVent.panel.motion\":[\"[Ṁöţîöñ ~~]\"],\"node.turbineVent.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.turbineVent.panel.neckHeight\":[\"[Ñéçķ Ĥéîĝĥţ ~~~]\"],\"node.turbineVent.panel.pause\":[\"[Þáûšé ~~]\"],\"node.turbineVent.panel.play\":[\"[Þļáý ~~]\"],\"node.turbineVent.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.turbineVent.panel.rotation\":[\"[Ŕöţáţîöñ ~~~]\"],\"node.turbineVent.panel.spinSpeed\":[\"[Šþîñ Šþééð ~~~]\"],\"node.turbineVent.panel.style\":[\"[Šţýļé ~~]\"],\"node.turbineVent.panel.turbineVent\":[\"[Ţûŕƀîñé Ṽéñţ ~~~~]\"],\"node.turbineVent.panel.vanes\":[\"[Ṽáñéš ~~]\"],\"node.unit.description\":[\"[Å ñáɱéð ĝŕöûþ öƒ žöñéš ƒöŕɱîñĝ öñé áþáŕţɱéñţ, ĥöţéļ ŕööɱ öŕ çöɱɱéŕçîáļ ļöţ. ~~~~~~~~~~~~~~~~~~~]\"],\"node.unit.kind.apartment\":[\"[Åþáŕţɱéñţ ~~~]\"],\"node.unit.kind.commercial\":[\"[Çöɱɱéŕçîáļ ~~~]\"],\"node.unit.kind.common\":[\"[Çöɱɱöñ ~~]\"],\"node.unit.kind.hotelRoom\":[\"[Ĥöţéļ ŕööɱ ~~~]\"],\"node.unit.label\":[\"[Ûñîţ ~~]\"],\"node.unit.panel.memberRoom\":[\"[Ŕööɱ ~~]\"],\"node.unit.panel.memberZone\":[\"[Žöñé ~~]\"],\"node.unit.panel.noZonesAssigned\":[\"[Ñö žöñéš áššîĝñéð ýéţ. ~~~~~~]\"],\"node.unit.panel.nonAdjacentLevels\":[\"[Ṁéɱƀéŕš šîţ öñ ļéṽéļš ţĥáţ áŕé ñöţ áðĵáçéñţ. ~~~~~~~~~~~]\"],\"node.unit.panel.removeMember\":[\"[Ŕéɱöṽé \",[\"name\"],\" ƒŕöɱ ûñîţ ~~~~~]\"],\"node.unit.panel.removeZone\":[\"[Ŕéɱöṽé žöñé ƒŕöɱ ûñîţ ~~~~~~]\"],\"node.unit.unitPanel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.unit.unitPanel.color\":[\"[Çöļöŕ ~~]\"],\"node.unit.unitPanel.delete\":[\"[Ðéļéţé ~~]\"],\"node.unit.unitPanel.focus\":[\"[Ƒöçûš ~~]\"],\"node.unit.unitPanel.grossArea\":[\"[Ĝŕöšš áŕéá ~~~]\"],\"node.unit.unitPanel.kind\":[\"[Ķîñð ~~]\"],\"node.unit.unitPanel.levels\":[\"[Ļéṽéļš ~~]\"],\"node.unit.unitPanel.members\":[\"[Ṁéɱƀéŕš ~~~]\"],\"node.unit.unitPanel.name\":[\"[Ñáɱé ~~]\"],\"node.unit.unitPanel.noRoomsYetPickThisUnit\":[\"[Ñö ŕööɱš ýéţ. Þîçķ ţĥîš ûñîţ ƒŕöɱ á ŕööɱ'š þáñéļ, öŕ ðŕáŵ žöñéš ŵĥîļé îţ îš ƒöçûšéð. ~~~~~~~~~~~~~~~~~~~~]\"],\"node.unit.unitPanel.report\":[\"[Ŕéþöŕţ ~~]\"],\"node.unit.unitPanel.unit\":[\"[Ûñîţ ~~]\"],\"node.wall.actions.dimensionsToTheExteriorFacesOutside\":[\"[Ðîɱéñšîöñš ţö ţĥé éẋţéŕîöŕ ƒáçéš (öûţšîðé šîžé). Çļîçķ ƒöŕ ƀöţĥ ~~~~~~~~~~~~~~~~]\"],\"node.wall.actions.dimensionsToTheInteriorAndExterior\":[\"[Ðîɱéñšîöñš ţö ţĥé îñţéŕîöŕ áñð éẋţéŕîöŕ ƒáçéš. Çļîçķ ƒöŕ îñţéŕîöŕ öñļý ~~~~~~~~~~~~~~~~~~]\"],\"node.wall.actions.dimensionsToTheInteriorFacesRoom\":[\"[Ðîɱéñšîöñš ţö ţĥé îñţéŕîöŕ ƒáçéš (ŕööɱ šîžé). Çļîçķ ƒöŕ éẋţéŕîöŕ öñļý ~~~~~~~~~~~~~~~~~]\"],\"node.wall.actions.id\":[\"[ÎÐ ~]\"],\"node.wall.actions.idOd\":[\"[ÎÐ+ÖÐ ~~]\"],\"node.wall.actions.mergeWalls\":[\"[Ṁéŕĝé ŵáļļš ~~~]\"],\"node.wall.actions.od\":[\"[ÖÐ ~]\"],\"node.wall.actions.splitWall\":[\"[Šþļîţ ŵáļļ ~~~]\"],\"node.wall.actions.theseWallsCannotBeMerged\":[\"[Ţĥéšé ŵáļļš çáññöţ ƀé ɱéŕĝéð. ~~~~~~~~]\"],\"node.wall.curtain.cell\":[\"[Çöļûɱñ \",[\"column\"],\", ŕöŵ \",[\"row\"],\": \",[\"type\",\"select\",{\"glass\":[\"ĝļášš\"],\"solid\":[\"šöļîð\"],\"other\":[\"éɱþţý\"]}],\" ~~~~~~~~]\"],\"node.wall.curtain.columnSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" çöļûɱñ\"],\"other\":[[\"number\"],\" çöļûɱñš\"]}],\" · \",[\"spacing\"],\" ĝŕîð šþáçîñĝ ~~~~~~~~]\"],\"node.wall.curtain.noPanels\":[\"[Ñö þáñéļš ~~~]\"],\"node.wall.curtain.rowSpacing\":[\"[\",[\"count\",\"plural\",{\"one\":[[\"number\"],\" ŕöŵ\"],\"other\":[[\"number\"],\" ŕöŵš\"]}],\" · \",[\"spacing\"],\" ĝŕîð šþáçîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.applyTint\":[\"[Åþþļý ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.borderOpeningFrame\":[\"[Ɓöŕðéŕ / öþéñîñĝ ƒŕáɱé ~~~~~~]\"],\"node.wall.curtainWallPanel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.wall.curtainWallPanel.bottomRow\":[\"[Ɓöţţöɱ ŕöŵ ~~~]\"],\"node.wall.curtainWallPanel.cancelTint\":[\"[Çáñçéļ ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.center\":[\"[Çéñţéŕ ~~]\"],\"node.wall.curtainWallPanel.chooseACellOrEnterIts\":[\"[Çĥööšé á çéļļ öŕ éñţéŕ îţš çöļûɱñ áñð ŕöŵ. Ûšé áŕŕöŵ ķéýš ţö ɱöṽé ƀéţŵééñ çéļļš. Çöļûɱñš ŕûñ ƒŕöɱ ţĥé ŵáļļ šţáŕţ; ŕöŵš çöûñţ ûþŵáŕð. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.clearHiddenOverrides\":[\"[Çļéáŕ ĥîððéñ öṽéŕŕîðéš ~~~~~~]\"],\"node.wall.curtainWallPanel.column\":[\"[Çöļûɱñ ~~]\"],\"node.wall.curtainWallPanel.columnLayout\":[\"[Çöļûɱñ ļáýöûţ ~~~~]\"],\"node.wall.curtainWallPanel.columnRow\":[\"[Çöļûɱñ \",[\"column\"],\", ŕöŵ \",[\"row\"],\" ~~~]\"],\"node.wall.curtainWallPanel.construction\":[\"[Çöñšţŕûçţîöñ ~~~~]\"],\"node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong\":[\"[Çöñţîñûöûš ɱûļļîöñš áñð ţŕáñšöɱš áššéɱƀļéð áļöñĝ ţĥé ŵáļļ. ~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.curtainWallPanelSelection\":[\"[Çûŕţáîñ ŵáļļ þáñéļ šéļéçţîöñ ~~~~~~~~]\"],\"node.wall.curtainWallPanel.customInfill\":[\"[Çûšţöɱ îñƒîļļ ~~~~]\"],\"node.wall.curtainWallPanel.defaultPanel\":[\"[Ðéƒáûļţ þáñéļ ~~~~]\"],\"node.wall.curtainWallPanel.editIndividualPanels\":[\"[Éðîţ îñðîṽîðûáļ þáñéļš ~~~~~~]\"],\"node.wall.curtainWallPanel.effectiveThicknessLimited\":[\"[Ƀƒéçţîṽé ţĥîçķñéšš: \",[\"thickness\"],\", ļîɱîţéð ƀý ƒŕáɱé ðéþţĥ. ~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.empty\":[\"[Éɱþţý ~~]\"],\"node.wall.curtainWallPanel.enablePanelEditing\":[\"[Éñáƀļé þáñéļ éðîţîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.end\":[\"[Éñð ~]\"],\"node.wall.curtainWallPanel.fixedSpacing\":[\"[Ƒîẋéð šþáçîñĝ ~~~~]\"],\"node.wall.curtainWallPanel.followingWallDefaults\":[\"[Ƒöļļöŵîñĝ ŵáļļ ðéƒáûļţš ~~~~~~~]\"],\"node.wall.curtainWallPanel.frameDimensions\":[\"[Ƒŕáɱé ðîɱéñšîöñš ~~~~~]\"],\"node.wall.curtainWallPanel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.wall.curtainWallPanel.fullyCapped\":[\"[Ƒûļļý çáþþéð ~~~~]\"],\"node.wall.curtainWallPanel.glass\":[\"[Ĝļášš ~~]\"],\"node.wall.curtainWallPanel.glassAppearance\":[\"[Ĝļášš áþþéáŕáñçé ~~~~~]\"],\"node.wall.curtainWallPanel.glassAppearanceIsControlledByThe\":[\"[Ĝļášš áþþéáŕáñçé îš çöñţŕöļļéð ƀý ţĥé þáîñţ ţööļ. ~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.glassTint\":[\"[Ĝļášš ţîñţ ~~~]\"],\"node.wall.curtainWallPanel.glazingGap\":[\"[Ĝļážîñĝ ĝáþ ~~~]\"],\"node.wall.curtainWallPanel.gridLimitedTo32PanelsPer\":[\"[Ĝŕîð ļîɱîţéð ţö 32 þáñéļš þéŕ áẋîš. Šþáçîñĝ éẋþáñðš ţö ƒîţ ţĥîš ŵáļļ. ~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.hiddenCustomPanelCount\":[\"[\",[\"length\",\"plural\",{\"one\":[[\"number\"],\" çûšţöɱ þáñéļ îš öûţšîðé ţĥîš ĝŕîð. Îţ ŕéţûŕñš îƒ ţĥé ĝŕîð éẋþáñðš.\"],\"other\":[[\"number\"],\" çûšţöɱ þáñéļš áŕé öûţšîðé ţĥîš ĝŕîð. Ţĥéý ŕéţûŕñ îƒ ţĥé ĝŕîð éẋþáñðš.\"]}],\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.horizontalCaps\":[\"[Ĥöŕîžöñţáļ çáþš ~~~~~]\"],\"node.wall.curtainWallPanel.horizontalFrameWidth\":[\"[Ĥöŕîžöñţáļ ƒŕáɱé ŵîðţĥ ~~~~~~]\"],\"node.wall.curtainWallPanel.layout\":[\"[Ļáýöûţ ~~]\"],\"node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness\":[\"[Ļöŵ ŕöûĝĥñéšš îš šɱööţĥ; ĥîĝĥ ŕöûĝĥñéšš ĝîṽéš á ɱáţţé šûŕƒáçé îñ ŕéñðéŕéð šĥáðîñĝ. ~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.maximumSpacing\":[\"[Ṁáẋîɱûɱ šþáçîñĝ ~~~~~]\"],\"node.wall.curtainWallPanel.moduleJointWidth\":[\"[Ṁöðûļé ĵöîñţ ŵîðţĥ ~~~~~]\"],\"node.wall.curtainWallPanel.none\":[\"[Ñöñé ~~]\"],\"node.wall.curtainWallPanel.opacity\":[\"[Öþáçîţý ~~~]\"],\"node.wall.curtainWallPanel.opaqueBand\":[\"[Öþáǫûé ƀáñð ~~~]\"],\"node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow\":[\"[Öþáǫûé ƀáñðš ŕéþļáçé á ƒûļļ ŕöŵ ŵîţĥ šöļîð þáñéļš. Åþþļý ţĥéîŕ ƒîñîšĥ ŵîţĥ ţĥé þáîñţ ţööļ. ~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.panelCount\":[\"[Þáñéļ çöûñţ ~~~]\"],\"node.wall.curtainWallPanel.panelInfill\":[\"[Þáñéļ îñƒîļļ ~~~~]\"],\"node.wall.curtainWallPanel.panelThickness\":[\"[Þáñéļ ţĥîçķñéšš ~~~~~]\"],\"node.wall.curtainWallPanel.panels\":[\"[Þáñéļš ~~]\"],\"node.wall.curtainWallPanel.resetThisPanel\":[\"[Ŕéšéţ ţĥîš þáñéļ ~~~~~]\"],\"node.wall.curtainWallPanel.row2\":[\"[Ŕöŵ ~]\"],\"node.wall.curtainWallPanel.rowLayout\":[\"[Ŕöŵ ļáýöûţ ~~~]\"],\"node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween\":[\"[Šéþáŕáţé ƒŕáɱéð ɱöðûļéš ŵîţĥ ĵöîñţš ƀéţŵééñ ûñîţš. ~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.solid\":[\"[Šöļîð ~~]\"],\"node.wall.curtainWallPanel.spacing\":[\"[Šþáçîñĝ ~~~]\"],\"node.wall.curtainWallPanel.start\":[\"[Šţáŕţ ~~]\"],\"node.wall.curtainWallPanel.stickBuilt\":[\"[Šţîçķ-ƀûîļţ ~~~]\"],\"node.wall.curtainWallPanel.structuralGlazing\":[\"[Šţŕûçţûŕáļ ĝļážîñĝ ~~~~~~]\"],\"node.wall.curtainWallPanel.surfaceRoughness\":[\"[Šûŕƒáçé ŕöûĝĥñéšš ~~~~~]\"],\"node.wall.curtainWallPanel.top\":[\"[Ţöþ ~]\"],\"node.wall.curtainWallPanel.topRow\":[\"[Ţöþ ŕöŵ ~~]\"],\"node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing\":[\"[Ûñçáþþéð ɱéɱƀéŕš šîţ ƀéĥîñð ţĥé ĝļážîñĝ. ~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.unitized\":[\"[Ûñîţîžéð ~~~]\"],\"node.wall.curtainWallPanel.useGlassSettingsInstead\":[\"[Ûšé ĝļášš šéţţîñĝš îñšţéáð ~~~~~~~]\"],\"node.wall.curtainWallPanel.useTheDoorOrWindowTool\":[\"[Ûšé ţĥé Ðööŕ öŕ Ŵîñðöŵ ţööļ ţö áðð áñ öþéñîñĝ ţĥŕöûĝĥ ţĥé ŵáļļ. ~~~~~~~~~~~~~~~]\"],\"node.wall.curtainWallPanel.verticalCaps\":[\"[Ṽéŕţîçáļ çáþš ~~~~]\"],\"node.wall.curtainWallPanel.verticalFrameWidth\":[\"[Ṽéŕţîçáļ ƒŕáɱé ŵîðţĥ ~~~~~~]\"],\"node.wall.curtainWallPanel.wallSystem\":[\"[Ŵáļļ šýšţéɱ ~~~]\"],\"node.wall.description\":[\"[Å šţŕáîĝĥţ öŕ çûŕṽéð ŵáļļ šéĝɱéñţ. Ĥöšţš ðööŕš, ŵîñðöŵš, ļéáñ-ţö éẋţéñšîöñš, áñð ŵáļļ-ɱöûñţéð îţéɱš. ~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.label\":[\"[Ŵáļļ ~~]\"],\"node.wall.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.wall.panel.assembly\":[\"[Åššéɱƀļý ~~~]\"],\"node.wall.panel.auto\":[\"[Åûţö ~~]\"],\"node.wall.panel.both\":[\"[Ɓöţĥ ~~]\"],\"node.wall.panel.bottom\":[\"[Ɓöţţöɱ ~~]\"],\"node.wall.panel.brickVeneer\":[\"[Ɓŕîçķ ṽéñééŕ ~~~~]\"],\"node.wall.panel.chairRail\":[\"[Çĥáîŕ ŕáîļ ~~~]\"],\"node.wall.panel.cmu\":[\"[ÇṀÛ ~]\"],\"node.wall.panel.colonial\":[\"[Çöļöñîáļ ~~~]\"],\"node.wall.panel.cove\":[\"[Çöṽé ~~]\"],\"node.wall.panel.craft\":[\"[Çŕáƒţ ~~]\"],\"node.wall.panel.crownMolding\":[\"[Çŕöŵñ ɱöļðîñĝ ~~~~]\"],\"node.wall.panel.curtainWall\":[\"[Çûŕţáîñ ŵáļļ ~~~~]\"],\"node.wall.panel.curve\":[\"[Çûŕṽé ~~]\"],\"node.wall.panel.custom\":[\"[Çûšţöɱ ~~]\"],\"node.wall.panel.customHeight\":[\"[Çûšţöɱ ĥéîĝĥţ ~~~~]\"],\"node.wall.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.wall.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.wall.panel.drywall\":[\"[Ðŕýŵáļļ ~~~]\"],\"node.wall.panel.extendsDownwardToMeetTheTerrain\":[\"[Éẋţéñðš ðöŵñŵáŕð ţö ɱééţ ţĥé ţéŕŕáîñ. Ĥéîĝĥţ áñð ţöþ šţáý ûñçĥáñĝéð. ~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.exterior\":[\"[Éẋţéŕîöŕ ~~~]\"],\"node.wall.panel.fiberCement\":[\"[Ƒîƀéŕ çéɱéñţ ~~~~]\"],\"node.wall.panel.fillToTerrain\":[\"[Ƒîļļ ţö ţéŕŕáîñ ~~~~]\"],\"node.wall.panel.flat\":[\"[Ƒļáţ ~~]\"],\"node.wall.panel.followsLevel\":[\"[Ƒöļļöŵš ļéṽéļ ~~~~]\"],\"node.wall.panel.frameDepth\":[\"[Ƒŕáɱé ðéþţĥ ~~~]\"],\"node.wall.panel.framersDefault\":[\"[Ƒŕáɱéŕ'š ðéƒáûļţ ~~~~~]\"],\"node.wall.panel.framing\":[\"[Ƒŕáɱîñĝ ~~~]\"],\"node.wall.panel.gypsum\":[\"[Ĝýþšûɱ ~~]\"],\"node.wall.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.wall.panel.hideCode\":[\"[Ĥîðé çöðé ~~~]\"],\"node.wall.panel.icf\":[\"[ÎÇƑ ~]\"],\"node.wall.panel.interior\":[\"[Îñţéŕîöŕ ~~~]\"],\"node.wall.panel.lapSiding\":[\"[Ļáþ šîðîñĝ ~~~]\"],\"node.wall.panel.layerThicknessesFollowThe2021Irc\":[\"[Ļáýéŕ ţĥîçķñéššéš ƒöļļöŵ ţĥé 2021 ÎŔÇ áššéɱƀļý ðáţá. Ðŕáƒţîñĝ áîð, ñöţ éñĝîñééŕîñĝ — ṽéŕîƒý ŵîţĥ ţĥé áûţĥöŕîţý ĥáṽîñĝ ĵûŕîšðîçţîöñ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.layered\":[\"[Ļáýéŕéð ~~~]\"],\"node.wall.panel.length\":[\"[Ļéñĝţĥ ~~]\"],\"node.wall.panel.loading\":[\"[Ļöáðîñĝ… ~~~]\"],\"node.wall.panel.modern\":[\"[Ṁöðéŕñ ~~]\"],\"node.wall.panel.none\":[\"[Ñöñé ~~]\"],\"node.wall.panel.noneSingleLayer\":[\"[Ñöñé (šîñĝļé ļáýéŕ) ~~~~~]\"],\"node.wall.panel.offset\":[\"[Öƒƒšéţ ~~]\"],\"node.wall.panel.ogee\":[\"[Öĝéé ~~]\"],\"node.wall.panel.osb\":[\"[ÖŠƁ ~]\"],\"node.wall.panel.partitionTheInteriorFinishIsApplied\":[\"[Þáŕţîţîöñ: ţĥé îñţéŕîöŕ ƒîñîšĥ îš áþþļîéð ţö ƀöţĥ ƒáçéš. ~~~~~~~~~~~~~~]\"],\"node.wall.panel.picture\":[\"[Þîçţûŕé ~~~]\"],\"node.wall.panel.plaster\":[\"[Þļášţéŕ ~~~]\"],\"node.wall.panel.plywood\":[\"[Þļýŵööð ~~~]\"],\"node.wall.panel.preset\":[\"[Þŕéšéţ ~~]\"],\"node.wall.panel.railing\":[\"[Ŕáîļîñĝ ~~~]\"],\"node.wall.panel.reference\":[\"[Ŕéƒéŕéñçé ~~~]\"],\"node.wall.panel.remove\":[\"[Ŕéɱöṽé ~~]\"],\"node.wall.panel.round\":[\"[Ŕöûñð ~~]\"],\"node.wall.panel.scriptedFinish\":[\"[Šçŕîþţéð ƒîñîšĥ ~~~~~]\"],\"node.wall.panel.sheathing\":[\"[Šĥéáţĥîñĝ ~~~]\"],\"node.wall.panel.shoe\":[\"[Šĥöé ~~]\"],\"node.wall.panel.sideA\":[\"[Šîðé Å ~~]\"],\"node.wall.panel.sideB\":[\"[Šîðé Ɓ ~~]\"],\"node.wall.panel.sideFace\":[\"[Šîðé \",[\"face\"],\" ~~]\"],\"node.wall.panel.skirting\":[\"[Šķîŕţîñĝ ~~~]\"],\"node.wall.panel.standard\":[\"[Šţáñðáŕð ~~~]\"],\"node.wall.panel.steelStuds\":[\"[Šţééļ šţûðš ~~~]\"],\"node.wall.panel.step\":[\"[Šţéþ ~~]\"],\"node.wall.panel.stoneVeneer\":[\"[Šţöñé ṽéñééŕ ~~~~]\"],\"node.wall.panel.stucco\":[\"[Šţûççö ~~]\"],\"node.wall.panel.studs\":[\"[Šţûðš ~~]\"],\"node.wall.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.wall.panel.thicknessFromAssembly\":[\"[\",[\"thickness\"],\" · ƒŕöɱ áššéɱƀļý ~~~~]\"],\"node.wall.panel.thisProject\":[\"[Ţĥîš þŕöĵéçţ ~~~~]\"],\"node.wall.panel.top\":[\"[Ţöþ ~]\"],\"node.wall.panel.totalThickness\":[\"[Ţöţáļ ţĥîçķñéšš ~~~~~]\"],\"node.wall.panel.unverifiedPresetnote\":[\"[Ûñṽéŕîƒîéð: \",[\"presetNote\"],\" ~~~]\"],\"node.wall.panel.viewCode\":[\"[Ṽîéŵ çöðé ~~~]\"],\"node.wall.panel.wall\":[\"[Ŵáļļ ~~]\"],\"node.wall.panel.wallType\":[\"[Ŵáļļ ţýþé ~~~]\"],\"node.wall.panel.whichFaceIsOutsideIsUndetermined\":[\"[Ŵĥîçĥ ƒáçé îš öûţšîðé îš ûñðéţéŕɱîñéð (ñö ŕööɱ ðéţéçţéð öñ éîţĥéŕ šîðé) — ţĥé éẋţéŕîöŕ ļáýéŕš áŕé ðŕáŵñ öñ šîðé Ɓ. ~~~~~~~~~~~~~~~~~~~~~~~~~~~]\"],\"node.wall.panel.woodStuds\":[\"[Ŵööð šţûðš ~~~]\"],\"node.wall.railingWallPanel.aGuardSOpeningsMustNot\":[\"[Å ĝûáŕð'š öþéñîñĝš ɱûšţ ñöţ þášš á 4 îñ šþĥéŕé (ÎŔÇ Ŕ312.1.3). ~~~~~~~~~~~~]\"],\"node.wall.railingWallPanel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.wall.railingWallPanel.infill\":[\"[Îñƒîļļ ~~]\"],\"node.wall.railingWallPanel.n2x6Cap\":[\"[2ẋ6 çáþ ~~]\"],\"node.wall.railingWallPanel.n36In\":[\"[36 îñ ~]\"],\"node.wall.railingWallPanel.n42In\":[\"[42 îñ ~]\"],\"node.wall.railingWallPanel.posts\":[\"[Þöšţš ~~]\"],\"node.wall.railingWallPanel.railing\":[\"[Ŕáîļîñĝ ~~~]\"],\"node.wall.railingWallPanel.style\":[\"[Šţýļé ~~]\"],\"node.wall.reference.center\":[\"[Çéñţéŕ ~~]\"],\"node.wall.reference.inside\":[\"[Îñšîðé ƒáçé ~~~]\"],\"node.wall.reference.left\":[\"[Ļéƒţ ~~]\"],\"node.wall.reference.outside\":[\"[Öûţšîðé ƒáçé ~~~~]\"],\"node.wall.reference.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.wall.trim.hide\":[\"[\",[\"trim\",\"select\",{\"skirting\":[\"Ĥîðé šķîŕţîñĝ\"],\"crown\":[\"Ĥîðé çŕöŵñ ɱöļðîñĝ\"],\"other\":[\"Ĥîðé çĥáîŕ ŕáîļ\"]}],\" ~~~~~~~~~~~~~]\"],\"node.wall.trim.show\":[\"[\",[\"trim\",\"select\",{\"skirting\":[\"Šĥöŵ šķîŕţîñĝ\"],\"crown\":[\"Šĥöŵ çŕöŵñ ɱöļðîñĝ\"],\"other\":[\"Šĥöŵ çĥáîŕ ŕáîļ\"]}],\" ~~~~~~~~~~~~~]\"],\"node.window.description\":[\"[Å ŵîñðöŵ çûţ îñţö á ŵáļļ. Åñîɱáţéð öþéñ/çļöšé ƒöŕ öþéñîñĝ ŵîñðöŵš. ~~~~~~~~~~~~~~~~]\"],\"node.window.label\":[\"[Ŵîñðöŵ ~~]\"],\"node.window.operation.raise\":[\"[Ŕáîšé ~~]\"],\"node.window.operation.slats\":[\"[Šļáţš ~~]\"],\"node.window.operation.slide\":[\"[Šļîðé ~~]\"],\"node.window.operation.swing\":[\"[Šŵîñĝ ~~]\"],\"node.window.operation.tilt\":[\"[Ţîļţ ~~]\"],\"node.window.panel.actions\":[\"[Åçţîöñš ~~~]\"],\"node.window.panel.all\":[\"[Åļļ ~]\"],\"node.window.panel.arch\":[\"[Åŕçĥ ~~]\"],\"node.window.panel.archHeight\":[\"[Åŕçĥ Ĥéîĝĥţ ~~~]\"],\"node.window.panel.auto\":[\"[Åûţö ~~]\"],\"node.window.panel.autoFollowsTheWall\":[\"[Åûţö ƒöļļöŵš ţĥé ŵáļļ ~~~~~~]\"],\"node.window.panel.colWidths\":[\"[Çöļ Ŵîðţĥš ~~~]\"],\"node.window.panel.columns\":[\"[Çöļûɱñš ~~~]\"],\"node.window.panel.corner\":[\"[Çöŕñéŕ ~~]\"],\"node.window.panel.cornerPost\":[\"[Çöŕñéŕ þöšţ ~~~]\"],\"node.window.panel.cornerRadius\":[\"[Çöŕñéŕ Ŕáðîûš ~~~~]\"],\"node.window.panel.customThePanesMatchNoStyle\":[\"[Çûšţöɱ: ţĥé þáñéš ɱáţçĥ ñö šţýļé. ~~~~~~~~]\"],\"node.window.panel.delete\":[\"[Ðéļéţé ~~]\"],\"node.window.panel.depth\":[\"[Ðéþţĥ ~~]\"],\"node.window.panel.dimensions\":[\"[Ðîɱéñšîöñš ~~~]\"],\"node.window.panel.divider\":[\"[Ðîṽîðéŕ ~~~]\"],\"node.window.panel.down\":[\"[Ðöŵñ ~~]\"],\"node.window.panel.duplicate\":[\"[Ðûþļîçáţé ~~~]\"],\"node.window.panel.enableSill\":[\"[Éñáƀļé Šîļļ ~~~]\"],\"node.window.panel.flipSide\":[\"[Ƒļîþ Šîðé ~~~]\"],\"node.window.panel.frame\":[\"[Ƒŕáɱé ~~]\"],\"node.window.panel.frameDepthAuto\":[\"[Ðéþţĥ (áûţö) ~~~]\"],\"node.window.panel.french\":[\"[Ƒŕéñçĥ ~~]\"],\"node.window.panel.fusedGlass\":[\"[Ƒûšéð ĝļášš ~~~]\"],\"node.window.panel.grid\":[\"[Ĝŕîð ~~]\"],\"node.window.panel.height\":[\"[Ĥéîĝĥţ ~~]\"],\"node.window.panel.individual\":[\"[Îñðîṽîðûáļ ~~~]\"],\"node.window.panel.left\":[\"[Ļéƒţ ~~]\"],\"node.window.panel.move\":[\"[Ṁöṽé ~~]\"],\"node.window.panel.opening\":[\"[Öþéñîñĝ ~~~]\"],\"node.window.panel.openingShape\":[\"[Öþéñîñĝ Šĥáþé ~~~~]\"],\"node.window.panel.otherSide\":[\"[Öţĥéŕ šîðé ~~~]\"],\"node.window.panel.pos\":[\"[þöš ~]\"],\"node.window.panel.position\":[\"[Þöšîţîöñ ~~~]\"],\"node.window.panel.rect\":[\"[Ŕéçţ ~~]\"],\"node.window.panel.revealRadius\":[\"[Ŕéṽéáļ Ŕáðîûš ~~~~]\"],\"node.window.panel.right\":[\"[Ŕîĝĥţ ~~]\"],\"node.window.panel.rounded\":[\"[Ŕöûñðéð ~~~]\"],\"node.window.panel.rowHeights\":[\"[Ŕöŵ Ĥéîĝĥţš ~~~]\"],\"node.window.panel.rows\":[\"[Ŕöŵš ~~]\"],\"node.window.panel.sill\":[\"[Šîļļ ~~]\"],\"node.window.panel.single\":[\"[Šîñĝļé ~~]\"],\"node.window.panel.sizeIsLimitedToTheWall\":[\"[Šîžé îš ļîɱîţéð ţö ţĥé ŵáļļ, îñçļûðîñĝ çļéáŕáñçé ƒöŕ ţĥé öþéñîñĝ ƒŕáɱé. ~~~~~~~~~~~~~~~~~~]\"],\"node.window.panel.style\":[\"[Šţýļé ~~]\"],\"node.window.panel.thickness\":[\"[Ţĥîçķñéšš ~~~]\"],\"node.window.panel.thisSide\":[\"[Ţĥîš šîðé ~~~]\"],\"node.window.panel.topShape\":[\"[Ţöþ Šĥáþé ~~~]\"],\"node.window.panel.type\":[\"[Ţýþé ~~]\"],\"node.window.panel.unwrap\":[\"[Ûñŵŕáþ ~~]\"],\"node.window.panel.up\":[\"[Ûþ ~]\"],\"node.window.panel.width\":[\"[Ŵîðţĥ ~~]\"],\"node.window.panel.window\":[\"[Ŵîñðöŵ ~~]\"],\"node.window.panel.windowType\":[\"[Ŵîñðöŵ Ţýþé ~~~]\"],\"node.window.panel.wrapTheCorner\":[\"[Ŵŕáþ ţĥé çöŕñéŕ ~~~~]\"],\"node.window.panel.wrapsTheCornerWithNamed\":[\"[Ŵŕáþš ţĥé çöŕñéŕ ŵîţĥ \",[\"partner\"],\" ~~~~~~]\"],\"node.window.panel.wrapsTheCornerWithPartner\":[\"[Ŵŕáþš ţĥé çöŕñéŕ ŵîţĥ îţš þáŕţñéŕ ~~~~~~~~~]\"],\"node.window.style.casement\":[\"[Çášéɱéñţ ~~~]\"],\"node.window.style.doubleHung\":[\"[Ðöûƀļé-ĥûñĝ ~~~]\"],\"node.window.style.grid\":[\"[Ĝŕîð ~~]\"],\"node.window.style.horizontalBands\":[\"[Ĥöŕîžöñţáļ ƀáñðš ~~~~~]\"],\"node.window.style.picture\":[\"[Þîçţûŕé ~~~]\"],\"node.window.style.single\":[\"[Šîñĝļé ~~]\"],\"node.window.style.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.window.style.tallGrid\":[\"[Ţáļļ ĝŕîð ~~~]\"],\"node.window.style.transom\":[\"[Ţŕáñšöɱ ~~~]\"],\"node.window.style.tripleHung\":[\"[Ţŕîþļé-ĥûñĝ ~~~]\"],\"node.window.style.wideGrid\":[\"[Ŵîðé ĝŕîð ~~~]\"],\"node.window.type.awning\":[\"[Åŵñîñĝ ~~]\"],\"node.window.type.bay\":[\"[Ɓáý ~]\"],\"node.window.type.bow\":[\"[Ɓöŵ ~]\"],\"node.window.type.casement\":[\"[Çášéɱéñţ ~~~]\"],\"node.window.type.doubleHung\":[\"[Ðöûƀļé Ĥûñĝ ~~~]\"],\"node.window.type.fixed\":[\"[Ƒîẋéð ~~]\"],\"node.window.type.hopper\":[\"[Ĥöþþéŕ ~~]\"],\"node.window.type.louvered\":[\"[Ļöûṽéŕéð ~~~]\"],\"node.window.type.singleHung\":[\"[Šîñĝļé Ĥûñĝ ~~~]\"],\"node.window.type.sliding\":[\"[Šļîðîñĝ ~~~]\"],\"node.zone.description\":[\"[Å þöļýĝöñáļ šîţé žöñé (ļáŵñ, ŵáţéŕ, þáṽîñĝ) ŵîţĥ á ŢŠĻ ĝŕáðîéñţ ɱáţéŕîáļ. ~~~~~~~~~~~~~~~~~~]\"],\"node.zone.label\":[\"[Žöñé ~~]\"],\"node.zone.quantitiesPanel.enclosedRoom\":[\"[Éñçļöšéð ŕööɱ ~~~~]\"],\"node.zone.quantitiesPanel.floorSurface\":[\"[Ƒļööŕ šûŕƒáçé ~~~~]\"],\"node.zone.quantitiesPanel.footprintOnly\":[\"[Ƒööţþŕîñţ öñļý ~~~~]\"],\"node.zone.quantitiesPanel.none\":[\"[Ñöñé ~~]\"],\"node.zone.quantitiesPanel.notProven\":[\"[Ñöţ þŕöṽéñ ~~~]\"],\"node.zone.quantitiesPanel.roomQuantities\":[\"[Ŕööɱ ǫûáñţîţîéš ~~~~~]\"],\"node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions\":[\"[Ţöþ ṽîéŵ ŵîţĥ žöñé éðĝé ðîɱéñšîöñš ~~~~~~~~~]\"],\"node.zone.quantitiesPanel.unit\":[\"[Ûñîţ ~~]\"],\"node.zone.quantitiesPanel.volume\":[\"[Ṽöļûɱé ~~]\"],\"node.zone.quantitiesPanel.wallSurface\":[\"[Ŵáļļ šûŕƒáçé ~~~~]\"],\"node.zone.quantitiesPanel.zoneBoundaryUnavailable\":[\"[Žöñé ƀöûñðáŕý ûñáṽáîļáƀļé ~~~~~~~]\"],\"node.zone.quantitiesPanel.zoneQuantities\":[\"[Žöñé ǫûáñţîţîéš ~~~~~]\"],\"viewer.camera.orbitLeft\":[\"[Öŕƀîţ ļéƒţ ~~~]\"],\"viewer.camera.orbitRight\":[\"[Öŕƀîţ ŕîĝĥţ ~~~]\"],\"viewer.camera.topView\":[\"[Ţöþ ṽîéŵ ~~~]\"],\"viewer.common.off\":[\"[Öƒƒ ~]\"],\"viewer.common.on\":[\"[Öñ ~]\"],\"viewer.display.camera\":[\"[Çáɱéŕá ~~]\"],\"viewer.display.colored\":[\"[Çöļöŕéð ~~~]\"],\"viewer.display.colors\":[\"[Çöļöŕš ~~]\"],\"viewer.display.edges.off\":[\"[Öƒƒ ~]\"],\"viewer.display.edges.offDetail\":[\"[Ñö éðĝé ļîñéš ~~~~]\"],\"viewer.display.edges.soft\":[\"[Šöƒţ ~~]\"],\"viewer.display.edges.softDetail\":[\"[Ƒáîñţ öûţļîñé öƒ ɱáĵöŕ çŕéášéš ~~~~~~~~]\"],\"viewer.display.edges.strong\":[\"[Šţŕöñĝ ~~]\"],\"viewer.display.edges.strongDetail\":[\"[Çŕîšþ, öþáǫûé éðĝé ļîñéš ~~~~~~]\"],\"viewer.display.edges.title\":[\"[Éðĝéš ~~]\"],\"viewer.display.monochrome\":[\"[Ṁöñöçĥŕöɱé ~~~]\"],\"viewer.display.orthographic\":[\"[Öŕţĥöĝŕáþĥîç ~~~~]\"],\"viewer.display.perspective\":[\"[Þéŕšþéçţîṽé ~~~~]\"],\"viewer.display.render.rendered\":[\"[Ŕéñðéŕéð ~~~]\"],\"viewer.display.render.renderedDetail\":[\"[Ƒûļļ áɱƀîéñţ öççļûšîöñ ~~~~~~]\"],\"viewer.display.render.solid\":[\"[Šöļîð ~~]\"],\"viewer.display.render.solidDetail\":[\"[Ƒļáţ áñð ƒášţ — ñö áɱƀîéñţ öççļûšîöñ ~~~~~~~~~]\"],\"viewer.display.render.title\":[\"[Ŕéñðéŕ ~~]\"],\"viewer.display.shadows\":[\"[Šĥáðöŵš ~~~]\"],\"viewer.display.title\":[\"[Ðîšþļáý šéţţîñĝš ~~~~~]\"],\"viewer.floorplan.alignNorth\":[\"[Åļîĝñ ṽîéŵ ţö ñöŕţĥ ~~~~~]\"],\"viewer.floorplan.empty\":[\"[Ñö ƒļööŕ þļáñš áŕé áṽáîļáƀļé ƒöŕ ţĥîš šçéñé. ~~~~~~~~~~~]\"],\"viewer.floorplan.fit\":[\"[Ƒîţ ƒļööŕ þļáñ ~~~~]\"],\"viewer.floorplan.floor\":[\"[Ƒļööŕ ~~]\"],\"viewer.floorplan.floorPlan\":[\"[Ƒļööŕ þļáñ ~~~]\"],\"viewer.floorplan.viewLabel\":[\"[\",[\"floor\"],\" 2Ð ṽîéŵ ~~]\"],\"viewer.floorplan.zoomIn\":[\"[Žööɱ îñ ~~]\"],\"viewer.floorplan.zoomOut\":[\"[Žööɱ öûţ ~~~]\"],\"viewer.header.back\":[\"[Ɓáçķ ~~]\"],\"viewer.header.levels\":[\"[Ļéṽéļš ~~]\"],\"viewer.header.rooms\":[\"[Ŕööɱš ~~]\"],\"viewer.header.selection\":[\"[Šéļéçţîöñ ~~~]\"],\"viewer.header.units\":[\"[Ûñîţš ~~]\"],\"viewer.header.untitled\":[\"[Ûñţîţļéð ~~~]\"],\"viewer.header.zones\":[\"[Žöñéš ~~]\"],\"viewer.levels.exploded\":[\"[Éẋþļöðéð ~~~]\"],\"viewer.levels.label\":[\"[Ļéṽéļš: \",[\"mode\"],\" ~~]\"],\"viewer.levels.manual\":[\"[Ṁáñûáļ ~~]\"],\"viewer.levels.solo\":[\"[Šöļö ~~]\"],\"viewer.levels.stacked\":[\"[Šţáçķéð ~~~]\"],\"viewer.stage.layout\":[\"[Ṽîéŵéŕ ļáýöûţ ~~~~]\"],\"viewer.stage.split\":[\"[Šþļîţ ~~]\"],\"viewer.theme.blueprint\":[\"[Ɓļûéþŕîñţ ~~~]\"],\"viewer.theme.mediterranean\":[\"[Ṁéðîţéŕŕáñéáñ ~~~~]\"],\"viewer.theme.night\":[\"[Ñîĝĥţ ~~]\"],\"viewer.theme.overcast\":[\"[Öṽéŕçášţ ~~~]\"],\"viewer.theme.paper\":[\"[Þáþéŕ ~~]\"],\"viewer.theme.studio\":[\"[Šţûðîö ~~]\"],\"viewer.theme.sunset\":[\"[Šûñšéţ ~~]\"],\"viewer.theme.title\":[\"[Šçéñé ţĥéɱé ~~~]\"],\"viewer.theme.twilight\":[\"[Ţŵîļîĝĥţ ~~~]\"],\"viewer.theme.verdant\":[\"[Ṽéŕðáñţ ~~~]\"],\"viewer.visibility.guides\":[\"[Ĝûîðéš ~~]\"],\"viewer.visibility.scans\":[\"[Šçáñš ~~]\"],\"viewer.visibility.title\":[\"[Ṽîšîƀîļîţý ~~~]\"],\"viewer.walkthrough.label\":[\"[Ŵáļķţĥŕöûĝĥ: \",[\"state\"],\" ~~~~]\"],\"viewer.walls.cutaway\":[\"[Çûţáŵáý ~~~]\"],\"viewer.walls.fullHeight\":[\"[Ƒûļļ ĥéîĝĥţ ~~~]\"],\"viewer.walls.label\":[\"[Ŵáļļš: \",[\"mode\"],\" ~~]\"],\"viewer.walls.low\":[\"[Ļöŵ ~]\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/i18n/locales/en/messages.po b/packages/editor/src/i18n/locales/en/messages.po index cf0d5474f6..0e34dcb3b1 100644 --- a/packages/editor/src/i18n/locales/en/messages.po +++ b/packages/editor/src/i18n/locales/en/messages.po @@ -259,6 +259,10 @@ msgstr "Liquid Line" msgid "app.lib.buildPalette.mep" msgstr "MEP" +#: apps/editor/lib/build-palette.ts +msgid "app.lib.buildPalette.post" +msgstr "Post" + #: apps/editor/lib/build-palette.ts msgid "app.lib.buildPalette.register" msgstr "Register" @@ -535,6 +539,10 @@ msgstr "Automatic dimensions" msgid "app.viewerToolbar.camera" msgstr "Camera" +#: apps/editor/components/viewer-toolbar.tsx +msgid "app.viewerToolbar.ceilings" +msgstr "Ceilings" + #: apps/editor/components/viewer-toolbar.tsx msgid "app.viewerToolbar.cleanPlanDimensionsAppearWithSelection" msgstr "Clean plan; dimensions appear with selection" @@ -939,6 +947,10 @@ msgstr "Point and ask (C)" msgid "editor.actionMenu.pointAskButton.pointAndAskWorksInThe" msgstr "Point and ask works in the 3D view" +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +msgid "editor.actionMenu.structureTools.beam" +msgstr "Beam" + #: packages/editor/src/components/ui/action-menu/structure-tools.tsx msgid "editor.actionMenu.structureTools.ceiling" msgstr "Ceiling" @@ -971,6 +983,14 @@ msgstr "Elevator" msgid "editor.actionMenu.structureTools.fence" msgstr "Fence" +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +msgid "editor.actionMenu.structureTools.footing" +msgstr "Footing" + +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +msgid "editor.actionMenu.structureTools.foundation" +msgstr "Foundation" + #: packages/editor/src/components/ui/action-menu/structure-tools.tsx msgid "editor.actionMenu.structureTools.gableRoof" msgstr "Gable Roof" @@ -1027,6 +1047,10 @@ msgstr "Window" msgid "editor.actionMenu.structureTools.zone" msgstr "Zone" +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels" +msgstr "Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it." + #: packages/editor/src/components/ui/action-menu/view-toggles.tsx msgid "editor.actionMenu.viewToggles.deleteGuideImage" msgstr "Delete guide image" @@ -1079,6 +1103,14 @@ msgstr "Hide scans" msgid "editor.actionMenu.viewToggles.levelsBelow" msgstr "{count} below" +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.noLevelAbove" +msgstr "No level above" + +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.noLevelBelow" +msgstr "No level below" + #: packages/editor/src/components/ui/action-menu/view-toggles.tsx msgid "editor.actionMenu.viewToggles.noLowerFloorAvailable" msgstr "No lower floor available." @@ -1159,10 +1191,34 @@ msgstr "Show reference floor" msgid "editor.actionMenu.viewToggles.showScans" msgstr "Show scans" +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.showWallsBelowAndAbove" +msgstr "Show walls below and above" + #: packages/editor/src/components/ui/action-menu/view-toggles.tsx msgid "editor.actionMenu.viewToggles.uploadScanOrGuideImage" msgstr "Upload scan or guide image" +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.wallsAbove" +msgstr "Walls above" + +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.wallsBelow" +msgstr "Walls below" + +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.wallsBelowAndAbove" +msgstr "Walls below and above" + +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveSettings" +msgstr "Walls below and above settings" + +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveShown" +msgstr "Walls below and above: shown" + #: packages/editor/src/components/editor/index.tsx msgid "editor.backToHome" msgstr "Back to home" @@ -1171,6 +1227,18 @@ msgstr "Back to home" msgid "editor.buildPanel.advanced" msgstr "Advanced" +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.exteriorWall.caption" +msgstr "{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.exteriorWall.label" +msgstr "Exterior wall" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.exteriorWall.title" +msgstr "Exterior walls: every wall drawn takes the exterior type and cladding" + #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.hint" msgstr "walls · floor · ceiling" @@ -1179,6 +1247,30 @@ msgstr "walls · floor · ceiling" msgid "editor.buildPanel.rooms.idle" msgstr "Pick a shape to start. Every room brings its own walls, floor and ceiling." +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.interiorWall.caption" +msgstr "{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.interiorWall.label" +msgstr "Interior wall" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.interiorWall.title" +msgstr "Interior walls: every wall drawn takes the interior type and paint" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.invisibleWall.caption" +msgstr "{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.invisibleWall.label" +msgstr "Invisible wall" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.invisibleWall.title" +msgstr "Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall." + #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.caption" msgstr "{lead} then the first one again to close. For L-shapes and angled rooms." @@ -1195,6 +1287,18 @@ msgstr "Click each corner," msgid "editor.buildPanel.rooms.polygon.title" msgstr "Polygon room" +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.railing.caption" +msgstr "{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.railing.label" +msgstr "Railing" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.rooms.railing.title" +msgstr "Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel." + #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.caption" msgstr "{lead} on the grid. Walls, floor and ceiling are created together." @@ -1217,7 +1321,7 @@ msgstr "Rooms" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.caption" -msgstr "{lead} Close a loop and it becomes a room; leave it open for partitions." +msgstr "{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.label" @@ -1225,12 +1329,272 @@ msgstr "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.lead" -msgstr "Draw one wall at a time." +msgstr "Click each corner;" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.title" msgstr "Walls" +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.default" +msgstr "Default" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.deleteFromThisProject" +msgstr "Delete from this project" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.duplicateAsANewType" +msgstr "Duplicate as a new type…" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.editLayers" +msgstr "Edit layers…" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateLabel" +msgstr "Edit or duplicate {label}" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType" +msgstr "Edit or duplicate this wall type" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.newWallType" +msgstr "New wall type…" + +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx +msgid "editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne" +msgstr "Presets stay as cited. Duplicate one to change its layers." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.add" +msgstr "Add" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.airSpace" +msgstr "Air space" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost" +msgstr "A wall type holds at most {limit, plural, one {# layer} other {# layers}}." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.brickVeneer" +msgstr "Brick veneer" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.cancel" +msgstr "Cancel" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.drywall" +msgstr "Drywall" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.duplicateWallType" +msgstr "Duplicate wall type" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.editWallType" +msgstr "Edit wall type" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming" +msgstr "Every wall type has one framing core" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.fiberCement" +msgstr "Fiber cement" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.finish" +msgstr "Finish" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.framingCore" +msgstr "Framing core" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother" +msgstr "{fullReason} Remove one to add another." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.gypsumSheathing" +msgstr "Gypsum sheathing" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.inside" +msgstr "Inside" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.insulation" +msgstr "Insulation" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.lapSiding" +msgstr "Lap siding" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.layer" +msgstr "Layer" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.layerRole" +msgstr "Layer role" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.lining" +msgstr "Lining" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA" +msgstr "Lists under Exterior: it has a cladding side." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch" +msgstr "Lists under Interior: both faces match." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside" +msgstr "List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type." + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.material" +msgstr "Material" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard" +msgstr "Mineral wool board" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.moveIn" +msgstr "Move in" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.moveOut" +msgstr "Move out" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.name" +msgstr "Name" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.newWallType" +msgstr "New wall type" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.osb" +msgstr "OSB" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.outside" +msgstr "Outside" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.plaster" +msgstr "Plaster" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.plywood" +msgstr "Plywood" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.removeLayer" +msgstr "Remove layer" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.rigidFoam" +msgstr "Rigid foam" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.saveWallType" +msgstr "Save wall type" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.sheathing" +msgstr "Sheathing" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.stoneVeneer" +msgstr "Stone veneer" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.stucco" +msgstr "Stucco" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.thickness" +msgstr "Thickness" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.totalThickness" +msgstr "Total {thickness}" + +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx +msgid "editor.buildPanel.wallTypeEditorDialog.wallType" +msgstr "Wall type" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.brick" +msgstr "Brick" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.cladding" +msgstr "Cladding" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.drawingExterior" +msgstr "Drawing: Exterior walls." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.drawingInterior" +msgstr "Drawing: Interior walls." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.drywallPaint" +msgstr "Drywall paint" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.exterior" +msgstr "Exterior" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.fiberCement" +msgstr "Fiber cement" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.forcedExterior" +msgstr "{drawing} Every wall takes the exterior type until you pick a shape or leave the tool." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.forcedInterior" +msgstr "{drawing} Every wall takes the interior type until you pick a shape or leave the tool." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.hint" +msgstr "New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint." + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.interior" +msgstr "Interior" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.plainWall" +msgstr "Plain wall" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.siding" +msgstr "Siding" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.stone" +msgstr "Stone" + +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +msgid "editor.buildPanel.wallTypes.stucco" +msgstr "Stucco" + #: packages/editor/src/components/editor/index.tsx msgid "editor.cameraControlsHint" msgstr "Camera controls hint" @@ -1263,6 +1627,10 @@ msgstr "Dismiss" msgid "editor.dismissCameraControlsHint" msgstr "Dismiss camera controls hint" +#: packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx +msgid "editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS" +msgstr "Click to edit the foundation’s outline" + #: packages/editor/src/components/editor/index.tsx msgid "editor.expandSidebar" msgstr "Expand sidebar" @@ -1343,13 +1711,57 @@ msgstr "Exit unit focus" msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" msgstr "Focused unit has zones here" +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundation" +msgstr "Foundation" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeBasement" +msgstr "Basement" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeBasementTitle" +msgstr "Basement walls cut into the ground" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeOff" +msgstr "Off" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeOffTitle" +msgstr "No foundation drawn: click to set one up" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgePiles" +msgstr "Piles" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgePilesTitle" +msgstr "Grade beam on piles" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeRaised" +msgstr "Raised" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeRaisedTitle" +msgstr "Raised floor on a stem wall" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeSlab" +msgstr "Slab" + +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.foundationBadgeSlabTitle" +msgstr "Slab on grade" + #: packages/editor/src/components/ui/floating-level-selector.tsx msgid "editor.floatingLevelSelector.groundFloorOverGrade" -msgstr "The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade" +msgstr "The ground floor stands {distance} over grade. {action}" #: packages/editor/src/components/ui/floating-level-selector.tsx msgid "editor.floatingLevelSelector.groundFloorUnderGrade" -msgstr "The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade" +msgstr "The ground floor stands {distance} under grade. {action}" #: packages/editor/src/components/ui/floating-level-selector.tsx msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" @@ -1383,6 +1795,10 @@ msgstr "Seat the building so this floor stands at grade; the floors under it are msgid "editor.floatingLevelSelector.setAsGroundFloor" msgstr "Set as ground floor" +#: packages/editor/src/components/ui/floating-level-selector.tsx +msgid "editor.floatingLevelSelector.showTheFoundation" +msgstr "Show the foundation" + #: packages/editor/src/components/ui/floating-level-selector.tsx msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" msgstr "The ground level cannot be deleted" @@ -3685,6 +4101,166 @@ msgstr "Structure with the current material and finish assignments." msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" msgstr "Walls, slabs, roofs, stairs, windows, and doors without finishes." +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx +msgid "editor.levelWallCopyPrompt.copy" +msgstr "Copy" + +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx +msgid "editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel" +msgstr "Copy exterior walls from the level {direction}?" + +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx +msgid "editor.levelWallCopyPrompt.noThanks" +msgstr "No thanks" + +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx +msgid "editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace" +msgstr "{sourceName}’s outside walls, in place, so this level lines up with it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.basementExcavation" +msgstr "Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.basementTopOverTheGround" +msgstr "The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.basementTopUnderTheGround" +msgstr "The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.crawlSpaceDugOut" +msgstr "Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1)." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.crippleWalls" +msgstr "Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.daylightWalls" +msgstr "The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.deepFill" +msgstr "That is {fill} of fill. A raised floor on a stem wall suits a floor this high." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.deepFillOnASlope" +msgstr "That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.floorOverTheGround" +msgstr "The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.floorUnderTheGround" +msgstr "The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.footingSteps" +msgstr "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.footingStepsUpTo" +msgstr "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.footingTooNarrow" +msgstr "The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1))." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.footingTooShallow" +msgstr "The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingCut" +msgstr "The ground is held {least} under the slab’s top on every side: cut {cut}." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingCutOnASlope" +msgstr "The ground is held {least} under the slab’s top on every side: cut {cut} on the high side." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingFilled" +msgstr "The ground is held {least} under the slab’s top on every side: filled {fill}." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingFilledAndCut" +msgstr "The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingFilledAndCutOnASlope" +msgstr "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.gradingFilledOnASlope" +msgstr "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.groundCutRoundTheStem" +msgstr "The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.raiseItOnAStemWall" +msgstr "Raise it on a stem wall" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.raiseTheFloor" +msgstr "Raise the floor" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatBasementWallTop" +msgstr "Seat the basement wall’s top {asked} over the high side of the ground" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatFloorTop" +msgstr "Seat the floor’s top {asked} over the high side of the ground" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatGroundFloor" +msgstr "Seat the ground floor {asked} over grade" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatItAt" +msgstr "Seat it at {asked}" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatRaisedFloorTop" +msgstr "Seat the raised floor’s top {asked} over the high side of the ground" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.seatSlabTop" +msgstr "Seat the slab’s top {asked} over the high side of the ground" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.setIt" +msgstr "Set it {height}" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.setItDeep" +msgstr "Set it {depth} deep" + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.sillTooNearTheGround" +msgstr "The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1)." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.slabEdgeSteps" +msgstr "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.slabEdgeStepsUpTo" +msgstr "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.slabEdgeTooShallow" +msgstr "The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." + +#: packages/editor/src/lib/building-foundation.ts +msgid "editor.lib.buildingFoundation.widenItTo" +msgstr "Widen it to {width}" + #: packages/editor/src/lib/contextual-help.ts msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" msgstr "Add or remove objects from the selection" @@ -3889,6 +4465,10 @@ msgstr "Split {kind}" msgid "editor.lib.hudTitle.terrace" msgstr "Terrace" +#: packages/editor/src/lib/tool-shortcuts.ts +msgid "editor.lib.toolShortcuts.wallsTooltip" +msgstr "{title} ({exterior} exterior, {interior} interior)" + #: packages/editor/src/lib/wall-draw-variant.ts msgid "editor.lib.wallDrawVariant.polygonRoom" msgstr "Polygon room" @@ -4021,46 +4601,574 @@ msgstr "Window" msgid "editor.nodeDisplay.zone" msgstr "Zone" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.cancel" -msgstr "Cancel" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe" +msgstr "At the high side of the ground, to scale" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.collectionName" -msgstr "Collection name…" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe2" +msgstr "At the high side of the ground, to scale; the dashed line is the grade" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.collections" -msgstr "Collections" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.auto" +msgstr "Auto" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.delete" -msgstr "Delete" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementSlab" +msgstr "Basement slab" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.deleteName" -msgstr "Delete \"{name}\"?" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWall" +msgstr "Basement wall" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.new" -msgstr "New" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWallHint" +msgstr "Poured concrete: 8 in for most basements (IRC Table R404.1.2(1))." -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" -msgstr "No collections yet. Create one to group elements together." +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWallItsOwn" +msgstr "Basement wall (its own)" -#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx -msgid "editor.panels.collections.collectionsPopover.rename" -msgstr "Rename" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWallThickness" +msgstr "Basement wall {thickness}" -#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx -msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGrade" +msgstr "Basement wall top over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGradeHint" +msgstr "Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.carpet" +msgstr "Carpet" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.ceiling" +msgstr "Ceiling" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.ceramicTile" +msgstr "Ceramic tile" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA" +msgstr "Close the outside walls into a loop to draw the foundation under them." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoists" +msgstr "Crawl space under joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint" +msgstr "Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.custom" +msgstr "Custom" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor" +msgstr "Delete the basement and it stands on its raised floor again." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.deleteTheBasementSlab" +msgstr "Delete the basement and it stands on its slab again." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.depthDeep" +msgstr "{depth} deep" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.depthDown" +msgstr "{depth} down" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.depthIJoist" +msgstr "{depth} I-joist" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.drawTheFoundation" +msgstr "Draw the foundation" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.drywall" +msgstr "Drywall" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.drywallCeiling" +msgstr "Drywall ceiling" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.edgeUnderGrade" +msgstr "Edge under grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.edgeUnderGradeHint" +msgstr "IRC R403.1.4: at least 12 in, or the frost line." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.edgeWidth" +msgstr "Edge width" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.edgeWidthWide" +msgstr "Edge {width} wide" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.engineeredWood" +msgstr "Engineered wood" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.finish" +msgstr "Finish" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.finishFloorOverGrade" +msgstr "Finish floor over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.finishFloorOverGradeHint" +msgstr "Measured over the highest grade under the outside walls. The foundation grows down the slope from there." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.floorFraming" +msgstr "Floor framing" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.floorFramingAndSill" +msgstr "Floor framing and sill {depth}" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.floorFramingHint" +msgstr "Floor top down to the stem: flooring, subfloor, joists, sill." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.floorFramingSillIncluded" +msgstr "Floor framing {depth}, sill {sill} included" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.flooring" +msgstr "Flooring" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingThickness" +msgstr "Footing thickness" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingUnderGrade" +msgstr "Footing under grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingUnderGradeHint" +msgstr "IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingWidth" +msgstr "Footing width" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingWidthHint" +msgstr "Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.footingWidthThickness" +msgstr "Footing {width} × {thickness}" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.foundation" +msgstr "Foundation" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.gradeBeamDepth" +msgstr "Grade beam depth" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.gradeBeamOnPiles" +msgstr "Grade beam on piles" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.gradeBeamWidth" +msgstr "Grade beam width" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.gradeBeamWidthDepth" +msgstr "Grade beam {width} × {depth}" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder" +msgstr "Half the edge’s depth under the slab, 6 to 12 in" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.hardwood" +msgstr "Hardwood" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.heightCrawl" +msgstr "{height} crawl" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.heightFinishFloor" +msgstr "{height} FF" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.heightTop" +msgstr "{height} top" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.iJoists" +msgstr "I-joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill" +msgstr "Its layers make {depth} with the sill; the framing follows them once a layer is set." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe" +msgstr "Its walls are poured under the house’s outside walls." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.itsWallsAreTheBasementS" +msgstr "Its walls are the basement’s own, on their footing." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.joists" +msgstr "Joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.kind" +msgstr "Kind" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.labelAuto" +msgstr "{label} (auto)" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.laminate" +msgstr "Laminate" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGrade" +msgstr "Least depth under grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGradeHint" +msgstr "The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.leastStepRun" +msgstr "Least step run" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.leastStepRunHint" +msgstr "IRC R403.1.5: the top of a footing is level; it steps where the ground falls." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.lumber" +msgstr "Lumber" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.nameTheMaterial" +msgstr "Name the material" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.off" +msgstr "Off" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.osb" +msgstr "OSB" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.osbSubfloor" +msgstr "OSB subfloor" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.panel" +msgstr "Panel" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.pileDiameter" +msgstr "Pile diameter" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.pileDiameterHint" +msgstr "Piles are sized by the engineer." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.pileDiameterSpacingDeep" +msgstr "Pile {diameter} @ {spacing}, {length} deep" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.pileLength" +msgstr "Pile length" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.pileSpacing" +msgstr "Pile spacing" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.plaster" +msgstr "Plaster" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.plasterCeiling" +msgstr "Plaster ceiling" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.plywood" +msgstr "Plywood" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.plywoodSubfloor" +msgstr "Plywood subfloor" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.preset" +msgstr "Preset" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.raisedFloor" +msgstr "Raised floor" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sectionBasement" +msgstr "Foundation section: basement, its wall's top {top} over grade, its floor {floor} under it" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloor" +msgstr "Foundation section: raised floor on a stem wall, finish floor {floor} over grade, crawl space {crawl} under the joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles" +msgstr "Foundation section: raised floor on a stem wall on piles, finish floor {floor} over grade, crawl space {crawl} under the joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGrade" +msgstr "Foundation section: slab on grade, finish floor {floor} over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles" +msgstr "Foundation section: slab on grade on piles, finish floor {floor} over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sill" +msgstr "Sill" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.size" +msgstr "Size" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.sizeJoists" +msgstr "{size} joists" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slab" +msgstr "Slab" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slabHint" +msgstr "IRC R506.1 asks 3½ in at least." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slabOnGrade" +msgstr "Slab on grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slabThickness" +msgstr "Slab {thickness}" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slabTopOverGrade" +msgstr "Slab top over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.slabTopOverGradeHint" +msgstr "The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in)." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.spacing" +msgstr "Spacing" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.spacingOnCentre" +msgstr "{spacing} o.c." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemTopOverGrade" +msgstr "Stem top over grade" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemTopOverGradeHint" +msgstr "IRC R317.1 keeps wood framing 8 in off the ground." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemWall" +msgstr "Stem wall" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemWallAndFooting" +msgstr "Stem wall and footing" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemWallHint" +msgstr "Table R404.1.2: 6 in for one storey, 8 in for more." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stemWallThickness" +msgstr "Stem wall {thickness}" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stepDownTheSlope" +msgstr "Step down the slope" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.stepHeight" +msgstr "Step height" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.subfloorSheathing" +msgstr "Subfloor sheathing" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeStepping" +msgstr "Tallest stem before stepping" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint" +msgstr "A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.taper" +msgstr "Taper" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.taperAt45" +msgstr "Taper {taper} at 45°" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.taperHint" +msgstr "The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnABasement" +msgstr "The house stands on a basement: its walls are the foundation walls, so only their footing is drawn." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement" +msgstr "The house stands on its basement, cut into the ground: the ground round it is left as it lies." + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.thickness" +msgstr "Thickness" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.titleMaterial" +msgstr "{title} material" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.turnedDownEdge" +msgstr "Turned-down edge" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.vinylPlank" +msgstr "Vinyl plank" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.woodPlank" +msgstr "Wood plank" + +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +msgid "editor.panels.buildingFoundationSection.woodPlankCeiling" +msgstr "Wood plank ceiling" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.cancel" +msgstr "Cancel" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.collectionName" +msgstr "Collection name…" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.collections" +msgstr "Collections" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.delete" +msgstr "Delete" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.deleteName" +msgstr "Delete \"{name}\"?" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.new" +msgstr "New" + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" +msgstr "No collections yet. Create one to group elements together." + +#: packages/editor/src/components/ui/panels/collections/collections-popover.tsx +msgid "editor.panels.collections.collectionsPopover.rename" +msgstr "Rename" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.alongHouse" +msgstr "Along house" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.boards" +msgstr "Boards" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.boardsSummary" +msgstr "{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." + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.deckBoards" +msgstr "Deck boards" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.diagonal" +msgstr "Diagonal" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.gap" +msgstr "Gap" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.outFromHouse" +msgstr "Out from house" + +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +msgid "editor.panels.deckBoardsFields.run" +msgstr "Run" + +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" msgstr "A thicker floor grows upward: the rooms below keep their height." +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.buildUpFollows" +msgstr "Its layers make {thickness}; the thickness follows them on the first change." + #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx msgid "editor.panels.floorFoundationPanel.edgeFinish" msgstr "Edge finish" +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.floor" +msgstr "Floor" + +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.floorBuildUp" +msgstr "Floor build-up" + #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx msgid "editor.panels.floorFoundationPanel.floorPlate" msgstr "Floor plate" @@ -4070,8 +5178,8 @@ msgid "editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn msgstr "Floor plate {thicknessLabel} thick, resting on the walls below" #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx -msgid "editor.panels.floorFoundationPanel.floorThickness" -msgstr "Floor thickness" +msgid "editor.panels.floorFoundationPanel.floorThicknessValue" +msgstr "Floor thickness {thickness}" #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx msgid "editor.panels.floorFoundationPanel.floorTopAbove" @@ -4141,6 +5249,10 @@ msgstr "Foundation · {foundation}" msgid "editor.panels.floorFoundationPanel.section.ground" msgstr "Ground" +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.section.layer" +msgstr "{layer} · {size}" + #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx msgid "editor.panels.floorFoundationPanel.section.slab" msgstr "Slab · {thickness}" @@ -4149,6 +5261,14 @@ msgstr "Slab · {thickness}" msgid "editor.panels.floorFoundationPanel.section.wallsBelow" msgstr "Walls below" +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.sectionLayers" +msgstr "Floor plate {thickness} thick: {layers}; resting on the walls below" + +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +msgid "editor.panels.floorFoundationPanel.sectionLayerSize" +msgstr "{layer} {size}" + #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx msgid "editor.panels.floorFoundationPanel.sectionOnGround" msgstr "Slab {thickness} thick on the ground, floor top {top} above the ground" @@ -4201,6 +5321,10 @@ msgstr "{area} · Drag its corners to change the shape." msgid "editor.panels.floorOpeningPanel.areaOwned" msgstr "{area} · Shape follows its owner." +#: packages/editor/src/components/ui/panels/floor-opening-panel.tsx +msgid "editor.panels.floorOpeningPanel.areaStairResize" +msgstr "{area} · Drag its sides or corners to resize it; it moves with the stair." + #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx msgid "editor.panels.floorOpeningPanel.delete" msgstr "Delete" @@ -4253,6 +5377,103 @@ msgstr "There is no storey below." msgid "editor.panels.floorOpeningPanel.title" msgstr "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.aRaisedFloorOnAStem" +msgstr "A raised floor on a stem wall of its own." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.aSlabOfItsOwn" +msgstr "A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.clickItsCornersClickTheFirst" +msgstr "Click its corners; click the first again, or press Enter, to close it." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA" +msgstr "Close the outside walls into a loop first, or draw one" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.deleteThisOutline" +msgstr "Delete this outline" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.draw" +msgstr "Draw" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.editItMyself" +msgstr "Edit it myself" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.followTheWalls" +msgstr "Follow the walls" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.footingsDrawn" +msgstr "{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.foundation" +msgstr "Foundation" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.houseFoundation" +msgstr "House foundation" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.howFarItsTopSitsUnder" +msgstr "How far its top sits under the house’s floor: a porch slab steps down from the door" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd" +msgstr "Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.outline" +msgstr "Outline" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors" +msgstr "Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.raisedFloor" +msgstr "Raised floor" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.reTraceFromWalls" +msgstr "Re-trace from walls" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.setTheOutlineBackToThe" +msgstr "Set the outline back to the outside faces of the walls, keeping it your own" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.slab" +msgstr "Slab" + +#. What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own. +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.theHouses" +msgstr "The house’s" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.theHousesOwnFoundation" +msgstr "The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline." + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.thisOutline" +msgstr "This outline" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.topUnderTheFloor" +msgstr "Top under the floor" + +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +msgid "editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt" +msgstr "Traced from the outside walls: it moves when they do." + #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx msgid "editor.panels.mergeRefusal.alreadyJoined" msgstr "One of them already wraps another corner. Unwrap it first." @@ -4406,6 +5627,10 @@ msgstr "Unwrap the corner" msgid "editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay" msgstr "Unwrap the corner: both windows stay, each framed at the corner" +#: packages/editor/src/components/ui/panels/panel-manager.tsx +msgid "editor.panels.panelManager.foundation" +msgstr "Foundation" + #: packages/editor/src/components/ui/panels/panel-manager.tsx msgid "editor.panels.panelManager.selection" msgstr "Selection" @@ -4626,6 +5851,14 @@ msgstr "A drawn slab: set its thickness and finishes here. It has no footprint h msgid "editor.panels.roomConstructionRows.atFloorLevel" msgstr "At floor level" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.beam" +msgstr "Beam" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.beamSize" +msgstr "Beam size" + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.builtOn" msgstr "Built on" @@ -4686,6 +5919,30 @@ msgstr "Floor thickness" msgid "editor.panels.roomConstructionRows.floorUp" msgstr "{distance} up" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingAutoHint" +msgstr "{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}." + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingAutomatic" +msgstr "Automatic" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingManual" +msgstr "Manual" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingManualHint" +msgstr "Kept as you edited them. Automatic rebuilds them from these settings." + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingOff" +msgstr "Off" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.framingOffHint" +msgstr "None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}." + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.handDrawnCeiling" msgstr "Hand-drawn ceiling" @@ -4730,6 +5987,10 @@ msgstr "Nothing to change." msgid "editor.panels.roomConstructionRows.notIncluded" msgstr "Not included" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.onTheFoundation" +msgstr "On the foundation" + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.onTheGround" msgstr "On the ground" @@ -4750,14 +6011,66 @@ msgstr "Partial · {coverage, number, percent} covered" msgid "editor.panels.roomConstructionRows.partialWalls" msgstr "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.postsAndBeam" +msgstr "Posts and beam" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.postSize" +msgstr "Post size" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.postSpacingMax" +msgstr "Post spacing (max)" + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.raised" msgstr "Raised {lift}" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.raisedFloor" +msgstr "Raised floor" + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.replace" msgstr "Replace" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomType" +msgstr "Room type" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypeDeck" +msgstr "Deck" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypeDeckHint" +msgstr "Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling." + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypePatio" +msgstr "Patio" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypePatioHint" +msgstr "Outdoors and open: a concrete slab 1½ in down from the house, no ceiling." + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypePorch" +msgstr "Porch" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypePorchHint" +msgstr "Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s." + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypeRoom" +msgstr "Room" + +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.roomTypeRoomHint" +msgstr "An ordinary room inside the house." + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.select" msgstr "Select" @@ -4774,6 +6087,10 @@ msgstr "Turning walls off keeps the ones shared with {rooms}." msgid "editor.panels.roomConstructionRows.sitsOnPreset" msgstr "Sits on: {name} · {preset}" +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +msgid "editor.panels.roomConstructionRows.slab" +msgstr "Slab" + #: packages/editor/src/components/ui/panels/room-construction-rows.tsx msgid "editor.panels.roomConstructionRows.theNextRoom" msgstr "the next room" @@ -5136,6 +6453,22 @@ msgstr "{current} · {distance} below {shared}" msgid "editor.room.builtOn.levelWith" msgstr "{current} · level with {shared}" +#: packages/editor/src/components/editor/room-handles.tsx +msgid "editor.roomHandles.cannotReadInput" +msgstr "Cannot read \"{input}\"" + +#: packages/editor/src/components/editor/room-handles.tsx +msgid "editor.roomHandles.dimensionValue" +msgstr "Dimension value" + +#: packages/editor/src/components/editor/room-handles.tsx +msgid "editor.roomHandles.floor" +msgstr "Floor" + +#: packages/editor/src/components/editor/room-handles.tsx +msgid "editor.roomHandles.typeASizeToMoveThe" +msgstr "Type a size to move the wall" + #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts msgid "editor.roomLabel.room" @@ -5474,6 +6807,46 @@ msgstr "No results for “{search}”" msgid "editor.sidebar.panels.itemsPanel.search" msgstr "Search..." +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools" +msgstr "Build Tools" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera" +msgstr "Camera" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation" +msgstr "Direct Manipulation" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools" +msgstr "Drawing Tools" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation" +msgstr "Editor Navigation" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement" +msgstr "Item Placement" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts" +msgstr "Keyboard shortcuts" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory" +msgstr "Modes & History" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection" +msgstr "Selection" + +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints" +msgstr "Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely." + #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx msgid "editor.sidebar.panels.sitePanel.addLevel" msgstr "Add level" @@ -5798,6 +7171,10 @@ msgstr "A curved wall takes no corner window" msgid "editor.toolHint.addACornerClickTheFirst.o4r67t" msgstr "Add a corner · click the first to close" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.addAWallEachOneStarts.4zkf12" +msgstr "Add a wall · each one starts where the last ended" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0" msgstr "Another opening is in the way at the corner" @@ -5838,6 +7215,10 @@ msgstr "Cancel run / exit" msgid "editor.toolHint.cancelStartPoint.1xbq2lh" msgstr "Cancel start point" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.closeTheOutline.f6cvzi" +msgstr "Close the outline" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.cornerWindow.1avkzpo" msgstr "Corner window" @@ -5882,6 +7263,10 @@ msgstr "Duct diameter down / up" msgid "editor.toolHint.endRunCancel.59dh2w" msgstr "End run / cancel" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.endTheRun.1c1raig" +msgstr "End the run" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.exit.engmg5" msgstr "Exit" @@ -5914,6 +7299,10 @@ msgstr "Finish multi-point witnesses" msgid "editor.toolHint.finishSlab.xmnm8e" msgstr "Finish slab" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.finishTheRun.ayliww" +msgstr "Finish the run" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.flipSide.172xku9" msgstr "Flip side" @@ -5926,6 +7315,14 @@ msgstr "Follow: trace a lineset" msgid "editor.toolHint.forcePlace.1hq1nr" msgstr "Force place" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.freeAngle.1ptblho" +msgstr "Free angle" + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.freePlace.1s1uusw" +msgstr "Free place" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.freePlacement.1nztdac" msgstr "Free placement" @@ -6010,6 +7407,10 @@ msgstr "Middle click" msgid "editor.toolHint.key.rightClick.1ovld0h" msgstr "Right click" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.key.room.1f3jkpm" +msgstr "Room" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.key.rShiftR.13bnobh" msgstr "R / Shift+R" @@ -6038,6 +7439,10 @@ msgstr "Mount: floor / ceiling / wall" msgid "editor.toolHint.noRoomForTheMatchingWindow.1755948" msgstr "No room for the matching window on the other wall" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.notARectangleDrawThisRoof.100pa5g" +msgstr "Not a rectangle: draw this roof instead" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.noWallMeetsHere.19n99yc" msgstr "No wall meets here" @@ -6054,6 +7459,10 @@ msgstr "Pick witness point" msgid "editor.toolHint.pipeSizeDownUp.wp04og" msgstr "Pipe size down / up" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j" +msgstr "Place a corner of the foundation" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.placeAndContinue.qfwkrq" msgstr "Place and continue" @@ -6206,6 +7615,10 @@ msgstr "Place window on wall" msgid "editor.toolHint.placeWithoutTheCornerSnap.1nifqew" msgstr "Place without the corner snap" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.pointInsideARoomPorchOr.58w64l" +msgstr "Point inside a room, porch or deck" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.removeLastPoint.2dmdcx" msgstr "Remove last point" @@ -6214,6 +7627,22 @@ msgstr "Remove last point" msgid "editor.toolHint.removeLastWitness.1b6vvzw" msgstr "Remove last witness" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.ridgeDirection.5m1369" +msgstr "Ridge direction" + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk" +msgstr "Ridge direction - click or press R to turn it 90°" + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.ridgeLongSide.ew0euv" +msgstr "Ridge: Long side" + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.ridgeShortSide.gye8pb" +msgstr "Ridge: Short side" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.rotate.1thqa0a" msgstr "Rotate" @@ -6234,10 +7663,6 @@ msgstr "Rotate ghost ±15°" msgid "editor.toolHint.rotateOrFlipTheRunSide.t5ior7" msgstr "Rotate or flip the run side" -#: packages/nodes/src/i18n/definition-messages.ts -msgid "editor.toolHint.rotateRoofDirection90.tvbji2" -msgstr "Rotate roof direction 90°" - #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.rotateSpawnPoint.1opcuu7" msgstr "Rotate spawn point" @@ -6246,10 +7671,18 @@ msgstr "Rotate spawn point" msgid "editor.toolHint.roundRectangularOval.e4nh6x" msgstr "Round / rectangular / oval" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.setBeamStartEndTheNext.di3bzj" +msgstr "Set beam start / end; the next beam starts there" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.setFenceStartEnd.1phbwsq" msgstr "Set fence start / end" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "editor.toolHint.setFootingStartEndOrPlace.nnv1av" +msgstr "Set footing start / end, or place a pad" + #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.setOneCornerThenTheOpposite.8tsl49" msgstr "Set one corner, then the opposite" @@ -6258,10 +7691,6 @@ msgstr "Set one corner, then the opposite" msgid "editor.toolHint.setRoofFootprint.p50n0o" msgstr "Set roof footprint" -#: packages/nodes/src/i18n/definition-messages.ts -msgid "editor.toolHint.setWallStartEnd.1r8ubcx" -msgstr "Set wall start / end" - #: packages/nodes/src/i18n/definition-messages.ts msgid "editor.toolHint.slopeLevel.ftqp67" msgstr "Slope / level" @@ -6570,37 +7999,121 @@ msgstr "Pin to the view bar" msgid "editor.views.viewBar.solid" msgstr "Solid" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.split" -msgstr "Split (\\)" +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.split" +msgstr "Split (\\)" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.split2" +msgstr "Split" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.swapPanes" +msgstr "Swap panes" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.unpinFromTheViewBar" +msgstr "Unpin from the view bar" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.unpinLabel" +msgstr "Unpin {label}" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.veilTheGroundOverALevel" +msgstr "Veil the ground over a level below grade" + +#: packages/editor/src/components/editor/views/view-bar.tsx +msgid "editor.views.viewBar.views" +msgstr "Views" + +#: packages/editor/src/components/editor/index.tsx +msgid "editor.youCanRetryTheSceneOr" +msgstr "You can retry the scene or return home without reloading the whole app shell." + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.beam.description" +msgstr "A framing beam drawn end to end: a porch header, a deck beam, a girder." + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.beam.label" +msgstr "Beam" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.actions" +msgstr "Actions" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.addPosts" +msgstr "Add posts" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.beam" +msgstr "Beam" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.beamSize" +msgstr "Beam {size}" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.delete" +msgstr "Delete" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.depth" +msgstr "Depth" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.dropped" +msgstr "Dropped" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.flush" +msgstr "Flush" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.followTheCeilingPlane" +msgstr "Follow the ceiling plane" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.lengthLongItsUnderside" +msgstr "{length} long; its underside {underside} over the floor, the ceiling plane {plane}." -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.split2" -msgstr "Split" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.material" +msgstr "Material" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.swapPanes" -msgstr "Swap panes" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.move" +msgstr "Move" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.unpinFromTheViewBar" -msgstr "Unpin from the view bar" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.n1Ply" +msgstr "1 ply" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.unpinLabel" -msgstr "Unpin {label}" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.placement" +msgstr "Placement" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.veilTheGroundOverALevel" -msgstr "Veil the ground over a level below grade" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.pliesPlies" +msgstr "{plies} plies" -#: packages/editor/src/components/editor/views/view-bar.tsx -msgid "editor.views.viewBar.views" -msgstr "Views" +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.size" +msgstr "Size" -#: packages/editor/src/components/editor/index.tsx -msgid "editor.youCanRetryTheSceneOr" -msgstr "You can retry the scene or return home without reloading the whole app shell." +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.top" +msgstr "Top" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.topElevation" +msgstr "Top elevation" + +#: packages/nodes/src/beam/panel.tsx +msgid "node.beam.panel.width" +msgstr "Width" #: packages/nodes/src/i18n/definition-messages.ts msgid "node.block.description" @@ -7710,6 +9223,54 @@ msgstr "An editable module inside a modular cabinet run." msgid "node.cabinetModule.label" msgstr "Cabinet Module" +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.bottomChord" +msgstr "Bottom chord" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.ceilingJoists" +msgstr "Ceiling joists" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.decking" +msgstr "Decking" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.finish" +msgstr "Finish" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.framing" +msgstr "Framing" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.joists" +msgstr "Joists" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.layers" +msgstr "Layers" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.none" +msgstr "None" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.preset" +msgstr "Preset" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside" +msgstr "The height is the finished underside; the layers rise from it." + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.thickness" +msgstr "Thickness" + +#: packages/nodes/src/ceiling/build-up-section.tsx +msgid "node.ceiling.buildUpSection.trussBottomChords" +msgstr "Truss bottom chords" + #: packages/nodes/src/i18n/definition-messages.ts msgid "node.ceiling.description" msgstr "A polygon-bounded ceiling surface that hosts ceiling-mounted items." @@ -8330,6 +9891,46 @@ msgstr "Yaw" msgid "node.column.panel.ySupport" msgstr "Y Support" +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.aSizeDeckPostStandsNo" +msgstr "A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up." + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.custom" +msgstr "Custom" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.fitToTheBeamOverIt" +msgstr "Fit to the beam over it ({height})" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.fitToTheCeilingPlane" +msgstr "Fit to the ceiling plane ({height})" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.height" +msgstr "Height" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.keepItsShapeAndStyleIt" +msgstr "Keep its shape and style it as a column" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.makeItAColumn" +msgstr "Make it a column" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.post" +msgstr "Post" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.treated" +msgstr "Treated" + +#: packages/nodes/src/column/post-section.tsx +msgid "node.column.postSection.untreated" +msgstr "Untreated" + #: packages/nodes/src/column/panel.tsx msgid "node.column.preset.aFrameSupport" msgstr "A-Frame Support" @@ -8506,6 +10107,14 @@ msgstr "Opening" msgid "node.cutout.room" msgstr "Room cutout" +#: packages/nodes/src/door/actions.tsx +msgid "node.door.actions.swapHingeSide" +msgstr "Swap hinge side" + +#: packages/nodes/src/door/actions.tsx +msgid "node.door.actions.swapSwingSide" +msgstr "Swap swing side" + #: packages/nodes/src/i18n/definition-messages.ts msgid "node.door.description" msgstr "A door cut into a wall. Animated open/close state." @@ -9643,6 +11252,118 @@ msgstr "Open Passage" msgid "node.floorOpening.label" msgstr "Floor opening" +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.footing.description" +msgstr "A concrete strip or pad footing poured under the lowest floor." + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.footing.label" +msgstr "Footing" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.actions" +msgstr "Actions" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.aGradeBeamOnPilesAs" +msgstr "A grade beam on piles, as the building is." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.aPierCarriesTheFloor" +msgstr "A pier carries the floor." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.aPileCapOnOnePile" +msgstr "A pile cap on one pile, as the building is." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.aStemWallCarriesTheFloor" +msgstr "A stem wall carries the floor." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.aThickenedSlabUnderTheFloor" +msgstr "A thickened slab under the floor slab." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.atLeast" +msgstr "At least" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.bottom" +msgstr ", bottom" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.clearTheDepth" +msgstr "Clear the depth" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.delete" +msgstr "Delete" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.depthUnderGrade" +msgstr "Depth under grade" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.depthUnderGradeBuildingS" +msgstr "Depth under grade (building's)" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.footing" +msgstr "Footing" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.ircR4031AsksAFooting" +msgstr "IRC R403.1 asks a footing at least 12 in wide and 6 in thick." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.length" +msgstr "Length" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.pad" +msgstr "Pad" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.strip" +msgstr "Strip" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.theFootingSThicknessUnderThe" +msgstr "the footing’s thickness under the floor reaches this far." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.theGradeBeamReachesThisFar" +msgstr "the grade beam reaches this far." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.theSlabAndTheFootingS" +msgstr "the slab and the footing’s thickness reach this far." + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.thickness" +msgstr "Thickness" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.top" +msgstr "Top" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.useTheBuildingSDepth" +msgstr "Use the building's depth" + +#: packages/nodes/src/footing/panel.tsx +msgid "node.footing.panel.width" +msgstr "Width" + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.foundation.description" +msgstr "The outline the foundation is poured to: drawn, or traced from the walls and stretched." + +#: packages/nodes/src/i18n/definition-messages.ts +msgid "node.foundation.label" +msgstr "Foundation" + #: packages/nodes/src/i18n/definition-messages.ts msgid "node.guide.description" msgstr "A measurement / reference annotation (linear, area, or arc)." @@ -9693,7 +11414,7 @@ msgstr "Hole {number}" #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx msgid "node.holeSummary" -msgstr "{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +msgstr "{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}" #: packages/nodes/src/i18n/definition-messages.ts msgid "node.hvacEquipment.description" @@ -10330,6 +12051,14 @@ msgstr "Draw Segment" msgid "node.roof.panel.duplicate" msgstr "Duplicate" +#: packages/nodes/src/roof/panel.tsx +msgid "node.roof.panel.eaveOverhang" +msgstr "Eave overhang" + +#: packages/nodes/src/roof/panel.tsx +msgid "node.roof.panel.eaves" +msgstr "Eaves" + #: packages/nodes/src/roof/panel.tsx msgid "node.roof.panel.elements" msgstr "Elements" @@ -10338,6 +12067,10 @@ msgstr "Elements" msgid "node.roof.panel.followsWalls" msgstr "Follows walls" +#: packages/nodes/src/roof/panel.tsx +msgid "node.roof.panel.gableOverhang" +msgstr "Gable overhang" + #: packages/nodes/src/roof/panel.tsx msgid "node.roof.panel.gutter" msgstr "gutter" @@ -10406,6 +12139,126 @@ msgstr "Turbine" msgid "node.roof.panel.turbineVent" msgstr "turbine vent" +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.applyToAllPlanesOfThis" +msgstr "Apply to all planes of this roof" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.architecturalShingle" +msgstr "Architectural shingle" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.asphalt3Tab" +msgstr "Asphalt 3-tab" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.clayTile" +msgstr "Clay tile" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.concreteTile" +msgstr "Concrete tile" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.covering" +msgstr "Covering" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.deck" +msgstr "Deck" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.editLayers" +msgstr "Edit layers" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.finishName" +msgstr "Finish name" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.iceAndWaterShield" +msgstr "Ice and water shield" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.insulationName" +msgstr "Insulation name" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.interiorFinish" +msgstr "Interior finish" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.layers" +msgstr "Layers" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.n15Felt" +msgstr "#15 felt" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.n30Felt" +msgstr "#30 felt" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.none" +msgstr "None" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.preset" +msgstr "Preset" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.rafters" +msgstr "Rafters" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.roofing" +msgstr "Roofing" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.roofingName" +msgstr "Roofing name" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.sheathing" +msgstr "Sheathing" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.sheathingName" +msgstr "Sheathing name" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.slate" +msgstr "Slate" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.standingSeamMetal" +msgstr "Standing-seam metal" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.synthetic" +msgstr "Synthetic" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.topChord" +msgstr "Top chord" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.trusses" +msgstr "Trusses" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.underlayment" +msgstr "Underlayment" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.underlaymentName" +msgstr "Underlayment name" + +#: packages/nodes/src/roof-segment/build-up-section.tsx +msgid "node.roofSegment.buildUpSection.woodShake" +msgstr "Wood shake" + #: packages/nodes/src/i18n/definition-messages.ts msgid "node.roofSegment.description" msgstr "A single pitched plane of a parent roof." @@ -10482,6 +12335,10 @@ msgstr "Duplicate" msgid "node.roofSegment.panel.dutch" msgstr "Dutch" +#: packages/nodes/src/roof-segment/panel.tsx +msgid "node.roofSegment.panel.eaveCut" +msgstr "Eave cut" + #: packages/nodes/src/roof-segment/panel.tsx msgid "node.roofSegment.panel.eaveOverhang" msgstr "Eave overhang" @@ -10550,6 +12407,14 @@ msgstr "Move" msgid "node.roofSegment.panel.pitch" msgstr "Pitch" +#: packages/nodes/src/roof-segment/panel.tsx +msgid "node.roofSegment.panel.plumb" +msgstr "Plumb" + +#: packages/nodes/src/roof-segment/panel.tsx +msgid "node.roofSegment.panel.plumbLevel" +msgstr "Plumb + level" + #: packages/nodes/src/roof-segment/panel.tsx msgid "node.roofSegment.panel.position" msgstr "Position" @@ -10586,6 +12451,10 @@ msgstr "Shingle Thick." msgid "node.roofSegment.panel.span" msgstr "Span" +#: packages/nodes/src/roof-segment/panel.tsx +msgid "node.roofSegment.panel.square" +msgstr "Square" + #: packages/nodes/src/roof-segment/panel.tsx msgid "node.roofSegment.panel.startAngle" msgstr "Start Angle" @@ -10652,6 +12521,67 @@ msgstr "Segment {number}" msgid "node.separator.label" msgstr "Separator" +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.batt" +msgstr "Batt" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.blown" +msgstr "Blown" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.drywall" +msgstr "Drywall" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.drywallThickness" +msgstr "Drywall thickness" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.insulation" +msgstr "Insulation" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.insulationThickness" +msgstr "Insulation thickness" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.labelThickness" +msgstr "{label} thickness" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.none" +msgstr "None" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.osb" +msgstr "OSB" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.plaster" +msgstr "Plaster" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.plywood" +msgstr "Plywood" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.rigidBoard" +msgstr "Rigid board" + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.spacing" +msgstr "Spacing" + +#. A framing spacing on centre (centre of one member to the next), e.g. 16" o.c. +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.spacingOC" +msgstr "{spacing} o.c." + +#: packages/nodes/src/shared/build-up-controls.tsx +msgid "node.shared.buildUpControls.woodPlank" +msgstr "Wood plank" + #: packages/nodes/src/shared/item-interaction-actions.tsx msgid "node.shared.itemInteractionActions.turnLightOff" msgstr "Turn light off" @@ -10660,6 +12590,14 @@ msgstr "Turn light off" msgid "node.shared.itemInteractionActions.turnLightOn" msgstr "Turn light on" +#: packages/nodes/src/shared/opening-casing-section.tsx +msgid "node.shared.openingCasingSection.casing" +msgstr "Casing" + +#: packages/nodes/src/shared/opening-casing-section.tsx +msgid "node.shared.openingCasingSection.width" +msgstr "Width" + #: packages/nodes/src/shared/run-hanger-inspector.tsx msgid "node.shared.runHangerInspector.automaticSupport" msgstr "Automatic support" @@ -11412,6 +13350,10 @@ msgstr "Center stringer" msgid "node.stair.constructionControls.closedRisers" msgstr "Closed risers" +#: packages/nodes/src/stair/construction-controls.tsx +msgid "node.stair.constructionControls.fromStructure" +msgstr "From structure" + #: packages/nodes/src/stair/construction-controls.tsx msgid "node.stair.constructionControls.inherit" msgstr "Inherit" @@ -11936,6 +13878,151 @@ msgstr "Top horizontal extension" msgid "node.stair.railingControls.wallFacing" msgstr "Wall-facing" +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.aSolidPouredFlightOnA" +msgstr "A solid poured flight on a footing; risers closed, no stringers." + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoAutocount" +msgstr "Auto ({autoCount})" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoClosed" +msgstr "Auto (closed)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoOpen" +msgstr "Auto (open)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoPressureTreated" +msgstr "Auto (pressure-treated)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoTreads" +msgstr "Auto ({treads})" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.autoUntreated" +msgstr "Auto (untreated)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.closed1x8Boards" +msgstr "Closed (1x8 boards)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.concrete" +msgstr "Concrete" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.countSteelChannels" +msgstr "{count, plural, one {# steel channel} other {# steel channels}}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.countSteelChannelsAtSpacing" +msgstr "{count, plural, one {# steel channel at {spacing} o.c.} other {# steel channels at {spacing} o.c.}}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.countStringers" +msgstr "{count, plural, one {# stringer} other {# stringers}}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.countStringersAtSpacing" +msgstr "{count, plural, one {# stringer at {spacing} o.c.} other {# stringers at {spacing} o.c.}}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.cut" +msgstr "Cut" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.flightLayout" +msgstr "Flight {number}: {layout}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.housedSides" +msgstr "Housed sides" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.inchesInOC" +msgstr "{inches} in o.c." + +#. The stringer count was chosen automatically, not set by hand. +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.layoutAuto" +msgstr "{layout} (auto)" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.lumber" +msgstr "Lumber" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.maxSpacing" +msgstr "Max spacing" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.nosing" +msgstr "Nosing" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.oneStock" +msgstr "One {stock}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.open" +msgstr "Open" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.originalBody" +msgstr "Original body" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.plywoodFinish" +msgstr "Plywood + finish" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.pressureTreated" +msgstr "Pressure-treated" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.risers" +msgstr "Risers" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.steelStringers" +msgstr "Steel stringers" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.stringerCount" +msgstr "Stringer count" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.stringers" +msgstr "Stringers" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.stringerType" +msgstr "Stringer type" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.structure" +msgstr "Structure" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.treads" +msgstr "Treads" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.twoStock" +msgstr "Two {stock}" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.untreated" +msgstr "Untreated" + +#: packages/nodes/src/stair/structure-controls.tsx +msgid "node.stair.structureControls.woodStringers" +msgstr "Wood stringers" + #: packages/nodes/src/i18n/definition-messages.ts msgid "node.stairSegment.description" msgstr "A single flight of a parent stair." @@ -12260,14 +14347,45 @@ msgstr "Report" msgid "node.unit.unitPanel.unit" msgstr "Unit" +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.dimensionsToTheExteriorFacesOutside" +msgstr "Dimensions to the exterior faces (outside size). Click for both" + +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.dimensionsToTheInteriorAndExterior" +msgstr "Dimensions to the interior and exterior faces. Click for interior only" + +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.dimensionsToTheInteriorFacesRoom" +msgstr "Dimensions to the interior faces (room size). Click for exterior only" + +#. Short button text: interior dimension, measured to the interior faces. +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.id" +msgstr "ID" + +#. Short button text: ID is the interior dimension, OD the outside dimension. +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.idOd" +msgstr "ID+OD" + #: packages/nodes/src/wall/actions.tsx msgid "node.wall.actions.mergeWalls" msgstr "Merge walls" +#. Short button text: outside dimension, measured to the exterior faces. +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.od" +msgstr "OD" + #: packages/nodes/src/wall/actions.tsx msgid "node.wall.actions.splitWall" msgstr "Split wall" +#: packages/nodes/src/wall/actions.tsx +msgid "node.wall.actions.theseWallsCannotBeMerged" +msgstr "These walls cannot be merged." + #: packages/nodes/src/wall/curtain-wall-panel.tsx msgid "node.wall.curtain.cell" msgstr "Column {column}, row {row}: {type, select, glass {glass} solid {solid} other {empty}}" @@ -12660,6 +14778,10 @@ msgstr "Follows level" msgid "node.wall.panel.frameDepth" msgstr "Frame depth" +#: packages/nodes/src/wall/panel.tsx +msgid "node.wall.panel.framersDefault" +msgstr "Framer's default" + #: packages/nodes/src/wall/panel.tsx msgid "node.wall.panel.framing" msgstr "Framing" @@ -12748,6 +14870,10 @@ msgstr "Plywood" msgid "node.wall.panel.preset" msgstr "Preset" +#: packages/nodes/src/wall/panel.tsx +msgid "node.wall.panel.railing" +msgstr "Railing" + #: packages/nodes/src/wall/panel.tsx msgid "node.wall.panel.reference" msgstr "Reference" @@ -12808,6 +14934,10 @@ msgstr "Stone veneer" msgid "node.wall.panel.stucco" msgstr "Stucco" +#: packages/nodes/src/wall/panel.tsx +msgid "node.wall.panel.studs" +msgstr "Studs" + #: packages/nodes/src/wall/panel.tsx msgid "node.wall.panel.thickness" msgstr "Thickness" @@ -12816,6 +14946,10 @@ msgstr "Thickness" msgid "node.wall.panel.thicknessFromAssembly" msgstr "{thickness} · from assembly" +#: packages/nodes/src/wall/panel.tsx +msgid "node.wall.panel.thisProject" +msgstr "This project" + #: packages/nodes/src/wall/panel.tsx msgid "node.wall.panel.top" msgstr "Top" @@ -12848,6 +14982,42 @@ msgstr "Which face is outside is undetermined (no room detected on either side) msgid "node.wall.panel.woodStuds" msgstr "Wood studs" +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.aGuardSOpeningsMustNot" +msgstr "A guard's openings must not pass a 4 in sphere (IRC R312.1.3)." + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.height" +msgstr "Height" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.infill" +msgstr "Infill" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.n2x6Cap" +msgstr "2x6 cap" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.n36In" +msgstr "36 in" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.n42In" +msgstr "42 in" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.posts" +msgstr "Posts" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.railing" +msgstr "Railing" + +#: packages/nodes/src/wall/railing-wall-panel.tsx +msgid "node.wall.railingWallPanel.style" +msgstr "Style" + #: packages/nodes/src/wall/panel.tsx msgid "node.wall.reference.center" msgstr "Center" diff --git a/packages/editor/src/i18n/locales/en/messages.ts b/packages/editor/src/i18n/locales/en/messages.ts index 13e08a189a..08f808e74f 100644 --- a/packages/editor/src/i18n/locales/en/messages.ts +++ b/packages/editor/src/i18n/locales/en/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"app.appShell.pascalHome\":[\"Pascal home\"],\"app.buildTab.addGate\":[\"Add Gate\"],\"app.buildTab.addOpenPassage\":[\"Add Open Passage\"],\"app.buildTab.addToRooms\":[\"Add to rooms\"],\"app.buildTab.allBuildTools\":[\"← All build tools\"],\"app.buildTab.chooseGateOrOpenPassageThen\":[\"Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph.\"],\"app.buildTab.clickALinesetToLayThe\":[\"Click a lineset to lay the line beside it.\"],\"app.buildTab.doubleGate\":[\"Double gate\"],\"app.buildTab.featuresExtensions\":[\"Features & extensions\"],\"app.buildTab.fenceFeatures\":[\"Fence features\"],\"app.buildTab.followLineset\":[\"Follow lineset\"],\"app.buildTab.gate\":[\"Gate\"],\"app.buildTab.gateStyle\":[\"Gate style\"],\"app.buildTab.horizontalBoards\":[\"Horizontal boards\"],\"app.buildTab.hoverAFenceToPreviewClick\":[\"Hover a fence to preview. Click to place. Esc cancels. Leave room between openings.\"],\"app.buildTab.leaves\":[\"Leaves\"],\"app.buildTab.liquidLine\":[\"Liquid Line\"],\"app.buildTab.matchFenceStyle\":[\"Match fence style\"],\"app.buildTab.mep\":[\"MEP\"],\"app.buildTab.off\":[\"Off\"],\"app.buildTab.on\":[\"On\"],\"app.buildTab.openRails\":[\"Open rails\"],\"app.buildTab.opening\":[\"Opening\"],\"app.buildTab.openingWidthM\":[\"Opening width (m)\"],\"app.buildTab.outdoor\":[\"Outdoor\"],\"app.buildTab.picket\":[\"Picket\"],\"app.buildTab.roofType\":[\"Roof type\"],\"app.buildTab.roomsAlreadyCreateTheirFloorAnd\":[\"Rooms already create their floor and ceiling. Use these for platforms and one-off structure.\"],\"app.buildTab.searchBuildToolsAndKitchenItems\":[\"Search build tools and kitchen items\"],\"app.buildTab.searchToolsFridgeSink\":[\"Search tools, fridge, sink…\"],\"app.buildTab.selectACurvedWallToMatch\":[\"Select a curved wall to match its radius and arc.\"],\"app.buildTab.singleGate\":[\"Single gate\"],\"app.buildTab.slabCeilingColumn\":[\"Slab, ceiling, column…\"],\"app.buildTab.solidPrivacy\":[\"Solid privacy\"],\"app.buildTab.traceALineAlongsideAnExisting\":[\"Trace a line alongside an existing lineset (F).\"],\"app.buildTab.verticalSlats\":[\"Vertical slats\"],\"app.editorNavbar.allProjects\":[\"All projects\"],\"app.editorNavbar.preview\":[\"Preview\"],\"app.editorNavbar.projectName\":[\"Project name\"],\"app.editorNavbar.renameProject\":[\"Rename project\"],\"app.editorNavbar.share\":[\"Share\"],\"app.kitchenBrowser.addLabel\":[\"Add \",[\"label\"]],\"app.kitchenBrowser.addToKitchen\":[\"Add to kitchen\"],\"app.kitchenBrowser.chooseAnItemThenClickTo\":[\"Choose an item, then click to place it. Appliances include their cabinetry.\"],\"app.kitchenBrowser.findInThisProjectLength\":[\"Find in this project · \",[\"length\"]],\"app.kitchenBrowser.findName\":[\"Find \",[\"name\"]],\"app.kitchenBrowser.groupAdd\":[\"Kitchen · Add\"],\"app.kitchenBrowser.groupInThisProject\":[\"Kitchen · In this project\"],\"app.kitchenBrowser.kitchenCabinet\":[\"Kitchen cabinet\"],\"app.kitchenBrowser.noMatchingKitchenItemsPlacedYet\":[\"No matching kitchen items placed yet.\"],\"app.kitchenBrowser.selectAndShowInScene\":[\"Select and show in scene\"],\"app.lib.buildPalette.ceiling\":[\"Ceiling\"],\"app.lib.buildPalette.column\":[\"Column\"],\"app.lib.buildPalette.door\":[\"Door\"],\"app.lib.buildPalette.duct\":[\"Duct\"],\"app.lib.buildPalette.dwvPipe\":[\"DWV Pipe\"],\"app.lib.buildPalette.elevator\":[\"Elevator\"],\"app.lib.buildPalette.fence\":[\"Fence\"],\"app.lib.buildPalette.hvacUnit\":[\"HVAC Unit\"],\"app.lib.buildPalette.kitchen\":[\"Kitchen\"],\"app.lib.buildPalette.lineset\":[\"Lineset\"],\"app.lib.buildPalette.liquidLine\":[\"Liquid Line\"],\"app.lib.buildPalette.mep\":[\"MEP\"],\"app.lib.buildPalette.register\":[\"Register\"],\"app.lib.buildPalette.roof\":[\"Roof\"],\"app.lib.buildPalette.shelf\":[\"Shelf\"],\"app.lib.buildPalette.slab\":[\"Slab\"],\"app.lib.buildPalette.spawnPoint\":[\"Spawn point\"],\"app.lib.buildPalette.stairs\":[\"Stairs\"],\"app.lib.buildPalette.terrace\":[\"Terrace\"],\"app.lib.buildPalette.terrain\":[\"Terrain\"],\"app.lib.buildPalette.wall\":[\"Wall\"],\"app.lib.buildPalette.window\":[\"Window\"],\"app.lib.buildPanelModel.addFitting\":[\"Add Fitting\"],\"app.lib.buildPanelModel.addGate\":[\"Add Gate\"],\"app.lib.buildPanelModel.addOpenPassage\":[\"Add Open Passage\"],\"app.lib.buildPanelModel.addTrap\":[\"Add Trap\"],\"app.lib.buildPanelModel.build\":[\"Build\"],\"app.lib.buildPanelModel.createFrom\":[\"Create from\"],\"app.lib.buildPanelModel.drawTheRoofFootprintWithTwo\":[\"Draw the roof footprint with two corner clicks.\"],\"app.lib.buildPanelModel.duct\":[\"Duct\"],\"app.lib.buildPanelModel.dwvPipe\":[\"DWV Pipe\"],\"app.lib.buildPanelModel.featuresExtensions\":[\"Features & extensions\"],\"app.lib.buildPanelModel.fence\":[\"Fence\"],\"app.lib.buildPanelModel.followLineset\":[\"Follow lineset\"],\"app.lib.buildPanelModel.gatesOpenings\":[\"Gates & openings\"],\"app.lib.buildPanelModel.hoverARoomToPreviewIts\":[\"Hover a room to preview its boundary, then click to place.\"],\"app.lib.buildPanelModel.kitchen\":[\"Kitchen\"],\"app.lib.buildPanelModel.mep\":[\"MEP\"],\"app.lib.buildPanelModel.modularCabinet\":[\"Modular Cabinet\"],\"app.lib.buildPanelModel.off\":[\"Off\"],\"app.lib.buildPanelModel.on\":[\"On\"],\"app.lib.buildPanelModel.painting\":[\"Painting\"],\"app.lib.buildPanelModel.roof\":[\"Roof\"],\"app.lib.buildPanelModel.roofType\":[\"Roof type\"],\"app.lib.buildPanelModel.toolCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" tool\"],\"other\":[\"#\",\" tools\"]}]],\"app.lib.buildTabState.conical\":[\"Conical\"],\"app.lib.buildTabState.draw\":[\"Draw\"],\"app.lib.buildTabState.dutch\":[\"Dutch\"],\"app.lib.buildTabState.flat\":[\"Flat\"],\"app.lib.buildTabState.gable\":[\"Gable\"],\"app.lib.buildTabState.gambrel\":[\"Gambrel\"],\"app.lib.buildTabState.hip\":[\"Hip\"],\"app.lib.buildTabState.mansard\":[\"Mansard\"],\"app.lib.buildTabState.room\":[\"Room\"],\"app.lib.buildTabState.shed\":[\"Shed\"],\"app.lib.buildTabState.wall\":[\"Wall\"],\"app.sceneLoader.anotherSessionSavedFirstRefresh\":[\"Another session saved first — refresh?\"],\"app.sceneLoader.build\":[\"Build\"],\"app.sceneLoader.dismiss\":[\"Dismiss\"],\"app.sceneLoader.items\":[\"Items\"],\"app.sceneLoader.paint\":[\"Paint\"],\"app.sceneLoader.reload\":[\"Reload\"],\"app.sceneLoader.scene\":[\"Scene\"],\"app.sceneLoader.settings\":[\"Settings\"],\"app.sceneLoader.yourChangesHavenTBeenSaved\":[\"Your changes haven't been saved. Reload to pick up the latest version.\"],\"app.sharePanel.anyoneWithTheLinkCanView\":[\"Anyone with the link can view this project. They cannot edit it.\"],\"app.sharePanel.copied\":[\"Copied\"],\"app.sharePanel.copy\":[\"Copy\"],\"app.sharePanel.couldNotReadTheSharingState\":[\"Could not read the sharing state.\"],\"app.sharePanel.couldNotTurnOffThePublic\":[\"Could not turn off the public link.\"],\"app.sharePanel.couldNotTurnOnThePublic\":[\"Could not turn on the public link.\"],\"app.sharePanel.embedCode\":[\"Embed code\"],\"app.sharePanel.embedLink\":[\"Embed link\"],\"app.sharePanel.hideControls\":[\"Hide controls\"],\"app.sharePanel.link\":[\"Link\"],\"app.sharePanel.onlyThe3dViewNoHeader\":[\"Only the 3D view, no header or toolbar.\"],\"app.sharePanel.open\":[\"Open\"],\"app.sharePanel.publicLink\":[\"Public link\"],\"app.sharePanel.shareProject\":[\"Share project\"],\"app.viewerToolbar.automaticDimensions\":[\"Automatic dimensions\"],\"app.viewerToolbar.camera\":[\"Camera\"],\"app.viewerToolbar.cleanPlanDimensionsAppearWithSelection\":[\"Clean plan; dimensions appear with selection\"],\"app.viewerToolbar.collapseSidebar\":[\"Collapse sidebar\"],\"app.viewerToolbar.crispOpaqueEdgeLines\":[\"Crisp, opaque edge lines\"],\"app.viewerToolbar.cutaway\":[\"Cutaway\"],\"app.viewerToolbar.default\":[\"Default\"],\"app.viewerToolbar.display\":[\"Display\"],\"app.viewerToolbar.displaySettings\":[\"Display settings\"],\"app.viewerToolbar.doorWindowMarks\":[\"Door/window marks\"],\"app.viewerToolbar.edges\":[\"Edges\"],\"app.viewerToolbar.enterVr\":[\"Enter VR\"],\"app.viewerToolbar.exitVr\":[\"Exit VR\"],\"app.viewerToolbar.expandSidebar\":[\"Expand sidebar\"],\"app.viewerToolbar.expert\":[\"Expert\"],\"app.viewerToolbar.exploded\":[\"Exploded\"],\"app.viewerToolbar.faceOfStud\":[\"Face of stud\"],\"app.viewerToolbar.faintOutlineOfMajorCreases\":[\"Faint outline of major creases\"],\"app.viewerToolbar.feetInches\":[\"Feet & inches\"],\"app.viewerToolbar.finishedFaces\":[\"Finished faces\"],\"app.viewerToolbar.flatAndFastNoAmbientOcclusion\":[\"Flat and fast — no ambient occlusion\"],\"app.viewerToolbar.floorPlanAnnotations\":[\"Floor plan annotations\"],\"app.viewerToolbar.floorPlanMode\":[\"Floor plan mode\"],\"app.viewerToolbar.fullAmbientOcclusion\":[\"Full ambient occlusion\"],\"app.viewerToolbar.fullDocumentationAndAnnotationControls\":[\"Full documentation and annotation controls\"],\"app.viewerToolbar.fullHeight\":[\"Full height\"],\"app.viewerToolbar.fullWallThickness\":[\"Full wall thickness\"],\"app.viewerToolbar.grid\":[\"Grid\"],\"app.viewerToolbar.levelsMode\":[\"Levels: \",[\"mode\"]],\"app.viewerToolbar.low\":[\"Low\"],\"app.viewerToolbar.magneticSnap\":[\"Magnetic snap\"],\"app.viewerToolbar.manual\":[\"Manual\"],\"app.viewerToolbar.manualDimensions\":[\"Manual dimensions\"],\"app.viewerToolbar.measurements\":[\"Measurements\"],\"app.viewerToolbar.meters\":[\"Meters\"],\"app.viewerToolbar.millimeters\":[\"Millimeters\"],\"app.viewerToolbar.n3dMeasurements\":[\"3D measurements\"],\"app.viewerToolbar.noEdgeLines\":[\"No edge lines\"],\"app.viewerToolbar.off\":[\"Off\"],\"app.viewerToolbar.on\":[\"On\"],\"app.viewerToolbar.orthographic\":[\"Orthographic\"],\"app.viewerToolbar.perspective\":[\"Perspective\"],\"app.viewerToolbar.preview\":[\"Preview\"],\"app.viewerToolbar.previewMode\":[\"Preview mode\"],\"app.viewerToolbar.render\":[\"Render\"],\"app.viewerToolbar.rendered\":[\"Rendered\"],\"app.viewerToolbar.roomLabels\":[\"Room labels\"],\"app.viewerToolbar.sceneTheme\":[\"Scene theme\"],\"app.viewerToolbar.shadows\":[\"Shadows\"],\"app.viewerToolbar.singleWallAxis\":[\"Single wall axis\"],\"app.viewerToolbar.soft\":[\"Soft\"],\"app.viewerToolbar.solid\":[\"Solid\"],\"app.viewerToolbar.solo\":[\"Solo\"],\"app.viewerToolbar.stack\":[\"Stack\"],\"app.viewerToolbar.stairAnnotations\":[\"Stair annotations\"],\"app.viewerToolbar.strong\":[\"Strong\"],\"app.viewerToolbar.structuralCoreFace\":[\"Structural core face\"],\"app.viewerToolbar.structuralGridsColumnCenters\":[\"Structural grids & column centers\"],\"app.viewerToolbar.translucent\":[\"Translucent\"],\"app.viewerToolbar.units\":[\"Units\"],\"app.viewerToolbar.walkthrough\":[\"Walkthrough\"],\"app.viewerToolbar.wallCenterline\":[\"Wall centerline\"],\"app.viewerToolbar.wallDimensions\":[\"Wall dimensions\"],\"app.viewerToolbar.wallsLabel\":[\"Walls: \",[\"label\"]],\"core.level.basement\":[\"Basement\"],\"core.level.basementNumber\":[\"Basement \",[\"number\"]],\"core.level.floorNumber\":[\"Floor \",[\"number\"]],\"core.level.groundFloor\":[\"Ground floor\"],\"editor.actionMenu.cameraActions.orbitLeft\":[\"Orbit Left\"],\"editor.actionMenu.cameraActions.orbitRight\":[\"Orbit Right\"],\"editor.actionMenu.cameraActions.topView\":[\"Top View\"],\"editor.actionMenu.controlModes.delete\":[\"Delete\"],\"editor.actionMenu.controlModes.select\":[\"Select\"],\"editor.actionMenu.followPascalPill.resumeFollowing\":[\"Resume following\"],\"editor.actionMenu.followPascalToggle.off\":[\"Follow Pascal: Off\"],\"editor.actionMenu.followPascalToggle.on\":[\"Follow Pascal: On\"],\"editor.actionMenu.furnishTools.appliance\":[\"Appliance\"],\"editor.actionMenu.furnishTools.bathroom\":[\"Bathroom\"],\"editor.actionMenu.furnishTools.furniture\":[\"Furniture\"],\"editor.actionMenu.furnishTools.kitchen\":[\"Kitchen\"],\"editor.actionMenu.furnishTools.outdoor\":[\"Outdoor\"],\"editor.actionMenu.measurementControl.angle\":[\"Angle\"],\"editor.actionMenu.measurementControl.angularDimension\":[\"Angular dimension\"],\"editor.actionMenu.measurementControl.arcLength\":[\"Arc length\"],\"editor.actionMenu.measurementControl.area\":[\"Area\"],\"editor.actionMenu.measurementControl.centerMark\":[\"Center mark\"],\"editor.actionMenu.measurementControl.chordDimension\":[\"Chord dimension\"],\"editor.actionMenu.measurementControl.continuousDimension\":[\"Continuous dimension\"],\"editor.actionMenu.measurementControl.coordinateDimensions\":[\"Coordinate dimensions\"],\"editor.actionMenu.measurementControl.diameterDimension\":[\"Diameter dimension\"],\"editor.actionMenu.measurementControl.distance\":[\"Distance\"],\"editor.actionMenu.measurementControl.floorPlan\":[\"Floor plan\"],\"editor.actionMenu.measurementControl.linearDimension\":[\"Linear dimension\"],\"editor.actionMenu.measurementControl.measureSelectedlabel\":[\"Measure: \",[\"selectedLabel\"]],\"editor.actionMenu.measurementControl.measurementOptions\":[\"Measurement options\"],\"editor.actionMenu.measurementControl.measurementType\":[\"Measurement type\"],\"editor.actionMenu.measurementControl.perimeter\":[\"Perimeter\"],\"editor.actionMenu.measurementControl.radiusDimension\":[\"Radius dimension\"],\"editor.actionMenu.measurementControl.smart\":[\"Smart\"],\"editor.actionMenu.measurementControl.volume\":[\"Volume\"],\"editor.actionMenu.pointAskButton.pointAndAsk\":[\"Point and ask\"],\"editor.actionMenu.pointAskButton.pointAndAskC\":[\"Point and ask (C)\"],\"editor.actionMenu.pointAskButton.pointAndAskWorksInThe\":[\"Point and ask works in the 3D view\"],\"editor.actionMenu.structureTools.ceiling\":[\"Ceiling\"],\"editor.actionMenu.structureTools.column\":[\"Column\"],\"editor.actionMenu.structureTools.door\":[\"Door\"],\"editor.actionMenu.structureTools.duct\":[\"Duct\"],\"editor.actionMenu.structureTools.ductFitting\":[\"Duct Fitting\"],\"editor.actionMenu.structureTools.dwvPipe\":[\"DWV Pipe\"],\"editor.actionMenu.structureTools.elevator\":[\"Elevator\"],\"editor.actionMenu.structureTools.fence\":[\"Fence\"],\"editor.actionMenu.structureTools.gableRoof\":[\"Gable Roof\"],\"editor.actionMenu.structureTools.hvacUnit\":[\"HVAC Unit\"],\"editor.actionMenu.structureTools.lineset\":[\"Lineset\"],\"editor.actionMenu.structureTools.liquidLine\":[\"Liquid Line\"],\"editor.actionMenu.structureTools.pipeFitting\":[\"Pipe Fitting\"],\"editor.actionMenu.structureTools.register\":[\"Register\"],\"editor.actionMenu.structureTools.shelf\":[\"Shelf\"],\"editor.actionMenu.structureTools.slab\":[\"Slab\"],\"editor.actionMenu.structureTools.spawnPoint\":[\"Spawn Point\"],\"editor.actionMenu.structureTools.stairs\":[\"Stairs\"],\"editor.actionMenu.structureTools.trap\":[\"Trap\"],\"editor.actionMenu.structureTools.wall\":[\"Wall\"],\"editor.actionMenu.structureTools.window\":[\"Window\"],\"editor.actionMenu.structureTools.zone\":[\"Zone\"],\"editor.actionMenu.viewToggles.deleteGuideImage\":[\"Delete guide image\"],\"editor.actionMenu.viewToggles.deleteScan\":[\"Delete scan\"],\"editor.actionMenu.viewToggles.guideCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" guide image on this level\"],\"other\":[\"#\",\" guide images on this level\"]}]],\"editor.actionMenu.viewToggles.guideImageNumber\":[\"Guide image \",[\"number\"]],\"editor.actionMenu.viewToggles.guideImageSettings\":[\"Guide image settings\"],\"editor.actionMenu.viewToggles.guideImages\":[\"Guide images\"],\"editor.actionMenu.viewToggles.guides\":[\"Guides\"],\"editor.actionMenu.viewToggles.guidesHidden\":[\"Guides: Hidden\"],\"editor.actionMenu.viewToggles.guidesVisible\":[\"Guides: Visible\"],\"editor.actionMenu.viewToggles.hideGuideImages\":[\"Hide guide images\"],\"editor.actionMenu.viewToggles.hideReferenceFloor\":[\"Hide reference floor\"],\"editor.actionMenu.viewToggles.hideScans\":[\"Hide scans\"],\"editor.actionMenu.viewToggles.levelsBelow\":[[\"count\"],\" below\"],\"editor.actionMenu.viewToggles.noLowerFloorAvailable\":[\"No lower floor available.\"],\"editor.actionMenu.viewToggles.opacity\":[\"Opacity\"],\"editor.actionMenu.viewToggles.referenceFloor\":[\"Reference floor\"],\"editor.actionMenu.viewToggles.referenceFloorSelectedlevelname\":[\"Reference floor: \",[\"selectedLevelName\"]],\"editor.actionMenu.viewToggles.referenceFloorSettings\":[\"Reference floor settings\"],\"editor.actionMenu.viewToggles.referenceSettings\":[\"Reference settings\"],\"editor.actionMenu.viewToggles.references\":[\"References\"],\"editor.actionMenu.viewToggles.referencesEmpty\":[\"Upload GLB meshes as scan references or blueprint images as guide references.\"],\"editor.actionMenu.viewToggles.referencesHidden\":[\"References: Hidden\"],\"editor.actionMenu.viewToggles.referencesVisible\":[\"References: Visible\"],\"editor.actionMenu.viewToggles.riserDiagram\":[\"Riser diagram\"],\"editor.actionMenu.viewToggles.scanCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" scan on this level\"],\"other\":[\"#\",\" scans on this level\"]}]],\"editor.actionMenu.viewToggles.scanNumber\":[\"Scan \",[\"number\"]],\"editor.actionMenu.viewToggles.scanSettings\":[\"Scan settings\"],\"editor.actionMenu.viewToggles.scans\":[\"Scans\"],\"editor.actionMenu.viewToggles.scansHidden\":[\"Scans: Hidden\"],\"editor.actionMenu.viewToggles.scansVisible\":[\"Scans: Visible\"],\"editor.actionMenu.viewToggles.showGuideImages\":[\"Show guide images\"],\"editor.actionMenu.viewToggles.showReferenceFloor\":[\"Show reference floor\"],\"editor.actionMenu.viewToggles.showScans\":[\"Show scans\"],\"editor.actionMenu.viewToggles.uploadScanOrGuideImage\":[\"Upload scan or guide image\"],\"editor.backToHome\":[\"Back to home\"],\"editor.buildPanel.advanced\":[\"Advanced\"],\"editor.buildPanel.rooms.hint\":[\"walls · floor · ceiling\"],\"editor.buildPanel.rooms.idle\":[\"Pick a shape to start. Every room brings its own walls, floor and ceiling.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" then the first one again to close. For L-shapes and angled rooms.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polygon\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Click each corner,\"],\"editor.buildPanel.rooms.polygon.title\":[\"Polygon room\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" on the grid. Walls, floor and ceiling are created together.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectangle\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Click two corners\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Rectangle room\"],\"editor.buildPanel.rooms.title\":[\"Rooms\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\" Close a loop and it becomes a room; leave it open for partitions.\"],\"editor.buildPanel.rooms.walls.label\":[\"Walls\"],\"editor.buildPanel.rooms.walls.lead\":[\"Draw one wall at a time.\"],\"editor.buildPanel.rooms.walls.title\":[\"Walls\"],\"editor.cameraControlsHint\":[\"Camera controls hint\"],\"editor.cameraHint.pan\":[\"Pan\"],\"editor.cameraHint.rotate\":[\"Rotate\"],\"editor.cameraHint.zoom\":[\"Zoom\"],\"editor.controls.finishSwatch.ariaLabel\":[[\"label\"],\": \",[\"finish\"]],\"editor.controls.finishSwatch.default\":[\"Default\"],\"editor.dismiss\":[\"Dismiss\"],\"editor.dismissCameraControlsHint\":[\"Dismiss camera controls hint\"],\"editor.expandSidebar\":[\"Expand sidebar\"],\"editor.floatingActionMenu.axis\":[\"Axis\"],\"editor.floatingActionMenu.axis2\":[\"⌥ axis\"],\"editor.floatingActionMenu.noEquipment\":[\"⚠ no equipment\"],\"editor.floatingActionMenu.rTRotate\":[\"R/T rotate\"],\"editor.floatingActionMenu.registerCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" register\"],\"other\":[\"#\",\" registers\"]}]],\"editor.floatingActionMenu.runCount\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" run\"],\"other\":[[\"number\"],\" runs\"]}]],\"editor.floatingActionMenu.system\":[\"System\"],\"editor.floatingLevelSelector.addAFloorUnderTheRoof\":[\"Add a floor under the roof\"],\"editor.floatingLevelSelector.addLevelAbove\":[\"Add level above\"],\"editor.floatingLevelSelector.addLevelBelow\":[\"Add level below\"],\"editor.floatingLevelSelector.cancel\":[\"Cancel\"],\"editor.floatingLevelSelector.delete\":[\"Delete\"],\"editor.floatingLevelSelector.deleteLevel\":[\"Delete level\"],\"editor.floatingLevelSelector.deleteLevelConfirm\":[\"Are you sure you want to delete \",[\"levelName\"],\"? All walls, floors, and objects on this level will be permanently removed.\"],\"editor.floatingLevelSelector.dragToReorder\":[\"Drag to reorder\"],\"editor.floatingLevelSelector.duplicateLevel\":[\"Duplicate level\"],\"editor.floatingLevelSelector.duplicateWithOptions\":[\"Duplicate with options...\"],\"editor.floatingLevelSelector.exitUnitFocus\":[\"Exit unit focus\"],\"editor.floatingLevelSelector.focusedUnitHasZonesHere\":[\"Focused unit has zones here\"],\"editor.floatingLevelSelector.groundFloorOverGrade\":[\"The ground floor stands \",[\"distance\"],\" over grade. Seat the ground floor 8 in over grade\"],\"editor.floatingLevelSelector.groundFloorUnderGrade\":[\"The ground floor stands \",[\"distance\"],\" under grade. Seat the ground floor 8 in over grade\"],\"editor.floatingLevelSelector.groundLineTheLevelsUnderIt\":[\"Ground line: the levels under it are below grade\"],\"editor.floatingLevelSelector.groundOffset\":[\"Ground · \",[\"offset\"]],\"editor.floatingLevelSelector.insertLevelHere\":[\"Insert level here\"],\"editor.floatingLevelSelector.levelHeight\":[\"Level height\"],\"editor.floatingLevelSelector.pasteCopiedSelection\":[\"Paste copied selection\"],\"editor.floatingLevelSelector.reorderLevelname\":[\"Reorder \",[\"levelName\"]],\"editor.floatingLevelSelector.seatTheBuildingSoThisFloor\":[\"Seat the building so this floor stands at grade; the floors under it are below grade\"],\"editor.floatingLevelSelector.setAsGroundFloor\":[\"Set as ground floor\"],\"editor.floatingLevelSelector.theGroundLevelCannotBeDeleted\":[\"The ground level cannot be deleted\"],\"editor.floatingLevelSelector.thisFloorStandsAtGrade\":[\"This floor stands at grade\"],\"editor.floatingLevelSelector.unit\":[\"Unit\"],\"editor.floorFootprint.area\":[\"Floor area\"],\"editor.floorFootprint.room\":[[\"room\"],\" floor\"],\"editor.floorFootprint.shared\":[\"Shared floor\"],\"editor.helpers.buildingHelper.cancel\":[\"Cancel\"],\"editor.helpers.buildingHelper.placeBuilding\":[\"Place building\"],\"editor.helpers.buildingHelper.rotate\":[\"Rotate\"],\"editor.helpers.contextualHelperPanel.cancelPlacement\":[\"Cancel placement\"],\"editor.helpers.contextualHelperPanel.continuationClickOrPressCTo\":[\"Continuation — click or press C to cycle\"],\"editor.helpers.contextualHelperPanel.continuationLabel\":[\"Continuation: \",[\"label\"]],\"editor.helpers.contextualHelperPanel.dragToDrawFence\":[\"Drag to draw fence\"],\"editor.helpers.contextualHelperPanel.eraseScope\":[\"Erase: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift\":[\"Erase scope — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.eraseScopeLabel\":[\"Erase scope: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT\":[\"Fence type — click or press T to switch between straight, curved and freehand\"],\"editor.helpers.contextualHelperPanel.fewerShortcuts\":[\"Fewer shortcuts\"],\"editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick\":[\"Finish curve (or double-click)\"],\"editor.helpers.contextualHelperPanel.gridLength\":[\"Grid: \",[\"length\"]],\"editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl\":[\"Grid step — click or tap Ctrl to cycle\"],\"editor.helpers.contextualHelperPanel.gridStepLength\":[\"Grid step: \",[\"length\"]],\"editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase\":[\"Hover a painted surface to erase\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPaint\":[\"Hover a surface to paint\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts\":[\"Hover a surface to pick its material\"],\"editor.helpers.contextualHelperPanel.moreShortcuts\":[\"More shortcuts\"],\"editor.helpers.contextualHelperPanel.paintScope\":[\"Paint: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift\":[\"Paint scope — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.paintScopeLabel\":[\"Paint scope: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.pickScope\":[\"Pick: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.placeGateOnAFence\":[\"Place gate on a fence\"],\"editor.helpers.contextualHelperPanel.placePassageOnAFence\":[\"Place passage on a fence\"],\"editor.helpers.contextualHelperPanel.selectAMaterialToPaint\":[\"Select a material to paint\"],\"editor.helpers.contextualHelperPanel.snappingAngles\":[\"Snapping: Angles\"],\"editor.helpers.contextualHelperPanel.snappingGrid\":[\"Snapping: Grid\"],\"editor.helpers.contextualHelperPanel.snappingLines\":[\"Snapping: Lines\"],\"editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift\":[\"Snapping mode — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.snappingOff\":[\"Snapping: Off\"],\"editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved\":[\"Straight continuation is unavailable for curved or freehand fences\"],\"editor.helpers.contextualHelperPanel.straightContinuous\":[\"Straight: Continuous\"],\"editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress\":[\"Straight fence continuation — click or press C to toggle\"],\"editor.helpers.contextualHelperPanel.straightSingle\":[\"Straight: Single\"],\"editor.helpers.helperManager.addPoint\":[\"Add point\"],\"editor.helpers.helperManager.addPointClickTheFirstTo\":[\"Add point · click the first to close\"],\"editor.helpers.helperManager.brushSize\":[\"Brush size\"],\"editor.helpers.helperManager.cancel\":[\"Cancel\"],\"editor.helpers.helperManager.cancelPicking\":[\"Cancel picking\"],\"editor.helpers.helperManager.cancelStroke\":[\"Cancel stroke\"],\"editor.helpers.helperManager.done\":[\"Done\"],\"editor.helpers.helperManager.drawTheMezzanine\":[\"Draw the mezzanine\"],\"editor.helpers.helperManager.drawTheOpening\":[\"Draw the opening\"],\"editor.helpers.helperManager.drawTheTerrace\":[\"Draw the terrace\"],\"editor.helpers.helperManager.exitSmartMeasure\":[\"Exit smart measure\"],\"editor.helpers.helperManager.finish\":[\"Finish\"],\"editor.helpers.helperManager.finishAtTheEdge\":[\"Finish at the edge\"],\"editor.helpers.helperManager.freeMove\":[\"Free move\"],\"editor.helpers.helperManager.freeMoveSlideDoorsAndWindows\":[\"Free move · slide doors and windows\"],\"editor.helpers.helperManager.holdToPickAMaterial\":[\"Hold to pick a material\"],\"editor.helpers.helperManager.inspectSurfaceDimensions\":[\"Inspect surface dimensions\"],\"editor.helpers.helperManager.paintWithThisMaterial\":[\"Paint with this material\"],\"editor.helpers.helperManager.pickTargetHeight\":[\"Pick target height\"],\"editor.helpers.helperManager.pinMeasurementLens\":[\"Pin measurement lens\"],\"editor.helpers.helperManager.place\":[\"Place\"],\"editor.helpers.helperManager.placeInsideTheRoom\":[\"Place inside the room\"],\"editor.helpers.helperManager.pressAnywhereDragToTheOpposite\":[\"Press anywhere, drag to the opposite corner\"],\"editor.helpers.helperManager.raiseOrLower\":[\"Raise or lower\"],\"editor.helpers.helperManager.removeAPaintedPartOrReset\":[\"Remove a painted part, or reset a surface\"],\"editor.helpers.helperManager.removeLastPoint\":[\"Remove last point\"],\"editor.helpers.helperManager.resize\":[\"Resize\"],\"editor.helpers.helperManager.rotate45\":[\"Rotate ±45°\"],\"editor.helpers.helperManager.rotateTheSelection45\":[\"Rotate the selection ±45°\"],\"editor.helpers.helperManager.setOneCornerThenTheOpposite\":[\"Set one corner, then the opposite\"],\"editor.helpers.itemHelper.cancel\":[\"Cancel\"],\"editor.helpers.itemHelper.forcePlace\":[\"Force place\"],\"editor.helpers.itemHelper.place\":[\"Place\"],\"editor.helpers.itemHelper.rotate\":[\"Rotate\"],\"editor.helpers.registeredToolHelper.guidedConstraintsBypassed\":[\"Guided constraints bypassed\"],\"editor.inspector.field.angle.g10zaw\":[\"Angle\"],\"editor.inspector.field.armLengthM.1jclqgw\":[\"Arm length m\"],\"editor.inspector.field.attachmentSide.6n9s5v\":[\"Attachment side\"],\"editor.inspector.field.autoHangers.10554ka\":[\"Auto hangers\"],\"editor.inspector.field.autoMiterCorners.zxr5lt\":[\"Auto miter corners\"],\"editor.inspector.field.autoSpan.1gas9r6\":[\"Auto span\"],\"editor.inspector.field.backRatio.1ughd99\":[\"Back ratio\"],\"editor.inspector.field.baseHeight.1hs4zdf\":[\"Base height\"],\"editor.inspector.field.baseStyle.24vty5\":[\"Base style\"],\"editor.inspector.field.beamHeight.1kq0oh1\":[\"Beam height\"],\"editor.inspector.field.beamWidth.1q1mzbe\":[\"Beam width\"],\"editor.inspector.field.bodyShape.arvkuw\":[\"Body shape\"],\"editor.inspector.field.bracketStyle.1vvoptm\":[\"Bracket style\"],\"editor.inspector.field.branchAngle.v56zme\":[\"Branch angle\"],\"editor.inspector.field.cabHeight.1yn34mk\":[\"Cab height\"],\"editor.inspector.field.canopyForm.1czzggp\":[\"Canopy form\"],\"editor.inspector.field.cap.s0thej\":[\"Cap\"],\"editor.inspector.field.capOverhang.kxxz9j\":[\"Cap overhang\"],\"editor.inspector.field.capShape.17mbxhu\":[\"Cap shape\"],\"editor.inspector.field.capThickness.u835q9\":[\"Cap thickness\"],\"editor.inspector.field.carcassHeight.1aks0zy\":[\"Carcass height\"],\"editor.inspector.field.cleanoutStyle.127zz6z\":[\"Cleanout style\"],\"editor.inspector.field.collarDiameter.1d5k28r\":[\"Collar diameter\"],\"editor.inspector.field.collarHeight.1dqwsrf\":[\"Collar height\"],\"editor.inspector.field.collarShape.1gacv11\":[\"Collar shape\"],\"editor.inspector.field.collarWidth.qvc9ik\":[\"Collar width\"],\"editor.inspector.field.columns.1sy73ks\":[\"Columns\"],\"editor.inspector.field.conicalFullCircle.1pgkpz5\":[\"Conical full circle\"],\"editor.inspector.field.connectionMode.1nty478\":[\"Connection mode\"],\"editor.inspector.field.connectionOffset.2m70s8\":[\"Connection offset\"],\"editor.inspector.field.cornerBevel.16iniho\":[\"Corner bevel\"],\"editor.inspector.field.coveringType.95yd3k\":[\"Covering type\"],\"editor.inspector.field.cricketHeight.pok31n\":[\"Cricket height\"],\"editor.inspector.field.cricketLength.i2aht8\":[\"Cricket length\"],\"editor.inspector.field.cricketSide.1xnwil7\":[\"Cricket side\"],\"editor.inspector.field.cricketStyle.10j0lat\":[\"Cricket style\"],\"editor.inspector.field.curb.5xi2ol\":[\"Curb\"],\"editor.inspector.field.curbHeight.1yn97sg\":[\"Curb height\"],\"editor.inspector.field.curveOffset.l4xhvr\":[\"Curve offset\"],\"editor.inspector.field.damperAngle.h5fxn7\":[\"Damper angle\"],\"editor.inspector.field.deckThickness.4jmqze\":[\"Deck thickness\"],\"editor.inspector.field.defaultLevelId.xmrjzp\":[\"Default level id\"],\"editor.inspector.field.depth.1lu11u2\":[\"Depth\"],\"editor.inspector.field.diameter.s3robe\":[\"Diameter\"],\"editor.inspector.field.diameter2.pm6cl4\":[\"Diameter2\"],\"editor.inspector.field.doorDurationMs.13nx909\":[\"Door duration ms\"],\"editor.inspector.field.doorHeight.92r9k4\":[\"Door height\"],\"editor.inspector.field.doorPanelStyle.2qog4q\":[\"Door panel style\"],\"editor.inspector.field.doorStyle.gerpos\":[\"Door style\"],\"editor.inspector.field.doorWidth.sy732l\":[\"Door width\"],\"editor.inspector.field.downspoutEnabled.1gn00nv\":[\"Downspout enabled\"],\"editor.inspector.field.downspoutPosition.fxcsop\":[\"Downspout position\"],\"editor.inspector.field.ductMaterial.i8eonq\":[\"Duct material\"],\"editor.inspector.field.dutchGabletRake.6paydp\":[\"Dutch gablet rake\"],\"editor.inspector.field.dutchHipHeightRatio.1etnwgg\":[\"Dutch hip height ratio\"],\"editor.inspector.field.dutchHipWidthRatio.1dopfvb\":[\"Dutch hip width ratio\"],\"editor.inspector.field.dutchTopRakeThickness.1w9khxt\":[\"Dutch top rake thickness\"],\"editor.inspector.field.dutchWaistLengthRatio.agtoxw\":[\"Dutch waist length ratio\"],\"editor.inspector.field.dwellMs.1lkk0o5\":[\"Dwell ms\"],\"editor.inspector.field.edgeInset.1obeimx\":[\"Edge inset\"],\"editor.inspector.field.elevation.4jbp44\":[\"Elevation\"],\"editor.inspector.field.endCapLeft.19jel4v\":[\"End cap left\"],\"editor.inspector.field.endCapRight.lhse9c\":[\"End cap right\"],\"editor.inspector.field.endCaps.4fsb5b\":[\"End caps\"],\"editor.inspector.field.endPost.1guwsk0\":[\"End post\"],\"editor.inspector.field.equipmentType.1mc2ywt\":[\"Equipment type\"],\"editor.inspector.field.fillToFloor.1oppnkf\":[\"Fill to floor\"],\"editor.inspector.field.finial.1ertf4s\":[\"Finial\"],\"editor.inspector.field.fittingType.18mz17s\":[\"Fitting type\"],\"editor.inspector.field.flashingHeight.3e8nn6\":[\"Flashing height\"],\"editor.inspector.field.flashingProjection.hhf0fk\":[\"Flashing projection\"],\"editor.inspector.field.flueCount.j3t2ci\":[\"Flue count\"],\"editor.inspector.field.flueDiameter.cjhubg\":[\"Flue diameter\"],\"editor.inspector.field.flueHeight.1dhc4y4\":[\"Flue height\"],\"editor.inspector.field.flueShape.1ealiy4\":[\"Flue shape\"],\"editor.inspector.field.flueSpacing.exhzco\":[\"Flue spacing\"],\"editor.inspector.field.footingStyle.e3btg6\":[\"Footing style\"],\"editor.inspector.field.frameDepth.1934a5n\":[\"Frame depth\"],\"editor.inspector.field.frameThickness.1agxzd8\":[\"Frame thickness\"],\"editor.inspector.field.framingStrategy.3jx9qe\":[\"Framing strategy\"],\"editor.inspector.field.fromLevelId.15ieejs\":[\"From level id\"],\"editor.inspector.field.gableOverhang.2z0obw\":[\"Gable overhang\"],\"editor.inspector.field.gambrelLowerHeightRatio.wqq7lg\":[\"Gambrel lower height ratio\"],\"editor.inspector.field.gambrelLowerWidthRatio.uvyb1v\":[\"Gambrel lower width ratio\"],\"editor.inspector.field.gapX.wjxa7\":[\"Gap x\"],\"editor.inspector.field.gapY.mkbl8\":[\"Gap y\"],\"editor.inspector.field.glassThickness.s97b0x\":[\"Glass thickness\"],\"editor.inspector.field.groundClearance.4os7f0\":[\"Ground clearance\"],\"editor.inspector.field.guardInfill.16heiig\":[\"Guard infill\"],\"editor.inspector.field.gutterEnabled.1oxiltf\":[\"Gutter enabled\"],\"editor.inspector.field.gutterProfile.1ngzqi3\":[\"Gutter profile\"],\"editor.inspector.field.gutterSize.jgavtz\":[\"Gutter size\"],\"editor.inspector.field.hangerMaxReach.1869epv\":[\"Hanger max reach\"],\"editor.inspector.field.hangerSpacing.i2b5d7\":[\"Hanger spacing\"],\"editor.inspector.field.hangerStyle.l7v7gb\":[\"Hanger style\"],\"editor.inspector.field.height.7puwki\":[\"Height\"],\"editor.inspector.field.height2.7b3jlc\":[\"Height2\"],\"editor.inspector.field.heightAboveRidge.1k99brk\":[\"Height above ridge\"],\"editor.inspector.field.highEdgeHeight.1s7v90t\":[\"High edge height\"],\"editor.inspector.field.highOverhang.67w55d\":[\"High overhang\"],\"editor.inspector.field.highSideMode.1rc1r5j\":[\"High side mode\"],\"editor.inspector.field.hoodOverhang.1q1hcin\":[\"Hood overhang\"],\"editor.inspector.field.infillPlacement.11dxzpo\":[\"Infill placement\"],\"editor.inspector.field.inletShape.ypayh2\":[\"Inlet shape\"],\"editor.inspector.field.innerRadius.1fkmg0j\":[\"Inner radius\"],\"editor.inspector.field.insulated.iwziey\":[\"Insulated\"],\"editor.inspector.field.insulationR.663ufx\":[\"Insulation r\"],\"editor.inspector.field.lanternHeight.oz7zpm\":[\"Lantern height\"],\"editor.inspector.field.lanternTopScale.hkmpp2\":[\"Lantern top scale\"],\"editor.inspector.field.ledgerDepth.7av8k9\":[\"Ledger depth\"],\"editor.inspector.field.ledgerHeight.zo2oif\":[\"Ledger height\"],\"editor.inspector.field.ledgerVerticalOffset.1athbgt\":[\"Ledger vertical offset\"],\"editor.inspector.field.leftEndCondition.1syqlaw\":[\"Left end condition\"],\"editor.inspector.field.leftOverhang.1ydl9ma\":[\"Left overhang\"],\"editor.inspector.field.length.1kpdcfp\":[\"Length\"],\"editor.inspector.field.liquidDiameter.w3pk7c\":[\"Liquid diameter\"],\"editor.inspector.field.louverCount.lhw2gr\":[\"Louver count\"],\"editor.inspector.field.lowBeamInset.1g7yw5l\":[\"Low beam inset\"],\"editor.inspector.field.lowEdgeHeight.47mae1\":[\"Low edge height\"],\"editor.inspector.field.lowOverhang.t4nz1h\":[\"Low overhang\"],\"editor.inspector.field.mansardSteepHeightRatio.1hbb8rq\":[\"Mansard steep height ratio\"],\"editor.inspector.field.mansardSteepWidthRatio.6xk6vd\":[\"Mansard steep width ratio\"],\"editor.inspector.field.matchHostRoofMaterial.s56zwj\":[\"Match host roof material\"],\"editor.inspector.field.matchHostRoofStructure.bojht3\":[\"Match host roof structure\"],\"editor.inspector.field.mount.h80tay\":[\"Mount\"],\"editor.inspector.field.mountingType.1xvin1e\":[\"Mounting type\"],\"editor.inspector.field.neckHeight.19ym8n1\":[\"Neck height\"],\"editor.inspector.field.opacity.1pmpfd2\":[\"Opacity\"],\"editor.inspector.field.openingAngle.brhi7c\":[\"Opening angle\"],\"editor.inspector.field.openingOffset.wpoqt6\":[\"Opening offset\"],\"editor.inspector.field.openingSide.18l2iwe\":[\"Opening side\"],\"editor.inspector.field.operationState.1lwrz07\":[\"Operation state\"],\"editor.inspector.field.outletShape.lgpqhx\":[\"Outlet shape\"],\"editor.inspector.field.overhang.b5ljpv\":[\"Overhang\"],\"editor.inspector.field.panelHeight.1kf31vk\":[\"Panel height\"],\"editor.inspector.field.panelWidth.946581\":[\"Panel width\"],\"editor.inspector.field.patternAlignment.jd1ht0\":[\"Pattern alignment\"],\"editor.inspector.field.patternCount.1r37pve\":[\"Pattern count\"],\"editor.inspector.field.patternDistribution.fmam51\":[\"Pattern distribution\"],\"editor.inspector.field.patternRemainder.i47p5y\":[\"Pattern remainder\"],\"editor.inspector.field.picketProfile.1xgzuyc\":[\"Picket profile\"],\"editor.inspector.field.picketRailCount.atgt6w\":[\"Picket rail count\"],\"editor.inspector.field.picketSpacing.p8d98w\":[\"Picket spacing\"],\"editor.inspector.field.picketTop.1tskplu\":[\"Picket top\"],\"editor.inspector.field.picketTopClearance.16eq1k0\":[\"Picket top clearance\"],\"editor.inspector.field.picketVariation.f7vl1c\":[\"Picket variation\"],\"editor.inspector.field.picketWidth.3fr3rh\":[\"Picket width\"],\"editor.inspector.field.pipeMaterial.1ggc2wy\":[\"Pipe material\"],\"editor.inspector.field.pitch.xe6ekx\":[\"Pitch\"],\"editor.inspector.field.position.1quewx6\":[\"Position\"],\"editor.inspector.field.postBracing.crq8nn\":[\"Post bracing\"],\"editor.inspector.field.postCap.1xcvxrv\":[\"Post cap\"],\"editor.inspector.field.postCount.22r56c\":[\"Post count\"],\"editor.inspector.field.postDepth.1ko6j1m\":[\"Post depth\"],\"editor.inspector.field.postInset.le36li\":[\"Post inset\"],\"editor.inspector.field.postLayoutMode.16jre62\":[\"Post layout mode\"],\"editor.inspector.field.postSize.1jodxek\":[\"Post size\"],\"editor.inspector.field.postSpacing.voppfi\":[\"Post spacing\"],\"editor.inspector.field.postThrough.1abgu4a\":[\"Post through\"],\"editor.inspector.field.postWidth.rk26fz\":[\"Post width\"],\"editor.inspector.field.profile.1kwlci6\":[\"Profile\"],\"editor.inspector.field.projection.jhtig0\":[\"Projection\"],\"editor.inspector.field.purlinHeight.eo9cjm\":[\"Purlin height\"],\"editor.inspector.field.purlinSpacing.4gbzb2\":[\"Purlin spacing\"],\"editor.inspector.field.purlinWidth.r6tu3j\":[\"Purlin width\"],\"editor.inspector.field.rafterEndInset.14c0wy5\":[\"Rafter end inset\"],\"editor.inspector.field.rafterHeight.1e1n99w\":[\"Rafter height\"],\"editor.inspector.field.rafterSpacing.19subc0\":[\"Rafter spacing\"],\"editor.inspector.field.rafterWidth.eksj4t\":[\"Rafter width\"],\"editor.inspector.field.railingHeight.6wf4bm\":[\"Railing height\"],\"editor.inspector.field.railingMode.ov7xpu\":[\"Railing mode\"],\"editor.inspector.field.railingStyle.1nyd2u\":[\"Railing style\"],\"editor.inspector.field.resizeLock.b2hbr0\":[\"Resize lock\"],\"editor.inspector.field.returnDiameter.166ozhe\":[\"Return diameter\"],\"editor.inspector.field.returnHeight.b9sq6y\":[\"Return height\"],\"editor.inspector.field.returnShape.1e3a27i\":[\"Return shape\"],\"editor.inspector.field.returnWidth.z9r3h3\":[\"Return width\"],\"editor.inspector.field.rightEndCondition.tjlw3l\":[\"Right end condition\"],\"editor.inspector.field.rightOverhang.yqbeft\":[\"Right overhang\"],\"editor.inspector.field.roofHeight.1uhy6qy\":[\"Roof height\"],\"editor.inspector.field.roofStyle.pawqfy\":[\"Roof style\"],\"editor.inspector.field.roofThickness.1xlr3rx\":[\"Roof thickness\"],\"editor.inspector.field.roofType.1m9kv5h\":[\"Roof type\"],\"editor.inspector.field.rotation.ly2rj\":[\"Rotation\"],\"editor.inspector.field.rows.1ba1neg\":[\"Rows\"],\"editor.inspector.field.scale.b6rpxt\":[\"Scale\"],\"editor.inspector.field.seamDetail.197za48\":[\"Seam detail\"],\"editor.inspector.field.segmentType.l667sy\":[\"Segment type\"],\"editor.inspector.field.shaftStyle.1i5ampa\":[\"Shaft style\"],\"editor.inspector.field.shaftWallThickness.bduslr\":[\"Shaft wall thickness\"],\"editor.inspector.field.shape.iq9t52\":[\"Shape\"],\"editor.inspector.field.shape2.fi0nx8\":[\"Shape2\"],\"editor.inspector.field.shedHighSide.bzzddg\":[\"Shed high side\"],\"editor.inspector.field.shingleThickness.1hn5wxr\":[\"Shingle thickness\"],\"editor.inspector.field.shoulderExtent.11j8uiv\":[\"Shoulder extent\"],\"editor.inspector.field.shoulderHeight.xyqn6g\":[\"Shoulder height\"],\"editor.inspector.field.shoulderStyle.1hkcv2w\":[\"Shoulder style\"],\"editor.inspector.field.showCenterColumn.1uggg45\":[\"Show center column\"],\"editor.inspector.field.showInfill.33rv7u\":[\"Show infill\"],\"editor.inspector.field.showStepSupports.1kg8h8m\":[\"Show step supports\"],\"editor.inspector.field.sideFlashing.cv744o\":[\"Side flashing\"],\"editor.inspector.field.size.1a4x3zw\":[\"Size\"],\"editor.inspector.field.skylightType.1a5vj3i\":[\"Skylight type\"],\"editor.inspector.field.slabOpeningMode.g4f730\":[\"Slab opening mode\"],\"editor.inspector.field.slatGap.slmk93\":[\"Slat gap\"],\"editor.inspector.field.slideDirection.1527q13\":[\"Slide direction\"],\"editor.inspector.field.span.1bmmuw1\":[\"Span\"],\"editor.inspector.field.speed.2w2xwg\":[\"Speed\"],\"editor.inspector.field.spinSpeed.1ozx746\":[\"Spin speed\"],\"editor.inspector.field.stairType.1u05e02\":[\"Stair type\"],\"editor.inspector.field.standoff.4wv2g0\":[\"Standoff\"],\"editor.inspector.field.standoffHeight.1qdnff\":[\"Standoff height\"],\"editor.inspector.field.startPost.1ood9kv\":[\"Start post\"],\"editor.inspector.field.stepCount.nzorrw\":[\"Step count\"],\"editor.inspector.field.strapSpacing.1cyt3dc\":[\"Strap spacing\"],\"editor.inspector.field.strapStyle.fh1oy4\":[\"Strap style\"],\"editor.inspector.field.style.nf3vcm\":[\"Style\"],\"editor.inspector.field.suctionDiameter.nutxtj\":[\"Suction diameter\"],\"editor.inspector.field.supplyDiameter.h5ldh5\":[\"Supply diameter\"],\"editor.inspector.field.supplyHeight.zy6ru1\":[\"Supply height\"],\"editor.inspector.field.supplyShape.k39wnb\":[\"Supply shape\"],\"editor.inspector.field.supplyWidth.g66axi\":[\"Supply width\"],\"editor.inspector.field.supportOffset.1w4cocr\":[\"Support offset\"],\"editor.inspector.field.sweepAngle.ucmmlw\":[\"Sweep angle\"],\"editor.inspector.field.system.13qbhrw\":[\"System\"],\"editor.inspector.field.terminal.1501knl\":[\"Terminal\"],\"editor.inspector.field.terminalType.1ne8lgb\":[\"Terminal type\"],\"editor.inspector.field.thickness.1l8hu45\":[\"Thickness\"],\"editor.inspector.field.tiltAngle.1312yw1\":[\"Tilt angle\"],\"editor.inspector.field.toLevelId.8i5qx7\":[\"To level id\"],\"editor.inspector.field.topLandingDepth.ki7gk2\":[\"Top landing depth\"],\"editor.inspector.field.topLandingMode.zk0h7e\":[\"Top landing mode\"],\"editor.inspector.field.topRailHeight.fscisv\":[\"Top rail height\"],\"editor.inspector.field.transitionMode.f3evgt\":[\"Transition mode\"],\"editor.inspector.field.transitionWidth.rtgz3m\":[\"Transition width\"],\"editor.inspector.field.vaneCount.55vtye\":[\"Vane count\"],\"editor.inspector.field.wallHeight.18vmh5c\":[\"Wall height\"],\"editor.inspector.field.wallShell.vqsyhv\":[\"Wall shell\"],\"editor.inspector.field.wallSkirtHeight.1uea2ad\":[\"Wall skirt height\"],\"editor.inspector.field.wallThickness.17wn4iv\":[\"Wall thickness\"],\"editor.inspector.field.width.gfxz9b\":[\"Width\"],\"editor.inspector.field.width2.1nqawvr\":[\"Width2\"],\"editor.inspector.field.withBack.1ay8nus\":[\"With back\"],\"editor.inspector.field.withBottom.1x1z1vo\":[\"With bottom\"],\"editor.inspector.field.withSides.uq066x\":[\"With sides\"],\"editor.inspector.group.advanced.qwfkor\":[\"Advanced\"],\"editor.inspector.group.air.15jotdj\":[\"Air\"],\"editor.inspector.group.appearance.dli9wf\":[\"Appearance\"],\"editor.inspector.group.body.ign4wl\":[\"Body\"],\"editor.inspector.group.cab.o4z1ox\":[\"Cab\"],\"editor.inspector.group.cabinet.rzvymr\":[\"Cabinet\"],\"editor.inspector.group.cap.s0thej\":[\"Cap\"],\"editor.inspector.group.collar.1am2iqo\":[\"Collar\"],\"editor.inspector.group.connection.2r1h4p\":[\"Connection\"],\"editor.inspector.group.connections.ai4w8u\":[\"Connections\"],\"editor.inspector.group.construction.j6p9kc\":[\"Construction\"],\"editor.inspector.group.cricket.6iwg8g\":[\"Cricket\"],\"editor.inspector.group.curb.5xi2ol\":[\"Curb\"],\"editor.inspector.group.curvePoints.ztdcnn\":[\"Curve points\"],\"editor.inspector.group.dimensions.pewa8g\":[\"Dimensions\"],\"editor.inspector.group.doors.18w2kpa\":[\"Doors\"],\"editor.inspector.group.dormer.1fjs5tq\":[\"Dormer\"],\"editor.inspector.group.dormerRoof.u9uy2g\":[\"Dormer roof\"],\"editor.inspector.group.drainage.7easy0\":[\"Drainage\"],\"editor.inspector.group.elevation.4jbp44\":[\"Elevation\"],\"editor.inspector.group.endCaps.4fsb5b\":[\"End caps\"],\"editor.inspector.group.equipment.1xtq0ev\":[\"Equipment\"],\"editor.inspector.group.face.1531fr0\":[\"Face\"],\"editor.inspector.group.fitting.171z1r4\":[\"Fitting\"],\"editor.inspector.group.flues.nq6pye\":[\"Flues\"],\"editor.inspector.group.footprint.g8q2pg\":[\"Footprint\"],\"editor.inspector.group.frame.q7q9fq\":[\"Frame\"],\"editor.inspector.group.geometry.a21os1\":[\"Geometry\"],\"editor.inspector.group.grid.d8gwxd\":[\"Grid\"],\"editor.inspector.group.hangers.agnfzn\":[\"Hangers\"],\"editor.inspector.group.hardware.1nygwm1\":[\"Hardware\"],\"editor.inspector.group.hungWall.1ftr1gz\":[\"Hung wall\"],\"editor.inspector.group.insulation.1kyo5e7\":[\"Insulation\"],\"editor.inspector.group.lantern.1h7o2xb\":[\"Lantern\"],\"editor.inspector.group.levels.1s6yaru\":[\"Levels\"],\"editor.inspector.group.line.166mv5z\":[\"Line\"],\"editor.inspector.group.lines.12bghr0\":[\"Lines\"],\"editor.inspector.group.motion.1be7sjr\":[\"Motion\"],\"editor.inspector.group.mounting.feutd2\":[\"Mounting\"],\"editor.inspector.group.opening.dfet3\":[\"Opening\"],\"editor.inspector.group.panelDimensions.b59qe6\":[\"Panel dimensions\"],\"editor.inspector.group.patternDistribution.fmam51\":[\"Pattern distribution\"],\"editor.inspector.group.placement.13lcvts\":[\"Placement\"],\"editor.inspector.group.position.1quewx6\":[\"Position\"],\"editor.inspector.group.profile.1kwlci6\":[\"Profile\"],\"editor.inspector.group.railing.19a5bnr\":[\"Railing\"],\"editor.inspector.group.return.1klrjrz\":[\"Return\"],\"editor.inspector.group.settings.ktdb8o\":[\"Settings\"],\"editor.inspector.group.shaft.mad9cv\":[\"Shaft\"],\"editor.inspector.group.shape.iq9t52\":[\"Shape\"],\"editor.inspector.group.shoulder.1nyfx25\":[\"Shoulder\"],\"editor.inspector.group.size.1a4x3zw\":[\"Size\"],\"editor.inspector.group.structure.gezdra\":[\"Structure\"],\"editor.inspector.group.style.nf3vcm\":[\"Style\"],\"editor.inspector.group.supply.1oy2zwu\":[\"Supply\"],\"editor.inspector.group.surface.wtamqk\":[\"Surface\"],\"editor.inspector.group.terminal.1501knl\":[\"Terminal\"],\"editor.inspector.group.topology.18t8dyc\":[\"Topology\"],\"editor.inspector.group.transform.1c55ayj\":[\"Transform\"],\"editor.inspector.group.trap.1qc7vhe\":[\"Trap\"],\"editor.inspector.group.type.1m2zofh\":[\"Type\"],\"editor.inspector.option.abs.132mz8b\":[\"Abs\"],\"editor.inspector.option.accessPanel.13sptb5\":[\"Access panel\"],\"editor.inspector.option.airHandler.1crnyg3\":[\"Air handler\"],\"editor.inspector.option.alternating.5jashi\":[\"Alternating\"],\"editor.inspector.option.arched.14188w0\":[\"Arched\"],\"editor.inspector.option.auto.540b06\":[\"Auto\"],\"editor.inspector.option.automatic.r122ro\":[\"Automatic\"],\"editor.inspector.option.back.1hzmxtu\":[\"Back\"],\"editor.inspector.option.balusters.1j3w6ng\":[\"Balusters\"],\"editor.inspector.option.band.160umau\":[\"Band\"],\"editor.inspector.option.basePlate.17fcg8k\":[\"Base plate\"],\"editor.inspector.option.boards.u14hig\":[\"Boards\"],\"editor.inspector.option.bookshelf.ls0zqk\":[\"Bookshelf\"],\"editor.inspector.option.both.mkyb42\":[\"Both\"],\"editor.inspector.option.bottom.9mpruy\":[\"Bottom\"],\"editor.inspector.option.box.1nbjcuq\":[\"Box\"],\"editor.inspector.option.break.17spnvc\":[\"Break\"],\"editor.inspector.option.butterfly.pvv0g4\":[\"Butterfly\"],\"editor.inspector.option.cable.fp50zq\":[\"Cable\"],\"editor.inspector.option.castIron.1q2z4qk\":[\"Cast iron\"],\"editor.inspector.option.ceiling.1t2zaqo\":[\"Ceiling\"],\"editor.inspector.option.center.kzdxno\":[\"Center\"],\"editor.inspector.option.centerOpening.bn1rvw\":[\"Center opening\"],\"editor.inspector.option.cleanout.lww8bm\":[\"Cleanout\"],\"editor.inspector.option.concretePad.m1zdij\":[\"Concrete pad\"],\"editor.inspector.option.condenser.a74nps\":[\"Condenser\"],\"editor.inspector.option.corbeled.bovs4v\":[\"Corbeled\"],\"editor.inspector.option.count.1qoi4r8\":[\"Count\"],\"editor.inspector.option.coupling.111aap2\":[\"Coupling\"],\"editor.inspector.option.coveringSpecific.1h9fo8w\":[\"Covering specific\"],\"editor.inspector.option.cross.1elubzv\":[\"Cross\"],\"editor.inspector.option.cubby.ivq4qc\":[\"Cubby\"],\"editor.inspector.option.curved.6noz1s\":[\"Curved\"],\"editor.inspector.option.cylinder.eol6vj\":[\"Cylinder\"],\"editor.inspector.option.damper.wgri80\":[\"Damper\"],\"editor.inspector.option.destination.gfzm1r\":[\"Destination\"],\"editor.inspector.option.diffuser.1igj0jh\":[\"Diffuser\"],\"editor.inspector.option.dogEar.xhtvjt\":[\"Dog ear\"],\"editor.inspector.option.dome.1c87e56\":[\"Dome\"],\"editor.inspector.option.double.13h5354\":[\"Double\"],\"editor.inspector.option.ductBoard.lc3o4t\":[\"Duct board\"],\"editor.inspector.option.dutch.1djx429\":[\"Dutch\"],\"editor.inspector.option.elbow.1flup74\":[\"Elbow\"],\"editor.inspector.option.end.1llf32i\":[\"End\"],\"editor.inspector.option.endCap.1p4v4na\":[\"End cap\"],\"editor.inspector.option.equalFit.11vw52c\":[\"Equal fit\"],\"editor.inspector.option.fixedCount.1o3dznu\":[\"Fixed count\"],\"editor.inspector.option.fixedSpacing.hl4dr4\":[\"Fixed spacing\"],\"editor.inspector.option.flat.ly0bei\":[\"Flat\"],\"editor.inspector.option.flex.ktpamq\":[\"Flex\"],\"editor.inspector.option.floating.1oadpln\":[\"Floating\"],\"editor.inspector.option.floor.q9caf1\":[\"Floor\"],\"editor.inspector.option.flush.olwwu5\":[\"Flush\"],\"editor.inspector.option.front.uhugl4\":[\"Front\"],\"editor.inspector.option.furnace.w0mzpf\":[\"Furnace\"],\"editor.inspector.option.gable.1ekyx6\":[\"Gable\"],\"editor.inspector.option.gambrel.1yo90d9\":[\"Gambrel\"],\"editor.inspector.option.generic.1074oey\":[\"Generic\"],\"editor.inspector.option.glass.163tc5n\":[\"Glass\"],\"editor.inspector.option.glassFrame.jp9s3m\":[\"Glass frame\"],\"editor.inspector.option.globe.1avkqoy\":[\"Globe\"],\"editor.inspector.option.grounded.jwoq73\":[\"Grounded\"],\"editor.inspector.option.guard.1m575se\":[\"Guard\"],\"editor.inspector.option.halfRound.m5hob0\":[\"Half round\"],\"editor.inspector.option.hidden.gp48rd\":[\"Hidden\"],\"editor.inspector.option.hip.r0jnjy\":[\"Hip\"],\"editor.inspector.option.horizontal.tyapah\":[\"Horizontal\"],\"editor.inspector.option.include.1a27vnr\":[\"Include\"],\"editor.inspector.option.independentHighBeam.1tn0zvy\":[\"Independent high beam\"],\"editor.inspector.option.industrial.8sd9u\":[\"Industrial\"],\"editor.inspector.option.inline.1uh05lg\":[\"Inline\"],\"editor.inspector.option.integrated.1xd9lv0\":[\"Integrated\"],\"editor.inspector.option.joined.1x2o8ds\":[\"Joined\"],\"editor.inspector.option.kStyle.m8kfwz\":[\"K style\"],\"editor.inspector.option.kickout.vt8s6p\":[\"Kickout\"],\"editor.inspector.option.knee.186hcui\":[\"Knee\"],\"editor.inspector.option.landing.tyqarg\":[\"Landing\"],\"editor.inspector.option.lantern.1h7o2xb\":[\"Lantern\"],\"editor.inspector.option.leave.1xf0sbk\":[\"Leave\"],\"editor.inspector.option.left.14n06mo\":[\"Left\"],\"editor.inspector.option.level.i4lhtp\":[\"Level\"],\"editor.inspector.option.lowProfile.1h9lgzg\":[\"Low profile\"],\"editor.inspector.option.mansard.dwzck9\":[\"Mansard\"],\"editor.inspector.option.manual.1wu7r43\":[\"Manual\"],\"editor.inspector.option.maximumSpacing.n1fzl4\":[\"Maximum spacing\"],\"editor.inspector.option.metal.1az9fgc\":[\"Metal\"],\"editor.inspector.option.metalPanel.1xdz476\":[\"Metal panel\"],\"editor.inspector.option.minimal.fwmiwg\":[\"Minimal\"],\"editor.inspector.option.mono.oszvd6\":[\"Mono\"],\"editor.inspector.option.none.deku7v\":[\"None\"],\"editor.inspector.option.omit.okcs7w\":[\"Omit\"],\"editor.inspector.option.open.n6hn1l\":[\"Open\"],\"editor.inspector.option.openRack.mvt1x0\":[\"Open rack\"],\"editor.inspector.option.opening.dfet3\":[\"Opening\"],\"editor.inspector.option.oval.1jiqzxx\":[\"Oval\"],\"editor.inspector.option.picket.zyeoy1\":[\"Picket\"],\"editor.inspector.option.pointed.effedk\":[\"Pointed\"],\"editor.inspector.option.postAndRail.lajk2u\":[\"Post and rail\"],\"editor.inspector.option.preserveHighEdge.y6ywf0\":[\"Preserve high edge\"],\"editor.inspector.option.preserveLowEdge.196m5pk\":[\"Preserve low edge\"],\"editor.inspector.option.preservePitch.1uq973r\":[\"Preserve pitch\"],\"editor.inspector.option.privacy.1th4hrb\":[\"Privacy\"],\"editor.inspector.option.purlins.od124c\":[\"Purlins\"],\"editor.inspector.option.pvc.1oc4u2w\":[\"Pvc\"],\"editor.inspector.option.pyramid.17ucsqv\":[\"Pyramid\"],\"editor.inspector.option.rafters.16tzbby\":[\"Rafters\"],\"editor.inspector.option.rail.39zqc9\":[\"Rail\"],\"editor.inspector.option.raised.4kyx9h\":[\"Raised\"],\"editor.inspector.option.rect.tpfvaf\":[\"Rect\"],\"editor.inspector.option.reducer.18byt67\":[\"Reducer\"],\"editor.inspector.option.return.1klrjrz\":[\"Return\"],\"editor.inspector.option.returnGrille.r28ncm\":[\"Return grille\"],\"editor.inspector.option.right.8humpx\":[\"Right\"],\"editor.inspector.option.round.1vx0wyj\":[\"Round\"],\"editor.inspector.option.rounded.199nwpm\":[\"Rounded\"],\"editor.inspector.option.sanitaryTee.oro3d4\":[\"Sanitary tee\"],\"editor.inspector.option.scalloped.1eltba0\":[\"Scalloped\"],\"editor.inspector.option.scoop.qy5zdv\":[\"Scoop\"],\"editor.inspector.option.segmentedPanel.5r3hwl\":[\"Segmented panel\"],\"editor.inspector.option.shed.6t994v\":[\"Shed\"],\"editor.inspector.option.sheetMetal.l164lv\":[\"Sheet metal\"],\"editor.inspector.option.shingle.pfmynh\":[\"Shingle\"],\"editor.inspector.option.shingled.1nm9inf\":[\"Shingled\"],\"editor.inspector.option.simple.12mfu73\":[\"Simple\"],\"editor.inspector.option.single.1uzxurt\":[\"Single\"],\"editor.inspector.option.singleLeft.1atdh02\":[\"Single left\"],\"editor.inspector.option.singleRight.1axbu67\":[\"Single right\"],\"editor.inspector.option.slantBox.1ta99vy\":[\"Slant box\"],\"editor.inspector.option.slat.1p9njbz\":[\"Slat\"],\"editor.inspector.option.sliding.cd0jox\":[\"Sliding\"],\"editor.inspector.option.slope.1bn2qw\":[\"Slope\"],\"editor.inspector.option.sloped.lo4sh0\":[\"Sloped\"],\"editor.inspector.option.solid.o4uy6s\":[\"Solid\"],\"editor.inspector.option.solidPanel.2yaw62\":[\"Solid panel\"],\"editor.inspector.option.spiral.91hcl4\":[\"Spiral\"],\"editor.inspector.option.splash.1uke4cm\":[\"Splash\"],\"editor.inspector.option.spread.1g1zysu\":[\"Spread\"],\"editor.inspector.option.square.1xl1lk6\":[\"Square\"],\"editor.inspector.option.stair.1xf86e\":[\"Stair\"],\"editor.inspector.option.standard.vuco\":[\"Standard\"],\"editor.inspector.option.start.30xvgf\":[\"Start\"],\"editor.inspector.option.step.jtn9kf\":[\"Step\"],\"editor.inspector.option.stepped.naxzh0\":[\"Stepped\"],\"editor.inspector.option.straight.lxqbfr\":[\"Straight\"],\"editor.inspector.option.strap.14q0ps9\":[\"Strap\"],\"editor.inspector.option.supply.1oy2zwu\":[\"Supply\"],\"editor.inspector.option.supplyRegister.1lzdbbn\":[\"Supply register\"],\"editor.inspector.option.tapered.3y5blo\":[\"Tapered\"],\"editor.inspector.option.targetSpacing.14uibc9\":[\"Target spacing\"],\"editor.inspector.option.tee.7d6tjp\":[\"Tee\"],\"editor.inspector.option.tilted.6n0u5j\":[\"Tilted\"],\"editor.inspector.option.top.2nypcs\":[\"Top\"],\"editor.inspector.option.transition.1rziz9e\":[\"Transition\"],\"editor.inspector.option.vent.xda5j2\":[\"Vent\"],\"editor.inspector.option.walkOn.1n5sg9l\":[\"Walk on\"],\"editor.inspector.option.wall.blj585\":[\"Wall\"],\"editor.inspector.option.wallAbutment.lza941\":[\"Wall abutment\"],\"editor.inspector.option.wallLedger.5m8ana\":[\"Wall ledger\"],\"editor.inspector.option.wallShelf.yiozfl\":[\"Wall shelf\"],\"editor.inspector.option.waste.3xrp79\":[\"Waste\"],\"editor.inspector.option.wye.14vupc8\":[\"Wye\"],\"editor.layers.collapse\":[\"Collapse\"],\"editor.layers.defaultName.ceiling\":[\"Ceiling (\",[\"area\"],\")\"],\"editor.layers.defaultName.collection\":[\"Collection\"],\"editor.layers.defaultName.flight\":[\"Flight (\",[\"width\"],\"×\",[\"length\"],\"m)\"],\"editor.layers.defaultName.landing\":[\"Landing (\",[\"width\"],\"×\",[\"length\"],\"m)\"],\"editor.layers.defaultName.node\":[\"Node\"],\"editor.layers.defaultName.roof\":[\"Roof (\",[\"count\",\"plural\",{\"one\":[\"#\",\" segment\"],\"other\":[\"#\",\" segments\"]}],\")\"],\"editor.layers.defaultName.roofSegment\":[[\"roofType\",\"select\",{\"hip\":[\"Hip\"],\"gable\":[\"Gable\"],\"shed\":[\"Shed\"],\"gambrel\":[\"Gambrel\"],\"dutch\":[\"Dutch\"],\"mansard\":[\"Mansard\"],\"flat\":[\"Flat\"],\"conical\":[\"Conical\"],\"barrel\":[\"Barrel\"],\"other\":[\"Roof\"]}],\" (\",[\"width\"],\"×\",[\"depth\"],\"m)\"],\"editor.layers.defaultName.room\":[\"Room (\",[\"area\"],\")\"],\"editor.layers.defaultName.slab\":[\"Slab (\",[\"area\"],\")\"],\"editor.layers.defaultName.staircase\":[\"Staircase (\",[\"count\",\"plural\",{\"one\":[\"#\",\" segment\"],\"other\":[\"#\",\" segments\"]}],\")\"],\"editor.layers.defaultName.zone\":[\"Zone (\",[\"area\"],\")\"],\"editor.layers.expand\":[\"Expand\"],\"editor.layers.levelOptions\":[[\"levelName\"],\" options\"],\"editor.layers.rename\":[\"Rename\"],\"editor.layers.spaceColour\":[[\"role\",\"select\",{\"room\":[\"Room colour\"],\"other\":[\"Zone colour\"]}]],\"editor.layers.unitColour\":[\"Unit colour\"],\"editor.levelDuplicateDialog.cancel\":[\"Cancel\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromLevel\":[\"Choose what to copy from \",[\"levelName\"],\".\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel\":[\"Choose what to copy from this level.\"],\"editor.levelDuplicateDialog.duplicate\":[\"Duplicate\"],\"editor.levelDuplicateDialog.duplicateLevel\":[\"Duplicate Level\"],\"editor.levelDuplicateDialog.everything\":[\"Everything\"],\"editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout\":[\"Structure, finishes, and placed items, without guide references.\"],\"editor.levelDuplicateDialog.structureFurniture\":[\"Structure + furniture\"],\"editor.levelDuplicateDialog.structureMaterials\":[\"Structure + materials\"],\"editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences\":[\"Structure, materials, furniture, and references.\"],\"editor.levelDuplicateDialog.structureOnly\":[\"Structure only\"],\"editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd\":[\"Structure with the current material and finish assignments.\"],\"editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd\":[\"Walls, slabs, roofs, stairs, windows, and doors without finishes.\"],\"editor.lib.contextualHelp.addOrRemoveObjectsFromThe\":[\"Add or remove objects from the selection\"],\"editor.lib.contextualHelp.adjustOneSideSCornerRadius\":[\"Adjust one side’s corner radius\"],\"editor.lib.contextualHelp.clearTheSelectionOrClickOutside\":[\"Clear the selection (or click outside)\"],\"editor.lib.contextualHelp.clickAHandleDotToShow\":[\"Click a handle dot to show move arrows\"],\"editor.lib.contextualHelp.clickOrDragTheSelectionTo\":[\"Click or drag the selection to move it as one\"],\"editor.lib.contextualHelp.clickTheHandleDotToShow\":[\"Click the handle dot to show move + rotate handles\"],\"editor.lib.contextualHelp.detachTheJointWhileDraggingAn\":[\"Detach the joint while dragging an arrow\"],\"editor.lib.contextualHelp.dragLeftOrRightToRotate\":[\"Drag left or right to rotate selected object\"],\"editor.lib.contextualHelp.dragSelectedMovableObject\":[\"Drag selected movable object\"],\"editor.lib.contextualHelp.groupSelectionSessionOnly\":[\"Group selection (session only)\"],\"editor.lib.contextualHelp.holdToRotateFreely\":[\"Hold to rotate freely\"],\"editor.lib.contextualHelp.rotate45\":[\"Rotate ±45°\"],\"editor.lib.contextualHelp.rotateTheSelection45\":[\"Rotate the selection ±45°\"],\"editor.lib.contextualHelp.rotatingFreely\":[\"Rotating freely (no angle step)\"],\"editor.lib.contextualHelp.switchTheRotationAxisYX\":[\"Switch the rotation axis (Y → X → Z)\"],\"editor.lib.contextualHelp.ungroupSessionSelection\":[\"Ungroup session selection\"],\"editor.lib.continuation.continuous\":[\"Continuous\"],\"editor.lib.continuation.continuousCanopy\":[\"Continuous canopy\"],\"editor.lib.continuation.continuousRun\":[\"Continuous run\"],\"editor.lib.continuation.curvedFence\":[\"Curved fence\"],\"editor.lib.continuation.freehandFence\":[\"Freehand fence\"],\"editor.lib.continuation.placeMultiple\":[\"Place multiple\"],\"editor.lib.continuation.placeOnce\":[\"Place once\"],\"editor.lib.continuation.polygonRoom\":[\"Polygon room\"],\"editor.lib.continuation.rectangleRoom\":[\"Rectangle room\"],\"editor.lib.continuation.singleCabinet\":[\"Single cabinet\"],\"editor.lib.continuation.singleCanopy\":[\"Single canopy\"],\"editor.lib.continuation.singleFence\":[\"Single fence\"],\"editor.lib.continuation.walls\":[\"Walls\"],\"editor.lib.hudTitle.cutOpening\":[\"Cut opening\"],\"editor.lib.hudTitle.divideRoom\":[\"Divide room\"],\"editor.lib.hudTitle.editKind\":[\"Edit \",[\"kind\"]],\"editor.lib.hudTitle.erase\":[\"Erase\"],\"editor.lib.hudTitle.levelTerrain\":[\"Level terrain\"],\"editor.lib.hudTitle.lowerTerrain\":[\"Lower terrain\"],\"editor.lib.hudTitle.mezzanine\":[\"Mezzanine\"],\"editor.lib.hudTitle.moveKind\":[\"Move \",[\"kind\"]],\"editor.lib.hudTitle.moveRoom\":[\"Move room\"],\"editor.lib.hudTitle.moveSelection\":[\"Move selection\"],\"editor.lib.hudTitle.paint\":[\"Paint\"],\"editor.lib.hudTitle.paintARectangle\":[\"Paint a rectangle\"],\"editor.lib.hudTitle.paintAShape\":[\"Paint a shape\"],\"editor.lib.hudTitle.pickMaterial\":[\"Pick material\"],\"editor.lib.hudTitle.raiseTerrain\":[\"Raise terrain\"],\"editor.lib.hudTitle.resize\":[\"Resize\"],\"editor.lib.hudTitle.rotate\":[\"Rotate\"],\"editor.lib.hudTitle.select\":[\"Select\"],\"editor.lib.hudTitle.smartMeasure\":[\"Smart measure\"],\"editor.lib.hudTitle.smoothTerrain\":[\"Smooth terrain\"],\"editor.lib.hudTitle.splitKind\":[\"Split \",[\"kind\"]],\"editor.lib.hudTitle.terrace\":[\"Terrace\"],\"editor.lib.wallDrawVariant.polygonRoom\":[\"Polygon room\"],\"editor.lib.wallDrawVariant.rectangleRoom\":[\"Rectangle room\"],\"editor.lib.wallDrawVariant.walls\":[\"Walls\"],\"editor.mechanismAction\":[[\"action\",\"select\",{\"close\":[\"Close\"],\"open\":[\"Open\"],\"stop\":[\"Stop\"],\"other\":[\"Play\"]}]],\"editor.nodeActionMenu.curve\":[\"Curve\"],\"editor.nodeActionMenu.cutOut\":[\"Cut out\"],\"editor.nodeActionMenu.delete\":[\"Delete\"],\"editor.nodeActionMenu.duplicate\":[\"Duplicate\"],\"editor.nodeActionMenu.editMesh\":[\"Edit mesh\"],\"editor.nodeActionMenu.findInCatalog\":[\"Find in catalog\"],\"editor.nodeActionMenu.groupSelection\":[\"Group selection\"],\"editor.nodeActionMenu.move\":[\"Move\"],\"editor.nodeActionMenu.rotateLeft\":[\"Rotate left\"],\"editor.nodeActionMenu.rotateRight\":[\"Rotate right\"],\"editor.nodeActionMenu.ungroupSelection\":[\"Ungroup selection\"],\"editor.nodeDisplay.ceiling\":[\"Ceiling\"],\"editor.nodeDisplay.column\":[\"Column\"],\"editor.nodeDisplay.door\":[\"Door\"],\"editor.nodeDisplay.elevator\":[\"Elevator\"],\"editor.nodeDisplay.fence\":[\"Fence\"],\"editor.nodeDisplay.guide\":[\"Guide image\"],\"editor.nodeDisplay.item\":[\"Item\"],\"editor.nodeDisplay.roof\":[\"Roof\"],\"editor.nodeDisplay.roofSegment\":[\"Roof segment\"],\"editor.nodeDisplay.room\":[\"Room\"],\"editor.nodeDisplay.scan\":[\"3D Scan\"],\"editor.nodeDisplay.selection\":[\"Selection\"],\"editor.nodeDisplay.slab\":[\"Slab\"],\"editor.nodeDisplay.stair\":[\"Stair\"],\"editor.nodeDisplay.stairSegment\":[\"Stair segment\"],\"editor.nodeDisplay.wall\":[\"Wall\"],\"editor.nodeDisplay.window\":[\"Window\"],\"editor.nodeDisplay.zone\":[\"Zone\"],\"editor.panels.collections.collectionsPopover.cancel\":[\"Cancel\"],\"editor.panels.collections.collectionsPopover.collectionName\":[\"Collection name…\"],\"editor.panels.collections.collectionsPopover.collections\":[\"Collections\"],\"editor.panels.collections.collectionsPopover.delete\":[\"Delete\"],\"editor.panels.collections.collectionsPopover.deleteName\":[\"Delete \\\"\",[\"name\"],\"\\\"?\"],\"editor.panels.collections.collectionsPopover.new\":[\"New\"],\"editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo\":[\"No collections yet. Create one to group elements together.\"],\"editor.panels.collections.collectionsPopover.rename\":[\"Rename\"],\"editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe\":[\"A thicker floor grows upward: the rooms below keep their height.\"],\"editor.panels.floorFoundationPanel.edgeFinish\":[\"Edge finish\"],\"editor.panels.floorFoundationPanel.floorPlate\":[\"Floor plate\"],\"editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn\":[\"Floor plate \",[\"thicknessLabel\"],\" thick, resting on the walls below\"],\"editor.panels.floorFoundationPanel.floorThickness\":[\"Floor thickness\"],\"editor.panels.floorFoundationPanel.floorTopAbove\":[\"Floor top +\",[\"height\"]],\"editor.panels.floorFoundationPanel.floorTopValue\":[\"Floor top \",[\"height\"]],\"editor.panels.floorFoundationPanel.foundationFinish\":[\"Foundation finish\"],\"editor.panels.floorFoundationPanel.foundationHeight\":[\"Foundation height\"],\"editor.panels.floorFoundationPanel.kicker\":[\"Floor plate\"],\"editor.panels.floorFoundationPanel.kickerLevel\":[\"Floor plate · \",[\"level\"]],\"editor.panels.floorFoundationPanel.kickerRestsOn\":[\"Floor plate · \",[\"level\"],\" · rests on \",[\"below\"]],\"editor.panels.floorFoundationPanel.lowerTogether\":[[\"count\",\"plural\",{\"2\":[\"Lower both\"],\"other\":[\"Lower all \",[\"number\"]]}]],\"editor.panels.floorFoundationPanel.notToScale\":[\"Not to scale\"],\"editor.panels.floorFoundationPanel.onTheGround\":[\"On the ground\"],\"editor.panels.floorFoundationPanel.raiseTogether\":[[\"count\",\"plural\",{\"2\":[\"Raise both\"],\"other\":[\"Raise all \",[\"number\"]]}]],\"editor.panels.floorFoundationPanel.raisedOnAFoundation\":[\"Raised on a foundation\"],\"editor.panels.floorFoundationPanel.section.floorPlate\":[\"Floor plate · \",[\"thickness\"]],\"editor.panels.floorFoundationPanel.section.floorTop\":[\"Floor top\"],\"editor.panels.floorFoundationPanel.section.foundation\":[\"Foundation\"],\"editor.panels.floorFoundationPanel.section.foundationKind\":[\"Foundation · \",[\"foundation\"]],\"editor.panels.floorFoundationPanel.section.ground\":[\"Ground\"],\"editor.panels.floorFoundationPanel.section.slab\":[\"Slab · \",[\"thickness\"]],\"editor.panels.floorFoundationPanel.section.wallsBelow\":[\"Walls below\"],\"editor.panels.floorFoundationPanel.sectionOnGround\":[\"Slab \",[\"thickness\"],\" thick on the ground, floor top \",[\"top\"],\" above the ground\"],\"editor.panels.floorFoundationPanel.sectionRaised\":[\"Slab \",[\"thickness\"],\" thick on a \",[\"foundation\"],\" foundation, floor top \",[\"top\"],\" above the ground\"],\"editor.panels.floorFoundationPanel.slab\":[\"Slab\"],\"editor.panels.floorFoundationPanel.slabThickness\":[\"Slab thickness\"],\"editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll\":[\"The floor's outside edge, all around the footprint.\"],\"editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround\":[\"The foundation stands on the ground; the slab sits on it.\"],\"editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation\":[\"The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.\"],\"editor.panels.floorFoundationPanel.underTheFloor\":[\"Under the floor\"],\"editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt\":[\"A hatch in the mezzanine: it cuts only the mezzanine’s floor.\"],\"editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow\":[\"Also cut the ceiling below\"],\"editor.panels.floorOpeningPanel.alsoCutTheFloorAbove\":[\"Also cut the floor above\"],\"editor.panels.floorOpeningPanel.areaDragCorners\":[[\"area\"],\" · Drag its corners to change the shape.\"],\"editor.panels.floorOpeningPanel.areaOwned\":[[\"area\"],\" · Shape follows its owner.\"],\"editor.panels.floorOpeningPanel.delete\":[\"Delete\"],\"editor.panels.floorOpeningPanel.done\":[\"Done\"],\"editor.panels.floorOpeningPanel.kicker\":[\"Opening\"],\"editor.panels.floorOpeningPanel.kickerLevel\":[\"Opening · \",[\"level\"]],\"editor.panels.floorOpeningPanel.kickerRoom\":[\"Opening · in \",[\"room\"]],\"editor.panels.floorOpeningPanel.kickerRoomLevel\":[\"Opening · in \",[\"room\"],\" · \",[\"level\"]],\"editor.panels.floorOpeningPanel.oneVoidWith\":[\"On \",[\"level\"],\", as one void with this opening.\"],\"editor.panels.floorOpeningPanel.ownedByElevator\":[\"Owned by Elevator\"],\"editor.panels.floorOpeningPanel.ownedByStaircase\":[\"Owned by Staircase\"],\"editor.panels.floorOpeningPanel.room\":[\"Room\"],\"editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis\":[\"There is no floor above this storey.\"],\"editor.panels.floorOpeningPanel.thereIsNoStoreyBelow\":[\"There is no storey below.\"],\"editor.panels.floorOpeningPanel.title\":[[\"source\",\"select\",{\"stair\":[\"Stair opening\"],\"elevator\":[\"Elevator opening\"],\"ceiling\":[\"Ceiling opening\"],\"other\":[\"Floor opening\"]}]],\"editor.panels.mergeRefusal.alreadyJoined\":[\"One of them already wraps another corner. Unwrap it first.\"],\"editor.panels.mergeRefusal.between\":[[\"kind\",\"select\",{\"item\":[\"There is an item between them.\"],\"door\":[\"There is a door between them.\"],\"window\":[\"There is a window between them.\"],\"shelf\":[\"There is a shelf between them.\"],\"other\":[\"There is something between them.\"]}]],\"editor.panels.mergeRefusal.cannotMerge\":[\"These windows cannot be merged.\"],\"editor.panels.mergeRefusal.cornerAngle\":[\"Their walls meet at too sharp or too flat an angle for a corner window.\"],\"editor.panels.mergeRefusal.heightExceedsWall\":[\"The window selected first is taller than the other wall. Select the other one first.\"],\"editor.panels.mergeRefusal.notAdjacent\":[\"They are too far apart. Windows merge with at most \",[\"reach\"],\" between them.\"],\"editor.panels.mergeRefusal.notAtCorner\":[\"One of them is too far from the corner. A window joins one within \",[\"reach\"],\" of it.\"],\"editor.panels.mergeRefusal.notOnWall\":[\"A roof or dormer window cannot join another window.\"],\"editor.panels.mergeRefusal.overlap\":[[\"kind\",\"select\",{\"item\":[\"Sliding them to the corner would overlap an item.\"],\"door\":[\"Sliding them to the corner would overlap a door.\"],\"window\":[\"Sliding them to the corner would overlap a window.\"],\"shelf\":[\"Sliding them to the corner would overlap a shelf.\"],\"other\":[\"Sliding them to the corner would overlap something.\"]}]],\"editor.panels.mergeRefusal.wallsNotMeeting\":[\"Their walls do not meet at a corner.\"],\"editor.panels.mixed\":[\"Mixed\"],\"editor.panels.mobilePanelSheet.close\":[\"Close\"],\"editor.panels.mobileSelectionBar.delete\":[\"Delete\"],\"editor.panels.mobileSelectionBar.duplicate\":[\"Duplicate\"],\"editor.panels.mobileSelectionBar.editProperties\":[\"Edit properties\"],\"editor.panels.mobileSelectionBar.editResolvedlabel\":[\"Edit \",[\"resolvedLabel\"]],\"editor.panels.mobileSelectionBar.move\":[\"Move\"],\"editor.panels.multiHeightMode.currentlyCurrentlabel\":[\"Currently \",[\"currentLabel\"]],\"editor.panels.multiHeightMode.customHeight\":[\"Custom height\"],\"editor.panels.multiHeightMode.followsLevel\":[\"Follows level\"],\"editor.panels.multiHeightMode.height\":[\"Height\"],\"editor.panels.multiHeightMode.top\":[\"Top\"],\"editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects\":[[\"label\"],\" (session only). Plain click reselects all members. Not saved with the project.\"],\"editor.panels.multiSelectionPanel.delete\":[\"Delete\"],\"editor.panels.multiSelectionPanel.duplicate\":[\"Duplicate\"],\"editor.panels.multiSelectionPanel.group\":[\"Group\"],\"editor.panels.multiSelectionPanel.groupCtrlCmdG\":[\"Group (Ctrl/Cmd+G)\"],\"editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects\":[[\"label\"],\" (session only). Plain click reselects all members. Not saved with the project.\"],\"editor.panels.multiSelectionPanel.lengthSelected\":[[\"length\"],\" selected\"],\"editor.panels.multiSelectionPanel.mergeWindows\":[\"Merge windows\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow\":[\"Merge windows into a corner window: both take the height, sill and type of the one you selected first\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth\":[\"Merge windows into one across both: it keeps the type and style of the one you selected first\"],\"editor.panels.multiSelectionPanel.selectBuilding\":[\"Select building\"],\"editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove\":[\"Select the whole building to move all its levels\"],\"editor.panels.multiSelectionPanel.ungroup\":[\"Ungroup\"],\"editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG\":[\"Ungroup (Ctrl/Cmd+Shift+G)\"],\"editor.panels.multiSelectionPanel.unwrapTheCorner\":[\"Unwrap the corner\"],\"editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay\":[\"Unwrap the corner: both windows stay, each framed at the corner\"],\"editor.panels.panelManager.selection\":[\"Selection\"],\"editor.panels.panelWrapper.back\":[\"Back\"],\"editor.panels.panelWrapper.close\":[\"Close\"],\"editor.panels.panelWrapper.closeTitle\":[\"Close \",[\"title\"]],\"editor.panels.panelWrapper.collapsePanel\":[\"Collapse panel\"],\"editor.panels.panelWrapper.collections\":[\"Collections\"],\"editor.panels.panelWrapper.expandPanel\":[\"Expand panel\"],\"editor.panels.panelWrapper.manageCollections\":[\"Manage collections…\"],\"editor.panels.panelWrapper.openTitle\":[\"Open \",[\"title\"]],\"editor.panels.panelWrapper.reset\":[\"Reset\"],\"editor.panels.panelWrapper.titleHitAnErrorAndWas\":[\"“\",[\"title\"],\"” hit an error and was unloaded for this session.\"],\"editor.panels.parametricFieldControl.mixed\":[\"Mixed\"],\"editor.panels.parametricInspector.actions\":[\"Actions\"],\"editor.panels.parametricInspector.delete\":[\"Delete\"],\"editor.panels.parametricInspector.deleteWithContents\":[\"Delete with contents\"],\"editor.panels.parametricInspector.move\":[\"Move\"],\"editor.panels.referencePanel.cancel\":[\"Cancel\"],\"editor.panels.referencePanel.capture\":[\"Capture\"],\"editor.panels.referencePanel.center\":[\"Center\"],\"editor.panels.referencePanel.clearScale\":[\"Clear Scale\"],\"editor.panels.referencePanel.clickBothEndsOfAKnown\":[\"Click both ends of a known distance on the plan, then type its real length.\"],\"editor.panels.referencePanel.delete\":[\"Delete\"],\"editor.panels.referencePanel.drawALineOverAKnown\":[\"Draw a line over a known dimension on the plan, then type its real length to scale the image exactly.\"],\"editor.panels.referencePanel.editScale\":[\"Edit Scale\"],\"editor.panels.referencePanel.guideImage\":[\"Guide Image\"],\"editor.panels.referencePanel.hide\":[\"Hide\"],\"editor.panels.referencePanel.hideScale\":[\"Hide Scale\"],\"editor.panels.referencePanel.image\":[\"Image\"],\"editor.panels.referencePanel.lock\":[\"Lock\"],\"editor.panels.referencePanel.move\":[\"Move\"],\"editor.panels.referencePanel.opacity\":[\"Opacity\"],\"editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage\":[\"Overlay image unavailable. Replace the image to restore it.\"],\"editor.panels.referencePanel.pos\":[\"pos\"],\"editor.panels.referencePanel.position\":[\"Position\"],\"editor.panels.referencePanel.quickActions\":[\"Quick Actions\"],\"editor.panels.referencePanel.referenceScale\":[\"Reference Scale\"],\"editor.panels.referencePanel.replace\":[\"Replace\"],\"editor.panels.referencePanel.replacing\":[\"Replacing...\"],\"editor.panels.referencePanel.resetImageScale\":[\"Reset Image Scale\"],\"editor.panels.referencePanel.resetRotation\":[\"Reset Rotation\"],\"editor.panels.referencePanel.rot\":[\"rot\"],\"editor.panels.referencePanel.rotation\":[\"Rotation\"],\"editor.panels.referencePanel.scale\":[\"scale\"],\"editor.panels.referencePanel.scaleOpacity\":[\"Scale & Opacity\"],\"editor.panels.referencePanel.scaled\":[\"Scaled · \",[\"scale\"]],\"editor.panels.referencePanel.scaledHidden\":[\"Scaled (hidden) · \",[\"scale\"]],\"editor.panels.referencePanel.setScale\":[\"Set Scale\"],\"editor.panels.referencePanel.show\":[\"Show\"],\"editor.panels.referencePanel.showScale\":[\"Show Scale\"],\"editor.panels.referencePanel.uncalibrated\":[\"Uncalibrated\"],\"editor.panels.referencePanel.unlock\":[\"Unlock\"],\"editor.panels.referencePanel.xyz\":[\"XYZ\"],\"editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness\":[\"A drawn slab: set its thickness and finishes here. It has no footprint height or foundation.\"],\"editor.panels.roomConstructionRows.addMezzanine\":[\"+ Add mezzanine\"],\"editor.panels.roomConstructionRows.atFloorLevel\":[\"At floor level\"],\"editor.panels.roomConstructionRows.builtOn\":[\"Built on\"],\"editor.panels.roomConstructionRows.builtWith\":[\"Built with\"],\"editor.panels.roomConstructionRows.ceiling\":[\"Ceiling\"],\"editor.panels.roomConstructionRows.ceilingAt\":[\"At \",[\"height\"]],\"editor.panels.roomConstructionRows.closeWithWalls\":[\"Close with walls\"],\"editor.panels.roomConstructionRows.drawnFloor\":[[\"ambiguous\",\"select\",{\"yes\":[\"Floor is several drawn slabs, mostly \",[\"name\"]],\"other\":[\"Floor is a drawn slab: \",[\"name\"]]}],[\"shared\",\"plural\",{\"0\":[],\"one\":[\" (shared with \",[\"sharedCount\"],\" room)\"],\"other\":[\" (shared with \",[\"sharedCount\"],\" rooms)\"]}]],\"editor.panels.roomConstructionRows.drawnFloorSwitchHint\":[\"This floor is a drawn slab: select it to edit or delete it.\"],\"editor.panels.roomConstructionRows.drawnSlab\":[\"Drawn slab\"],\"editor.panels.roomConstructionRows.drawnSlabAt\":[\"Drawn slab at \",[\"height\"]],\"editor.panels.roomConstructionRows.editShape\":[\"Edit shape\"],\"editor.panels.roomConstructionRows.flatAtWallHeight\":[\"Flat, at wall height\"],\"editor.panels.roomConstructionRows.floor\":[\"Floor\"],\"editor.panels.roomConstructionRows.floorDown\":[[\"distance\"],\" down\"],\"editor.panels.roomConstructionRows.floorThickness\":[\"Floor thickness\"],\"editor.panels.roomConstructionRows.floorUp\":[[\"distance\"],\" up\"],\"editor.panels.roomConstructionRows.handDrawnCeiling\":[\"Hand-drawn ceiling\"],\"editor.panels.roomConstructionRows.height\":[\"Height\"],\"editor.panels.roomConstructionRows.includeFloor\":[\"Include floor\"],\"editor.panels.roomConstructionRows.includePart\":[[\"part\",\"select\",{\"floor\":[\"Include floor\"],\"walls\":[\"Include walls\"],\"other\":[\"Include ceiling\"]}]],\"editor.panels.roomConstructionRows.includeWalls\":[\"Include walls\"],\"editor.panels.roomConstructionRows.keepOutsideDimensions\":[\"Keep outside dimensions\"],\"editor.panels.roomConstructionRows.lowered\":[\"Lowered \",[\"lift\"]],\"editor.panels.roomConstructionRows.newFloor\":[\"+ New floor\"],\"editor.panels.roomConstructionRows.noFloor\":[\"No floor\"],\"editor.panels.roomConstructionRows.notIncluded\":[\"Not included\"],\"editor.panels.roomConstructionRows.nothingToChange\":[\"Nothing to change.\"],\"editor.panels.roomConstructionRows.onTheGround\":[\"On the ground\"],\"editor.panels.roomConstructionRows.onTheWallsBelow\":[\"On the walls below\"],\"editor.panels.roomConstructionRows.openSides\":[[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" open side\"],\"other\":[[\"openSideCount\"],\" open sides\"]}]],\"editor.panels.roomConstructionRows.partialCovered\":[\"Partial · \",[\"coverage\",\"number\",\"percent\"],\" covered\"],\"editor.panels.roomConstructionRows.partialWalls\":[\"Partial · \",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" wall\"],\"other\":[[\"wallCount\"],\" walls\"]}],\", \",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" open side\"],\"other\":[[\"openSideCount\"],\" open sides\"]}]],\"editor.panels.roomConstructionRows.raised\":[\"Raised \",[\"lift\"]],\"editor.panels.roomConstructionRows.replace\":[\"Replace\"],\"editor.panels.roomConstructionRows.select\":[\"Select\"],\"editor.panels.roomConstructionRows.shape\":[\"Shape\"],\"editor.panels.roomConstructionRows.sharedWallsNote\":[\"Turning walls off keeps the ones shared with \",[\"rooms\"],\".\"],\"editor.panels.roomConstructionRows.sitsOnPreset\":[\"Sits on: \",[\"name\"],\" · \",[\"preset\"]],\"editor.panels.roomConstructionRows.theNextRoom\":[\"the next room\"],\"editor.panels.roomConstructionRows.useExisting\":[\"Use existing\"],\"editor.panels.roomConstructionRows.wallCount\":[[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" wall\"],\"other\":[[\"wallCount\"],\" walls\"]}]],\"editor.panels.roomConstructionRows.walls\":[\"Walls\"],\"editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding\":[\"Walls thicken inward, so the building's outer size stays the same.\"],\"editor.panels.roomOpeningsField.cutOpening\":[\"Cut opening\"],\"editor.panels.roomOpeningsField.done\":[\"Done\"],\"editor.panels.roomOpeningsField.editFloor\":[\"Edit floor\"],\"editor.panels.roomOpeningsField.elevatorOpening\":[\"Elevator opening\"],\"editor.panels.roomOpeningsField.noOpeningsYetCutOneFor\":[\"No openings yet. Cut one for a stair void or a hatch.\"],\"editor.panels.roomOpeningsField.openingNumber\":[\"Opening \",[\"number\"]],\"editor.panels.roomOpeningsField.openings\":[[\"count\",\"plural\",{\"0\":[\"No openings\"],\"one\":[[\"number\"],\" opening\"],\"other\":[[\"number\"],\" openings\"]}]],\"editor.panels.roomOpeningsField.stairOpening\":[\"Stair opening\"],\"editor.panels.roomPanel.addStairs\":[\"Add stairs\"],\"editor.panels.roomPanel.addToAUnit\":[\"Add to a unit\"],\"editor.panels.roomPanel.area\":[\"Area\"],\"editor.panels.roomPanel.browse\":[\"Browse\"],\"editor.panels.roomPanel.builtWith\":[\"Built with\"],\"editor.panels.roomPanel.ceiling\":[\"Ceiling\"],\"editor.panels.roomPanel.deleteRoom\":[\"Delete room\"],\"editor.panels.roomPanel.editShape\":[\"Edit shape\"],\"editor.panels.roomPanel.floor\":[\"Floor\"],\"editor.panels.roomPanel.headroom\":[[\"height\"],\" headroom\"],\"editor.panels.roomPanel.height\":[\"Height\"],\"editor.panels.roomPanel.hideCode\":[\"Hide code\"],\"editor.panels.roomPanel.kickerLevel\":[\"Room · \",[\"level\"]],\"editor.panels.roomPanel.kickerMezzanineIn\":[\"Mezzanine · in \",[\"room\"]],\"editor.panels.roomPanel.kickerNotEnclosed\":[\"Room · not enclosed\"],\"editor.panels.roomPanel.loading\":[\"Loading…\"],\"editor.panels.roomPanel.mezzanine\":[\"Mezzanine\"],\"editor.panels.roomPanel.mezzanineName\":[\"Mezzanine name\"],\"editor.panels.roomPanel.mezzanineNames\":[\"Mezzanine names\"],\"editor.panels.roomPanel.newUnit\":[\"New unit\"],\"editor.panels.roomPanel.none\":[\"None\"],\"editor.panels.roomPanel.notEnoughHeadroom\":[\"Not enough headroom\"],\"editor.panels.roomPanel.openRoomArea\":[[\"area\"],\" · Close its walls to build its floor and ceiling.\"],\"editor.panels.roomPanel.openingCounts\":[[\"doors\",\"plural\",{\"one\":[[\"doorCount\"],\" door\"],\"other\":[[\"doorCount\"],\" doors\"]}],\" · \",[\"windows\",\"plural\",{\"one\":[[\"windowCount\"],\" window\"],\"other\":[[\"windowCount\"],\" windows\"]}]],\"editor.panels.roomPanel.overTheMezzanine\":[\"Over the mezzanine\"],\"editor.panels.roomPanel.perimeter\":[\"Perimeter\"],\"editor.panels.roomPanel.pick\":[\"Pick\"],\"editor.panels.roomPanel.pickingItAddsANumber\":[\"Picking it adds a number\"],\"editor.panels.roomPanel.remove\":[\"Remove\"],\"editor.panels.roomPanel.removeFromUnit\":[\"Remove from unit\"],\"editor.panels.roomPanel.removeFromUnitNamed\":[\"Remove from \",[\"unit\"]],\"editor.panels.roomPanel.rename\":[\"Rename\"],\"editor.panels.roomPanel.renameNamed\":[[\"name\"],\" · Rename\"],\"editor.panels.roomPanel.revert\":[\"Revert\"],\"editor.panels.roomPanel.room\":[\"Room\"],\"editor.panels.roomPanel.roomColourUsedInThePlan\":[\"Room colour — used in the plan and labels\"],\"editor.panels.roomPanel.roomName\":[\"Room name\"],\"editor.panels.roomPanel.roomNames\":[\"Room names\"],\"editor.panels.roomPanel.scriptedFinish\":[\"Scripted finish\"],\"editor.panels.roomPanel.shape\":[\"Shape\"],\"editor.panels.roomPanel.showMezzanineNames\":[\"Show mezzanine names\"],\"editor.panels.roomPanel.showRoomNames\":[\"Show room names\"],\"editor.panels.roomPanel.stairs\":[\"Stairs\"],\"editor.panels.roomPanel.unit\":[\"Unit\"],\"editor.panels.roomPanel.use\":[\"Use\"],\"editor.panels.roomPanel.useTyped\":[[\"use\"],\" “\",[\"name\"],\"”\"],\"editor.panels.roomPanel.usedOnThisLevel\":[[\"used\"],\" on this level\"],\"editor.panels.roomPanel.viewCode\":[\"View code\"],\"editor.panels.scriptUnreadable\":[\"The script could not be read.\"],\"editor.panels.shapeChoice.polygon\":[\"Polygon\"],\"editor.panels.shapeChoice.rectangle\":[\"Rectangle\"],\"editor.panels.wallRegionList.deleteRegion\":[\"Delete region\"],\"editor.panels.wallRegionList.paintRegions\":[\"Paint regions\"],\"editor.proceduralItems.inspector.adjustDesign\":[\"Adjust design\"],\"editor.proceduralItems.inspector.back\":[\"Back\"],\"editor.proceduralItems.inspector.editParts\":[\"Edit parts\"],\"editor.proceduralItems.inspector.invalidAttachment\":[\"Invalid attachment\"],\"editor.proceduralItems.inspector.invalidEdit\":[\"Invalid edit\"],\"editor.proceduralItems.inspector.invalidPlacement\":[\"Invalid placement\"],\"editor.proceduralItems.inspector.invalidPosition\":[\"Invalid position\"],\"editor.proceduralItems.inspector.invalidReset\":[\"Invalid reset\"],\"editor.proceduralItems.inspector.pos\":[\"pos\"],\"editor.proceduralItems.inspector.position\":[\"Position\"],\"editor.proceduralItems.inspector.resetThisPart\":[\"Reset this part\"],\"editor.reloadEditor\":[\"Reload editor\"],\"editor.riserDiagramPanel.close\":[\"Close riser diagram\"],\"editor.riserDiagramPanel.dwvPlumbingIsometric\":[\"DWV plumbing isometric\"],\"editor.riserDiagramPanel.dwvRiserDiagram\":[\"DWV riser diagram\"],\"editor.riserDiagramPanel.noDrainWasteOrVentPipes\":[\"No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram.\"],\"editor.riserDiagramPanel.riserDiagram\":[\"Riser Diagram\"],\"editor.riserDiagramPanel.trap\":[[\"diameter\"],\"\\\" P-trap\"],\"editor.riserDiagramPanel.vent\":[\"Vent\"],\"editor.riserDiagramPanel.ventTermination\":[[\"diameter\"],\"\\\" VTR\"],\"editor.riserDiagramPanel.waste\":[\"Waste\"],\"editor.room.builtOn.above\":[[\"current\"],\" · \",[\"distance\"],\" above \",[\"shared\"]],\"editor.room.builtOn.below\":[[\"current\"],\" · \",[\"distance\"],\" below \",[\"shared\"]],\"editor.room.builtOn.levelWith\":[[\"current\"],\" · level with \",[\"shared\"]],\"editor.roomLabel.room\":[\"Room\"],\"editor.roomLabel.zone\":[\"Zone\"],\"editor.roomName.attic\":[\"Attic\"],\"editor.roomName.balcony\":[\"Balcony\"],\"editor.roomName.basement\":[\"Basement\"],\"editor.roomName.bathroom\":[\"Bathroom\"],\"editor.roomName.bedroom\":[\"Bedroom\"],\"editor.roomName.boilerRoom\":[\"Boiler room\"],\"editor.roomName.carport\":[\"Carport\"],\"editor.roomName.cellar\":[\"Cellar\"],\"editor.roomName.closet\":[\"Closet\"],\"editor.roomName.corridor\":[\"Corridor\"],\"editor.roomName.courtyard\":[\"Courtyard\"],\"editor.roomName.deck\":[\"Deck\"],\"editor.roomName.den\":[\"Den\"],\"editor.roomName.diningRoom\":[\"Dining room\"],\"editor.roomName.dressingRoom\":[\"Dressing room\"],\"editor.roomName.enSuite\":[\"En-suite\"],\"editor.roomName.entrance\":[\"Entrance\"],\"editor.roomName.familyRoom\":[\"Family room\"],\"editor.roomName.gallery\":[\"Gallery\"],\"editor.roomName.gamesRoom\":[\"Games room\"],\"editor.roomName.garage\":[\"Garage\"],\"editor.roomName.garden\":[\"Garden\"],\"editor.roomName.gardenShed\":[\"Garden shed\"],\"editor.roomName.guestRoom\":[\"Guest room\"],\"editor.roomName.gym\":[\"Gym\"],\"editor.roomName.hallway\":[\"Hallway\"],\"editor.roomName.homeCinema\":[\"Home cinema\"],\"editor.roomName.kidsRoom\":[\"Kids' room\"],\"editor.roomName.kitchen\":[\"Kitchen\"],\"editor.roomName.kitchenette\":[\"Kitchenette\"],\"editor.roomName.landing\":[\"Landing\"],\"editor.roomName.laundry\":[\"Laundry\"],\"editor.roomName.library\":[\"Library\"],\"editor.roomName.livingRoom\":[\"Living room\"],\"editor.roomName.loft\":[\"Loft\"],\"editor.roomName.lounge\":[\"Lounge\"],\"editor.roomName.mezzanine\":[\"Mezzanine\"],\"editor.roomName.mudroom\":[\"Mudroom\"],\"editor.roomName.nursery\":[\"Nursery\"],\"editor.roomName.office\":[\"Office\"],\"editor.roomName.pantry\":[\"Pantry\"],\"editor.roomName.patio\":[\"Patio\"],\"editor.roomName.playroom\":[\"Playroom\"],\"editor.roomName.poolArea\":[\"Pool area\"],\"editor.roomName.porch\":[\"Porch\"],\"editor.roomName.powderRoom\":[\"Powder room\"],\"editor.roomName.primaryBedroom\":[\"Primary bedroom\"],\"editor.roomName.readingNook\":[\"Reading nook\"],\"editor.roomName.section.bathrooms\":[\"Bathrooms\"],\"editor.roomName.section.circulation\":[\"Circulation\"],\"editor.roomName.section.living\":[\"Living\"],\"editor.roomName.section.mezzanine\":[\"Mezzanine\"],\"editor.roomName.section.outdoor\":[\"Outdoor\"],\"editor.roomName.section.sleeping\":[\"Sleeping\"],\"editor.roomName.section.utility\":[\"Utility & storage\"],\"editor.roomName.section.work\":[\"Work & hobbies\"],\"editor.roomName.showerRoom\":[\"Shower room\"],\"editor.roomName.stairwell\":[\"Stairwell\"],\"editor.roomName.storage\":[\"Storage\"],\"editor.roomName.studio\":[\"Studio\"],\"editor.roomName.study\":[\"Study\"],\"editor.roomName.sunroom\":[\"Sunroom\"],\"editor.roomName.terrace\":[\"Terrace\"],\"editor.roomName.toilet\":[\"Toilet\"],\"editor.roomName.utilityRoom\":[\"Utility room\"],\"editor.roomName.veranda\":[\"Veranda\"],\"editor.roomName.walkInCloset\":[\"Walk-in closet\"],\"editor.roomName.workshop\":[\"Workshop\"],\"editor.sidebar.iconRail.settings\":[\"Settings\"],\"editor.sidebar.iconRail.site\":[\"Site\"],\"editor.sidebar.panels.itemsPanel.all\":[\"All\"],\"editor.sidebar.panels.itemsPanel.community\":[\"Community\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.all\":[\"All\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.community\":[\"Community\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.library\":[\"Library\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.mine\":[\"Mine\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch\":[\"No results for “\",[\"search\"],\"”\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.search\":[\"Search...\"],\"editor.sidebar.panels.itemsPanel.library\":[\"Library\"],\"editor.sidebar.panels.itemsPanel.mine\":[\"Mine\"],\"editor.sidebar.panels.itemsPanel.noResultsForSearch\":[\"No results for “\",[\"search\"],\"”\"],\"editor.sidebar.panels.itemsPanel.search\":[\"Search...\"],\"editor.sidebar.panels.sitePanel.addLevel\":[\"Add level\"],\"editor.sidebar.panels.sitePanel.addPoint\":[\"Add point\"],\"editor.sidebar.panels.sitePanel.area\":[\"Area:\"],\"editor.sidebar.panels.sitePanel.baseElevation\":[\"Base elevation\"],\"editor.sidebar.panels.sitePanel.building\":[\"Building\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel\":[\"Add new level\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.building\":[\"Building\"],\"editor.sidebar.panels.sitePanel.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.sitePanel.capture\":[\"Capture\"],\"editor.sidebar.panels.sitePanel.clearSelection\":[\"Clear selection\"],\"editor.sidebar.panels.sitePanel.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo\":[\"Close walls around a space to make a room.\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour\":[\"Collection colour\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collections\":[\"Collections\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection\":[\"Delete collection\"],\"editor.sidebar.panels.sitePanel.delete\":[\"Delete\"],\"editor.sidebar.panels.sitePanel.deleteLevel\":[\"Delete level\"],\"editor.sidebar.panels.sitePanel.drawAZone\":[\"Draw a zone\"],\"editor.sidebar.panels.sitePanel.duplicate\":[\"Duplicate\"],\"editor.sidebar.panels.sitePanel.duplicateLevel\":[\"Duplicate level\"],\"editor.sidebar.panels.sitePanel.duplicateLevelWithOptions\":[\"Duplicate level with options\"],\"editor.sidebar.panels.sitePanel.duplicateWithOptions\":[\"Duplicate with options...\"],\"editor.sidebar.panels.sitePanel.elements\":[\"Elements\"],\"editor.sidebar.panels.sitePanel.guide\":[\"Guide\"],\"editor.sidebar.panels.sitePanel.hide\":[\"Hide\"],\"editor.sidebar.panels.sitePanel.level\":[\"Level\"],\"editor.sidebar.panels.sitePanel.newUnit\":[\"New unit\"],\"editor.sidebar.panels.sitePanel.noBuildingsYet\":[\"No buildings yet\"],\"editor.sidebar.panels.sitePanel.noElementsOnThisLevel\":[\"No elements on this level\"],\"editor.sidebar.panels.sitePanel.noLevelsYet\":[\"No levels yet\"],\"editor.sidebar.panels.sitePanel.objectsSelected\":[[\"count\",\"plural\",{\"one\":[\"#\",\" object selected\"],\"other\":[\"#\",\" objects selected\"]}]],\"editor.sidebar.panels.sitePanel.otherZones\":[\"Other zones\"],\"editor.sidebar.panels.sitePanel.perimeter\":[\"Perimeter:\"],\"editor.sidebar.panels.sitePanel.propertyLine\":[\"Property Line\"],\"editor.sidebar.panels.sitePanel.rooms\":[\"Rooms\"],\"editor.sidebar.panels.sitePanel.selectALevelToViewContent\":[\"Select a level to view content\"],\"editor.sidebar.panels.sitePanel.show\":[\"Show\"],\"editor.sidebar.panels.sitePanel.site\":[\"Site\"],\"editor.sidebar.panels.sitePanel.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted\":[\"The ground level cannot be deleted\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.hide\":[\"Hide\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.show\":[\"Show\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit\":[\"Delete unit\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.focused\":[\"Focused\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit\":[\"Remove from unit\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.room\":[\"Room\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.zone\":[\"Zone\"],\"editor.sidebar.panels.sitePanel.units\":[\"Units\"],\"editor.sidebar.panels.sitePanel.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.sitePanel.uploadScanFloorplan\":[\"Upload scan/floorplan\"],\"editor.sidebar.panels.sitePanel.uploadingProgress\":[[\"assetType\",\"select\",{\"guide\":[\"Uploading guide... \",[\"progress\"],\"%\"],\"other\":[\"Uploading scan... \",[\"progress\"],\"%\"]}]],\"editor.sidebar.panels.sitePanel.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname\":[\"In \",[\"unitName\"]],\"editor.sidebar.panels.zonePanel.actions\":[\"Actions\"],\"editor.sidebar.panels.zonePanel.addOne\":[\"Add one\"],\"editor.sidebar.panels.zonePanel.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.zonePanel.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.zonePanel.delete\":[\"Delete\"],\"editor.sidebar.panels.zonePanel.deleteWithContents\":[\"Delete with contents\"],\"editor.sidebar.panels.zonePanel.noZonesOnThisLevel\":[\"No zones on this level.\"],\"editor.sidebar.panels.zonePanel.saveToCatalog\":[\"Save to catalog\"],\"editor.sidebar.panels.zonePanel.selectALevelToViewAnd\":[\"Select a level to view and create zones\"],\"editor.sidebar.panels.zonePanel.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.zonePanel.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.zonePanel.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.usePluginPanels.labelPluginCrashed\":[\"\\\"\",[\"label\"],\"\\\" plugin crashed\"],\"editor.sidebar.usePluginPanels.loading\":[\"Loading…\"],\"editor.sidebar.usePluginPanels.plugins\":[\"Plugins\"],\"editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd\":[\"This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again.\"],\"editor.snapTarget.ceiling\":[\"Ceiling attachment\"],\"editor.snapTarget.roof\":[\"Roof attachment\"],\"editor.snapTarget.wall\":[\"Wall attachment\"],\"editor.theEditorSceneFailedToRender\":[\"The editor scene failed to render\"],\"editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i\":[\"A curtain wall takes no corner window\"],\"editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5\":[\"A curved wall takes no corner window\"],\"editor.toolHint.addACornerClickTheFirst.o4r67t\":[\"Add a corner · click the first to close\"],\"editor.toolHint.anotherOpeningIsInTheWay.11l9dd0\":[\"Another opening is in the way at the corner\"],\"editor.toolHint.autoHangers.10554ka\":[\"Auto hangers\"],\"editor.toolHint.autoHangersClickOrPressH.rwa3ka\":[\"Auto hangers - click or press H to toggle\"],\"editor.toolHint.autoHangersOff.7yx01x\":[\"Auto hangers: Off\"],\"editor.toolHint.autoHangersOn.10b1umh\":[\"Auto hangers: On\"],\"editor.toolHint.bypassSnapping.w5lmdv\":[\"Bypass snapping\"],\"editor.toolHint.cancel.ew9em3\":[\"Cancel\"],\"editor.toolHint.cancelMove.lvp31c\":[\"Cancel move\"],\"editor.toolHint.cancelRunExit.zqjf3h\":[\"Cancel run / exit\"],\"editor.toolHint.cancelStartPoint.1xbq2lh\":[\"Cancel start point\"],\"editor.toolHint.cornerWindow.1avkzpo\":[\"Corner window\"],\"editor.toolHint.cuts.1y0axps\":[\"Cuts\"],\"editor.toolHint.cutsValue.ouzhvb\":[\"Cuts: \",[\"value\"]],\"editor.toolHint.cutsValueEven.1ngsz3\":[\"Cuts: \",[\"value\"],\", even\"],\"editor.toolHint.cycleMonoGableButterfly.1poga3m\":[\"Cycle mono / gable / butterfly\"],\"editor.toolHint.cycleSnappingMode.1ct0gn6\":[\"Cycle snapping mode\"],\"editor.toolHint.doorStyleClickOrPressL.33pe65\":[\"Door style — click or press L to cycle\"],\"editor.toolHint.doorTypeClickOrPressO.1hhh8gt\":[\"Door type — click or press O to cycle\"],\"editor.toolHint.dropDownspoutFromOutlet.1k4h1m4\":[\"Drop downspout from outlet\"],\"editor.toolHint.ductDiameterDownUp.1wdz5vq\":[\"Duct diameter down / up\"],\"editor.toolHint.endRunCancel.59dh2w\":[\"End run / cancel\"],\"editor.toolHint.exit.engmg5\":[\"Exit\"],\"editor.toolHint.exitDrawing.9noxyb\":[\"Exit drawing\"],\"editor.toolHint.finishAndContinue.1gpfsks\":[\"Finish and continue\"],\"editor.toolHint.finishCeiling.1c1lfel\":[\"Finish ceiling\"],\"editor.toolHint.finishGridAxis.1wvrfhr\":[\"Finish grid axis\"],\"editor.toolHint.finishMeasurement.1e4gv16\":[\"Finish measurement\"],\"editor.toolHint.finishMultiPointWitnesses.8wzskz\":[\"Finish multi-point witnesses\"],\"editor.toolHint.finishSlab.xmnm8e\":[\"Finish slab\"],\"editor.toolHint.flipSide.172xku9\":[\"Flip side\"],\"editor.toolHint.followTraceALineset.mgquwe\":[\"Follow: trace a lineset\"],\"editor.toolHint.forcePlace.1hq1nr\":[\"Force place\"],\"editor.toolHint.freePlacement.1nztdac\":[\"Free placement\"],\"editor.toolHint.goVerticalClickToPlace.dg8g6m\":[\"Go vertical ↕, click to place\"],\"editor.toolHint.highlightOutlet.65hoq8\":[\"Highlight outlet\"],\"editor.toolHint.joinAsACornerWindow.jvdp4j\":[\"Join as a corner window\"],\"editor.toolHint.key.altDrag.ai3wn9\":[\"Alt + drag\"],\"editor.toolHint.key.backspace.qy6qx8\":[\"Backspace\"],\"editor.toolHint.key.click.m8sun\":[\"Click\"],\"editor.toolHint.key.clickAgain.190ws9j\":[\"Click again\"],\"editor.toolHint.key.command.1j2mkia\":[\"Command\"],\"editor.toolHint.key.control.10fbl8e\":[\"Control\"],\"editor.toolHint.key.delete.oay2cq\":[\"Delete\"],\"editor.toolHint.key.doubleClick.vlma6o\":[\"Double click\"],\"editor.toolHint.key.drag.8pwtpf\":[\"Drag\"],\"editor.toolHint.key.enter.1ctpui5\":[\"Enter\"],\"editor.toolHint.key.hover.6xw79p\":[\"Hover\"],\"editor.toolHint.key.hoverADuctEnd.19axr3b\":[\"Hover a duct end\"],\"editor.toolHint.key.hoverAPipeEnd.1jbum5f\":[\"Hover a pipe end\"],\"editor.toolHint.key.hoverGutter.1dwcsno\":[\"Hover gutter\"],\"editor.toolHint.key.leftClick.s4gwfc\":[\"Left click\"],\"editor.toolHint.key.middleClick.1jbaan4\":[\"Middle click\"],\"editor.toolHint.key.rShiftR.13bnobh\":[\"R / Shift+R\"],\"editor.toolHint.key.rightClick.1ovld0h\":[\"Right click\"],\"editor.toolHint.key.scroll.ai6wr0\":[\"Scroll\"],\"editor.toolHint.key.scrollWheel.18j0ix7\":[\"Scroll wheel\"],\"editor.toolHint.key.space.1hrh82t\":[\"Space\"],\"editor.toolHint.key.tab.1xsal0s\":[\"Tab\"],\"editor.toolHint.mountFloorCeilingWall.32dxpv\":[\"Mount: floor / ceiling / wall\"],\"editor.toolHint.noRoomForTheMatchingWindow.1755948\":[\"No room for the matching window on the other wall\"],\"editor.toolHint.noWallMeetsHere.19n99yc\":[\"No wall meets here\"],\"editor.toolHint.numberOfCutsScrollOrClick.1kqe84b\":[\"Number of cuts — scroll or click to change\"],\"editor.toolHint.pickWitnessPoint.k3hgh1\":[\"Pick witness point\"],\"editor.toolHint.pipeSizeDownUp.wp04og\":[\"Pipe size down / up\"],\"editor.toolHint.placeAndContinue.qfwkrq\":[\"Place and continue\"],\"editor.toolHint.placeBlock.1aylwrx\":[\"Place block\"],\"editor.toolHint.placeBoxVentOnRoof.kf88e5\":[\"Place box vent on roof\"],\"editor.toolHint.placeCabinet.b0ouyk\":[\"Place cabinet\"],\"editor.toolHint.placeCanopyOrSetTheNext.12l7zf8\":[\"Place canopy or set the next run point\"],\"editor.toolHint.placeChimneyOnRoof.19dbqu\":[\"Place chimney on roof\"],\"editor.toolHint.placeColumn.1w2vq86\":[\"Place column\"],\"editor.toolHint.placeCupolaOnRoof.ofx3bn\":[\"Place cupola on roof\"],\"editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj\":[\"Place dimension line when needed\"],\"editor.toolHint.placeDoorOnWall.1jwadf\":[\"Place door on wall\"],\"editor.toolHint.placeDormerOnRoof.1w518a6\":[\"Place dormer on roof\"],\"editor.toolHint.placeElevator.1f65zva\":[\"Place elevator\"],\"editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn\":[\"Place eyebrow vent on roof\"],\"editor.toolHint.placeFitting.1pmx3vn\":[\"Place fitting\"],\"editor.toolHint.placeGutterOnRoofEave.llwzf9\":[\"Place gutter on roof eave\"],\"editor.toolHint.placeItLockedTo45.16e4csk\":[\"Place it (locked to 45°)\"],\"editor.toolHint.placeItem.teynf\":[\"Place item\"],\"editor.toolHint.placeMeasurementPoint.bsatli\":[\"Place measurement point\"],\"editor.toolHint.placeOnAFence.1dptstr\":[\"Place on a fence\"],\"editor.toolHint.placeRegister.1uebycd\":[\"Place register\"],\"editor.toolHint.placeRidgeVentOnRoof.2tpd8r\":[\"Place ridge vent on roof\"],\"editor.toolHint.placeShelf.y4l3iw\":[\"Place shelf\"],\"editor.toolHint.placeSkylightOnRoof.e215pe\":[\"Place skylight on roof\"],\"editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn\":[\"Place solar panel array on roof\"],\"editor.toolHint.placeSpawnPoint.205o25\":[\"Place spawn point\"],\"editor.toolHint.placeStairs.zwxxu2\":[\"Place stairs\"],\"editor.toolHint.placeTrap.1gxemn3\":[\"Place trap\"],\"editor.toolHint.placeTurbineVentOnRoof.1iqeluf\":[\"Place turbine vent on roof\"],\"editor.toolHint.placeUnit.8zkx4q\":[\"Place unit\"],\"editor.toolHint.placeWindowOnWall.1k3qnfb\":[\"Place window on wall\"],\"editor.toolHint.placeWithoutTheCornerSnap.1nifqew\":[\"Place without the corner snap\"],\"editor.toolHint.placement.13lcvts\":[\"Placement\"],\"editor.toolHint.placementAuto.1caa52d\":[\"Placement: Auto\"],\"editor.toolHint.placementGround.1ruz145\":[\"Placement: Ground\"],\"editor.toolHint.placementRoof.renkw8\":[\"Placement: Roof\"],\"editor.toolHint.placementSurfaceClickOrPressP.w56h19\":[\"Placement surface - click or press P to cycle\"],\"editor.toolHint.placementType.1mdpl60\":[\"Placement type\"],\"editor.toolHint.placementTypeClickOrPressI.10cgxh2\":[\"Placement type — click or press I to toggle\"],\"editor.toolHint.removeLastPoint.2dmdcx\":[\"Remove last point\"],\"editor.toolHint.removeLastWitness.1b6vvzw\":[\"Remove last witness\"],\"editor.toolHint.rotate.1thqa0a\":[\"Rotate\"],\"editor.toolHint.rotate45.9zq68u\":[\"Rotate ±45°\"],\"editor.toolHint.rotate45FloorCeiling.m9t57d\":[\"Rotate ±45° (floor / ceiling)\"],\"editor.toolHint.rotateGhost15.rsa52c\":[\"Rotate ghost ±15°\"],\"editor.toolHint.rotateOrFlipTheRunSide.t5ior7\":[\"Rotate or flip the run side\"],\"editor.toolHint.rotateRoofDirection90.tvbji2\":[\"Rotate roof direction 90°\"],\"editor.toolHint.rotateSpawnPoint.1opcuu7\":[\"Rotate spawn point\"],\"editor.toolHint.roundRectangularOval.e4nh6x\":[\"Round / rectangular / oval\"],\"editor.toolHint.setFenceStartEnd.1phbwsq\":[\"Set fence start / end\"],\"editor.toolHint.setOneCornerThenTheOpposite.8tsl49\":[\"Set one corner, then the opposite\"],\"editor.toolHint.setRoofFootprint.p50n0o\":[\"Set roof footprint\"],\"editor.toolHint.setWallStartEnd.1r8ubcx\":[\"Set wall start / end\"],\"editor.toolHint.slopeLevel.ftqp67\":[\"Slope / level\"],\"editor.toolHint.snapOntoTheRun.86yhmp\":[\"Snap onto the run\"],\"editor.toolHint.splitAtTheMarks.x5tpdd\":[\"Split at the marks\"],\"editor.toolHint.startGridAxis.k92yza\":[\"Start grid axis\"],\"editor.toolHint.startLineset.23q8dh\":[\"Start lineset\"],\"editor.toolHint.startLiquidLine.1rlxsgd\":[\"Start liquid line\"],\"editor.toolHint.startRun.r6fc5u\":[\"Start run\"],\"editor.toolHint.startSegment.rolym6\":[\"Start segment\"],\"editor.toolHint.stepBackOrCancel.zbfdkn\":[\"Step back or cancel\"],\"editor.toolHint.style.nf3vcm\":[\"Style\"],\"editor.toolHint.styleBarn.k9n1x\":[\"Style: Barn\"],\"editor.toolHint.styleCasement.ziklec\":[\"Style: Casement\"],\"editor.toolHint.styleCraftsman.52e6a9\":[\"Style: Craftsman\"],\"editor.toolHint.styleDoubleHung.1lg8pkg\":[\"Style: Double-hung\"],\"editor.toolHint.styleFrench.1ue3j64\":[\"Style: French\"],\"editor.toolHint.styleGlass.qudnb4\":[\"Style: Glass\"],\"editor.toolHint.styleGrid.1vuq4po\":[\"Style: Grid\"],\"editor.toolHint.styleHalfLouvered.13b1few\":[\"Style: Half-louvered\"],\"editor.toolHint.styleHorizontalBands.12ethxk\":[\"Style: Horizontal bands\"],\"editor.toolHint.styleModern.1fh8hhn\":[\"Style: Modern\"],\"editor.toolHint.stylePanel.922jze\":[\"Style: Panel\"],\"editor.toolHint.stylePaneledGlass.17a17en\":[\"Style: Paneled glass\"],\"editor.toolHint.styleShaker.49z17k\":[\"Style: Shaker\"],\"editor.toolHint.styleSingle.6f3cm0\":[\"Style: Single\"],\"editor.toolHint.styleSixPanel.u49laj\":[\"Style: Six-panel\"],\"editor.toolHint.styleSliding.q81s0i\":[\"Style: Sliding\"],\"editor.toolHint.styleTallGrid.1eotg3x\":[\"Style: Tall grid\"],\"editor.toolHint.styleTransom.kb9bh6\":[\"Style: Transom\"],\"editor.toolHint.styleTripleHung.pxnqi3\":[\"Style: Triple-hung\"],\"editor.toolHint.styleWideGrid.12n27et\":[\"Style: Wide grid\"],\"editor.toolHint.switchRotationAxisYXZ.15lohm6\":[\"Switch rotation axis (Y → X → Z)\"],\"editor.toolHint.theWallsMeetAtTooSharp.kh3wi3\":[\"The walls meet at too sharp or too flat an angle\"],\"editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4\":[\"The window waiting there is taller than this wall\"],\"editor.toolHint.threeOrMoreWallsMeetHere.rgifif\":[\"Three or more walls meet here\"],\"editor.toolHint.traceCeilingOutline.1b639wv\":[\"Trace ceiling outline\"],\"editor.toolHint.traceSlabOutline.z0j2lc\":[\"Trace slab outline\"],\"editor.toolHint.type.1m2zofh\":[\"Type\"],\"editor.toolHint.typeAwning.1cj59al\":[\"Type: Awning\"],\"editor.toolHint.typeBarn.sbnygq\":[\"Type: Barn\"],\"editor.toolHint.typeBay.1200hc1\":[\"Type: Bay\"],\"editor.toolHint.typeBow.14s2t9p\":[\"Type: Bow\"],\"editor.toolHint.typeCabinet.1r68je5\":[\"Type: Cabinet\"],\"editor.toolHint.typeCasement.176uo9j\":[\"Type: Casement\"],\"editor.toolHint.typeDouble.af7w4i\":[\"Type: Double\"],\"editor.toolHint.typeDoubleHung.t482fu\":[\"Type: Double Hung\"],\"editor.toolHint.typeFixed.v1cjez\":[\"Type: Fixed\"],\"editor.toolHint.typeFolding.sxxyng\":[\"Type: Folding\"],\"editor.toolHint.typeFrench.1y57z1b\":[\"Type: French\"],\"editor.toolHint.typeHinged.1xrcfju\":[\"Type: Hinged\"],\"editor.toolHint.typeIsland.ufr3ce\":[\"Type: Island\"],\"editor.toolHint.typeLouvered.oxl1cz\":[\"Type: Louvered\"],\"editor.toolHint.typePocket.hc65x9\":[\"Type: Pocket\"],\"editor.toolHint.typeSingleHung.p53pyl\":[\"Type: Single Hung\"],\"editor.toolHint.typeSliding.nwmr3f\":[\"Type: Sliding\"],\"editor.toolHint.verticalStackClickToPlace.ijcn4q\":[\"Vertical stack ↕, click to place\"],\"editor.toolHint.wasteVent.1hvi3vj\":[\"Waste / vent\"],\"editor.toolHint.windowStyleClickOrPressL.ah7qm9\":[\"Window style — click or press L to cycle\"],\"editor.toolHint.windowTypeClickOrPressO.wv4x35\":[\"Window type — click or press O to cycle\"],\"editor.tools.distribution.distributionRunTool.lengthField\":[\"Length: \",[\"field\"],\" m\"],\"editor.tools.distribution.distributionRunTool.tabDirection\":[\"Tab: direction\"],\"editor.tools.distribution.distributionRunTool.typeALength\":[\"type a length\"],\"editor.views.plugins\":[\"Plugins\"],\"editor.views.view2d\":[\"2D\"],\"editor.views.view3d\":[\"3D\"],\"editor.views.viewBar.auto\":[\"Auto\"],\"editor.views.viewBar.browsePlugins\":[\"Browse plugins\"],\"editor.views.viewBar.closeLabel\":[\"Close \",[\"label\"]],\"editor.views.viewBar.closeSplit\":[\"Close split (\\\\)\"],\"editor.views.viewBar.ground\":[\"Ground\"],\"editor.views.viewBar.groundSolid\":[[\"standing\",\"select\",{\"partly\":[\"Partly below grade · ground solid\"],\"below\":[\"Below grade · ground solid\"],\"other\":[\"Ground solid\"]}]],\"editor.views.viewBar.groundVeiled\":[[\"standing\",\"select\",{\"partly\":[\"Partly below grade · ground veiled\"],\"below\":[\"Below grade · ground veiled\"],\"other\":[\"Ground veiled\"]}]],\"editor.views.viewBar.keepTheGroundSolid\":[\"Keep the ground solid\"],\"editor.views.viewBar.moreViews\":[\"More views\"],\"editor.views.viewBar.paneViews\":[\"Pane \",[\"pane\"],\" views\"],\"editor.views.viewBar.pinLabel\":[\"Pin \",[\"label\"]],\"editor.views.viewBar.pinToTheViewBar\":[\"Pin to the view bar\"],\"editor.views.viewBar.solid\":[\"Solid\"],\"editor.views.viewBar.split\":[\"Split (\\\\)\"],\"editor.views.viewBar.split2\":[\"Split\"],\"editor.views.viewBar.swapPanes\":[\"Swap panes\"],\"editor.views.viewBar.unpinFromTheViewBar\":[\"Unpin from the view bar\"],\"editor.views.viewBar.unpinLabel\":[\"Unpin \",[\"label\"]],\"editor.views.viewBar.veilTheGroundOverALevel\":[\"Veil the ground over a level below grade\"],\"editor.views.viewBar.views\":[\"Views\"],\"editor.youCanRetryTheSceneOr\":[\"You can retry the scene or return home without reloading the whole app shell.\"],\"node.block.description\":[\"A topology-backed solid edited directly in the canvas.\"],\"node.block.label\":[\"Block\"],\"node.boxVent.description\":[\"Small louvered exhaust vent that sits on a roof slope.\"],\"node.boxVent.label\":[\"Box Vent\"],\"node.boxVent.panel.actions\":[\"Actions\"],\"node.boxVent.panel.baseFlange\":[\"Base Flange\"],\"node.boxVent.panel.baseHeight\":[\"Base Height\"],\"node.boxVent.panel.baseInset\":[\"Base Inset\"],\"node.boxVent.panel.box\":[\"Box\"],\"node.boxVent.panel.boxVent\":[\"Box Vent\"],\"node.boxVent.panel.cap\":[\"Cap\"],\"node.boxVent.panel.capHeight\":[\"Cap Height\"],\"node.boxVent.panel.cornerBevel\":[\"Corner Bevel\"],\"node.boxVent.panel.delete\":[\"Delete\"],\"node.boxVent.panel.depth\":[\"Depth\"],\"node.boxVent.panel.dimensions\":[\"Dimensions\"],\"node.boxVent.panel.dome\":[\"Dome\"],\"node.boxVent.panel.domeCurvature\":[\"Dome Curvature\"],\"node.boxVent.panel.duplicate\":[\"Duplicate\"],\"node.boxVent.panel.gapHeight\":[\"Gap Height\"],\"node.boxVent.panel.height\":[\"Height\"],\"node.boxVent.panel.hoodOverhang\":[\"Hood Overhang\"],\"node.boxVent.panel.move\":[\"Move\"],\"node.boxVent.panel.position\":[\"Position\"],\"node.boxVent.panel.rotation\":[\"Rotation\"],\"node.boxVent.panel.style\":[\"Style\"],\"node.boxVent.panel.topTaper\":[\"Top Taper\"],\"node.boxVent.panel.width\":[\"Width\"],\"node.building.description\":[\"A building container holding one or more levels.\"],\"node.building.label\":[\"Building\"],\"node.cabinet.compartmentCard.baskets\":[\"Baskets\"],\"node.cabinet.compartmentCard.bottom\":[\"Bottom\"],\"node.cabinet.compartmentCard.bottomFreezer\":[\"Bottom Freezer\"],\"node.cabinet.compartmentCard.bowls\":[\"Bowls\"],\"node.cabinet.compartmentCard.burnersOn\":[\"Burners on\"],\"node.cabinet.compartmentCard.chimney\":[\"Chimney\"],\"node.cabinet.compartmentCard.decrease\":[\"Decrease \",[\"label\"]],\"node.cabinet.compartmentCard.door\":[\"Door\"],\"node.cabinet.compartmentCard.double\":[\"Double\"],\"node.cabinet.compartmentCard.drawer\":[\"Drawer\"],\"node.cabinet.compartmentCard.drawers\":[\"Drawers\"],\"node.cabinet.compartmentCard.flipHinge\":[\"Flip hinge\"],\"node.cabinet.compartmentCard.flipTheDoorHingeToThe\":[\"Flip the door hinge to the opposite side\"],\"node.cabinet.compartmentCard.fridge\":[\"Fridge\"],\"node.cabinet.compartmentCard.gas\":[\"Gas\"],\"node.cabinet.compartmentCard.glass\":[\"Glass\"],\"node.cabinet.compartmentCard.height\":[\"Height\"],\"node.cabinet.compartmentCard.hob\":[\"Hob\"],\"node.cabinet.compartmentCard.increase\":[\"Increase \",[\"label\"]],\"node.cabinet.compartmentCard.induction\":[\"Induction\"],\"node.cabinet.compartmentCard.layout\":[\"Layout\"],\"node.cabinet.compartmentCard.left\":[\"Left\"],\"node.cabinet.compartmentCard.micro\":[\"Micro\"],\"node.cabinet.compartmentCard.moveDown\":[\"Move down\"],\"node.cabinet.compartmentCard.moveUp\":[\"Move up\"],\"node.cabinet.compartmentCard.oven\":[\"Oven\"],\"node.cabinet.compartmentCard.pullout\":[\"Pullout\"],\"node.cabinet.compartmentCard.rack\":[\"Rack\"],\"node.cabinet.compartmentCard.removeCompartment\":[\"Remove compartment\"],\"node.cabinet.compartmentCard.right\":[\"Right\"],\"node.cabinet.compartmentCard.shelf\":[\"Shelf\"],\"node.cabinet.compartmentCard.shelves\":[\"Shelves\"],\"node.cabinet.compartmentCard.shelvesInside\":[\"Shelves inside\"],\"node.cabinet.compartmentCard.single\":[\"Single\"],\"node.cabinet.compartmentCard.sink\":[\"Sink\"],\"node.cabinet.compartmentCard.style\":[\"Style\"],\"node.cabinet.compartmentCard.surface\":[\"Surface\"],\"node.cabinet.compartmentCard.top\":[\"Top\"],\"node.cabinet.compartmentCard.topFreezer\":[\"Top Freezer\"],\"node.cabinet.compartmentCard.topGrate\":[\"Top grate\"],\"node.cabinet.compartmentCard.tray\":[\"Tray\"],\"node.cabinet.compartmentCard.type\":[\"Type\"],\"node.cabinet.compartmentCard.washer\":[\"Washer\"],\"node.cabinet.compartmentCard.wire\":[\"Wire\"],\"node.cabinet.description\":[\"A configurable parametric base cabinet.\"],\"node.cabinet.essentialControls.bottomFreezer\":[\"Bottom freezer\"],\"node.cabinet.essentialControls.cooktopType\":[\"Cooktop type\"],\"node.cabinet.essentialControls.doorDirection\":[\"Door direction\"],\"node.cabinet.essentialControls.double\":[\"Double\"],\"node.cabinet.essentialControls.fridgeType\":[\"Fridge type\"],\"node.cabinet.essentialControls.gas\":[\"Gas\"],\"node.cabinet.essentialControls.glass\":[\"Glass\"],\"node.cabinet.essentialControls.induction\":[\"Induction\"],\"node.cabinet.essentialControls.itemOptions\":[\"Item options\"],\"node.cabinet.essentialControls.leftHinge\":[\"Left hinge\"],\"node.cabinet.essentialControls.rightHinge\":[\"Right hinge\"],\"node.cabinet.essentialControls.single\":[\"Single\"],\"node.cabinet.essentialControls.sinkLayout\":[\"Sink layout\"],\"node.cabinet.essentialControls.topFreezer\":[\"Top freezer\"],\"node.cabinet.kitchenCollection.chooseLabel\":[\"Choose \",[\"label\"]],\"node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge\":[\"No kitchen items match. Try “fridge”, “sink”, or “cabinet”.\"],\"node.cabinet.kitchenCollection.searchFridgeSinkCabinets\":[\"Search fridge, sink, cabinets…\"],\"node.cabinet.kitchenCollection.searchKitchenItems\":[\"Search kitchen items\"],\"node.cabinet.kitchenGroup.appliances\":[\"Appliances & fixtures\"],\"node.cabinet.kitchenGroup.cabinets\":[\"Cabinets\"],\"node.cabinet.kitchenModuleActions.addBeside\":[\"Add beside\"],\"node.cabinet.kitchenModuleActions.addBesideSelectedItem\":[\"Add beside selected item\"],\"node.cabinet.kitchenModuleActions.addLabel\":[\"Add \",[\"label\"]],\"node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis\":[\"An upper cabinet attached to this item will be removed to make room.\"],\"node.cabinet.kitchenModuleActions.cabinet\":[\"Cabinet\"],\"node.cabinet.kitchenModuleActions.cancel\":[\"Cancel\"],\"node.cabinet.kitchenModuleActions.cannotFit\":[\"This \",[\"width\"],\" item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again.\"],\"node.cabinet.kitchenModuleActions.insertsBeside\":[[\"side\",\"select\",{\"left\":[\"Inserts on the left of the selected item.\"],\"other\":[\"Inserts on the right of the selected item.\"]}]],\"node.cabinet.kitchenModuleActions.kitchenChanged\":[\"The kitchen changed. Select an item again to refresh the preview.\"],\"node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs\":[\"Kitchen run preview, highlighted item is the proposed change\"],\"node.cabinet.kitchenModuleActions.leftSide\":[\"Left side\"],\"node.cabinet.kitchenModuleActions.moved\":[[\"name\"],\": moves \",[\"distance\"]],\"node.cabinet.kitchenModuleActions.previewItem\":[[\"name\"],\": \",[\"width\"]],\"node.cabinet.kitchenModuleActions.replaceSelectedItem\":[\"Replace selected item\"],\"node.cabinet.kitchenModuleActions.replaceWith\":[\"Replace with…\"],\"node.cabinet.kitchenModuleActions.replaceWithLabel\":[\"Replace with \",[\"label\"]],\"node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem\":[\"Replaces only the selected item.\"],\"node.cabinet.kitchenModuleActions.resized\":[[\"name\"],\": \",[\"before\"],\" → \",[\"after\"]],\"node.cabinet.kitchenModuleActions.rightSide\":[\"Right side\"],\"node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen\":[\"Unable to update this kitchen.\"],\"node.cabinet.label\":[\"Modular Cabinet\"],\"node.cabinet.panel.aboveCeiling\":[\"Finished height extends \",[\"overflow\"],\" mm above the ceiling.\"],\"node.cabinet.panel.addChimney\":[\"Add chimney\"],\"node.cabinet.panel.addCompartment\":[\"Add compartment\"],\"node.cabinet.panel.addWallCabinet\":[\"Add wall cabinet\"],\"node.cabinet.panel.advanced\":[\"Advanced\"],\"node.cabinet.panel.appliance\":[\"Appliance\"],\"node.cabinet.panel.applianceFront\":[\"Appliance front\"],\"node.cabinet.panel.auto\":[\"Auto\"],\"node.cabinet.panel.bar\":[\"Bar\"],\"node.cabinet.panel.baseCabinet\":[\"Base Cabinet\"],\"node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview\":[\"Cabinet construction, compartments, and opening preview.\"],\"node.cabinet.panel.cabinetPanel\":[\"Cabinet panel\"],\"node.cabinet.panel.cabinetType\":[\"Cabinet Type\"],\"node.cabinet.panel.center\":[\"Center\"],\"node.cabinet.panel.close\":[\"Close\"],\"node.cabinet.panel.closeCabinet\":[\"Close cabinet\"],\"node.cabinet.panel.commonWallCabinetHeightsUseThe\":[\"Common wall-cabinet heights. Use the slider below for a custom height.\"],\"node.cabinet.panel.compartments\":[\"Compartments\"],\"node.cabinet.panel.cutout\":[\"Cutout\"],\"node.cabinet.panel.depth\":[\"Depth\"],\"node.cabinet.panel.dimensions\":[\"Dimensions\"],\"node.cabinet.panel.editKitchenRun\":[\"Edit kitchen run\"],\"node.cabinet.panel.fillToCeiling\":[\"Fill to ceiling\"],\"node.cabinet.panel.finish\":[\"Finish\"],\"node.cabinet.panel.frontConstruction\":[\"Front construction\"],\"node.cabinet.panel.handles\":[\"Handles\"],\"node.cabinet.panel.height\":[\"Height\"],\"node.cabinet.panel.heightPreset\":[\"Height preset\"],\"node.cabinet.panel.hole\":[\"Hole\"],\"node.cabinet.panel.inset\":[\"Inset\"],\"node.cabinet.panel.kitchen\":[\"Kitchen\"],\"node.cabinet.panel.kitchenRun\":[\"Kitchen run\"],\"node.cabinet.panel.knob\":[\"Knob\"],\"node.cabinet.panel.mounting\":[\"Mounting\"],\"node.cabinet.panel.none\":[\"None\"],\"node.cabinet.panel.open\":[\"Open\"],\"node.cabinet.panel.openAnimation\":[\"Open Animation\"],\"node.cabinet.panel.openCabinet\":[\"Open cabinet\"],\"node.cabinet.panel.overlay\":[\"Overlay\"],\"node.cabinet.panel.panelReady\":[\"Panel-ready\"],\"node.cabinet.panel.planningChecks\":[\"Planning checks\"],\"node.cabinet.panel.play\":[\"Play\"],\"node.cabinet.panel.playAnimation\":[\"Play animation\"],\"node.cabinet.panel.position\":[\"Position\"],\"node.cabinet.panel.raisedArch\":[\"Raised Arch\"],\"node.cabinet.panel.removeWallCabinet\":[\"Remove wall cabinet\"],\"node.cabinet.panel.replaceWith\":[\"Replace with…\"],\"node.cabinet.panel.revealGap\":[\"Reveal gap\"],\"node.cabinet.panel.shaker\":[\"Shaker\"],\"node.cabinet.panel.sharedCountertopPlinthAndFinishes\":[\"Shared countertop, plinth, and finishes\"],\"node.cabinet.panel.slab\":[\"Slab\"],\"node.cabinet.panel.solidMatchingFrontBetweenTheCabinet\":[\"Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler.\"],\"node.cabinet.panel.standardWidth\":[\"Standard width\"],\"node.cabinet.panel.stop\":[\"Stop\"],\"node.cabinet.panel.stopAnimation\":[\"Stop animation\"],\"node.cabinet.panel.style\":[\"Style\"],\"node.cabinet.panel.tallCabinet\":[\"Tall Cabinet\"],\"node.cabinet.panel.top\":[\"Top\"],\"node.cabinet.panel.topCabinet\":[\"Top Cabinet\"],\"node.cabinet.panel.topCeiling\":[\"Top / Ceiling\"],\"node.cabinet.panel.topDepth\":[\"Top depth\"],\"node.cabinet.panel.topHeight\":[\"Top height\"],\"node.cabinet.panel.trimSoffit\":[\"Trim / Soffit\"],\"node.cabinet.panel.usesTheCabinetFrontStyleReveal\":[\"Uses the cabinet front style, reveal, handle, and front material settings below.\"],\"node.cabinet.panel.wallFiller\":[\"Wall filler\"],\"node.cabinet.panel.width\":[\"Width\"],\"node.cabinet.preset.baseCabinet\":[\"Base cabinet\"],\"node.cabinet.preset.dishwasher\":[\"Dishwasher\"],\"node.cabinet.preset.drawerCabinet\":[\"Drawer cabinet\"],\"node.cabinet.preset.fridge\":[\"Fridge\"],\"node.cabinet.preset.gasCooktop\":[\"Gas cooktop\"],\"node.cabinet.preset.inductionCooktop\":[\"Induction cooktop\"],\"node.cabinet.preset.ovenTower\":[\"Oven Tower\"],\"node.cabinet.preset.sinkCabinet\":[\"Sink cabinet\"],\"node.cabinet.preset.tallPantry\":[\"Tall Pantry\"],\"node.cabinet.preset.wallCabinet\":[\"Wall cabinet\"],\"node.cabinet.runPanel.addLeft\":[\"Add left\"],\"node.cabinet.runPanel.addRight\":[\"Add right\"],\"node.cabinet.runPanel.alignCabinetsHandles\":[\"Align cabinets & handles\"],\"node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop\":[\"Applies depth, carcass, plinth, and countertop thickness to this run.\"],\"node.cabinet.runPanel.arrayCreate\":[[\"count\",\"plural\",{\"one\":[\"Create \",[\"number\"],\" module copy\"],\"other\":[\"Create \",[\"number\"],\" module copies\"]}]],\"node.cabinet.runPanel.arrayCreateNamed\":[[\"count\",\"plural\",{\"one\":[\"Create \",[\"number\"],\" \",[\"name\"],\" copy\"],\"other\":[\"Create \",[\"number\"],\" \",[\"name\"],\" copies\"]}]],\"node.cabinet.runPanel.arrayInvalidOptions\":[\"Choose a copy count from 1 to 20 and spacing from 0 to 2 m\"],\"node.cabinet.runPanel.arrayNoRoom\":[\"There is not enough room in this run for the requested array\"],\"node.cabinet.runPanel.arrayNoSource\":[\"Choose a standard or appliance module as the source\"],\"node.cabinet.runPanel.auto\":[\"Auto\"],\"node.cabinet.runPanel.back\":[\"Back\"],\"node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping\":[\"Balances standard base cabinets while keeping appliance and corner-filler widths fixed.\"],\"node.cabinet.runPanel.bar\":[\"Bar\"],\"node.cabinet.runPanel.barCounter\":[\"Bar counter\"],\"node.cabinet.runPanel.barDepth\":[\"Bar depth\"],\"node.cabinet.runPanel.barHeight\":[\"Bar height\"],\"node.cabinet.runPanel.carcassHeight\":[\"Carcass height\"],\"node.cabinet.runPanel.center\":[\"Center\"],\"node.cabinet.runPanel.commonWallCabinetHeightsUseThe\":[\"Common wall-cabinet heights. Use the slider below for a custom height.\"],\"node.cabinet.runPanel.copies\":[\"Copies\"],\"node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure\":[\"Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space.\"],\"node.cabinet.runPanel.cornerFillersCannotBeUsedAs\":[\"Corner fillers cannot be used as array sources\"],\"node.cabinet.runPanel.countertopDepth\":[\"Countertop depth\"],\"node.cabinet.runPanel.countertopHeight\":[\"Countertop height\"],\"node.cabinet.runPanel.createArray\":[\"Create array\"],\"node.cabinet.runPanel.cutout\":[\"Cutout\"],\"node.cabinet.runPanel.deleteModule\":[\"Delete \",[\"name\"]],\"node.cabinet.runPanel.depth\":[\"Depth\"],\"node.cabinet.runPanel.direction\":[\"Direction\"],\"node.cabinet.runPanel.duplicateAlongRun\":[\"Duplicate along run\"],\"node.cabinet.runPanel.equalizeAll\":[\"Equalize all resizeable standard base cabinets in this run\"],\"node.cabinet.runPanel.equalizeAlreadyEqual\":[\"The resizeable cabinet widths are already equal\"],\"node.cabinet.runPanel.equalizeNeedsTwo\":[\"At least two standard base cabinets are required\"],\"node.cabinet.runPanel.equalizeNoFit\":[\"The available run width cannot satisfy the cabinet width limits\"],\"node.cabinet.runPanel.equalizeWidths\":[\"Equalize widths\"],\"node.cabinet.runPanel.fillerPlanSide\":[[\"side\",\"select\",{\"left\":[\"Left: \",[\"width\"],\" mm\"],\"other\":[\"Right: \",[\"width\"],\" mm\"]}]],\"node.cabinet.runPanel.finishAlign\":[\"Finish & align\"],\"node.cabinet.runPanel.finishedBack\":[\"Finished back\"],\"node.cabinet.runPanel.finishedExposedEnds\":[\"Finished exposed ends\"],\"node.cabinet.runPanel.fitWallFillers\":[\"Fit wall fillers\"],\"node.cabinet.runPanel.fits50150MmGapsAt\":[\"Fits 50–150 mm gaps at run ends beside perpendicular walls.\"],\"node.cabinet.runPanel.fronts\":[\"Fronts\"],\"node.cabinet.runPanel.handles\":[\"Handles\"],\"node.cabinet.runPanel.heightPreset\":[\"Height preset\"],\"node.cabinet.runPanel.hole\":[\"Hole\"],\"node.cabinet.runPanel.inset\":[\"Inset\"],\"node.cabinet.runPanel.islandBar\":[\"Island & Bar\"],\"node.cabinet.runPanel.kitchenRun\":[\"Kitchen run\"],\"node.cabinet.runPanel.knob\":[\"Knob\"],\"node.cabinet.runPanel.left\":[\"Left\"],\"node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd\":[\"Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments.\"],\"node.cabinet.runPanel.metricProfile\":[\"Metric · 600 mm\"],\"node.cabinet.runPanel.moduleNumber\":[\"Module \",[\"number\"]],\"node.cabinet.runPanel.modules\":[\"Modules\"],\"node.cabinet.runPanel.mounting\":[\"Mounting\"],\"node.cabinet.runPanel.noEligibleModule\":[\"No eligible module\"],\"node.cabinet.runPanel.none\":[\"None\"],\"node.cabinet.runPanel.overlay\":[\"Overlay\"],\"node.cabinet.runPanel.plinthHeight\":[\"Plinth height\"],\"node.cabinet.runPanel.position\":[\"Position\"],\"node.cabinet.runPanel.raisedArch\":[\"Raised Arch\"],\"node.cabinet.runPanel.revealGap\":[\"Reveal gap\"],\"node.cabinet.runPanel.right\":[\"Right\"],\"node.cabinet.runPanel.seatingOverhang\":[\"Seating overhang\"],\"node.cabinet.runPanel.shaker\":[\"Shaker\"],\"node.cabinet.runPanel.sharedPlinthCountertop\":[\"Shared Plinth & Countertop\"],\"node.cabinet.runPanel.showCountertop\":[\"Show countertop\"],\"node.cabinet.runPanel.showPlinth\":[\"Show plinth\"],\"node.cabinet.runPanel.slab\":[\"Slab\"],\"node.cabinet.runPanel.sourceModule\":[\"Source module\"],\"node.cabinet.runPanel.spacing\":[\"Spacing\"],\"node.cabinet.runPanel.standardDimensions\":[\"Standard dimensions\"],\"node.cabinet.runPanel.style\":[\"Style\"],\"node.cabinet.runPanel.summaryCompartment\":[[\"type\",\"select\",{\"shelf\":[\"Shelf\"],\"drawer\":[\"Drawer\"],\"door\":[\"Door\"],\"oven\":[\"Oven\"],\"microwave\":[\"Microwave\"],\"dishwasher\":[\"Dishwasher\"],\"sink\":[\"Sink\"],\"cooktop_gas\":[\"Gas cooktop\"],\"cooktop_induction\":[\"Induction cooktop\"],\"pull_out_pantry\":[\"Pull-out pantry\"],\"fridge_single\":[\"Fridge\"],\"fridge_double\":[\"Double fridge\"],\"fridge_top_freezer\":[\"Top-freezer fridge\"],\"fridge_bottom_freezer\":[\"Bottom-freezer fridge\"],\"hood_pyramid\":[\"Pyramid hood\"],\"hood_curved_glass\":[\"Curved glass hood\"],\"other\":[[\"raw\"]]}]],\"node.cabinet.runPanel.summaryCompartments\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" compartment\"],\"other\":[[\"number\"],\" compartments\"]}]],\"node.cabinet.runPanel.summaryEmpty\":[\"Empty\"],\"node.cabinet.runPanel.summaryTall\":[\"Tall cabinet\"],\"node.cabinet.runPanel.top\":[\"Top\"],\"node.cabinet.runPanel.unableToFinish\":[\"Unable to finish this kitchen run.\"],\"node.cabinet.runPanel.usProfile\":[\"US · 24 in\"],\"node.cabinet.runPanel.useAsArraySource\":[\"Use \",[\"name\"],\" as array source\"],\"node.cabinet.runPanel.useThisModuleAsTheArray\":[\"Use this module as the array source\"],\"node.cabinet.runPanel.waterfallEnds\":[\"Waterfall ends\"],\"node.cabinetModule.description\":[\"An editable module inside a modular cabinet run.\"],\"node.cabinetModule.label\":[\"Cabinet Module\"],\"node.ceiling.description\":[\"A polygon-bounded ceiling surface that hosts ceiling-mounted items.\"],\"node.ceiling.label\":[\"Ceiling\"],\"node.ceiling.panel.addHole\":[\"Add Hole\"],\"node.ceiling.panel.area\":[\"Area\"],\"node.ceiling.panel.auto\":[\"Auto\"],\"node.ceiling.panel.ceiling\":[\"Ceiling\"],\"node.ceiling.panel.customHeight\":[\"Custom height\"],\"node.ceiling.panel.cutOpening\":[\"Cut opening\"],\"node.ceiling.panel.done\":[\"Done\"],\"node.ceiling.panel.followsLevel\":[\"Follows level\"],\"node.ceiling.panel.height\":[\"Height\"],\"node.ceiling.panel.high30m\":[\"High (3.0m)\"],\"node.ceiling.panel.high90\":[\"High (9'0\\\")\"],\"node.ceiling.panel.holes\":[\"Holes\"],\"node.ceiling.panel.info\":[\"Info\"],\"node.ceiling.panel.limitedByLevel\":[\"Limited by the level to \",[\"height\"],\" — raise the level height for a taller ceiling.\"],\"node.ceiling.panel.low24m\":[\"Low (2.4m)\"],\"node.ceiling.panel.low80\":[\"Low (8'0\\\")\"],\"node.ceiling.panel.move\":[\"Move\"],\"node.ceiling.panel.noHoles\":[\"No holes\"],\"node.ceiling.panel.standard25m\":[\"Standard (2.5m)\"],\"node.ceiling.panel.standard86\":[\"Standard (8'6\\\")\"],\"node.ceiling.panel.tallerThanLevel\":[\"Taller than this level (\",[\"available\"],\" available). Raise the level height first.\"],\"node.chimney.description\":[\"Vertical masonry stack on a roof segment.\"],\"node.chimney.label\":[\"Chimney\"],\"node.chimney.panel.aboveRidge\":[\"Above Ridge\"],\"node.chimney.panel.actions\":[\"Actions\"],\"node.chimney.panel.back\":[\"Back\"],\"node.chimney.panel.bands\":[\"Bands\"],\"node.chimney.panel.cap\":[\"Cap\"],\"node.chimney.panel.chimney\":[\"Chimney\"],\"node.chimney.panel.chimneyType\":[\"Chimney Type\"],\"node.chimney.panel.corbeled\":[\"Corbeled\"],\"node.chimney.panel.cornerBevel\":[\"Corner Bevel\"],\"node.chimney.panel.count\":[\"Count\"],\"node.chimney.panel.cricket\":[\"Cricket\"],\"node.chimney.panel.cutoutOffset\":[\"Cutout Offset\"],\"node.chimney.panel.delete\":[\"Delete\"],\"node.chimney.panel.depth\":[\"Depth\"],\"node.chimney.panel.diameter\":[\"Diameter\"],\"node.chimney.panel.double\":[\"Double\"],\"node.chimney.panel.extent\":[\"Extent\"],\"node.chimney.panel.flat\":[\"Flat\"],\"node.chimney.panel.flues\":[\"Flues\"],\"node.chimney.panel.footprint\":[\"Footprint\"],\"node.chimney.panel.front\":[\"Front\"],\"node.chimney.panel.height\":[\"Height\"],\"node.chimney.panel.hollowDepth\":[\"Hollow Depth\"],\"node.chimney.panel.length\":[\"Length\"],\"node.chimney.panel.none\":[\"None\"],\"node.chimney.panel.offset\":[\"Offset\"],\"node.chimney.panel.overhang\":[\"Overhang\"],\"node.chimney.panel.panels\":[\"Panels\"],\"node.chimney.panel.position\":[\"Position\"],\"node.chimney.panel.rectangular\":[\"Rectangular\"],\"node.chimney.panel.rotation\":[\"Rotation\"],\"node.chimney.panel.round\":[\"Round\"],\"node.chimney.panel.shoulder\":[\"Shoulder\"],\"node.chimney.panel.sideMargin\":[\"Side Margin\"],\"node.chimney.panel.simple\":[\"Simple\"],\"node.chimney.panel.single\":[\"Single\"],\"node.chimney.panel.sloped\":[\"Sloped\"],\"node.chimney.panel.spacing\":[\"Spacing\"],\"node.chimney.panel.square\":[\"Square\"],\"node.chimney.panel.stepped\":[\"Stepped\"],\"node.chimney.panel.style\":[\"Style\"],\"node.chimney.panel.tapered\":[\"Tapered\"],\"node.chimney.panel.thickness\":[\"Thickness\"],\"node.chimney.panel.topOffset\":[\"Top Offset\"],\"node.chimney.panel.wallThickness\":[\"Wall Thickness\"],\"node.chimney.panel.width\":[\"Width\"],\"node.chimney.preset.brick\":[\"Brick\"],\"node.chimney.preset.modern\":[\"Modern\"],\"node.chimney.preset.round\":[\"Round\"],\"node.column.description\":[\"A parametric column with configurable cross-section, base, and capital.\"],\"node.column.label\":[\"Column\"],\"node.column.panel.aFrame\":[\"A-Frame\"],\"node.column.panel.actions\":[\"Actions\"],\"node.column.panel.applyPreset\":[\"Apply preset...\"],\"node.column.panel.applyProportion\":[\"Apply proportion...\"],\"node.column.panel.bottomDepth\":[\"Bottom Depth\"],\"node.column.panel.bottomHeight\":[\"Bottom Height\"],\"node.column.panel.bottomSpread\":[\"Bottom Spread\"],\"node.column.panel.bottomStepSpread\":[\"Bottom Step Spread\"],\"node.column.panel.bottomTiers\":[\"Bottom Tiers\"],\"node.column.panel.bottomWidth\":[\"Bottom Width\"],\"node.column.panel.boxFrame\":[\"Box Frame\"],\"node.column.panel.braceDepth\":[\"Brace Depth\"],\"node.column.panel.braceWidth\":[\"Brace Width\"],\"node.column.panel.bulge\":[\"Bulge\"],\"node.column.panel.bulged\":[\"Bulged\"],\"node.column.panel.column\":[\"Column\"],\"node.column.panel.connectorPlates\":[\"Connector Plates\"],\"node.column.panel.delete\":[\"Delete\"],\"node.column.panel.depth\":[\"Depth\"],\"node.column.panel.dimensions\":[\"Dimensions\"],\"node.column.panel.edgeSoftness\":[\"Edge Softness\"],\"node.column.panel.endWidth\":[\"End Width\"],\"node.column.panel.ends\":[\"Ends\"],\"node.column.panel.forkSpread\":[\"Fork Spread\"],\"node.column.panel.heavy\":[\"Heavy\"],\"node.column.panel.height\":[\"Height\"],\"node.column.panel.heightAndFootprintAreControlledBy\":[\"Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to.\"],\"node.column.panel.hourglass\":[\"Hourglass\"],\"node.column.panel.kBrace\":[\"K Brace\"],\"node.column.panel.move\":[\"Move\"],\"node.column.panel.neckWidth\":[\"Neck Width\"],\"node.column.panel.noBottom\":[\"No Bottom\"],\"node.column.panel.noTop\":[\"No Top\"],\"node.column.panel.plinthThickness\":[\"Plinth Thickness\"],\"node.column.panel.portalFrame\":[\"Portal Frame\"],\"node.column.panel.preset\":[\"Preset\"],\"node.column.panel.rectangular\":[\"Rectangular\"],\"node.column.panel.ringPairs\":[\"Ring Pairs\"],\"node.column.panel.ringSpread\":[\"Ring Spread\"],\"node.column.panel.ringThickness\":[\"Ring Thickness\"],\"node.column.panel.round\":[\"Round\"],\"node.column.panel.roundBandWidth\":[\"Round Band Width\"],\"node.column.panel.roundedBottom\":[\"Rounded Bottom\"],\"node.column.panel.roundedTop\":[\"Rounded Top\"],\"node.column.panel.segmentTwist\":[\"Segment Twist\"],\"node.column.panel.shaft\":[\"Shaft\"],\"node.column.panel.shaftCornerRadius\":[\"Shaft Corner Radius\"],\"node.column.panel.shaftWidth\":[\"Shaft Width\"],\"node.column.panel.shape\":[\"Shape\"],\"node.column.panel.shortStout\":[\"Short / Stout\"],\"node.column.panel.simpleBlockBottom\":[\"Simple Block Bottom\"],\"node.column.panel.simpleTop\":[\"Simple Top\"],\"node.column.panel.singleStrut\":[\"Single Strut\"],\"node.column.panel.slender\":[\"Slender\"],\"node.column.panel.square\":[\"Square\"],\"node.column.panel.squarePlinthBottom\":[\"Square Plinth Bottom\"],\"node.column.panel.standard\":[\"Standard\"],\"node.column.panel.steppedBottom\":[\"Stepped Bottom\"],\"node.column.panel.steppedTop\":[\"Stepped Top\"],\"node.column.panel.straight\":[\"Straight\"],\"node.column.panel.taper\":[\"Taper\"],\"node.column.panel.tapered\":[\"Tapered\"],\"node.column.panel.topDepth\":[\"Top Depth\"],\"node.column.panel.topHeight\":[\"Top Height\"],\"node.column.panel.topSpread\":[\"Top Spread\"],\"node.column.panel.topStepSpread\":[\"Top Step Spread\"],\"node.column.panel.topTiers\":[\"Top Tiers\"],\"node.column.panel.topWidth\":[\"Top Width\"],\"node.column.panel.transform\":[\"Transform\"],\"node.column.panel.trestle\":[\"Trestle\"],\"node.column.panel.tripod\":[\"Tripod\"],\"node.column.panel.twistSegments\":[\"Twist Segments\"],\"node.column.panel.vSupport\":[\"V Support\"],\"node.column.panel.vertical\":[\"Vertical\"],\"node.column.panel.waist\":[\"Waist\"],\"node.column.panel.width\":[\"Width\"],\"node.column.panel.xBrace\":[\"X Brace\"],\"node.column.panel.ySupport\":[\"Y Support\"],\"node.column.panel.yaw\":[\"Yaw\"],\"node.column.preset.aFrameSupport\":[\"A-Frame Support\"],\"node.column.preset.basicPillar\":[\"Straight Round\"],\"node.column.preset.boxFrameSupport\":[\"Box Frame\"],\"node.column.preset.bulgedPillar\":[\"Soft Bulged\"],\"node.column.preset.hourglassPillar\":[\"Hourglass\"],\"node.column.preset.kBraceSupport\":[\"K Brace\"],\"node.column.preset.portalFrameSupport\":[\"Portal Frame\"],\"node.column.preset.singleStrutSupport\":[\"Single Strut\"],\"node.column.preset.squarePillar\":[\"Square Block\"],\"node.column.preset.taperedPillar\":[\"Tapered Round\"],\"node.column.preset.trestleSupport\":[\"Trestle Frame\"],\"node.column.preset.tripodSupport\":[\"Tripod Support\"],\"node.column.preset.vFrameSupport\":[\"V Support\"],\"node.column.preset.xBraceSupport\":[\"X Brace\"],\"node.column.preset.yFrameSupport\":[\"Y Support\"],\"node.constructionDimension.description\":[\"Associative linear, curved, circular, angular, or coordinate plan dimension.\"],\"node.constructionDimension.label\":[\"Construction Dimension\"],\"node.cornerRadius.bottomLeft\":[\"Bottom Left\"],\"node.cornerRadius.bottomRight\":[\"Bottom Right\"],\"node.cornerRadius.topLeft\":[\"Top Left\"],\"node.cornerRadius.topRight\":[\"Top Right\"],\"node.cupola.description\":[\"Louvered roof lantern with a dome or pyramid cap and optional finial.\"],\"node.cupola.label\":[\"Cupola\"],\"node.cupola.panel.actions\":[\"Actions\"],\"node.cupola.panel.cupola\":[\"Cupola\"],\"node.cupola.panel.delete\":[\"Delete\"],\"node.cupola.panel.depth\":[\"Depth\"],\"node.cupola.panel.dimensions\":[\"Dimensions\"],\"node.cupola.panel.dome\":[\"Dome\"],\"node.cupola.panel.duplicate\":[\"Duplicate\"],\"node.cupola.panel.finial\":[\"Finial\"],\"node.cupola.panel.height\":[\"Height\"],\"node.cupola.panel.move\":[\"Move\"],\"node.cupola.panel.noFinial\":[\"No Finial\"],\"node.cupola.panel.position\":[\"Position\"],\"node.cupola.panel.pyramid\":[\"Pyramid\"],\"node.cupola.panel.rotation\":[\"Rotation\"],\"node.cupola.panel.style\":[\"Style\"],\"node.cupola.panel.width\":[\"Width\"],\"node.cutout.autoElevator\":[\"Auto elevator cutout\"],\"node.cutout.autoStair\":[\"Auto stair cutout\"],\"node.cutout.opening\":[\"Opening\"],\"node.cutout.room\":[\"Room cutout\"],\"node.door.description\":[\"A door cut into a wall. Animated open/close state.\"],\"node.door.label\":[\"Door\"],\"node.door.panel.actions\":[\"Actions\"],\"node.door.panel.addSegment\":[\"+ Add Segment\"],\"node.door.panel.all\":[\"All\"],\"node.door.panel.arch\":[\"Arch\"],\"node.door.panel.archHeight\":[\"Arch Height\"],\"node.door.panel.auto\":[\"Auto\"],\"node.door.panel.autoFollowsTheWall\":[\"Auto follows the wall\"],\"node.door.panel.barHeight\":[\"Bar Height\"],\"node.door.panel.columns\":[\"Columns\"],\"node.door.panel.contentPadding\":[\"Content Padding\"],\"node.door.panel.cornerRadius\":[\"Corner Radius\"],\"node.door.panel.customTheSegmentsMatchNoStyle\":[\"Custom: the segments match no style.\"],\"node.door.panel.delete\":[\"Delete\"],\"node.door.panel.depth\":[\"Depth\"],\"node.door.panel.dimensions\":[\"Dimensions\"],\"node.door.panel.direction\":[\"Direction\"],\"node.door.panel.divider\":[\"Divider\"],\"node.door.panel.door\":[\"Door\"],\"node.door.panel.doorCloser\":[\"Door Closer\"],\"node.door.panel.duplicate\":[\"Duplicate\"],\"node.door.panel.empty\":[\"Empty\"],\"node.door.panel.enableHandle\":[\"Enable Handle\"],\"node.door.panel.enableThreshold\":[\"Enable Threshold\"],\"node.door.panel.flipSide\":[\"Flip Side\"],\"node.door.panel.fold\":[\"Fold\"],\"node.door.panel.frame\":[\"Frame\"],\"node.door.panel.frameDepthAuto\":[\"Depth (auto)\"],\"node.door.panel.garage\":[\"Garage\"],\"node.door.panel.glass\":[\"Glass\"],\"node.door.panel.handle\":[\"Handle\"],\"node.door.panel.handleSide\":[\"Handle Side\"],\"node.door.panel.hardware\":[\"Hardware\"],\"node.door.panel.height\":[\"Height\"],\"node.door.panel.hingesSide\":[\"Hinges Side\"],\"node.door.panel.horizontal\":[\"Horizontal\"],\"node.door.panel.individual\":[\"Individual\"],\"node.door.panel.inset\":[\"Inset\"],\"node.door.panel.inward\":[\"Inward\"],\"node.door.panel.left\":[\"Left\"],\"node.door.panel.move\":[\"Move\"],\"node.door.panel.open\":[\"Open\"],\"node.door.panel.opening\":[\"Opening\"],\"node.door.panel.openingShape\":[\"Opening Shape\"],\"node.door.panel.outward\":[\"Outward\"],\"node.door.panel.panel\":[\"Panel\"],\"node.door.panel.panels\":[\"Panels\"],\"node.door.panel.panicBar\":[\"Panic Bar\"],\"node.door.panel.pocket\":[\"Pocket\"],\"node.door.panel.position\":[\"Position\"],\"node.door.panel.rail\":[\"Rail\"],\"node.door.panel.rect\":[\"Rect\"],\"node.door.panel.remove\":[\"- Remove\"],\"node.door.panel.revealRadius\":[\"Reveal Radius\"],\"node.door.panel.right\":[\"Right\"],\"node.door.panel.rounded\":[\"Rounded\"],\"node.door.panel.segments\":[\"Segments\"],\"node.door.panel.sizeIsLimitedToTheWall\":[\"Size is limited to the wall, including clearance for the opening frame.\"],\"node.door.panel.slide\":[\"Slide\"],\"node.door.panel.style\":[\"Style\"],\"node.door.panel.swing\":[\"Swing\"],\"node.door.panel.thickness\":[\"Thickness\"],\"node.door.panel.threshold\":[\"Threshold\"],\"node.door.panel.topShape\":[\"Top Shape\"],\"node.door.panel.type\":[\"Type\"],\"node.door.panel.vertical\":[\"Vertical\"],\"node.door.panel.wall\":[\"wall\"],\"node.door.panel.width\":[\"Width\"],\"node.door.style.barn\":[\"Barn\"],\"node.door.style.craftsman\":[\"Craftsman\"],\"node.door.style.french\":[\"French\"],\"node.door.style.glass\":[\"Glass\"],\"node.door.style.halfLouvered\":[\"Half-louvered\"],\"node.door.style.modern\":[\"Modern\"],\"node.door.style.panel\":[\"Panel\"],\"node.door.style.paneledGlass\":[\"Paneled glass\"],\"node.door.style.shaker\":[\"Shaker\"],\"node.door.style.sixPanel\":[\"Six-panel\"],\"node.door.type.barn\":[\"Barn\"],\"node.door.type.double\":[\"Double\"],\"node.door.type.folding\":[\"Folding\"],\"node.door.type.french\":[\"French\"],\"node.door.type.garageRollup\":[\"Roll-up\"],\"node.door.type.garageSectional\":[\"Sectional\"],\"node.door.type.garageTiltup\":[\"Tilt-up\"],\"node.door.type.hinged\":[\"Hinged\"],\"node.door.type.pocket\":[\"Pocket\"],\"node.door.type.sliding\":[\"Sliding\"],\"node.dormer.description\":[\"House-shaped protrusion on a roof segment.\"],\"node.dormer.label\":[\"Dormer\"],\"node.dormer.panel.depth\":[\"Depth\"],\"node.dormer.panel.dimensions\":[\"Dimensions\"],\"node.dormer.panel.dormer\":[\"Dormer\"],\"node.dormer.panel.dutch\":[\"Dutch\"],\"node.dormer.panel.flat\":[\"Flat\"],\"node.dormer.panel.gable\":[\"Gable\"],\"node.dormer.panel.gambrel\":[\"Gambrel\"],\"node.dormer.panel.hip\":[\"Hip\"],\"node.dormer.panel.mansard\":[\"Mansard\"],\"node.dormer.panel.pitchDirection\":[\"Pitch Direction\"],\"node.dormer.panel.pitchRise\":[\"Pitch Rise\"],\"node.dormer.panel.riseBack\":[\"Rise Back\"],\"node.dormer.panel.riseFront\":[\"Rise Front\"],\"node.dormer.panel.roofHeight\":[\"Roof Height\"],\"node.dormer.panel.roofType\":[\"Roof Type\"],\"node.dormer.panel.section\":[\"Section\"],\"node.dormer.panel.shed\":[\"Shed\"],\"node.dormer.panel.wallHeight\":[\"Wall Height\"],\"node.dormer.panel.width\":[\"Width\"],\"node.dormer.panel.windows\":[\"Windows\"],\"node.dormer.panelActionsSection.actions\":[\"Actions\"],\"node.dormer.panelActionsSection.delete\":[\"Delete\"],\"node.dormer.panelActionsSection.duplicate\":[\"Duplicate\"],\"node.dormer.panelActionsSection.move\":[\"Move\"],\"node.dormer.panelPositionSection.position\":[\"Position\"],\"node.dormer.panelPositionSection.rotation\":[\"Rotation\"],\"node.dormer.panelWindowsSection.addWindow\":[\"Add Window\"],\"node.dormer.panelWindowsSection.editNamed\":[\"Edit \",[\"name\"]],\"node.dormer.panelWindowsSection.editWindow\":[\"Edit window\"],\"node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd\":[\"Increase the dormer width to add another window.\"],\"node.dormer.panelWindowsSection.move\":[\"Move\"],\"node.dormer.panelWindowsSection.moveNamed\":[\"Move \",[\"name\"]],\"node.dormer.panelWindowsSection.moveWindow\":[\"Move window\"],\"node.dormer.panelWindowsSection.noWindows\":[\"No windows\"],\"node.dormer.panelWindowsSection.windowNumber\":[\"Window \",[\"number\"]],\"node.dormer.panelWindowsSection.windowSummary\":[[\"face\",\"select\",{\"back\":[\"Back\"],\"left\":[\"Left\"],\"right\":[\"Right\"],\"other\":[\"Front\"]}],\" · \",[\"width\"],\" × \",[\"height\"],\" m\"],\"node.dormer.panelWindowsSection.windowsLength\":[\"Windows (\",[\"length\"],\")\"],\"node.downspout.description\":[\"Vertical drop pipe from a gutter outlet to the ground.\"],\"node.downspout.inspectorEditors.position\":[\"Position\"],\"node.downspout.label\":[\"Downspout\"],\"node.ductFitting.description\":[\"Elbow, tee, reducer, or square-to-round transition connecting duct runs.\"],\"node.ductFitting.inspectorEditors.swapWH\":[\"Swap W/H\"],\"node.ductFitting.inspectorEditors.swapWidthAndHeight\":[\"Swap width and height\"],\"node.ductFitting.label\":[\"Duct Fitting\"],\"node.ductFitting.tool.axis\":[\"Axis\"],\"node.ductFitting.tool.axis2\":[\"⌥ axis\"],\"node.ductFitting.tool.hoverADuctFaceThatFits\":[\"Hover a duct face that fits the door\"],\"node.ductFitting.tool.placeAccessDoor\":[\"Place access door\"],\"node.ductFitting.tool.rTRotate\":[\"R/T rotate\"],\"node.ductSegment.description\":[\"HVAC duct run — polyline of round, rect, or flat-oval sections.\"],\"node.ductSegment.label\":[\"Duct\"],\"node.ductTerminal.description\":[\"Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar.\"],\"node.ductTerminal.label\":[\"Register\"],\"node.ductTerminal.tool.mSurface\":[\"M surface\"],\"node.ductTerminal.tool.mount\":[[\"mount\",\"select\",{\"floor\":[\"Mount Floor\"],\"ceiling\":[\"Mount Ceiling\"],\"wall\":[\"Mount Wall\"],\"other\":[\"Mount \",[\"label\"]]}]],\"node.ductTerminal.tool.rTRotate\":[\"R/T rotate\"],\"node.ductTerminal.tool.snappedToDuct\":[\"Snapped to duct\"],\"node.elevator.description\":[\"A multi-level elevator shaft with configurable openings per level.\"],\"node.elevator.label\":[\"Elevator\"],\"node.elevator.panel.access\":[\"Access\"],\"node.elevator.panel.actions\":[\"Actions\"],\"node.elevator.panel.cab\":[\"Cab\"],\"node.elevator.panel.cabHeight\":[\"Cab Height\"],\"node.elevator.panel.centerOpening\":[\"Center opening\"],\"node.elevator.panel.defaultFloor\":[\"Default Floor\"],\"node.elevator.panel.delete\":[\"Delete\"],\"node.elevator.panel.depth\":[\"Depth\"],\"node.elevator.panel.destination\":[\"Destination\"],\"node.elevator.panel.disabled\":[\"Disabled\"],\"node.elevator.panel.doorHeight\":[\"Door Height\"],\"node.elevator.panel.doorTime\":[\"Door Time\"],\"node.elevator.panel.doorType\":[\"Door Type\"],\"node.elevator.panel.doorWidth\":[\"Door Width\"],\"node.elevator.panel.doors\":[\"Doors\"],\"node.elevator.panel.duplicate\":[\"Duplicate\"],\"node.elevator.panel.dwell\":[\"Dwell\"],\"node.elevator.panel.elevator\":[\"Elevator\"],\"node.elevator.panel.from\":[\"From\"],\"node.elevator.panel.glass\":[\"Glass\"],\"node.elevator.panel.glassFrame\":[\"Glass frame\"],\"node.elevator.panel.motion\":[\"Motion\"],\"node.elevator.panel.move\":[\"Move\"],\"node.elevator.panel.openingStyle\":[\"Opening Style\"],\"node.elevator.panel.position\":[\"Position\"],\"node.elevator.panel.rotation\":[\"Rotation\"],\"node.elevator.panel.segmentedPanel\":[\"Segmented panel\"],\"node.elevator.panel.service\":[\"Service\"],\"node.elevator.panel.shaft\":[\"Shaft\"],\"node.elevator.panel.shaftDepth\":[\"Shaft Depth\"],\"node.elevator.panel.shaftStyle\":[\"Shaft Style\"],\"node.elevator.panel.shaftWidth\":[\"Shaft Width\"],\"node.elevator.panel.singleLeft\":[\"Single left\"],\"node.elevator.panel.singleRight\":[\"Single right\"],\"node.elevator.panel.solid\":[\"Solid\"],\"node.elevator.panel.solidPanel\":[\"Solid panel\"],\"node.elevator.panel.speed\":[\"Speed\"],\"node.elevator.panel.stopStoporder\":[\"Stop \",[\"stopOrder\"]],\"node.elevator.panel.to\":[\"To\"],\"node.elevator.panel.wallThickness\":[\"Wall Thickness\"],\"node.elevator.panel.width\":[\"Width\"],\"node.elevator.panel.yaw\":[\"Yaw\"],\"node.eyebrowVent.description\":[\"Low curved lens-shaped roof vent with a louvered front.\"],\"node.eyebrowVent.label\":[\"Eyebrow Vent\"],\"node.eyebrowVent.panel.actions\":[\"Actions\"],\"node.eyebrowVent.panel.backHeight\":[\"Back height\"],\"node.eyebrowVent.panel.delete\":[\"Delete\"],\"node.eyebrowVent.panel.depth\":[\"Depth\"],\"node.eyebrowVent.panel.dimensions\":[\"Dimensions\"],\"node.eyebrowVent.panel.duplicate\":[\"Duplicate\"],\"node.eyebrowVent.panel.eyebrowVent\":[\"Eyebrow Vent\"],\"node.eyebrowVent.panel.halfRound\":[\"Half-round\"],\"node.eyebrowVent.panel.height\":[\"Height\"],\"node.eyebrowVent.panel.louvers\":[\"Louvers\"],\"node.eyebrowVent.panel.move\":[\"Move\"],\"node.eyebrowVent.panel.position\":[\"Position\"],\"node.eyebrowVent.panel.rotation\":[\"Rotation\"],\"node.eyebrowVent.panel.scoop\":[\"Scoop\"],\"node.eyebrowVent.panel.slantBox\":[\"Slant-box\"],\"node.eyebrowVent.panel.style\":[\"Style\"],\"node.eyebrowVent.panel.width\":[\"Width\"],\"node.fence.description\":[\"A straight or curved fence segment with configurable posts and infill.\"],\"node.fence.inspectorEditors.addPointAfter\":[\"Add point after\"],\"node.fence.inspectorEditors.chooseASlabOrShapedSurface\":[\"Choose a slab or shaped surface on this level.\"],\"node.fence.inspectorEditors.chooseASurface\":[\"Choose a surface\"],\"node.fence.inspectorEditors.corner\":[\"Corner\"],\"node.fence.inspectorEditors.curve\":[\"Curve\"],\"node.fence.inspectorEditors.curved\":[\"Curved\"],\"node.fence.inspectorEditors.distributionInfo\":[[\"style\",\"select\",{\"picket\":[\"Distribution controls pickets in each bay between posts.\"],\"horizontal\":[\"Distribution controls posts along the full fence.\"],\"other\":[\"Distribution controls infill pieces between the two end posts.\"]}],\" Spacing is measured between centers. The available width limits the count so pieces do not overlap.\"],\"node.fence.inspectorEditors.editPath\":[\"Edit path\"],\"node.fence.inspectorEditors.extendPath\":[\"Extend path\"],\"node.fence.inspectorEditors.followHighestSurface\":[\"Follow highest surface\"],\"node.fence.inspectorEditors.followSelectedSurface\":[\"Follow selected surface\"],\"node.fence.inspectorEditors.holdStartingHeight\":[\"Hold starting height\"],\"node.fence.inspectorEditors.join\":[\"Join\"],\"node.fence.inspectorEditors.length\":[\"Length\"],\"node.fence.inspectorEditors.pathSummary\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" point\"],\"other\":[[\"number\"],\" points\"]}],\" · \",[\"length\"],\" m\"],\"node.fence.inspectorEditors.picketRailProjection\":[\"Picket rail projection\"],\"node.fence.inspectorEditors.point\":[\"Point\"],\"node.fence.inspectorEditors.removePoint\":[\"Remove point\"],\"node.fence.inspectorEditors.setEachSpanToStraightOr\":[\"Set each span to straight or curved, then drag its points and curve handles in either view.\"],\"node.fence.inspectorEditors.smooth\":[\"Smooth\"],\"node.fence.inspectorEditors.spanToPointNumber\":[\"Span to point \",[\"number\"]],\"node.fence.inspectorEditors.straight\":[\"Straight\"],\"node.fence.inspectorEditors.surface\":[\"Surface\"],\"node.fence.inspectorEditors.surfaceBehavior\":[\"Surface behavior\"],\"node.fence.label\":[\"Fence\"],\"node.fence.moveEndpointTool.detachEndpoint\":[\"Detach endpoint\"],\"node.fence.moveEndpointTool.dragEndpoint\":[\"Drag endpoint\"],\"node.fenceFeature.actions.close\":[\"Close\"],\"node.fenceFeature.actions.open\":[\"Open\"],\"node.fenceFeature.inspector.boardWidth\":[\"Board width\"],\"node.fenceFeature.inspector.bracing\":[\"Bracing\"],\"node.fenceFeature.inspector.distanceFromStart\":[\"Distance from start\"],\"node.fenceFeature.inspector.fenceUnavailable\":[\"Fence unavailable.\"],\"node.fenceFeature.inspector.frameWidth\":[\"Frame width\"],\"node.fenceFeature.inspector.gateHeight\":[\"Gate height\"],\"node.fenceFeature.inspector.gateStyle\":[\"Gate style\"],\"node.fenceFeature.inspector.groundClearance\":[\"Ground clearance\"],\"node.fenceFeature.inspector.hingeSide\":[\"Hinge side\"],\"node.fenceFeature.inspector.hingesAndLatch\":[\"Hinges and latch\"],\"node.fenceFeature.inspector.horizontalBoards\":[\"Horizontal boards\"],\"node.fenceFeature.inspector.infillSpacing\":[\"Infill spacing\"],\"node.fenceFeature.inspector.jambPosts\":[\"Jamb posts\"],\"node.fenceFeature.inspector.keepTheOpeningInsideTheFence\":[\"Keep the opening inside the fence and clear of other gates or passages.\"],\"node.fenceFeature.inspector.leafThickness\":[\"Leaf thickness\"],\"node.fenceFeature.inspector.leaves\":[\"Leaves\"],\"node.fenceFeature.inspector.leftLeafProportion\":[\"Left leaf proportion\"],\"node.fenceFeature.inspector.matchFenceHeight\":[\"Match fence height\"],\"node.fenceFeature.inspector.matchFenceStyle\":[\"Match fence style\"],\"node.fenceFeature.inspector.openRails\":[\"Open rails\"],\"node.fenceFeature.inspector.openingAngle\":[\"Opening angle\"],\"node.fenceFeature.inspector.openingWidth\":[\"Opening width\"],\"node.fenceFeature.inspector.option.cross\":[\"Cross\"],\"node.fenceFeature.inspector.option.diagonal\":[\"Diagonal\"],\"node.fenceFeature.inspector.option.doubleGate\":[\"Double gate\"],\"node.fenceFeature.inspector.option.inward\":[\"Inward\"],\"node.fenceFeature.inspector.option.left\":[\"Left\"],\"node.fenceFeature.inspector.option.none\":[\"None\"],\"node.fenceFeature.inspector.option.outward\":[\"Outward\"],\"node.fenceFeature.inspector.option.right\":[\"Right\"],\"node.fenceFeature.inspector.option.singleGate\":[\"Single gate\"],\"node.fenceFeature.inspector.picket\":[\"Picket\"],\"node.fenceFeature.inspector.postHeight\":[\"Post height\"],\"node.fenceFeature.inspector.removeGate\":[\"Remove gate\"],\"node.fenceFeature.inspector.removeOpening\":[\"Remove opening\"],\"node.fenceFeature.inspector.solidPrivacy\":[\"Solid privacy\"],\"node.fenceFeature.inspector.swingDirection\":[\"Swing direction\"],\"node.fenceFeature.inspector.verticalSlats\":[\"Vertical slats\"],\"node.fenceGate.description\":[\"A gate hosted by a fence.\"],\"node.fenceGate.label\":[\"Gate\"],\"node.fenceOpening.description\":[\"An intentional opening in a fence.\"],\"node.fenceOpening.label\":[\"Open Passage\"],\"node.floorOpening.label\":[\"Floor opening\"],\"node.guide.description\":[\"A measurement / reference annotation (linear, area, or arc).\"],\"node.guide.label\":[\"Guide\"],\"node.gutter.description\":[\"Rain-water channel running along the eave of a roof segment.\"],\"node.gutter.downspoutsPanel.addDownspout\":[\"Add Downspout\"],\"node.gutter.downspoutsPanel.downspoutNumber\":[\"Downspout \",[\"number\"]],\"node.gutter.downspoutsPanel.downspouts\":[\"Downspouts\"],\"node.gutter.downspoutsPanel.removeDownspout\":[\"Remove downspout\"],\"node.gutter.label\":[\"Gutter\"],\"node.hole.manual\":[\"Manual\"],\"node.holeEditing\":[\"Hole \",[\"number\"],\" (Editing)\"],\"node.holeNumber\":[\"Hole \",[\"number\"]],\"node.holeSummary\":[[\"area\"],\" m² · \",[\"count\",\"plural\",{\"one\":[[\"points\"],\" pt\"],\"other\":[[\"points\"],\" pts\"]}],\" · \",[\"source\"]],\"node.hvacEquipment.description\":[\"Furnace, air handler, or condenser — duct runs connect to its supply/return collars.\"],\"node.hvacEquipment.label\":[\"HVAC Unit\"],\"node.hvacEquipment.tool.rTRotate\":[\"R/T rotate\"],\"node.importedMesh.description\":[\"Geometry preserved from an imported model when no native Pascal shape exists.\"],\"node.importedMesh.label\":[\"Imported Mesh\"],\"node.item.authoredParams.couldnTRebuildWithTheseValues\":[\"Couldn't rebuild with these values.\"],\"node.item.authoredParams.parameters\":[\"Parameters\"],\"node.item.authoredParams.rebuilding\":[\"Rebuilding…\"],\"node.item.authoredParams.rebuildingInBlender\":[\"Rebuilding in Blender…\"],\"node.item.authoredParams.resetParam\":[\"Reset \",[\"param\"]],\"node.item.authoredParams.resetParameters\":[\"Reset parameters\"],\"node.item.authoredParams.resetToDefault\":[\"Reset to default\"],\"node.item.description\":[\"A catalog-backed item (furniture, fixtures, decorations).\"],\"node.item.label\":[\"Item\"],\"node.item.panel.actions\":[\"Actions\"],\"node.item.panel.delete\":[\"Delete\"],\"node.item.panel.dimensions\":[\"Dimensions\"],\"node.item.panel.duplicate\":[\"Duplicate\"],\"node.item.panel.info\":[\"Info\"],\"node.item.panel.move\":[\"Move\"],\"node.item.panel.pos\":[\"pos\"],\"node.item.panel.position\":[\"Position\"],\"node.item.panel.rot\":[\"rot\"],\"node.item.panel.rotation\":[\"Rotation\"],\"node.item.panel.scale\":[\"Scale\"],\"node.item.panel.scale2\":[\"scale\"],\"node.item.panel.uniformScale\":[\"Uniform Scale\"],\"node.item.panel.xyz\":[\"XYZ\"],\"node.leanToExtension.description\":[\"An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage.\"],\"node.leanToExtension.label\":[\"Canopy\"],\"node.level.description\":[\"A single floor of a building, holding walls / slabs / ceilings / items.\"],\"node.level.label\":[\"Level\"],\"node.lineset.description\":[\"Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil.\"],\"node.lineset.label\":[\"Lineset\"],\"node.liquidLine.description\":[\"Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset.\"],\"node.liquidLine.label\":[\"Liquid Line\"],\"node.liquidLine.tool.clickToTraceThisLinesetRun\":[\"Click to trace this lineset run\"],\"node.liquidLine.tool.followHoverALineset\":[\"Follow: hover a lineset\"],\"node.measurement.description\":[\"A persistent distance, angle, area, perimeter, or volume annotation.\"],\"node.measurement.label\":[\"Measurement\"],\"node.numberedName\":[[\"kind\"],\" \",[\"number\"]],\"node.panel.currently\":[\"Currently \",[\"value\"]],\"node.pipeFitting.description\":[\"DWV joint — elbow bend, 45° wye, or sanitary tee.\"],\"node.pipeFitting.label\":[\"Pipe Fitting\"],\"node.pipeFitting.tool.axis\":[\"Axis\"],\"node.pipeFitting.tool.axis2\":[\"⌥ axis\"],\"node.pipeFitting.tool.rTRotate\":[\"R/T rotate\"],\"node.pipeSegment.description\":[\"Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S.\"],\"node.pipeSegment.label\":[\"DWV Pipe\"],\"node.pipeTrap.description\":[\"DWV P-trap — water seal on the waste line. The trap arm runs to the vent.\"],\"node.pipeTrap.label\":[\"Trap\"],\"node.pipeTrap.tool.diameterTrap\":[[\"diameter\"],\"\\\" Trap\"],\"node.pipeTrap.tool.rTRotate\":[\"R/T rotate\"],\"node.proceduralItem.description\":[\"Experimental editable recipe\"],\"node.proceduralItem.label\":[\"Procedural item\"],\"node.ridgeVent.description\":[\"Ventilation strip running along the ridge of a roof segment.\"],\"node.ridgeVent.label\":[\"Ridge Vent\"],\"node.ridgeVent.panel.actions\":[\"Actions\"],\"node.ridgeVent.panel.delete\":[\"Delete\"],\"node.ridgeVent.panel.dimensions\":[\"Dimensions\"],\"node.ridgeVent.panel.duplicate\":[\"Duplicate\"],\"node.ridgeVent.panel.endCaps\":[\"End Caps\"],\"node.ridgeVent.panel.flanged\":[\"Flanged\"],\"node.ridgeVent.panel.height\":[\"Height\"],\"node.ridgeVent.panel.length\":[\"Length\"],\"node.ridgeVent.panel.move\":[\"Move\"],\"node.ridgeVent.panel.open\":[\"Open\"],\"node.ridgeVent.panel.position\":[\"Position\"],\"node.ridgeVent.panel.ridgeVent\":[\"Ridge Vent\"],\"node.ridgeVent.panel.rotation\":[\"Rotation\"],\"node.ridgeVent.panel.shingled\":[\"Shingled\"],\"node.ridgeVent.panel.standard\":[\"Standard\"],\"node.ridgeVent.panel.style\":[\"Style\"],\"node.ridgeVent.panel.width\":[\"Width\"],\"node.roof.barrelPanel.archBays\":[\"Arch bays\"],\"node.roof.barrelPanel.barrelArch\":[\"Barrel arch\"],\"node.roof.barrelPanel.flatPanesFollowACircularArch\":[\"Flat panes follow a circular arch. Rise is measured from the eaves.\"],\"node.roof.barrelPanel.rise\":[\"Rise\"],\"node.roof.description\":[\"A pitched / hip / gable roof composed of one or more segments.\"],\"node.roof.glazing.face.archBay\":[\"Arch bay \",[\"number\"]],\"node.roof.glazing.face.back\":[\"Back slope\"],\"node.roof.glazing.face.endLeft\":[\"Left end\"],\"node.roof.glazing.face.endRight\":[\"Right end\"],\"node.roof.glazing.face.front\":[\"Front slope\"],\"node.roof.glazing.face.slope\":[\"Shed slope\"],\"node.roof.glazingControls.alignment\":[\"Alignment\"],\"node.roof.glazingControls.center\":[\"Center\"],\"node.roof.glazingControls.chooseALayoutToEditThe\":[\"Choose a layout to edit the grid.\"],\"node.roof.glazingControls.empty\":[\"Empty\"],\"node.roof.glazingControls.end\":[\"End\"],\"node.roof.glazingControls.fixedSpacing\":[\"Fixed spacing\"],\"node.roof.glazingControls.glass\":[\"Glass\"],\"node.roof.glazingControls.layout\":[\"Layout\"],\"node.roof.glazingControls.maxSpacing\":[\"Max spacing\"],\"node.roof.glazingControls.maximumSpacing\":[\"Maximum spacing\"],\"node.roof.glazingControls.mixed\":[\"Mixed\"],\"node.roof.glazingControls.panelCount\":[\"Panel count\"],\"node.roof.glazingControls.panels\":[\"Panels\"],\"node.roof.glazingControls.solid\":[\"Solid\"],\"node.roof.glazingControls.spacing\":[\"Spacing\"],\"node.roof.glazingControls.start\":[\"Start\"],\"node.roof.glazingControls.titleAlignment\":[[\"title\"],\" alignment\"],\"node.roof.glazingPanel.changesApplyToAllLengthRoof\":[\"Changes apply to all \",[\"length\"],\" roof segments.\"],\"node.roof.glazingPanel.chooseOneRoofSystemToEdit\":[\"Choose one roof system to edit settings for all selected segments.\"],\"node.roof.glazingPanel.closeTheRoofEnds\":[\"Close the roof ends\"],\"node.roof.glazingPanel.defaultPanelInfill\":[\"Default panel infill\"],\"node.roof.glazingPanel.dividesEachRoofFaceIntoColumns\":[\"Divides each roof face into columns.\"],\"node.roof.glazingPanel.dividesTheLengthOfEachArch\":[\"Divides the length of each arch bay into columns.\"],\"node.roof.glazingPanel.endInfillFillsTheSpaceAbove\":[\"End infill fills the space above the walls. Edit room walls and ceilings separately.\"],\"node.roof.glazingPanel.frameColor\":[\"Frame color\"],\"node.roof.glazingPanel.frameDepth\":[\"Frame depth\"],\"node.roof.glazingPanel.frameWidth\":[\"Frame width\"],\"node.roof.glazingPanel.framing\":[\"Framing\"],\"node.roof.glazingPanel.glass\":[\"Glass\"],\"node.roof.glazingPanel.glassTint\":[\"Glass tint\"],\"node.roof.glazingPanel.glazed\":[\"Glazed\"],\"node.roof.glazingPanel.glazingIsAvailableForGableStraight\":[\"Glazing is available for gable, straight shed, and barrel roofs.\"],\"node.roof.glazingPanel.gridAcrossTheRoof\":[\"Grid across the roof\"],\"node.roof.glazingPanel.gridAlongTheLength\":[\"Grid along the length\"],\"node.roof.glazingPanel.gridAlongTheSlope\":[\"Grid along the slope\"],\"node.roof.glazingPanel.gridWithinEachArchBay\":[\"Grid within each arch bay\"],\"node.roof.glazingPanel.infill\":[\"Infill\"],\"node.roof.glazingPanel.opacity\":[\"Opacity\"],\"node.roof.glazingPanel.paneThickness\":[\"Pane thickness\"],\"node.roof.glazingPanel.roofSystem\":[\"Roof system\"],\"node.roof.glazingPanel.roughness\":[\"Roughness\"],\"node.roof.glazingPanel.spacingIsMeasuredOnTheSlope\":[\"Spacing is measured on the slope, not its horizontal projection.\"],\"node.roof.glazingPanel.standard\":[\"Standard\"],\"node.roof.glazingPanel.subdividesEachFlatArchBayArch\":[\"Subdivides each flat arch bay. Arch bays controls the divisions around the curve.\"],\"node.roof.glazingPanelEditor.chooseAPanelToOverrideIts\":[\"Choose a panel to override its default infill.\"],\"node.roof.glazingPanelEditor.column2\":[\"Column\"],\"node.roof.glazingPanelEditor.custom\":[\"Custom\"],\"node.roof.glazingPanelEditor.default\":[\"Default\"],\"node.roof.glazingPanelEditor.grid\":[[\"columns\",\"plural\",{\"one\":[[\"columnCount\"],\" column\"],\"other\":[[\"columnCount\"],\" columns\"]}],\" · \",[\"rows\",\"plural\",{\"one\":[[\"rowCount\"],\" row\"],\"other\":[[\"rowCount\"],\" rows\"]}]],\"node.roof.glazingPanelEditor.individualPanels\":[\"Individual panels\"],\"node.roof.glazingPanelEditor.panelInfill\":[\"Panel infill\"],\"node.roof.glazingPanelEditor.resetToDefault\":[\"Reset to default\"],\"node.roof.glazingPanelEditor.roofFace\":[\"Roof face\"],\"node.roof.glazingPanelEditor.row2\":[\"Row\"],\"node.roof.glazingPanelEditor.selectedPanelInfill\":[\"Selected panel infill\"],\"node.roof.label\":[\"Roof\"],\"node.roof.panel.actions\":[\"Actions\"],\"node.roof.panel.addChimney\":[\"Add Chimney\"],\"node.roof.panel.addCupola\":[\"Add Cupola\"],\"node.roof.panel.addDormer\":[\"Add Dormer\"],\"node.roof.panel.addEyebrowVent\":[\"Add Eyebrow Vent\"],\"node.roof.panel.addGutter\":[\"Add Gutter\"],\"node.roof.panel.addSkylight\":[\"Add Skylight\"],\"node.roof.panel.addSolarPanel\":[\"Add Solar Panel\"],\"node.roof.panel.addVent\":[\"Add Vent\"],\"node.roof.panel.box\":[\"Box\"],\"node.roof.panel.boxVent\":[\"box vent\"],\"node.roof.panel.chimney\":[\"chimney\"],\"node.roof.panel.custom\":[\"Custom\"],\"node.roof.panel.delete\":[\"Delete\"],\"node.roof.panel.dormer\":[\"dormer\"],\"node.roof.panel.drawSegment\":[\"Draw Segment\"],\"node.roof.panel.duplicate\":[\"Duplicate\"],\"node.roof.panel.elements\":[\"Elements\"],\"node.roof.panel.followsWalls\":[\"Follows walls\"],\"node.roof.panel.gutter\":[\"gutter\"],\"node.roof.panel.level\":[\"Level\"],\"node.roof.panel.move\":[\"Move\"],\"node.roof.panel.moveToLevel\":[\"Move to \",[\"level\"]],\"node.roof.panel.moveToTheRoofLevel\":[\"Move to the roof level\"],\"node.roof.panel.onStoreyWalls\":[\"On \",[\"storey\"],\" with its walls, so it hides that floor's plan in solo and exploded views.\",[\"roofs\",\"plural\",{\"0\":[],\"1\":[],\"other\":[\" The floor's \",[\"roofCount\"],\" roofs move together.\"]}]],\"node.roof.panel.position\":[\"Position\"],\"node.roof.panel.ridge\":[\"Ridge\"],\"node.roof.panel.ridgeVent\":[\"ridge vent\"],\"node.roof.panel.roof\":[\"Roof\"],\"node.roof.panel.rotation\":[\"Rotation\"],\"node.roof.panel.segmentType\":[[\"roofType\",\"select\",{\"hip\":[\"Hip\"],\"gable\":[\"Gable\"],\"shed\":[\"Shed\"],\"gambrel\":[\"Gambrel\"],\"dutch\":[\"Dutch\"],\"mansard\":[\"Mansard\"],\"flat\":[\"Flat\"],\"conical\":[\"Conical\"],\"barrel\":[\"Barrel\"],\"other\":[\"Roof\"]}]],\"node.roof.panel.segments\":[\"Segments\"],\"node.roof.panel.skylight\":[\"skylight\"],\"node.roof.panel.solarPanel\":[\"solar panel\"],\"node.roof.panel.turbine\":[\"Turbine\"],\"node.roof.panel.turbineVent\":[\"turbine vent\"],\"node.roofSegment.description\":[\"A single pitched plane of a parent roof.\"],\"node.roofSegment.label\":[\"Roof Segment\"],\"node.roofSegment.panel.actions\":[\"Actions\"],\"node.roofSegment.panel.angle\":[\"Angle\"],\"node.roofSegment.panel.arc\":[\"Arc\"],\"node.roofSegment.panel.autoGutters\":[\"Auto gutters\"],\"node.roofSegment.panel.autoRidgeVent\":[\"Auto ridge vent\"],\"node.roofSegment.panel.barrel\":[\"Barrel\"],\"node.roofSegment.panel.clippedVersion\":[\"Clipped version\"],\"node.roofSegment.panel.conical\":[\"Conical\"],\"node.roofSegment.panel.conicalShape\":[\"Conical Shape\"],\"node.roofSegment.panel.deckThick\":[\"Deck Thick.\"],\"node.roofSegment.panel.delete\":[\"Delete\"],\"node.roofSegment.panel.depth\":[\"Depth\"],\"node.roofSegment.panel.diameter\":[\"Diameter\"],\"node.roofSegment.panel.doneEditing\":[\"Done editing\"],\"node.roofSegment.panel.drainage\":[\"Drainage\"],\"node.roofSegment.panel.duplicate\":[\"Duplicate\"],\"node.roofSegment.panel.dutch\":[\"Dutch\"],\"node.roofSegment.panel.eaveOverhang\":[\"Eave overhang\"],\"node.roofSegment.panel.editFootprint\":[\"Edit footprint\"],\"node.roofSegment.panel.endOverhang\":[\"End overhang\"],\"node.roofSegment.panel.fascia\":[\"Fascia\"],\"node.roofSegment.panel.fasciaHeight\":[\"Fascia height\"],\"node.roofSegment.panel.flat\":[\"Flat\"],\"node.roofSegment.panel.footprint\":[\"Footprint\"],\"node.roofSegment.panel.gable\":[\"Gable\"],\"node.roofSegment.panel.gableOverhang\":[\"Gable overhang\"],\"node.roofSegment.panel.gambrel\":[\"Gambrel\"],\"node.roofSegment.panel.hip\":[\"Hip\"],\"node.roofSegment.panel.kinkDepth\":[\"Kink Depth\"],\"node.roofSegment.panel.kinkHeight\":[\"Kink Height\"],\"node.roofSegment.panel.length\":[\"Length\"],\"node.roofSegment.panel.mansard\":[\"Mansard\"],\"node.roofSegment.panel.move\":[\"Move\"],\"node.roofSegment.panel.pitch\":[\"Pitch\"],\"node.roofSegment.panel.position\":[\"Position\"],\"node.roofSegment.panel.reset\":[\"Reset\"],\"node.roofSegment.panel.roofSegment\":[\"Roof Segment\"],\"node.roofSegment.panel.roofType\":[\"Roof Type\"],\"node.roofSegment.panel.rotation\":[\"Rotation\"],\"node.roofSegment.panel.shape\":[\"Shape\"],\"node.roofSegment.panel.shed\":[\"Shed\"],\"node.roofSegment.panel.shingleThick\":[\"Shingle Thick.\"],\"node.roofSegment.panel.span\":[\"Span\"],\"node.roofSegment.panel.startAngle\":[\"Start Angle\"],\"node.roofSegment.panel.structure\":[\"Structure\"],\"node.roofSegment.panel.topRakeLength\":[\"Top Rake Length\"],\"node.roofSegment.panel.topRakeThick\":[\"Top Rake Thick.\"],\"node.roofSegment.panel.trim\":[\"Trim\"],\"node.roofSegment.panel.waistHeight\":[\"Waist Height\"],\"node.roofSegment.panel.waistLength\":[\"Waist Length\"],\"node.roofSegment.panel.waistWidth\":[\"Waist Width\"],\"node.roofSegment.panel.wall\":[\"Wall\"],\"node.roofSegment.panel.wallHeight\":[\"Wall Height\"],\"node.roofSegment.panel.wallThick\":[\"Wall Thick.\"],\"node.roofSegment.panel.width\":[\"Width\"],\"node.scan.description\":[\"A captured session with optional mesh, motion, media, and sensor data.\"],\"node.scan.label\":[\"Capture\"],\"node.segmentNumber\":[\"Segment \",[\"number\"]],\"node.separator.label\":[\"Separator\"],\"node.shared.itemInteractionActions.turnLightOff\":[\"Turn light off\"],\"node.shared.itemInteractionActions.turnLightOn\":[\"Turn light on\"],\"node.shared.runHangerInspector.automaticSupport\":[\"Automatic support\"],\"node.shared.runHangerInspector.hangerOnSegment\":[\"Hanger \",[\"number\"],\" · segment \",[\"segment\"]],\"node.shared.runHangerInspector.individualHangers\":[\"Individual hangers\"],\"node.shared.runHangerInspector.missingSupport\":[\"Missing support\"],\"node.shared.runHangerInspector.noSupportWithinReachMoveThe\":[\"No support within reach. Move the hanger or choose another host.\"],\"node.shared.runHangerInspector.positionLabel\":[\"Hanger \",[\"number\"],\" position in metres\"],\"node.shared.runHangerInspector.positionM\":[\"Position (m)\"],\"node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath\":[\"Positions are measured along each path segment. Changing spacing regenerates the numbered slots.\"],\"node.shared.runHangerInspector.revealSupport\":[\"Reveal support\"],\"node.shared.runHangerInspector.skip\":[\"Skip\"],\"node.shared.runHangerInspector.supportLabel\":[\"Hanger \",[\"number\"],\" support\"],\"node.shared.systemCheckPanel.aboutTheseChecks\":[\"About these checks\"],\"node.shared.systemCheckPanel.allFindings\":[\"All findings\"],\"node.shared.systemCheckPanel.allSystems\":[\"All systems\"],\"node.shared.systemCheckPanel.connections\":[\"Connections\"],\"node.shared.systemCheckPanel.drainage\":[\"Drainage\"],\"node.shared.systemCheckPanel.error\":[\"Error\"],\"node.shared.systemCheckPanel.errors\":[\"Errors\"],\"node.shared.systemCheckPanel.filterSystemChecks\":[\"Filter system checks\"],\"node.shared.systemCheckPanel.hangers\":[\"Hangers\"],\"node.shared.systemCheckPanel.intersections\":[\"Intersections\"],\"node.shared.systemCheckPanel.locate\":[\"Locate \",[\"name\"],\" (\",[\"id\"],\")\"],\"node.shared.systemCheckPanel.missingItem\":[\"Missing item\"],\"node.shared.systemCheckPanel.noFindings\":[\"No findings\"],\"node.shared.systemCheckPanel.noFindingsInThisCategory\":[\"No findings in this category\"],\"node.shared.systemCheckPanel.openEnds\":[\"Open ends\"],\"node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay\":[\"Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances.\"],\"node.shared.systemCheckPanel.selectedConnectedSystem\":[\"Selected connected system\"],\"node.shared.systemCheckPanel.selectedRuns\":[\"Selected: \",[\"count\",\"plural\",{\"one\":[[\"number\"],\" run\"],\"other\":[[\"number\"],\" runs\"]}],\" · \",[\"length\"],\" m\"],\"node.shared.systemCheckPanel.separateBranches\":[\"Separate branches\"],\"node.shared.systemCheckPanel.showAllFindings\":[\"Show all findings\"],\"node.shared.systemCheckPanel.systemChecks\":[\"System checks\"],\"node.shared.systemCheckPanel.systemFinding\":[\"System finding\"],\"node.shared.systemCheckPanel.systemFindings\":[\"System findings\"],\"node.shared.systemCheckPanel.warning\":[\"Warning\"],\"node.shared.systemCheckPanel.warnings\":[\"Warnings\"],\"node.shelf.description\":[\"A configurable shelving unit. Items host on each row.\"],\"node.shelf.label\":[\"Shelf\"],\"node.site.description\":[\"The top-level container holding buildings, zones, and the property boundary.\"],\"node.site.label\":[\"Site\"],\"node.site.panel.address\":[\"Address\"],\"node.site.panel.apn\":[\"APN\"],\"node.site.panel.autoMostNorthFacingEdge\":[\"Auto — most north-facing edge\"],\"node.site.panel.contourIntervalSitePlanAnd3d\":[\"Contour interval (site plan and 3D)\"],\"node.site.panel.county\":[\"County\"],\"node.site.panel.eGRs60\":[\"e.g. RS-60\"],\"node.site.panel.eGTampaLdc27156\":[\"e.g. Tampa LDC §27-156\"],\"node.site.panel.every12In\":[\"every 12 in\"],\"node.site.panel.every24In\":[\"every 24 in\"],\"node.site.panel.every6In\":[\"every 6 in\"],\"node.site.panel.findParcel\":[\"Find parcel\"],\"node.site.panel.finding\":[\"Finding…\"],\"node.site.panel.frontStreetEdge\":[\"Front (street) edge\"],\"node.site.panel.lookingUpTheParcel\":[\"Looking up the parcel and the streets around it…\"],\"node.site.panel.lotArea\":[\"Lot area\"],\"node.site.panel.n1200WCassStTampaFl\":[\"1200 W Cass St, Tampa, FL\"],\"node.site.panel.noSurveyedLinesContoursAreDrawn\":[\"No surveyed lines — contours are drawn from the sculpted ground.\"],\"node.site.panel.none\":[\"none\"],\"node.site.panel.northRotation\":[\"North rotation\"],\"node.site.panel.orientation\":[\"Orientation\"],\"node.site.panel.parcel\":[\"Parcel\"],\"node.site.panel.parcelLookupFailed\":[\"Parcel lookup failed.\"],\"node.site.panel.projectLook\":[\"Project look\"],\"node.site.panel.saveCurrentLook\":[\"Save current look\"],\"node.site.panel.saveTheCurrentLightingAndDisplay\":[\"Save the current lighting and display as this project's default. Personal display changes stay in this browser.\"],\"node.site.panel.setbacks\":[\"Setbacks\"],\"node.site.panel.showGround\":[\"Show ground\"],\"node.site.panel.showTheContourLinesIn3d\":[\"Show the contour lines in 3D\"],\"node.site.panel.side\":[\"side\"],\"node.site.panel.site\":[\"Site\"],\"node.site.panel.source\":[\"Source\"],\"node.site.panel.sourceCitation\":[\"Source / citation\"],\"node.site.panel.streetAddress\":[\"Street address\"],\"node.site.panel.survey\":[\"survey\"],\"node.site.panel.surveyedContours\":[[\"count\",\"plural\",{\"one\":[[\"lineCount\"],\" surveyed line\"],\"other\":[[\"lineCount\"],\" surveyed lines\"]}],\" at \",[\"interval\"],\" ft (\",[\"datum\"],\") — \",[\"source\"]],\"node.site.panel.terrain\":[\"Terrain\"],\"node.site.panel.typeAnAddressFirst\":[\"Type an address first.\"],\"node.site.panel.useProjectLook\":[\"Use project look\"],\"node.site.panel.zone\":[\"Zone\"],\"node.site.setback.front\":[\"Front\"],\"node.site.setback.leftOverride\":[\"Left (override)\"],\"node.site.setback.rear\":[\"Rear\"],\"node.site.setback.rightOverride\":[\"Right (override)\"],\"node.site.setback.side\":[\"Side\"],\"node.skylight.description\":[\"Framed glass opening on a roof segment.\"],\"node.skylight.label\":[\"Skylight\"],\"node.skylight.panel.actions\":[\"Actions\"],\"node.skylight.panel.alongX\":[\"Along X\"],\"node.skylight.panel.alongZ\":[\"Along Z\"],\"node.skylight.panel.bottom\":[\"Bottom\"],\"node.skylight.panel.curb\":[\"Curb\"],\"node.skylight.panel.cutoutOffset\":[\"Cutout Offset\"],\"node.skylight.panel.delete\":[\"Delete\"],\"node.skylight.panel.depth\":[\"Depth\"],\"node.skylight.panel.dimensions\":[\"Dimensions\"],\"node.skylight.panel.frame\":[\"Frame\"],\"node.skylight.panel.glassThickness\":[\"Glass Thickness\"],\"node.skylight.panel.height\":[\"Height\"],\"node.skylight.panel.lanternHeight\":[\"Lantern Height\"],\"node.skylight.panel.left\":[\"Left\"],\"node.skylight.panel.motor\":[\"Motor\"],\"node.skylight.panel.motorHousing\":[\"Motor Housing\"],\"node.skylight.panel.no\":[\"No\"],\"node.skylight.panel.noMotor\":[\"No Motor\"],\"node.skylight.panel.open\":[\"Open\"],\"node.skylight.panel.openingAngle\":[\"Opening Angle\"],\"node.skylight.panel.position\":[\"Position\"],\"node.skylight.panel.right\":[\"Right\"],\"node.skylight.panel.rotation\":[\"Rotation\"],\"node.skylight.panel.skylight\":[\"Skylight\"],\"node.skylight.panel.thickness\":[\"Thickness\"],\"node.skylight.panel.top\":[\"Top\"],\"node.skylight.panel.topScale\":[\"Top Scale\"],\"node.skylight.panel.trackWidth\":[\"Track Width\"],\"node.skylight.panel.type\":[\"Type\"],\"node.skylight.panel.width\":[\"Width\"],\"node.skylight.panel.yes\":[\"Yes\"],\"node.skylight.type.flat\":[\"Flat roof skylight\"],\"node.skylight.type.lantern\":[\"Roof lantern\"],\"node.skylight.type.opening\":[\"Opening skylight\"],\"node.skylight.type.sliding\":[\"Sliding skylight\"],\"node.skylight.type.walkOn\":[\"Walk-on rooflight\"],\"node.slab.description\":[\"A polygon-bounded floor surface that hosts items on top.\"],\"node.slab.label\":[\"Slab\"],\"node.slab.panel.addHole\":[\"Add Hole\"],\"node.slab.panel.area\":[\"Area\"],\"node.slab.panel.auto\":[\"Auto\"],\"node.slab.panel.base\":[\"Base\"],\"node.slab.panel.cutHatch\":[\"Cut hatch\"],\"node.slab.panel.cutOpening\":[\"Cut opening\"],\"node.slab.panel.depth\":[\"Depth\"],\"node.slab.panel.done\":[\"Done\"],\"node.slab.panel.elevation\":[\"Elevation\"],\"node.slab.panel.extendsThePerimeterDownToTerrain\":[\"Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged.\"],\"node.slab.panel.fixed\":[\"Fixed\"],\"node.slab.panel.floor\":[\"Floor\"],\"node.slab.panel.followsTerrain\":[\"Follows terrain\"],\"node.slab.panel.foundation\":[\"Foundation\"],\"node.slab.panel.holes\":[\"Holes\"],\"node.slab.panel.info\":[\"Info\"],\"node.slab.panel.move\":[\"Move\"],\"node.slab.panel.noHoles\":[\"No holes\"],\"node.slab.panel.rim\":[\"Rim\"],\"node.slab.panel.slab\":[\"Slab\"],\"node.slab.panel.standard2\":[\"Standard (2\\\")\"],\"node.slab.panel.standard5cm\":[\"Standard (5cm)\"],\"node.slab.panel.sunken15cm\":[\"Sunken (15cm)\"],\"node.slab.panel.sunken6\":[\"Sunken (6\\\")\"],\"node.slab.panel.surface\":[\"Surface\"],\"node.slab.panel.thick15cm\":[\"Thick (15cm)\"],\"node.slab.panel.thick6\":[\"Thick (6\\\")\"],\"node.slab.panel.thickness\":[\"Thickness\"],\"node.slab.panel.thin1\":[\"Thin (1\\\")\"],\"node.slab.panel.thin2cm\":[\"Thin (2cm)\"],\"node.solarPanel.description\":[\"Grid of photovoltaic panels mounted on a roof segment.\"],\"node.solarPanel.label\":[\"Solar Panel\"],\"node.solarPanel.panel.actions\":[\"Actions\"],\"node.solarPanel.panel.array\":[\"Array\"],\"node.solarPanel.panel.autoFitToRoof\":[\"Auto-fit to roof\"],\"node.solarPanel.panel.columns\":[\"Columns\"],\"node.solarPanel.panel.customDimensionsDonTMatchAny\":[\"Custom — dimensions don't match any preset\"],\"node.solarPanel.panel.delete\":[\"Delete\"],\"node.solarPanel.panel.flipOrientation\":[\"Flip orientation\"],\"node.solarPanel.panel.flush\":[\"Flush\"],\"node.solarPanel.panel.frameDepth\":[\"Frame depth\"],\"node.solarPanel.panel.frameThickness\":[\"Frame thickness\"],\"node.solarPanel.panel.gapX\":[\"Gap X\"],\"node.solarPanel.panel.gapY\":[\"Gap Y\"],\"node.solarPanel.panel.height\":[\"Height\"],\"node.solarPanel.panel.mounting\":[\"Mounting\"],\"node.solarPanel.panel.panel\":[\"Panel\"],\"node.solarPanel.panel.preset\":[\"Preset\"],\"node.solarPanel.panel.rows\":[\"Rows\"],\"node.solarPanel.panel.setbacksTooLarge\":[\"Setbacks too large to fit a panel.\"],\"node.solarPanel.panel.solarPanel\":[\"Solar Panel\"],\"node.solarPanel.panel.standoff\":[\"Standoff\"],\"node.solarPanel.panel.tiltAngle\":[\"Tilt angle\"],\"node.solarPanel.panel.tilted\":[\"Tilted\"],\"node.solarPanel.panel.width\":[\"Width\"],\"node.solarPanel.preset.compact\":[\"Compact\"],\"node.solarPanel.preset.frameless\":[\"Frameless\"],\"node.solarPanel.preset.residential\":[\"Residential\"],\"node.solarPanel.preset.residentialLarge\":[\"Residential Large\"],\"node.spawn.description\":[\"Player or camera origin within a level. One per level.\"],\"node.spawn.label\":[\"Spawn Point\"],\"node.stair.constructionControls.body\":[\"Body\"],\"node.stair.constructionControls.centerStringer\":[\"Center stringer\"],\"node.stair.constructionControls.closedRisers\":[\"Closed risers\"],\"node.stair.constructionControls.inherit\":[\"Inherit\"],\"node.stair.constructionControls.nosing\":[\"Nosing\"],\"node.stair.constructionControls.open\":[\"Open\"],\"node.stair.constructionControls.original\":[\"Original\"],\"node.stair.constructionControls.riserThickness\":[\"Riser thickness\"],\"node.stair.constructionControls.sideStringers\":[\"Side stringers\"],\"node.stair.constructionControls.solid\":[\"Solid\"],\"node.stair.constructionControls.stringerDepth\":[\"Stringer depth\"],\"node.stair.constructionControls.stringerWidth\":[\"Stringer width\"],\"node.stair.constructionControls.treadFinish\":[\"Tread finish\"],\"node.stair.constructionControls.treadThickness\":[\"Tread thickness\"],\"node.stair.constructionControls.waist\":[\"Waist\"],\"node.stair.constructionControls.waistThickness\":[\"Waist thickness\"],\"node.stair.description\":[\"A stair composed of one or more flights with configurable treads, risers, railings.\"],\"node.stair.diagnostic.arrivalMismatch\":[\"The flight chain does not reach the declared destination.\"],\"node.stair.diagnostic.comfortTarget\":[\"Two risers plus going is outside the 0.60–0.65 m design range.\"],\"node.stair.diagnostic.goingTarget\":[\"Going is below the design target.\"],\"node.stair.diagnostic.headroomTarget\":[\"Overhead surfaces leave less than the target headroom.\"],\"node.stair.diagnostic.headroomUnresolved\":[\"Detailed headroom could not be evaluated: check riser counts and the surface query budget.\"],\"node.stair.diagnostic.invalidFlight\":[\"Flight dimensions and integer riser count must be positive.\"],\"node.stair.diagnostic.invalidRise\":[\"The destination must be above the stair base.\"],\"node.stair.diagnostic.landingOverlap\":[\"Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle.\"],\"node.stair.diagnostic.narrowWinderEnd\":[\"The narrow end of a winder tread is below 0.10 m; check the intended stair use.\"],\"node.stair.diagnostic.nonuniformRisers\":[\"Flight riser heights differ. Use uniform-riser repair only if this is a proposed design.\"],\"node.stair.diagnostic.raisedLanding\":[\"A landing has a stored rise; inspect its transition before repair.\"],\"node.stair.diagnostic.riserTarget\":[\"Riser height exceeds the design target.\"],\"node.stair.label\":[\"Stair\"],\"node.stair.layout.straight\":[\"Straight\"],\"node.stair.layout.turn\":[[\"layout\"],\" turn\"],\"node.stair.panel.actions\":[\"Actions\"],\"node.stair.panel.addFlight\":[\"Add flight\"],\"node.stair.panel.addLanding\":[\"Add landing\"],\"node.stair.panel.applyLabelLayout\":[\"Apply \",[\"label\"],\" layout\"],\"node.stair.panel.autoCutout\":[\"Auto Cutout\"],\"node.stair.panel.balusters\":[\"Balusters\"],\"node.stair.panel.boards\":[\"Boards\"],\"node.stair.panel.both\":[\"Both\"],\"node.stair.panel.cable\":[\"Cable\"],\"node.stair.panel.centerColumn\":[\"Center Column\"],\"node.stair.panel.clockwise\":[\"Clockwise\"],\"node.stair.panel.connectedTo\":[\"Connected to \",[\"surface\"],\". Move the stair to connect to another edge.\"],\"node.stair.panel.connectedToSurface\":[\"Connected to a surface. Move the stair to connect to another edge.\"],\"node.stair.panel.construction\":[\"Construction\"],\"node.stair.panel.counterclockwise\":[\"Counterclockwise\"],\"node.stair.panel.curved\":[\"Curved\"],\"node.stair.panel.customRise\":[\"Custom rise\"],\"node.stair.panel.deck\":[\"Deck\"],\"node.stair.panel.delete\":[\"Delete\"],\"node.stair.panel.designTargets\":[\"Design targets\"],\"node.stair.panel.destination\":[\"Destination\"],\"node.stair.panel.dragNearADeckPatioConcrete\":[\"Drag near a deck, patio, concrete slab, or landing edge to fit the stair.\"],\"node.stair.panel.duplicate\":[\"Duplicate\"],\"node.stair.panel.equalAngle\":[\"Equal angle\"],\"node.stair.panel.equalGoing\":[\"Equal going\"],\"node.stair.panel.fitRisersAndGoing\":[\"Fit risers and going\"],\"node.stair.panel.fitToFloor\":[\"Fit To Floor\"],\"node.stair.panel.fitUniformRisers\":[\"Fit uniform risers\"],\"node.stair.panel.flightMeasurements\":[\"Flight \",[\"number\"],\": \",[\"count\",\"plural\",{\"one\":[[\"riserCount\"],\" riser\"],\"other\":[[\"riserCount\"],\" risers\"]}],\" · R \",[\"riser\"],\" · T \",[\"going\"],\" · \",[\"slope\"]],\"node.stair.panel.floorsCeilingsAndStairsOnly\":[\"Floors, ceilings and stairs only.\"],\"node.stair.panel.followsDeck\":[\"Follows deck\"],\"node.stair.panel.followsLevel\":[\"Follows level\"],\"node.stair.panel.fromLevel\":[\"From Level\"],\"node.stair.panel.geometry\":[\"Geometry\"],\"node.stair.panel.glass\":[\"Glass\"],\"node.stair.panel.headroom\":[\"Headroom:\"],\"node.stair.panel.height\":[\"Height\"],\"node.stair.panel.innerGap\":[\"Inner gap\"],\"node.stair.panel.innerRadius\":[\"Inner Radius\"],\"node.stair.panel.integrated\":[\"Integrated\"],\"node.stair.panel.landing\":[\"Landing\"],\"node.stair.panel.layout\":[\"Layout\"],\"node.stair.panel.left\":[\"Left\"],\"node.stair.panel.leftTurn\":[\"Left turn\"],\"node.stair.panel.measurements\":[\"Measurements\"],\"node.stair.panel.metal\":[\"Metal\"],\"node.stair.panel.move\":[\"Move\"],\"node.stair.panel.movingThisStairKeepsItsCurrent\":[\"Moving this stair keeps its current rise, steps, and run.\"],\"node.stair.panel.noOverheadSurface\":[\"No overhead surface\"],\"node.stair.panel.none\":[\"None\"],\"node.stair.panel.notEvaluated\":[\"Not evaluated\"],\"node.stair.panel.opening\":[\"Opening\"],\"node.stair.panel.openingOffset\":[\"Opening Offset\"],\"node.stair.panel.position\":[\"Position\"],\"node.stair.panel.postRail\":[\"Post & rail\"],\"node.stair.panel.railing\":[\"Railing\"],\"node.stair.panel.right\":[\"Right\"],\"node.stair.panel.rightTurn\":[\"Right turn\"],\"node.stair.panel.rise\":[\"Rise\"],\"node.stair.panel.rotation\":[\"Rotation\"],\"node.stair.panel.segmentType\":[[\"type\",\"select\",{\"landing\":[\"Landing\"],\"other\":[\"Stair\"]}]],\"node.stair.panel.segments\":[\"Segments\"],\"node.stair.panel.sizing\":[\"Sizing\"],\"node.stair.panel.snapToNearbySurfaces\":[\"Snap to nearby surfaces\"],\"node.stair.panel.spiral\":[\"Spiral\"],\"node.stair.panel.staircase\":[\"Staircase\"],\"node.stair.panel.stepSupports\":[\"Step Supports\"],\"node.stair.panel.steps\":[\"Steps\"],\"node.stair.panel.straight\":[\"Straight\"],\"node.stair.panel.style\":[\"Style\"],\"node.stair.panel.surfaceConnection\":[\"Surface connection\"],\"node.stair.panel.sweep\":[\"Sweep\"],\"node.stair.panel.targetGoing\":[\"Target going\"],\"node.stair.panel.targetHeadroom\":[\"Target headroom\"],\"node.stair.panel.targetMaxRiser\":[\"Target max riser\"],\"node.stair.panel.targetMinimumGoing\":[\"Target minimum going\"],\"node.stair.panel.thickness\":[\"Thickness\"],\"node.stair.panel.to\":[\"To\"],\"node.stair.panel.topLanding\":[\"Top Landing\"],\"node.stair.panel.type\":[\"Type\"],\"node.stair.panel.unavailable\":[\"Unavailable\"],\"node.stair.panel.walkingLineOffset\":[\"Walking line offset\"],\"node.stair.panel.width\":[\"Width\"],\"node.stair.panel.winder\":[\"Winder\"],\"node.stair.railingControls.both\":[\"Both\"],\"node.stair.railingControls.bottomExtension\":[\"Bottom sloped extension\"],\"node.stair.railingControls.continuous\":[\"Continuous\"],\"node.stair.railingControls.floor\":[\"Floor\"],\"node.stair.railingControls.handrailDiameter\":[\"Handrail diameter\"],\"node.stair.railingControls.handrailHeight\":[\"Handrail height\"],\"node.stair.railingControls.handrailInset\":[\"Handrail inset\"],\"node.stair.railingControls.left\":[\"Left\"],\"node.stair.railingControls.noReturn\":[\"No return\"],\"node.stair.railingControls.none\":[\"None\"],\"node.stair.railingControls.originalLayout\":[\"Original layout\"],\"node.stair.railingControls.postFacing\":[\"Post-facing\"],\"node.stair.railingControls.returnLength\":[\"Return length\"],\"node.stair.railingControls.right\":[\"Right\"],\"node.stair.railingControls.separateHandrail\":[\"Separate handrail\"],\"node.stair.railingControls.setTheLengthToReachThe\":[\"Set the length to reach the wall or post.\"],\"node.stair.railingControls.topExtension\":[\"Top horizontal extension\"],\"node.stair.railingControls.wallFacing\":[\"Wall-facing\"],\"node.stairSegment.description\":[\"A single flight of a parent stair.\"],\"node.stairSegment.label\":[\"Stair Segment\"],\"node.stairSegment.panel.actions\":[\"Actions\"],\"node.stairSegment.panel.attachment\":[\"Attachment\"],\"node.stairSegment.panel.delete\":[\"Delete\"],\"node.stairSegment.panel.dimensions\":[\"Dimensions\"],\"node.stairSegment.panel.duplicate\":[\"Duplicate\"],\"node.stairSegment.panel.editingSwitchesTheStairToCustom\":[\"Editing switches the stair to Custom rise\"],\"node.stairSegment.panel.equalAngle\":[\"Equal angle\"],\"node.stairSegment.panel.equalGoing\":[\"Equal going\"],\"node.stairSegment.panel.fillToFloor\":[\"Fill to floor\"],\"node.stairSegment.panel.flight\":[\"Flight\"],\"node.stairSegment.panel.flightShape\":[\"Flight shape\"],\"node.stairSegment.panel.front\":[\"Front\"],\"node.stairSegment.panel.height\":[\"Height\"],\"node.stairSegment.panel.innerGap\":[\"Inner gap\"],\"node.stairSegment.panel.landing\":[\"Landing\"],\"node.stairSegment.panel.left\":[\"Left\"],\"node.stairSegment.panel.leftTurn\":[\"Left turn\"],\"node.stairSegment.panel.length\":[\"Length\"],\"node.stairSegment.panel.move\":[\"Move\"],\"node.stairSegment.panel.position\":[\"Position\"],\"node.stairSegment.panel.right\":[\"Right\"],\"node.stairSegment.panel.rightTurn\":[\"Right turn\"],\"node.stairSegment.panel.rotation\":[\"Rotation\"],\"node.stairSegment.panel.stairSegment\":[\"Stair Segment\"],\"node.stairSegment.panel.steps\":[\"Steps\"],\"node.stairSegment.panel.straight\":[\"Straight\"],\"node.stairSegment.panel.structure\":[\"Structure\"],\"node.stairSegment.panel.thickness\":[\"Thickness\"],\"node.stairSegment.panel.type\":[\"Type\"],\"node.stairSegment.panel.walkingLineOffset\":[\"Walking line offset\"],\"node.stairSegment.panel.width\":[\"Width\"],\"node.stairSegment.panel.winder\":[\"Winder\"],\"node.structuralGrid.description\":[\"Persistent construction grid axis with identification bubbles.\"],\"node.structuralGrid.label\":[\"Structural Grid\"],\"node.turbineVent.description\":[\"Wind-driven spinning whirlybird exhaust vent for a roof slope.\"],\"node.turbineVent.label\":[\"Turbine Vent\"],\"node.turbineVent.panel.actions\":[\"Actions\"],\"node.turbineVent.panel.cylinder\":[\"Cylinder\"],\"node.turbineVent.panel.delete\":[\"Delete\"],\"node.turbineVent.panel.diameter\":[\"Diameter\"],\"node.turbineVent.panel.dimensions\":[\"Dimensions\"],\"node.turbineVent.panel.duplicate\":[\"Duplicate\"],\"node.turbineVent.panel.globe\":[\"Globe\"],\"node.turbineVent.panel.height\":[\"Height\"],\"node.turbineVent.panel.motion\":[\"Motion\"],\"node.turbineVent.panel.move\":[\"Move\"],\"node.turbineVent.panel.neckHeight\":[\"Neck Height\"],\"node.turbineVent.panel.pause\":[\"Pause\"],\"node.turbineVent.panel.play\":[\"Play\"],\"node.turbineVent.panel.position\":[\"Position\"],\"node.turbineVent.panel.rotation\":[\"Rotation\"],\"node.turbineVent.panel.spinSpeed\":[\"Spin Speed\"],\"node.turbineVent.panel.style\":[\"Style\"],\"node.turbineVent.panel.turbineVent\":[\"Turbine Vent\"],\"node.turbineVent.panel.vanes\":[\"Vanes\"],\"node.unit.description\":[\"A named group of zones forming one apartment, hotel room or commercial lot.\"],\"node.unit.kind.apartment\":[\"Apartment\"],\"node.unit.kind.commercial\":[\"Commercial\"],\"node.unit.kind.common\":[\"Common\"],\"node.unit.kind.hotelRoom\":[\"Hotel room\"],\"node.unit.label\":[\"Unit\"],\"node.unit.panel.memberRoom\":[\"Room\"],\"node.unit.panel.memberZone\":[\"Zone\"],\"node.unit.panel.noZonesAssigned\":[\"No zones assigned yet.\"],\"node.unit.panel.nonAdjacentLevels\":[\"Members sit on levels that are not adjacent.\"],\"node.unit.panel.removeMember\":[\"Remove \",[\"name\"],\" from unit\"],\"node.unit.panel.removeZone\":[\"Remove zone from unit\"],\"node.unit.unitPanel.actions\":[\"Actions\"],\"node.unit.unitPanel.color\":[\"Color\"],\"node.unit.unitPanel.delete\":[\"Delete\"],\"node.unit.unitPanel.focus\":[\"Focus\"],\"node.unit.unitPanel.grossArea\":[\"Gross area\"],\"node.unit.unitPanel.kind\":[\"Kind\"],\"node.unit.unitPanel.levels\":[\"Levels\"],\"node.unit.unitPanel.members\":[\"Members\"],\"node.unit.unitPanel.name\":[\"Name\"],\"node.unit.unitPanel.noRoomsYetPickThisUnit\":[\"No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused.\"],\"node.unit.unitPanel.report\":[\"Report\"],\"node.unit.unitPanel.unit\":[\"Unit\"],\"node.wall.actions.mergeWalls\":[\"Merge walls\"],\"node.wall.actions.splitWall\":[\"Split wall\"],\"node.wall.curtain.cell\":[\"Column \",[\"column\"],\", row \",[\"row\"],\": \",[\"type\",\"select\",{\"glass\":[\"glass\"],\"solid\":[\"solid\"],\"other\":[\"empty\"]}]],\"node.wall.curtain.columnSpacing\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" column\"],\"other\":[[\"number\"],\" columns\"]}],\" · \",[\"spacing\"],\" grid spacing\"],\"node.wall.curtain.noPanels\":[\"No panels\"],\"node.wall.curtain.rowSpacing\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" row\"],\"other\":[[\"number\"],\" rows\"]}],\" · \",[\"spacing\"],\" grid spacing\"],\"node.wall.curtainWallPanel.applyTint\":[\"Apply tint\"],\"node.wall.curtainWallPanel.borderOpeningFrame\":[\"Border / opening frame\"],\"node.wall.curtainWallPanel.bottom\":[\"Bottom\"],\"node.wall.curtainWallPanel.bottomRow\":[\"Bottom row\"],\"node.wall.curtainWallPanel.cancelTint\":[\"Cancel tint\"],\"node.wall.curtainWallPanel.center\":[\"Center\"],\"node.wall.curtainWallPanel.chooseACellOrEnterIts\":[\"Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward.\"],\"node.wall.curtainWallPanel.clearHiddenOverrides\":[\"Clear hidden overrides\"],\"node.wall.curtainWallPanel.column\":[\"Column\"],\"node.wall.curtainWallPanel.columnLayout\":[\"Column layout\"],\"node.wall.curtainWallPanel.columnRow\":[\"Column \",[\"column\"],\", row \",[\"row\"]],\"node.wall.curtainWallPanel.construction\":[\"Construction\"],\"node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong\":[\"Continuous mullions and transoms assembled along the wall.\"],\"node.wall.curtainWallPanel.curtainWallPanelSelection\":[\"Curtain wall panel selection\"],\"node.wall.curtainWallPanel.customInfill\":[\"Custom infill\"],\"node.wall.curtainWallPanel.defaultPanel\":[\"Default panel\"],\"node.wall.curtainWallPanel.editIndividualPanels\":[\"Edit individual panels\"],\"node.wall.curtainWallPanel.effectiveThicknessLimited\":[\"Effective thickness: \",[\"thickness\"],\", limited by frame depth.\"],\"node.wall.curtainWallPanel.empty\":[\"Empty\"],\"node.wall.curtainWallPanel.enablePanelEditing\":[\"Enable panel editing\"],\"node.wall.curtainWallPanel.end\":[\"End\"],\"node.wall.curtainWallPanel.fixedSpacing\":[\"Fixed spacing\"],\"node.wall.curtainWallPanel.followingWallDefaults\":[\"Following wall defaults\"],\"node.wall.curtainWallPanel.frameDimensions\":[\"Frame dimensions\"],\"node.wall.curtainWallPanel.framing\":[\"Framing\"],\"node.wall.curtainWallPanel.fullyCapped\":[\"Fully capped\"],\"node.wall.curtainWallPanel.glass\":[\"Glass\"],\"node.wall.curtainWallPanel.glassAppearance\":[\"Glass appearance\"],\"node.wall.curtainWallPanel.glassAppearanceIsControlledByThe\":[\"Glass appearance is controlled by the paint tool.\"],\"node.wall.curtainWallPanel.glassTint\":[\"Glass tint\"],\"node.wall.curtainWallPanel.glazingGap\":[\"Glazing gap\"],\"node.wall.curtainWallPanel.gridLimitedTo32PanelsPer\":[\"Grid limited to 32 panels per axis. Spacing expands to fit this wall.\"],\"node.wall.curtainWallPanel.hiddenCustomPanelCount\":[[\"length\",\"plural\",{\"one\":[[\"number\"],\" custom panel is outside this grid. It returns if the grid expands.\"],\"other\":[[\"number\"],\" custom panels are outside this grid. They return if the grid expands.\"]}]],\"node.wall.curtainWallPanel.horizontalCaps\":[\"Horizontal caps\"],\"node.wall.curtainWallPanel.horizontalFrameWidth\":[\"Horizontal frame width\"],\"node.wall.curtainWallPanel.layout\":[\"Layout\"],\"node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness\":[\"Low roughness is smooth; high roughness gives a matte surface in rendered shading.\"],\"node.wall.curtainWallPanel.maximumSpacing\":[\"Maximum spacing\"],\"node.wall.curtainWallPanel.moduleJointWidth\":[\"Module joint width\"],\"node.wall.curtainWallPanel.none\":[\"None\"],\"node.wall.curtainWallPanel.opacity\":[\"Opacity\"],\"node.wall.curtainWallPanel.opaqueBand\":[\"Opaque band\"],\"node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow\":[\"Opaque bands replace a full row with solid panels. Apply their finish with the paint tool.\"],\"node.wall.curtainWallPanel.panelCount\":[\"Panel count\"],\"node.wall.curtainWallPanel.panelInfill\":[\"Panel infill\"],\"node.wall.curtainWallPanel.panelThickness\":[\"Panel thickness\"],\"node.wall.curtainWallPanel.panels\":[\"Panels\"],\"node.wall.curtainWallPanel.resetThisPanel\":[\"Reset this panel\"],\"node.wall.curtainWallPanel.row2\":[\"Row\"],\"node.wall.curtainWallPanel.rowLayout\":[\"Row layout\"],\"node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween\":[\"Separate framed modules with joints between units.\"],\"node.wall.curtainWallPanel.solid\":[\"Solid\"],\"node.wall.curtainWallPanel.spacing\":[\"Spacing\"],\"node.wall.curtainWallPanel.start\":[\"Start\"],\"node.wall.curtainWallPanel.stickBuilt\":[\"Stick-built\"],\"node.wall.curtainWallPanel.structuralGlazing\":[\"Structural glazing\"],\"node.wall.curtainWallPanel.surfaceRoughness\":[\"Surface roughness\"],\"node.wall.curtainWallPanel.top\":[\"Top\"],\"node.wall.curtainWallPanel.topRow\":[\"Top row\"],\"node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing\":[\"Uncapped members sit behind the glazing.\"],\"node.wall.curtainWallPanel.unitized\":[\"Unitized\"],\"node.wall.curtainWallPanel.useGlassSettingsInstead\":[\"Use glass settings instead\"],\"node.wall.curtainWallPanel.useTheDoorOrWindowTool\":[\"Use the Door or Window tool to add an opening through the wall.\"],\"node.wall.curtainWallPanel.verticalCaps\":[\"Vertical caps\"],\"node.wall.curtainWallPanel.verticalFrameWidth\":[\"Vertical frame width\"],\"node.wall.curtainWallPanel.wallSystem\":[\"Wall system\"],\"node.wall.description\":[\"A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items.\"],\"node.wall.label\":[\"Wall\"],\"node.wall.panel.actions\":[\"Actions\"],\"node.wall.panel.assembly\":[\"Assembly\"],\"node.wall.panel.auto\":[\"Auto\"],\"node.wall.panel.both\":[\"Both\"],\"node.wall.panel.bottom\":[\"Bottom\"],\"node.wall.panel.brickVeneer\":[\"Brick veneer\"],\"node.wall.panel.chairRail\":[\"Chair rail\"],\"node.wall.panel.cmu\":[\"CMU\"],\"node.wall.panel.colonial\":[\"Colonial\"],\"node.wall.panel.cove\":[\"Cove\"],\"node.wall.panel.craft\":[\"Craft\"],\"node.wall.panel.crownMolding\":[\"Crown molding\"],\"node.wall.panel.curtainWall\":[\"Curtain wall\"],\"node.wall.panel.curve\":[\"Curve\"],\"node.wall.panel.custom\":[\"Custom\"],\"node.wall.panel.customHeight\":[\"Custom height\"],\"node.wall.panel.depth\":[\"Depth\"],\"node.wall.panel.dimensions\":[\"Dimensions\"],\"node.wall.panel.drywall\":[\"Drywall\"],\"node.wall.panel.extendsDownwardToMeetTheTerrain\":[\"Extends downward to meet the terrain. Height and top stay unchanged.\"],\"node.wall.panel.exterior\":[\"Exterior\"],\"node.wall.panel.fiberCement\":[\"Fiber cement\"],\"node.wall.panel.fillToTerrain\":[\"Fill to terrain\"],\"node.wall.panel.flat\":[\"Flat\"],\"node.wall.panel.followsLevel\":[\"Follows level\"],\"node.wall.panel.frameDepth\":[\"Frame depth\"],\"node.wall.panel.framing\":[\"Framing\"],\"node.wall.panel.gypsum\":[\"Gypsum\"],\"node.wall.panel.height\":[\"Height\"],\"node.wall.panel.hideCode\":[\"Hide code\"],\"node.wall.panel.icf\":[\"ICF\"],\"node.wall.panel.interior\":[\"Interior\"],\"node.wall.panel.lapSiding\":[\"Lap siding\"],\"node.wall.panel.layerThicknessesFollowThe2021Irc\":[\"Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction.\"],\"node.wall.panel.layered\":[\"Layered\"],\"node.wall.panel.length\":[\"Length\"],\"node.wall.panel.loading\":[\"Loading…\"],\"node.wall.panel.modern\":[\"Modern\"],\"node.wall.panel.none\":[\"None\"],\"node.wall.panel.noneSingleLayer\":[\"None (single layer)\"],\"node.wall.panel.offset\":[\"Offset\"],\"node.wall.panel.ogee\":[\"Ogee\"],\"node.wall.panel.osb\":[\"OSB\"],\"node.wall.panel.partitionTheInteriorFinishIsApplied\":[\"Partition: the interior finish is applied to both faces.\"],\"node.wall.panel.picture\":[\"Picture\"],\"node.wall.panel.plaster\":[\"Plaster\"],\"node.wall.panel.plywood\":[\"Plywood\"],\"node.wall.panel.preset\":[\"Preset\"],\"node.wall.panel.reference\":[\"Reference\"],\"node.wall.panel.remove\":[\"Remove\"],\"node.wall.panel.round\":[\"Round\"],\"node.wall.panel.scriptedFinish\":[\"Scripted finish\"],\"node.wall.panel.sheathing\":[\"Sheathing\"],\"node.wall.panel.shoe\":[\"Shoe\"],\"node.wall.panel.sideA\":[\"Side A\"],\"node.wall.panel.sideB\":[\"Side B\"],\"node.wall.panel.sideFace\":[\"Side \",[\"face\"]],\"node.wall.panel.skirting\":[\"Skirting\"],\"node.wall.panel.standard\":[\"Standard\"],\"node.wall.panel.steelStuds\":[\"Steel studs\"],\"node.wall.panel.step\":[\"Step\"],\"node.wall.panel.stoneVeneer\":[\"Stone veneer\"],\"node.wall.panel.stucco\":[\"Stucco\"],\"node.wall.panel.thickness\":[\"Thickness\"],\"node.wall.panel.thicknessFromAssembly\":[[\"thickness\"],\" · from assembly\"],\"node.wall.panel.top\":[\"Top\"],\"node.wall.panel.totalThickness\":[\"Total thickness\"],\"node.wall.panel.unverifiedPresetnote\":[\"Unverified: \",[\"presetNote\"]],\"node.wall.panel.viewCode\":[\"View code\"],\"node.wall.panel.wall\":[\"Wall\"],\"node.wall.panel.wallType\":[\"Wall type\"],\"node.wall.panel.whichFaceIsOutsideIsUndetermined\":[\"Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B.\"],\"node.wall.panel.woodStuds\":[\"Wood studs\"],\"node.wall.reference.center\":[\"Center\"],\"node.wall.reference.inside\":[\"Inside face\"],\"node.wall.reference.left\":[\"Left\"],\"node.wall.reference.outside\":[\"Outside face\"],\"node.wall.reference.right\":[\"Right\"],\"node.wall.trim.hide\":[[\"trim\",\"select\",{\"skirting\":[\"Hide skirting\"],\"crown\":[\"Hide crown molding\"],\"other\":[\"Hide chair rail\"]}]],\"node.wall.trim.show\":[[\"trim\",\"select\",{\"skirting\":[\"Show skirting\"],\"crown\":[\"Show crown molding\"],\"other\":[\"Show chair rail\"]}]],\"node.window.description\":[\"A window cut into a wall. Animated open/close for opening windows.\"],\"node.window.label\":[\"Window\"],\"node.window.operation.raise\":[\"Raise\"],\"node.window.operation.slats\":[\"Slats\"],\"node.window.operation.slide\":[\"Slide\"],\"node.window.operation.swing\":[\"Swing\"],\"node.window.operation.tilt\":[\"Tilt\"],\"node.window.panel.actions\":[\"Actions\"],\"node.window.panel.all\":[\"All\"],\"node.window.panel.arch\":[\"Arch\"],\"node.window.panel.archHeight\":[\"Arch Height\"],\"node.window.panel.auto\":[\"Auto\"],\"node.window.panel.autoFollowsTheWall\":[\"Auto follows the wall\"],\"node.window.panel.colWidths\":[\"Col Widths\"],\"node.window.panel.columns\":[\"Columns\"],\"node.window.panel.corner\":[\"Corner\"],\"node.window.panel.cornerPost\":[\"Corner post\"],\"node.window.panel.cornerRadius\":[\"Corner Radius\"],\"node.window.panel.customThePanesMatchNoStyle\":[\"Custom: the panes match no style.\"],\"node.window.panel.delete\":[\"Delete\"],\"node.window.panel.depth\":[\"Depth\"],\"node.window.panel.dimensions\":[\"Dimensions\"],\"node.window.panel.divider\":[\"Divider\"],\"node.window.panel.down\":[\"Down\"],\"node.window.panel.duplicate\":[\"Duplicate\"],\"node.window.panel.enableSill\":[\"Enable Sill\"],\"node.window.panel.flipSide\":[\"Flip Side\"],\"node.window.panel.frame\":[\"Frame\"],\"node.window.panel.frameDepthAuto\":[\"Depth (auto)\"],\"node.window.panel.french\":[\"French\"],\"node.window.panel.fusedGlass\":[\"Fused glass\"],\"node.window.panel.grid\":[\"Grid\"],\"node.window.panel.height\":[\"Height\"],\"node.window.panel.individual\":[\"Individual\"],\"node.window.panel.left\":[\"Left\"],\"node.window.panel.move\":[\"Move\"],\"node.window.panel.opening\":[\"Opening\"],\"node.window.panel.openingShape\":[\"Opening Shape\"],\"node.window.panel.otherSide\":[\"Other side\"],\"node.window.panel.pos\":[\"pos\"],\"node.window.panel.position\":[\"Position\"],\"node.window.panel.rect\":[\"Rect\"],\"node.window.panel.revealRadius\":[\"Reveal Radius\"],\"node.window.panel.right\":[\"Right\"],\"node.window.panel.rounded\":[\"Rounded\"],\"node.window.panel.rowHeights\":[\"Row Heights\"],\"node.window.panel.rows\":[\"Rows\"],\"node.window.panel.sill\":[\"Sill\"],\"node.window.panel.single\":[\"Single\"],\"node.window.panel.sizeIsLimitedToTheWall\":[\"Size is limited to the wall, including clearance for the opening frame.\"],\"node.window.panel.style\":[\"Style\"],\"node.window.panel.thickness\":[\"Thickness\"],\"node.window.panel.thisSide\":[\"This side\"],\"node.window.panel.topShape\":[\"Top Shape\"],\"node.window.panel.type\":[\"Type\"],\"node.window.panel.unwrap\":[\"Unwrap\"],\"node.window.panel.up\":[\"Up\"],\"node.window.panel.width\":[\"Width\"],\"node.window.panel.window\":[\"Window\"],\"node.window.panel.windowType\":[\"Window Type\"],\"node.window.panel.wrapTheCorner\":[\"Wrap the corner\"],\"node.window.panel.wrapsTheCornerWithNamed\":[\"Wraps the corner with \",[\"partner\"]],\"node.window.panel.wrapsTheCornerWithPartner\":[\"Wraps the corner with its partner\"],\"node.window.style.casement\":[\"Casement\"],\"node.window.style.doubleHung\":[\"Double-hung\"],\"node.window.style.grid\":[\"Grid\"],\"node.window.style.horizontalBands\":[\"Horizontal bands\"],\"node.window.style.picture\":[\"Picture\"],\"node.window.style.single\":[\"Single\"],\"node.window.style.sliding\":[\"Sliding\"],\"node.window.style.tallGrid\":[\"Tall grid\"],\"node.window.style.transom\":[\"Transom\"],\"node.window.style.tripleHung\":[\"Triple-hung\"],\"node.window.style.wideGrid\":[\"Wide grid\"],\"node.window.type.awning\":[\"Awning\"],\"node.window.type.bay\":[\"Bay\"],\"node.window.type.bow\":[\"Bow\"],\"node.window.type.casement\":[\"Casement\"],\"node.window.type.doubleHung\":[\"Double Hung\"],\"node.window.type.fixed\":[\"Fixed\"],\"node.window.type.hopper\":[\"Hopper\"],\"node.window.type.louvered\":[\"Louvered\"],\"node.window.type.singleHung\":[\"Single Hung\"],\"node.window.type.sliding\":[\"Sliding\"],\"node.zone.description\":[\"A polygonal site zone (lawn, water, paving) with a TSL gradient material.\"],\"node.zone.label\":[\"Zone\"],\"node.zone.quantitiesPanel.enclosedRoom\":[\"Enclosed room\"],\"node.zone.quantitiesPanel.floorSurface\":[\"Floor surface\"],\"node.zone.quantitiesPanel.footprintOnly\":[\"Footprint only\"],\"node.zone.quantitiesPanel.none\":[\"None\"],\"node.zone.quantitiesPanel.notProven\":[\"Not proven\"],\"node.zone.quantitiesPanel.roomQuantities\":[\"Room quantities\"],\"node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions\":[\"Top view with zone edge dimensions\"],\"node.zone.quantitiesPanel.unit\":[\"Unit\"],\"node.zone.quantitiesPanel.volume\":[\"Volume\"],\"node.zone.quantitiesPanel.wallSurface\":[\"Wall surface\"],\"node.zone.quantitiesPanel.zoneBoundaryUnavailable\":[\"Zone boundary unavailable\"],\"node.zone.quantitiesPanel.zoneQuantities\":[\"Zone quantities\"],\"viewer.camera.orbitLeft\":[\"Orbit left\"],\"viewer.camera.orbitRight\":[\"Orbit right\"],\"viewer.camera.topView\":[\"Top view\"],\"viewer.common.off\":[\"Off\"],\"viewer.common.on\":[\"On\"],\"viewer.display.camera\":[\"Camera\"],\"viewer.display.colored\":[\"Colored\"],\"viewer.display.colors\":[\"Colors\"],\"viewer.display.edges.off\":[\"Off\"],\"viewer.display.edges.offDetail\":[\"No edge lines\"],\"viewer.display.edges.soft\":[\"Soft\"],\"viewer.display.edges.softDetail\":[\"Faint outline of major creases\"],\"viewer.display.edges.strong\":[\"Strong\"],\"viewer.display.edges.strongDetail\":[\"Crisp, opaque edge lines\"],\"viewer.display.edges.title\":[\"Edges\"],\"viewer.display.monochrome\":[\"Monochrome\"],\"viewer.display.orthographic\":[\"Orthographic\"],\"viewer.display.perspective\":[\"Perspective\"],\"viewer.display.render.rendered\":[\"Rendered\"],\"viewer.display.render.renderedDetail\":[\"Full ambient occlusion\"],\"viewer.display.render.solid\":[\"Solid\"],\"viewer.display.render.solidDetail\":[\"Flat and fast — no ambient occlusion\"],\"viewer.display.render.title\":[\"Render\"],\"viewer.display.shadows\":[\"Shadows\"],\"viewer.display.title\":[\"Display settings\"],\"viewer.floorplan.alignNorth\":[\"Align view to north\"],\"viewer.floorplan.empty\":[\"No floor plans are available for this scene.\"],\"viewer.floorplan.fit\":[\"Fit floor plan\"],\"viewer.floorplan.floor\":[\"Floor\"],\"viewer.floorplan.floorPlan\":[\"Floor plan\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\" 2D view\"],\"viewer.floorplan.zoomIn\":[\"Zoom in\"],\"viewer.floorplan.zoomOut\":[\"Zoom out\"],\"viewer.header.back\":[\"Back\"],\"viewer.header.levels\":[\"Levels\"],\"viewer.header.rooms\":[\"Rooms\"],\"viewer.header.selection\":[\"Selection\"],\"viewer.header.units\":[\"Units\"],\"viewer.header.untitled\":[\"Untitled\"],\"viewer.header.zones\":[\"Zones\"],\"viewer.levels.exploded\":[\"Exploded\"],\"viewer.levels.label\":[\"Levels: \",[\"mode\"]],\"viewer.levels.manual\":[\"Manual\"],\"viewer.levels.solo\":[\"Solo\"],\"viewer.levels.stacked\":[\"Stacked\"],\"viewer.stage.layout\":[\"Viewer layout\"],\"viewer.stage.split\":[\"Split\"],\"viewer.theme.blueprint\":[\"Blueprint\"],\"viewer.theme.mediterranean\":[\"Mediterranean\"],\"viewer.theme.night\":[\"Night\"],\"viewer.theme.overcast\":[\"Overcast\"],\"viewer.theme.paper\":[\"Paper\"],\"viewer.theme.studio\":[\"Studio\"],\"viewer.theme.sunset\":[\"Sunset\"],\"viewer.theme.title\":[\"Scene theme\"],\"viewer.theme.twilight\":[\"Twilight\"],\"viewer.theme.verdant\":[\"Verdant\"],\"viewer.visibility.guides\":[\"Guides\"],\"viewer.visibility.scans\":[\"Scans\"],\"viewer.visibility.title\":[\"Visibility\"],\"viewer.walkthrough.label\":[\"Walkthrough: \",[\"state\"]],\"viewer.walls.cutaway\":[\"Cutaway\"],\"viewer.walls.fullHeight\":[\"Full height\"],\"viewer.walls.label\":[\"Walls: \",[\"mode\"]],\"viewer.walls.low\":[\"Low\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"app.appShell.pascalHome\":[\"Pascal home\"],\"app.buildTab.addGate\":[\"Add Gate\"],\"app.buildTab.addOpenPassage\":[\"Add Open Passage\"],\"app.buildTab.addToRooms\":[\"Add to rooms\"],\"app.buildTab.allBuildTools\":[\"← All build tools\"],\"app.buildTab.chooseGateOrOpenPassageThen\":[\"Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph.\"],\"app.buildTab.clickALinesetToLayThe\":[\"Click a lineset to lay the line beside it.\"],\"app.buildTab.doubleGate\":[\"Double gate\"],\"app.buildTab.featuresExtensions\":[\"Features & extensions\"],\"app.buildTab.fenceFeatures\":[\"Fence features\"],\"app.buildTab.followLineset\":[\"Follow lineset\"],\"app.buildTab.gate\":[\"Gate\"],\"app.buildTab.gateStyle\":[\"Gate style\"],\"app.buildTab.horizontalBoards\":[\"Horizontal boards\"],\"app.buildTab.hoverAFenceToPreviewClick\":[\"Hover a fence to preview. Click to place. Esc cancels. Leave room between openings.\"],\"app.buildTab.leaves\":[\"Leaves\"],\"app.buildTab.liquidLine\":[\"Liquid Line\"],\"app.buildTab.matchFenceStyle\":[\"Match fence style\"],\"app.buildTab.mep\":[\"MEP\"],\"app.buildTab.off\":[\"Off\"],\"app.buildTab.on\":[\"On\"],\"app.buildTab.openRails\":[\"Open rails\"],\"app.buildTab.opening\":[\"Opening\"],\"app.buildTab.openingWidthM\":[\"Opening width (m)\"],\"app.buildTab.outdoor\":[\"Outdoor\"],\"app.buildTab.picket\":[\"Picket\"],\"app.buildTab.roofType\":[\"Roof type\"],\"app.buildTab.roomsAlreadyCreateTheirFloorAnd\":[\"Rooms already create their floor and ceiling. Use these for platforms and one-off structure.\"],\"app.buildTab.searchBuildToolsAndKitchenItems\":[\"Search build tools and kitchen items\"],\"app.buildTab.searchToolsFridgeSink\":[\"Search tools, fridge, sink…\"],\"app.buildTab.selectACurvedWallToMatch\":[\"Select a curved wall to match its radius and arc.\"],\"app.buildTab.singleGate\":[\"Single gate\"],\"app.buildTab.slabCeilingColumn\":[\"Slab, ceiling, column…\"],\"app.buildTab.solidPrivacy\":[\"Solid privacy\"],\"app.buildTab.traceALineAlongsideAnExisting\":[\"Trace a line alongside an existing lineset (F).\"],\"app.buildTab.verticalSlats\":[\"Vertical slats\"],\"app.editorNavbar.allProjects\":[\"All projects\"],\"app.editorNavbar.preview\":[\"Preview\"],\"app.editorNavbar.projectName\":[\"Project name\"],\"app.editorNavbar.renameProject\":[\"Rename project\"],\"app.editorNavbar.share\":[\"Share\"],\"app.kitchenBrowser.addLabel\":[\"Add \",[\"label\"]],\"app.kitchenBrowser.addToKitchen\":[\"Add to kitchen\"],\"app.kitchenBrowser.chooseAnItemThenClickTo\":[\"Choose an item, then click to place it. Appliances include their cabinetry.\"],\"app.kitchenBrowser.findInThisProjectLength\":[\"Find in this project · \",[\"length\"]],\"app.kitchenBrowser.findName\":[\"Find \",[\"name\"]],\"app.kitchenBrowser.groupAdd\":[\"Kitchen · Add\"],\"app.kitchenBrowser.groupInThisProject\":[\"Kitchen · In this project\"],\"app.kitchenBrowser.kitchenCabinet\":[\"Kitchen cabinet\"],\"app.kitchenBrowser.noMatchingKitchenItemsPlacedYet\":[\"No matching kitchen items placed yet.\"],\"app.kitchenBrowser.selectAndShowInScene\":[\"Select and show in scene\"],\"app.lib.buildPalette.ceiling\":[\"Ceiling\"],\"app.lib.buildPalette.column\":[\"Column\"],\"app.lib.buildPalette.door\":[\"Door\"],\"app.lib.buildPalette.duct\":[\"Duct\"],\"app.lib.buildPalette.dwvPipe\":[\"DWV Pipe\"],\"app.lib.buildPalette.elevator\":[\"Elevator\"],\"app.lib.buildPalette.fence\":[\"Fence\"],\"app.lib.buildPalette.hvacUnit\":[\"HVAC Unit\"],\"app.lib.buildPalette.kitchen\":[\"Kitchen\"],\"app.lib.buildPalette.lineset\":[\"Lineset\"],\"app.lib.buildPalette.liquidLine\":[\"Liquid Line\"],\"app.lib.buildPalette.mep\":[\"MEP\"],\"app.lib.buildPalette.post\":[\"Post\"],\"app.lib.buildPalette.register\":[\"Register\"],\"app.lib.buildPalette.roof\":[\"Roof\"],\"app.lib.buildPalette.shelf\":[\"Shelf\"],\"app.lib.buildPalette.slab\":[\"Slab\"],\"app.lib.buildPalette.spawnPoint\":[\"Spawn point\"],\"app.lib.buildPalette.stairs\":[\"Stairs\"],\"app.lib.buildPalette.terrace\":[\"Terrace\"],\"app.lib.buildPalette.terrain\":[\"Terrain\"],\"app.lib.buildPalette.wall\":[\"Wall\"],\"app.lib.buildPalette.window\":[\"Window\"],\"app.lib.buildPanelModel.addFitting\":[\"Add Fitting\"],\"app.lib.buildPanelModel.addGate\":[\"Add Gate\"],\"app.lib.buildPanelModel.addOpenPassage\":[\"Add Open Passage\"],\"app.lib.buildPanelModel.addTrap\":[\"Add Trap\"],\"app.lib.buildPanelModel.build\":[\"Build\"],\"app.lib.buildPanelModel.createFrom\":[\"Create from\"],\"app.lib.buildPanelModel.drawTheRoofFootprintWithTwo\":[\"Draw the roof footprint with two corner clicks.\"],\"app.lib.buildPanelModel.duct\":[\"Duct\"],\"app.lib.buildPanelModel.dwvPipe\":[\"DWV Pipe\"],\"app.lib.buildPanelModel.featuresExtensions\":[\"Features & extensions\"],\"app.lib.buildPanelModel.fence\":[\"Fence\"],\"app.lib.buildPanelModel.followLineset\":[\"Follow lineset\"],\"app.lib.buildPanelModel.gatesOpenings\":[\"Gates & openings\"],\"app.lib.buildPanelModel.hoverARoomToPreviewIts\":[\"Hover a room to preview its boundary, then click to place.\"],\"app.lib.buildPanelModel.kitchen\":[\"Kitchen\"],\"app.lib.buildPanelModel.mep\":[\"MEP\"],\"app.lib.buildPanelModel.modularCabinet\":[\"Modular Cabinet\"],\"app.lib.buildPanelModel.off\":[\"Off\"],\"app.lib.buildPanelModel.on\":[\"On\"],\"app.lib.buildPanelModel.painting\":[\"Painting\"],\"app.lib.buildPanelModel.roof\":[\"Roof\"],\"app.lib.buildPanelModel.roofType\":[\"Roof type\"],\"app.lib.buildPanelModel.toolCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" tool\"],\"other\":[\"#\",\" tools\"]}]],\"app.lib.buildTabState.conical\":[\"Conical\"],\"app.lib.buildTabState.draw\":[\"Draw\"],\"app.lib.buildTabState.dutch\":[\"Dutch\"],\"app.lib.buildTabState.flat\":[\"Flat\"],\"app.lib.buildTabState.gable\":[\"Gable\"],\"app.lib.buildTabState.gambrel\":[\"Gambrel\"],\"app.lib.buildTabState.hip\":[\"Hip\"],\"app.lib.buildTabState.mansard\":[\"Mansard\"],\"app.lib.buildTabState.room\":[\"Room\"],\"app.lib.buildTabState.shed\":[\"Shed\"],\"app.lib.buildTabState.wall\":[\"Wall\"],\"app.sceneLoader.anotherSessionSavedFirstRefresh\":[\"Another session saved first — refresh?\"],\"app.sceneLoader.build\":[\"Build\"],\"app.sceneLoader.dismiss\":[\"Dismiss\"],\"app.sceneLoader.items\":[\"Items\"],\"app.sceneLoader.paint\":[\"Paint\"],\"app.sceneLoader.reload\":[\"Reload\"],\"app.sceneLoader.scene\":[\"Scene\"],\"app.sceneLoader.settings\":[\"Settings\"],\"app.sceneLoader.yourChangesHavenTBeenSaved\":[\"Your changes haven't been saved. Reload to pick up the latest version.\"],\"app.sharePanel.anyoneWithTheLinkCanView\":[\"Anyone with the link can view this project. They cannot edit it.\"],\"app.sharePanel.copied\":[\"Copied\"],\"app.sharePanel.copy\":[\"Copy\"],\"app.sharePanel.couldNotReadTheSharingState\":[\"Could not read the sharing state.\"],\"app.sharePanel.couldNotTurnOffThePublic\":[\"Could not turn off the public link.\"],\"app.sharePanel.couldNotTurnOnThePublic\":[\"Could not turn on the public link.\"],\"app.sharePanel.embedCode\":[\"Embed code\"],\"app.sharePanel.embedLink\":[\"Embed link\"],\"app.sharePanel.hideControls\":[\"Hide controls\"],\"app.sharePanel.link\":[\"Link\"],\"app.sharePanel.onlyThe3dViewNoHeader\":[\"Only the 3D view, no header or toolbar.\"],\"app.sharePanel.open\":[\"Open\"],\"app.sharePanel.publicLink\":[\"Public link\"],\"app.sharePanel.shareProject\":[\"Share project\"],\"app.viewerToolbar.automaticDimensions\":[\"Automatic dimensions\"],\"app.viewerToolbar.camera\":[\"Camera\"],\"app.viewerToolbar.ceilings\":[\"Ceilings\"],\"app.viewerToolbar.cleanPlanDimensionsAppearWithSelection\":[\"Clean plan; dimensions appear with selection\"],\"app.viewerToolbar.collapseSidebar\":[\"Collapse sidebar\"],\"app.viewerToolbar.crispOpaqueEdgeLines\":[\"Crisp, opaque edge lines\"],\"app.viewerToolbar.cutaway\":[\"Cutaway\"],\"app.viewerToolbar.default\":[\"Default\"],\"app.viewerToolbar.display\":[\"Display\"],\"app.viewerToolbar.displaySettings\":[\"Display settings\"],\"app.viewerToolbar.doorWindowMarks\":[\"Door/window marks\"],\"app.viewerToolbar.edges\":[\"Edges\"],\"app.viewerToolbar.enterVr\":[\"Enter VR\"],\"app.viewerToolbar.exitVr\":[\"Exit VR\"],\"app.viewerToolbar.expandSidebar\":[\"Expand sidebar\"],\"app.viewerToolbar.expert\":[\"Expert\"],\"app.viewerToolbar.exploded\":[\"Exploded\"],\"app.viewerToolbar.faceOfStud\":[\"Face of stud\"],\"app.viewerToolbar.faintOutlineOfMajorCreases\":[\"Faint outline of major creases\"],\"app.viewerToolbar.feetInches\":[\"Feet & inches\"],\"app.viewerToolbar.finishedFaces\":[\"Finished faces\"],\"app.viewerToolbar.flatAndFastNoAmbientOcclusion\":[\"Flat and fast — no ambient occlusion\"],\"app.viewerToolbar.floorPlanAnnotations\":[\"Floor plan annotations\"],\"app.viewerToolbar.floorPlanMode\":[\"Floor plan mode\"],\"app.viewerToolbar.fullAmbientOcclusion\":[\"Full ambient occlusion\"],\"app.viewerToolbar.fullDocumentationAndAnnotationControls\":[\"Full documentation and annotation controls\"],\"app.viewerToolbar.fullHeight\":[\"Full height\"],\"app.viewerToolbar.fullWallThickness\":[\"Full wall thickness\"],\"app.viewerToolbar.grid\":[\"Grid\"],\"app.viewerToolbar.levelsMode\":[\"Levels: \",[\"mode\"]],\"app.viewerToolbar.low\":[\"Low\"],\"app.viewerToolbar.magneticSnap\":[\"Magnetic snap\"],\"app.viewerToolbar.manual\":[\"Manual\"],\"app.viewerToolbar.manualDimensions\":[\"Manual dimensions\"],\"app.viewerToolbar.measurements\":[\"Measurements\"],\"app.viewerToolbar.meters\":[\"Meters\"],\"app.viewerToolbar.millimeters\":[\"Millimeters\"],\"app.viewerToolbar.n3dMeasurements\":[\"3D measurements\"],\"app.viewerToolbar.noEdgeLines\":[\"No edge lines\"],\"app.viewerToolbar.off\":[\"Off\"],\"app.viewerToolbar.on\":[\"On\"],\"app.viewerToolbar.orthographic\":[\"Orthographic\"],\"app.viewerToolbar.perspective\":[\"Perspective\"],\"app.viewerToolbar.preview\":[\"Preview\"],\"app.viewerToolbar.previewMode\":[\"Preview mode\"],\"app.viewerToolbar.render\":[\"Render\"],\"app.viewerToolbar.rendered\":[\"Rendered\"],\"app.viewerToolbar.roomLabels\":[\"Room labels\"],\"app.viewerToolbar.sceneTheme\":[\"Scene theme\"],\"app.viewerToolbar.shadows\":[\"Shadows\"],\"app.viewerToolbar.singleWallAxis\":[\"Single wall axis\"],\"app.viewerToolbar.soft\":[\"Soft\"],\"app.viewerToolbar.solid\":[\"Solid\"],\"app.viewerToolbar.solo\":[\"Solo\"],\"app.viewerToolbar.stack\":[\"Stack\"],\"app.viewerToolbar.stairAnnotations\":[\"Stair annotations\"],\"app.viewerToolbar.strong\":[\"Strong\"],\"app.viewerToolbar.structuralCoreFace\":[\"Structural core face\"],\"app.viewerToolbar.structuralGridsColumnCenters\":[\"Structural grids & column centers\"],\"app.viewerToolbar.translucent\":[\"Translucent\"],\"app.viewerToolbar.units\":[\"Units\"],\"app.viewerToolbar.walkthrough\":[\"Walkthrough\"],\"app.viewerToolbar.wallCenterline\":[\"Wall centerline\"],\"app.viewerToolbar.wallDimensions\":[\"Wall dimensions\"],\"app.viewerToolbar.wallsLabel\":[\"Walls: \",[\"label\"]],\"core.level.basement\":[\"Basement\"],\"core.level.basementNumber\":[\"Basement \",[\"number\"]],\"core.level.floorNumber\":[\"Floor \",[\"number\"]],\"core.level.groundFloor\":[\"Ground floor\"],\"editor.actionMenu.cameraActions.orbitLeft\":[\"Orbit Left\"],\"editor.actionMenu.cameraActions.orbitRight\":[\"Orbit Right\"],\"editor.actionMenu.cameraActions.topView\":[\"Top View\"],\"editor.actionMenu.controlModes.delete\":[\"Delete\"],\"editor.actionMenu.controlModes.select\":[\"Select\"],\"editor.actionMenu.followPascalPill.resumeFollowing\":[\"Resume following\"],\"editor.actionMenu.followPascalToggle.off\":[\"Follow Pascal: Off\"],\"editor.actionMenu.followPascalToggle.on\":[\"Follow Pascal: On\"],\"editor.actionMenu.furnishTools.appliance\":[\"Appliance\"],\"editor.actionMenu.furnishTools.bathroom\":[\"Bathroom\"],\"editor.actionMenu.furnishTools.furniture\":[\"Furniture\"],\"editor.actionMenu.furnishTools.kitchen\":[\"Kitchen\"],\"editor.actionMenu.furnishTools.outdoor\":[\"Outdoor\"],\"editor.actionMenu.measurementControl.angle\":[\"Angle\"],\"editor.actionMenu.measurementControl.angularDimension\":[\"Angular dimension\"],\"editor.actionMenu.measurementControl.arcLength\":[\"Arc length\"],\"editor.actionMenu.measurementControl.area\":[\"Area\"],\"editor.actionMenu.measurementControl.centerMark\":[\"Center mark\"],\"editor.actionMenu.measurementControl.chordDimension\":[\"Chord dimension\"],\"editor.actionMenu.measurementControl.continuousDimension\":[\"Continuous dimension\"],\"editor.actionMenu.measurementControl.coordinateDimensions\":[\"Coordinate dimensions\"],\"editor.actionMenu.measurementControl.diameterDimension\":[\"Diameter dimension\"],\"editor.actionMenu.measurementControl.distance\":[\"Distance\"],\"editor.actionMenu.measurementControl.floorPlan\":[\"Floor plan\"],\"editor.actionMenu.measurementControl.linearDimension\":[\"Linear dimension\"],\"editor.actionMenu.measurementControl.measureSelectedlabel\":[\"Measure: \",[\"selectedLabel\"]],\"editor.actionMenu.measurementControl.measurementOptions\":[\"Measurement options\"],\"editor.actionMenu.measurementControl.measurementType\":[\"Measurement type\"],\"editor.actionMenu.measurementControl.perimeter\":[\"Perimeter\"],\"editor.actionMenu.measurementControl.radiusDimension\":[\"Radius dimension\"],\"editor.actionMenu.measurementControl.smart\":[\"Smart\"],\"editor.actionMenu.measurementControl.volume\":[\"Volume\"],\"editor.actionMenu.pointAskButton.pointAndAsk\":[\"Point and ask\"],\"editor.actionMenu.pointAskButton.pointAndAskC\":[\"Point and ask (C)\"],\"editor.actionMenu.pointAskButton.pointAndAskWorksInThe\":[\"Point and ask works in the 3D view\"],\"editor.actionMenu.structureTools.beam\":[\"Beam\"],\"editor.actionMenu.structureTools.ceiling\":[\"Ceiling\"],\"editor.actionMenu.structureTools.column\":[\"Column\"],\"editor.actionMenu.structureTools.door\":[\"Door\"],\"editor.actionMenu.structureTools.duct\":[\"Duct\"],\"editor.actionMenu.structureTools.ductFitting\":[\"Duct Fitting\"],\"editor.actionMenu.structureTools.dwvPipe\":[\"DWV Pipe\"],\"editor.actionMenu.structureTools.elevator\":[\"Elevator\"],\"editor.actionMenu.structureTools.fence\":[\"Fence\"],\"editor.actionMenu.structureTools.footing\":[\"Footing\"],\"editor.actionMenu.structureTools.foundation\":[\"Foundation\"],\"editor.actionMenu.structureTools.gableRoof\":[\"Gable Roof\"],\"editor.actionMenu.structureTools.hvacUnit\":[\"HVAC Unit\"],\"editor.actionMenu.structureTools.lineset\":[\"Lineset\"],\"editor.actionMenu.structureTools.liquidLine\":[\"Liquid Line\"],\"editor.actionMenu.structureTools.pipeFitting\":[\"Pipe Fitting\"],\"editor.actionMenu.structureTools.register\":[\"Register\"],\"editor.actionMenu.structureTools.shelf\":[\"Shelf\"],\"editor.actionMenu.structureTools.slab\":[\"Slab\"],\"editor.actionMenu.structureTools.spawnPoint\":[\"Spawn Point\"],\"editor.actionMenu.structureTools.stairs\":[\"Stairs\"],\"editor.actionMenu.structureTools.trap\":[\"Trap\"],\"editor.actionMenu.structureTools.wall\":[\"Wall\"],\"editor.actionMenu.structureTools.window\":[\"Window\"],\"editor.actionMenu.structureTools.zone\":[\"Zone\"],\"editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels\":[\"Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it.\"],\"editor.actionMenu.viewToggles.deleteGuideImage\":[\"Delete guide image\"],\"editor.actionMenu.viewToggles.deleteScan\":[\"Delete scan\"],\"editor.actionMenu.viewToggles.guideCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" guide image on this level\"],\"other\":[\"#\",\" guide images on this level\"]}]],\"editor.actionMenu.viewToggles.guideImageNumber\":[\"Guide image \",[\"number\"]],\"editor.actionMenu.viewToggles.guideImageSettings\":[\"Guide image settings\"],\"editor.actionMenu.viewToggles.guideImages\":[\"Guide images\"],\"editor.actionMenu.viewToggles.guides\":[\"Guides\"],\"editor.actionMenu.viewToggles.guidesHidden\":[\"Guides: Hidden\"],\"editor.actionMenu.viewToggles.guidesVisible\":[\"Guides: Visible\"],\"editor.actionMenu.viewToggles.hideGuideImages\":[\"Hide guide images\"],\"editor.actionMenu.viewToggles.hideReferenceFloor\":[\"Hide reference floor\"],\"editor.actionMenu.viewToggles.hideScans\":[\"Hide scans\"],\"editor.actionMenu.viewToggles.levelsBelow\":[[\"count\"],\" below\"],\"editor.actionMenu.viewToggles.noLevelAbove\":[\"No level above\"],\"editor.actionMenu.viewToggles.noLevelBelow\":[\"No level below\"],\"editor.actionMenu.viewToggles.noLowerFloorAvailable\":[\"No lower floor available.\"],\"editor.actionMenu.viewToggles.opacity\":[\"Opacity\"],\"editor.actionMenu.viewToggles.referenceFloor\":[\"Reference floor\"],\"editor.actionMenu.viewToggles.referenceFloorSelectedlevelname\":[\"Reference floor: \",[\"selectedLevelName\"]],\"editor.actionMenu.viewToggles.referenceFloorSettings\":[\"Reference floor settings\"],\"editor.actionMenu.viewToggles.referenceSettings\":[\"Reference settings\"],\"editor.actionMenu.viewToggles.references\":[\"References\"],\"editor.actionMenu.viewToggles.referencesEmpty\":[\"Upload GLB meshes as scan references or blueprint images as guide references.\"],\"editor.actionMenu.viewToggles.referencesHidden\":[\"References: Hidden\"],\"editor.actionMenu.viewToggles.referencesVisible\":[\"References: Visible\"],\"editor.actionMenu.viewToggles.riserDiagram\":[\"Riser diagram\"],\"editor.actionMenu.viewToggles.scanCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" scan on this level\"],\"other\":[\"#\",\" scans on this level\"]}]],\"editor.actionMenu.viewToggles.scanNumber\":[\"Scan \",[\"number\"]],\"editor.actionMenu.viewToggles.scanSettings\":[\"Scan settings\"],\"editor.actionMenu.viewToggles.scans\":[\"Scans\"],\"editor.actionMenu.viewToggles.scansHidden\":[\"Scans: Hidden\"],\"editor.actionMenu.viewToggles.scansVisible\":[\"Scans: Visible\"],\"editor.actionMenu.viewToggles.showGuideImages\":[\"Show guide images\"],\"editor.actionMenu.viewToggles.showReferenceFloor\":[\"Show reference floor\"],\"editor.actionMenu.viewToggles.showScans\":[\"Show scans\"],\"editor.actionMenu.viewToggles.showWallsBelowAndAbove\":[\"Show walls below and above\"],\"editor.actionMenu.viewToggles.uploadScanOrGuideImage\":[\"Upload scan or guide image\"],\"editor.actionMenu.viewToggles.wallsAbove\":[\"Walls above\"],\"editor.actionMenu.viewToggles.wallsBelow\":[\"Walls below\"],\"editor.actionMenu.viewToggles.wallsBelowAndAbove\":[\"Walls below and above\"],\"editor.actionMenu.viewToggles.wallsBelowAndAboveSettings\":[\"Walls below and above settings\"],\"editor.actionMenu.viewToggles.wallsBelowAndAboveShown\":[\"Walls below and above: shown\"],\"editor.backToHome\":[\"Back to home\"],\"editor.buildPanel.advanced\":[\"Advanced\"],\"editor.buildPanel.rooms.exteriorWall.caption\":[[\"lead\"],\" every wall takes the exterior type, wherever it lands. Esc or double-click ends the run.\"],\"editor.buildPanel.rooms.exteriorWall.label\":[\"Exterior wall\"],\"editor.buildPanel.rooms.exteriorWall.title\":[\"Exterior walls: every wall drawn takes the exterior type and cladding\"],\"editor.buildPanel.rooms.hint\":[\"walls · floor · ceiling\"],\"editor.buildPanel.rooms.idle\":[\"Pick a shape to start. Every room brings its own walls, floor and ceiling.\"],\"editor.buildPanel.rooms.interiorWall.caption\":[[\"lead\"],\" every wall takes the interior type, wherever it lands. Esc or double-click ends the run.\"],\"editor.buildPanel.rooms.interiorWall.label\":[\"Interior wall\"],\"editor.buildPanel.rooms.interiorWall.title\":[\"Interior walls: every wall drawn takes the interior type and paint\"],\"editor.buildPanel.rooms.invisibleWall.caption\":[[\"lead\"],\" every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run.\"],\"editor.buildPanel.rooms.invisibleWall.label\":[\"Invisible wall\"],\"editor.buildPanel.rooms.invisibleWall.title\":[\"Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" then the first one again to close. For L-shapes and angled rooms.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polygon\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Click each corner,\"],\"editor.buildPanel.rooms.polygon.title\":[\"Polygon room\"],\"editor.buildPanel.rooms.railing.caption\":[[\"lead\"],\" every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run.\"],\"editor.buildPanel.rooms.railing.label\":[\"Railing\"],\"editor.buildPanel.rooms.railing.title\":[\"Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel.\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" on the grid. Walls, floor and ceiling are created together.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectangle\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Click two corners\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Rectangle room\"],\"editor.buildPanel.rooms.title\":[\"Rooms\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\" every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room.\"],\"editor.buildPanel.rooms.walls.label\":[\"Walls\"],\"editor.buildPanel.rooms.walls.lead\":[\"Click each corner;\"],\"editor.buildPanel.rooms.walls.title\":[\"Walls\"],\"editor.buildPanel.wallTypeControls.default\":[\"Default\"],\"editor.buildPanel.wallTypeControls.deleteFromThisProject\":[\"Delete from this project\"],\"editor.buildPanel.wallTypeControls.duplicateAsANewType\":[\"Duplicate as a new type…\"],\"editor.buildPanel.wallTypeControls.editLayers\":[\"Edit layers…\"],\"editor.buildPanel.wallTypeControls.editOrDuplicateLabel\":[\"Edit or duplicate \",[\"label\"]],\"editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType\":[\"Edit or duplicate this wall type\"],\"editor.buildPanel.wallTypeControls.newWallType\":[\"New wall type…\"],\"editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne\":[\"Presets stay as cited. Duplicate one to change its layers.\"],\"editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost\":[\"A wall type holds at most \",[\"limit\",\"plural\",{\"one\":[\"#\",\" layer\"],\"other\":[\"#\",\" layers\"]}],\".\"],\"editor.buildPanel.wallTypeEditorDialog.add\":[\"Add\"],\"editor.buildPanel.wallTypeEditorDialog.airSpace\":[\"Air space\"],\"editor.buildPanel.wallTypeEditorDialog.brickVeneer\":[\"Brick veneer\"],\"editor.buildPanel.wallTypeEditorDialog.cancel\":[\"Cancel\"],\"editor.buildPanel.wallTypeEditorDialog.drywall\":[\"Drywall\"],\"editor.buildPanel.wallTypeEditorDialog.duplicateWallType\":[\"Duplicate wall type\"],\"editor.buildPanel.wallTypeEditorDialog.editWallType\":[\"Edit wall type\"],\"editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming\":[\"Every wall type has one framing core\"],\"editor.buildPanel.wallTypeEditorDialog.fiberCement\":[\"Fiber cement\"],\"editor.buildPanel.wallTypeEditorDialog.finish\":[\"Finish\"],\"editor.buildPanel.wallTypeEditorDialog.framingCore\":[\"Framing core\"],\"editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother\":[[\"fullReason\"],\" Remove one to add another.\"],\"editor.buildPanel.wallTypeEditorDialog.gypsumSheathing\":[\"Gypsum sheathing\"],\"editor.buildPanel.wallTypeEditorDialog.inside\":[\"Inside\"],\"editor.buildPanel.wallTypeEditorDialog.insulation\":[\"Insulation\"],\"editor.buildPanel.wallTypeEditorDialog.lapSiding\":[\"Lap siding\"],\"editor.buildPanel.wallTypeEditorDialog.layer\":[\"Layer\"],\"editor.buildPanel.wallTypeEditorDialog.layerRole\":[\"Layer role\"],\"editor.buildPanel.wallTypeEditorDialog.lining\":[\"Lining\"],\"editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside\":[\"List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type.\"],\"editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA\":[\"Lists under Exterior: it has a cladding side.\"],\"editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch\":[\"Lists under Interior: both faces match.\"],\"editor.buildPanel.wallTypeEditorDialog.material\":[\"Material\"],\"editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard\":[\"Mineral wool board\"],\"editor.buildPanel.wallTypeEditorDialog.moveIn\":[\"Move in\"],\"editor.buildPanel.wallTypeEditorDialog.moveOut\":[\"Move out\"],\"editor.buildPanel.wallTypeEditorDialog.name\":[\"Name\"],\"editor.buildPanel.wallTypeEditorDialog.newWallType\":[\"New wall type\"],\"editor.buildPanel.wallTypeEditorDialog.osb\":[\"OSB\"],\"editor.buildPanel.wallTypeEditorDialog.outside\":[\"Outside\"],\"editor.buildPanel.wallTypeEditorDialog.plaster\":[\"Plaster\"],\"editor.buildPanel.wallTypeEditorDialog.plywood\":[\"Plywood\"],\"editor.buildPanel.wallTypeEditorDialog.removeLayer\":[\"Remove layer\"],\"editor.buildPanel.wallTypeEditorDialog.rigidFoam\":[\"Rigid foam\"],\"editor.buildPanel.wallTypeEditorDialog.saveWallType\":[\"Save wall type\"],\"editor.buildPanel.wallTypeEditorDialog.sheathing\":[\"Sheathing\"],\"editor.buildPanel.wallTypeEditorDialog.stoneVeneer\":[\"Stone veneer\"],\"editor.buildPanel.wallTypeEditorDialog.stucco\":[\"Stucco\"],\"editor.buildPanel.wallTypeEditorDialog.thickness\":[\"Thickness\"],\"editor.buildPanel.wallTypeEditorDialog.totalThickness\":[\"Total \",[\"thickness\"]],\"editor.buildPanel.wallTypeEditorDialog.wallType\":[\"Wall type\"],\"editor.buildPanel.wallTypes.brick\":[\"Brick\"],\"editor.buildPanel.wallTypes.cladding\":[\"Cladding\"],\"editor.buildPanel.wallTypes.drawingExterior\":[\"Drawing: Exterior walls.\"],\"editor.buildPanel.wallTypes.drawingInterior\":[\"Drawing: Interior walls.\"],\"editor.buildPanel.wallTypes.drywallPaint\":[\"Drywall paint\"],\"editor.buildPanel.wallTypes.exterior\":[\"Exterior\"],\"editor.buildPanel.wallTypes.fiberCement\":[\"Fiber cement\"],\"editor.buildPanel.wallTypes.forcedExterior\":[[\"drawing\"],\" Every wall takes the exterior type until you pick a shape or leave the tool.\"],\"editor.buildPanel.wallTypes.forcedInterior\":[[\"drawing\"],\" Every wall takes the interior type until you pick a shape or leave the tool.\"],\"editor.buildPanel.wallTypes.hint\":[\"New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint.\"],\"editor.buildPanel.wallTypes.interior\":[\"Interior\"],\"editor.buildPanel.wallTypes.plainWall\":[\"Plain wall\"],\"editor.buildPanel.wallTypes.siding\":[\"Siding\"],\"editor.buildPanel.wallTypes.stone\":[\"Stone\"],\"editor.buildPanel.wallTypes.stucco\":[\"Stucco\"],\"editor.cameraControlsHint\":[\"Camera controls hint\"],\"editor.cameraHint.pan\":[\"Pan\"],\"editor.cameraHint.rotate\":[\"Rotate\"],\"editor.cameraHint.zoom\":[\"Zoom\"],\"editor.controls.finishSwatch.ariaLabel\":[[\"label\"],\": \",[\"finish\"]],\"editor.controls.finishSwatch.default\":[\"Default\"],\"editor.dismiss\":[\"Dismiss\"],\"editor.dismissCameraControlsHint\":[\"Dismiss camera controls hint\"],\"editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS\":[\"Click to edit the foundation’s outline\"],\"editor.expandSidebar\":[\"Expand sidebar\"],\"editor.floatingActionMenu.axis\":[\"Axis\"],\"editor.floatingActionMenu.axis2\":[\"⌥ axis\"],\"editor.floatingActionMenu.noEquipment\":[\"⚠ no equipment\"],\"editor.floatingActionMenu.rTRotate\":[\"R/T rotate\"],\"editor.floatingActionMenu.registerCount\":[[\"count\",\"plural\",{\"one\":[\"#\",\" register\"],\"other\":[\"#\",\" registers\"]}]],\"editor.floatingActionMenu.runCount\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" run\"],\"other\":[[\"number\"],\" runs\"]}]],\"editor.floatingActionMenu.system\":[\"System\"],\"editor.floatingLevelSelector.addAFloorUnderTheRoof\":[\"Add a floor under the roof\"],\"editor.floatingLevelSelector.addLevelAbove\":[\"Add level above\"],\"editor.floatingLevelSelector.addLevelBelow\":[\"Add level below\"],\"editor.floatingLevelSelector.cancel\":[\"Cancel\"],\"editor.floatingLevelSelector.delete\":[\"Delete\"],\"editor.floatingLevelSelector.deleteLevel\":[\"Delete level\"],\"editor.floatingLevelSelector.deleteLevelConfirm\":[\"Are you sure you want to delete \",[\"levelName\"],\"? All walls, floors, and objects on this level will be permanently removed.\"],\"editor.floatingLevelSelector.dragToReorder\":[\"Drag to reorder\"],\"editor.floatingLevelSelector.duplicateLevel\":[\"Duplicate level\"],\"editor.floatingLevelSelector.duplicateWithOptions\":[\"Duplicate with options...\"],\"editor.floatingLevelSelector.exitUnitFocus\":[\"Exit unit focus\"],\"editor.floatingLevelSelector.focusedUnitHasZonesHere\":[\"Focused unit has zones here\"],\"editor.floatingLevelSelector.foundation\":[\"Foundation\"],\"editor.floatingLevelSelector.foundationBadgeBasement\":[\"Basement\"],\"editor.floatingLevelSelector.foundationBadgeBasementTitle\":[\"Basement walls cut into the ground\"],\"editor.floatingLevelSelector.foundationBadgeOff\":[\"Off\"],\"editor.floatingLevelSelector.foundationBadgeOffTitle\":[\"No foundation drawn: click to set one up\"],\"editor.floatingLevelSelector.foundationBadgePiles\":[\"Piles\"],\"editor.floatingLevelSelector.foundationBadgePilesTitle\":[\"Grade beam on piles\"],\"editor.floatingLevelSelector.foundationBadgeRaised\":[\"Raised\"],\"editor.floatingLevelSelector.foundationBadgeRaisedTitle\":[\"Raised floor on a stem wall\"],\"editor.floatingLevelSelector.foundationBadgeSlab\":[\"Slab\"],\"editor.floatingLevelSelector.foundationBadgeSlabTitle\":[\"Slab on grade\"],\"editor.floatingLevelSelector.groundFloorOverGrade\":[\"The ground floor stands \",[\"distance\"],\" over grade. \",[\"action\"]],\"editor.floatingLevelSelector.groundFloorUnderGrade\":[\"The ground floor stands \",[\"distance\"],\" under grade. \",[\"action\"]],\"editor.floatingLevelSelector.groundLineTheLevelsUnderIt\":[\"Ground line: the levels under it are below grade\"],\"editor.floatingLevelSelector.groundOffset\":[\"Ground · \",[\"offset\"]],\"editor.floatingLevelSelector.insertLevelHere\":[\"Insert level here\"],\"editor.floatingLevelSelector.levelHeight\":[\"Level height\"],\"editor.floatingLevelSelector.pasteCopiedSelection\":[\"Paste copied selection\"],\"editor.floatingLevelSelector.reorderLevelname\":[\"Reorder \",[\"levelName\"]],\"editor.floatingLevelSelector.seatTheBuildingSoThisFloor\":[\"Seat the building so this floor stands at grade; the floors under it are below grade\"],\"editor.floatingLevelSelector.setAsGroundFloor\":[\"Set as ground floor\"],\"editor.floatingLevelSelector.showTheFoundation\":[\"Show the foundation\"],\"editor.floatingLevelSelector.theGroundLevelCannotBeDeleted\":[\"The ground level cannot be deleted\"],\"editor.floatingLevelSelector.thisFloorStandsAtGrade\":[\"This floor stands at grade\"],\"editor.floatingLevelSelector.unit\":[\"Unit\"],\"editor.floorFootprint.area\":[\"Floor area\"],\"editor.floorFootprint.room\":[[\"room\"],\" floor\"],\"editor.floorFootprint.shared\":[\"Shared floor\"],\"editor.helpers.buildingHelper.cancel\":[\"Cancel\"],\"editor.helpers.buildingHelper.placeBuilding\":[\"Place building\"],\"editor.helpers.buildingHelper.rotate\":[\"Rotate\"],\"editor.helpers.contextualHelperPanel.cancelPlacement\":[\"Cancel placement\"],\"editor.helpers.contextualHelperPanel.continuationClickOrPressCTo\":[\"Continuation — click or press C to cycle\"],\"editor.helpers.contextualHelperPanel.continuationLabel\":[\"Continuation: \",[\"label\"]],\"editor.helpers.contextualHelperPanel.dragToDrawFence\":[\"Drag to draw fence\"],\"editor.helpers.contextualHelperPanel.eraseScope\":[\"Erase: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift\":[\"Erase scope — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.eraseScopeLabel\":[\"Erase scope: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT\":[\"Fence type — click or press T to switch between straight, curved and freehand\"],\"editor.helpers.contextualHelperPanel.fewerShortcuts\":[\"Fewer shortcuts\"],\"editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick\":[\"Finish curve (or double-click)\"],\"editor.helpers.contextualHelperPanel.gridLength\":[\"Grid: \",[\"length\"]],\"editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl\":[\"Grid step — click or tap Ctrl to cycle\"],\"editor.helpers.contextualHelperPanel.gridStepLength\":[\"Grid step: \",[\"length\"]],\"editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase\":[\"Hover a painted surface to erase\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPaint\":[\"Hover a surface to paint\"],\"editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts\":[\"Hover a surface to pick its material\"],\"editor.helpers.contextualHelperPanel.moreShortcuts\":[\"More shortcuts\"],\"editor.helpers.contextualHelperPanel.paintScope\":[\"Paint: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift\":[\"Paint scope — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.paintScopeLabel\":[\"Paint scope: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.pickScope\":[\"Pick: \",[\"scope\"]],\"editor.helpers.contextualHelperPanel.placeGateOnAFence\":[\"Place gate on a fence\"],\"editor.helpers.contextualHelperPanel.placePassageOnAFence\":[\"Place passage on a fence\"],\"editor.helpers.contextualHelperPanel.selectAMaterialToPaint\":[\"Select a material to paint\"],\"editor.helpers.contextualHelperPanel.snappingAngles\":[\"Snapping: Angles\"],\"editor.helpers.contextualHelperPanel.snappingGrid\":[\"Snapping: Grid\"],\"editor.helpers.contextualHelperPanel.snappingLines\":[\"Snapping: Lines\"],\"editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift\":[\"Snapping mode — click or press Shift to cycle\"],\"editor.helpers.contextualHelperPanel.snappingOff\":[\"Snapping: Off\"],\"editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved\":[\"Straight continuation is unavailable for curved or freehand fences\"],\"editor.helpers.contextualHelperPanel.straightContinuous\":[\"Straight: Continuous\"],\"editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress\":[\"Straight fence continuation — click or press C to toggle\"],\"editor.helpers.contextualHelperPanel.straightSingle\":[\"Straight: Single\"],\"editor.helpers.helperManager.addPoint\":[\"Add point\"],\"editor.helpers.helperManager.addPointClickTheFirstTo\":[\"Add point · click the first to close\"],\"editor.helpers.helperManager.brushSize\":[\"Brush size\"],\"editor.helpers.helperManager.cancel\":[\"Cancel\"],\"editor.helpers.helperManager.cancelPicking\":[\"Cancel picking\"],\"editor.helpers.helperManager.cancelStroke\":[\"Cancel stroke\"],\"editor.helpers.helperManager.done\":[\"Done\"],\"editor.helpers.helperManager.drawTheMezzanine\":[\"Draw the mezzanine\"],\"editor.helpers.helperManager.drawTheOpening\":[\"Draw the opening\"],\"editor.helpers.helperManager.drawTheTerrace\":[\"Draw the terrace\"],\"editor.helpers.helperManager.exitSmartMeasure\":[\"Exit smart measure\"],\"editor.helpers.helperManager.finish\":[\"Finish\"],\"editor.helpers.helperManager.finishAtTheEdge\":[\"Finish at the edge\"],\"editor.helpers.helperManager.freeMove\":[\"Free move\"],\"editor.helpers.helperManager.freeMoveSlideDoorsAndWindows\":[\"Free move · slide doors and windows\"],\"editor.helpers.helperManager.holdToPickAMaterial\":[\"Hold to pick a material\"],\"editor.helpers.helperManager.inspectSurfaceDimensions\":[\"Inspect surface dimensions\"],\"editor.helpers.helperManager.paintWithThisMaterial\":[\"Paint with this material\"],\"editor.helpers.helperManager.pickTargetHeight\":[\"Pick target height\"],\"editor.helpers.helperManager.pinMeasurementLens\":[\"Pin measurement lens\"],\"editor.helpers.helperManager.place\":[\"Place\"],\"editor.helpers.helperManager.placeInsideTheRoom\":[\"Place inside the room\"],\"editor.helpers.helperManager.pressAnywhereDragToTheOpposite\":[\"Press anywhere, drag to the opposite corner\"],\"editor.helpers.helperManager.raiseOrLower\":[\"Raise or lower\"],\"editor.helpers.helperManager.removeAPaintedPartOrReset\":[\"Remove a painted part, or reset a surface\"],\"editor.helpers.helperManager.removeLastPoint\":[\"Remove last point\"],\"editor.helpers.helperManager.resize\":[\"Resize\"],\"editor.helpers.helperManager.rotate45\":[\"Rotate ±45°\"],\"editor.helpers.helperManager.rotateTheSelection45\":[\"Rotate the selection ±45°\"],\"editor.helpers.helperManager.setOneCornerThenTheOpposite\":[\"Set one corner, then the opposite\"],\"editor.helpers.itemHelper.cancel\":[\"Cancel\"],\"editor.helpers.itemHelper.forcePlace\":[\"Force place\"],\"editor.helpers.itemHelper.place\":[\"Place\"],\"editor.helpers.itemHelper.rotate\":[\"Rotate\"],\"editor.helpers.registeredToolHelper.guidedConstraintsBypassed\":[\"Guided constraints bypassed\"],\"editor.inspector.field.angle.g10zaw\":[\"Angle\"],\"editor.inspector.field.armLengthM.1jclqgw\":[\"Arm length m\"],\"editor.inspector.field.attachmentSide.6n9s5v\":[\"Attachment side\"],\"editor.inspector.field.autoHangers.10554ka\":[\"Auto hangers\"],\"editor.inspector.field.autoMiterCorners.zxr5lt\":[\"Auto miter corners\"],\"editor.inspector.field.autoSpan.1gas9r6\":[\"Auto span\"],\"editor.inspector.field.backRatio.1ughd99\":[\"Back ratio\"],\"editor.inspector.field.baseHeight.1hs4zdf\":[\"Base height\"],\"editor.inspector.field.baseStyle.24vty5\":[\"Base style\"],\"editor.inspector.field.beamHeight.1kq0oh1\":[\"Beam height\"],\"editor.inspector.field.beamWidth.1q1mzbe\":[\"Beam width\"],\"editor.inspector.field.bodyShape.arvkuw\":[\"Body shape\"],\"editor.inspector.field.bracketStyle.1vvoptm\":[\"Bracket style\"],\"editor.inspector.field.branchAngle.v56zme\":[\"Branch angle\"],\"editor.inspector.field.cabHeight.1yn34mk\":[\"Cab height\"],\"editor.inspector.field.canopyForm.1czzggp\":[\"Canopy form\"],\"editor.inspector.field.cap.s0thej\":[\"Cap\"],\"editor.inspector.field.capOverhang.kxxz9j\":[\"Cap overhang\"],\"editor.inspector.field.capShape.17mbxhu\":[\"Cap shape\"],\"editor.inspector.field.capThickness.u835q9\":[\"Cap thickness\"],\"editor.inspector.field.carcassHeight.1aks0zy\":[\"Carcass height\"],\"editor.inspector.field.cleanoutStyle.127zz6z\":[\"Cleanout style\"],\"editor.inspector.field.collarDiameter.1d5k28r\":[\"Collar diameter\"],\"editor.inspector.field.collarHeight.1dqwsrf\":[\"Collar height\"],\"editor.inspector.field.collarShape.1gacv11\":[\"Collar shape\"],\"editor.inspector.field.collarWidth.qvc9ik\":[\"Collar width\"],\"editor.inspector.field.columns.1sy73ks\":[\"Columns\"],\"editor.inspector.field.conicalFullCircle.1pgkpz5\":[\"Conical full circle\"],\"editor.inspector.field.connectionMode.1nty478\":[\"Connection mode\"],\"editor.inspector.field.connectionOffset.2m70s8\":[\"Connection offset\"],\"editor.inspector.field.cornerBevel.16iniho\":[\"Corner bevel\"],\"editor.inspector.field.coveringType.95yd3k\":[\"Covering type\"],\"editor.inspector.field.cricketHeight.pok31n\":[\"Cricket height\"],\"editor.inspector.field.cricketLength.i2aht8\":[\"Cricket length\"],\"editor.inspector.field.cricketSide.1xnwil7\":[\"Cricket side\"],\"editor.inspector.field.cricketStyle.10j0lat\":[\"Cricket style\"],\"editor.inspector.field.curb.5xi2ol\":[\"Curb\"],\"editor.inspector.field.curbHeight.1yn97sg\":[\"Curb height\"],\"editor.inspector.field.curveOffset.l4xhvr\":[\"Curve offset\"],\"editor.inspector.field.damperAngle.h5fxn7\":[\"Damper angle\"],\"editor.inspector.field.deckThickness.4jmqze\":[\"Deck thickness\"],\"editor.inspector.field.defaultLevelId.xmrjzp\":[\"Default level id\"],\"editor.inspector.field.depth.1lu11u2\":[\"Depth\"],\"editor.inspector.field.diameter.s3robe\":[\"Diameter\"],\"editor.inspector.field.diameter2.pm6cl4\":[\"Diameter2\"],\"editor.inspector.field.doorDurationMs.13nx909\":[\"Door duration ms\"],\"editor.inspector.field.doorHeight.92r9k4\":[\"Door height\"],\"editor.inspector.field.doorPanelStyle.2qog4q\":[\"Door panel style\"],\"editor.inspector.field.doorStyle.gerpos\":[\"Door style\"],\"editor.inspector.field.doorWidth.sy732l\":[\"Door width\"],\"editor.inspector.field.downspoutEnabled.1gn00nv\":[\"Downspout enabled\"],\"editor.inspector.field.downspoutPosition.fxcsop\":[\"Downspout position\"],\"editor.inspector.field.ductMaterial.i8eonq\":[\"Duct material\"],\"editor.inspector.field.dutchGabletRake.6paydp\":[\"Dutch gablet rake\"],\"editor.inspector.field.dutchHipHeightRatio.1etnwgg\":[\"Dutch hip height ratio\"],\"editor.inspector.field.dutchHipWidthRatio.1dopfvb\":[\"Dutch hip width ratio\"],\"editor.inspector.field.dutchTopRakeThickness.1w9khxt\":[\"Dutch top rake thickness\"],\"editor.inspector.field.dutchWaistLengthRatio.agtoxw\":[\"Dutch waist length ratio\"],\"editor.inspector.field.dwellMs.1lkk0o5\":[\"Dwell ms\"],\"editor.inspector.field.edgeInset.1obeimx\":[\"Edge inset\"],\"editor.inspector.field.elevation.4jbp44\":[\"Elevation\"],\"editor.inspector.field.endCapLeft.19jel4v\":[\"End cap left\"],\"editor.inspector.field.endCapRight.lhse9c\":[\"End cap right\"],\"editor.inspector.field.endCaps.4fsb5b\":[\"End caps\"],\"editor.inspector.field.endPost.1guwsk0\":[\"End post\"],\"editor.inspector.field.equipmentType.1mc2ywt\":[\"Equipment type\"],\"editor.inspector.field.fillToFloor.1oppnkf\":[\"Fill to floor\"],\"editor.inspector.field.finial.1ertf4s\":[\"Finial\"],\"editor.inspector.field.fittingType.18mz17s\":[\"Fitting type\"],\"editor.inspector.field.flashingHeight.3e8nn6\":[\"Flashing height\"],\"editor.inspector.field.flashingProjection.hhf0fk\":[\"Flashing projection\"],\"editor.inspector.field.flueCount.j3t2ci\":[\"Flue count\"],\"editor.inspector.field.flueDiameter.cjhubg\":[\"Flue diameter\"],\"editor.inspector.field.flueHeight.1dhc4y4\":[\"Flue height\"],\"editor.inspector.field.flueShape.1ealiy4\":[\"Flue shape\"],\"editor.inspector.field.flueSpacing.exhzco\":[\"Flue spacing\"],\"editor.inspector.field.footingStyle.e3btg6\":[\"Footing style\"],\"editor.inspector.field.frameDepth.1934a5n\":[\"Frame depth\"],\"editor.inspector.field.frameThickness.1agxzd8\":[\"Frame thickness\"],\"editor.inspector.field.framingStrategy.3jx9qe\":[\"Framing strategy\"],\"editor.inspector.field.fromLevelId.15ieejs\":[\"From level id\"],\"editor.inspector.field.gableOverhang.2z0obw\":[\"Gable overhang\"],\"editor.inspector.field.gambrelLowerHeightRatio.wqq7lg\":[\"Gambrel lower height ratio\"],\"editor.inspector.field.gambrelLowerWidthRatio.uvyb1v\":[\"Gambrel lower width ratio\"],\"editor.inspector.field.gapX.wjxa7\":[\"Gap x\"],\"editor.inspector.field.gapY.mkbl8\":[\"Gap y\"],\"editor.inspector.field.glassThickness.s97b0x\":[\"Glass thickness\"],\"editor.inspector.field.groundClearance.4os7f0\":[\"Ground clearance\"],\"editor.inspector.field.guardInfill.16heiig\":[\"Guard infill\"],\"editor.inspector.field.gutterEnabled.1oxiltf\":[\"Gutter enabled\"],\"editor.inspector.field.gutterProfile.1ngzqi3\":[\"Gutter profile\"],\"editor.inspector.field.gutterSize.jgavtz\":[\"Gutter size\"],\"editor.inspector.field.hangerMaxReach.1869epv\":[\"Hanger max reach\"],\"editor.inspector.field.hangerSpacing.i2b5d7\":[\"Hanger spacing\"],\"editor.inspector.field.hangerStyle.l7v7gb\":[\"Hanger style\"],\"editor.inspector.field.height.7puwki\":[\"Height\"],\"editor.inspector.field.height2.7b3jlc\":[\"Height2\"],\"editor.inspector.field.heightAboveRidge.1k99brk\":[\"Height above ridge\"],\"editor.inspector.field.highEdgeHeight.1s7v90t\":[\"High edge height\"],\"editor.inspector.field.highOverhang.67w55d\":[\"High overhang\"],\"editor.inspector.field.highSideMode.1rc1r5j\":[\"High side mode\"],\"editor.inspector.field.hoodOverhang.1q1hcin\":[\"Hood overhang\"],\"editor.inspector.field.infillPlacement.11dxzpo\":[\"Infill placement\"],\"editor.inspector.field.inletShape.ypayh2\":[\"Inlet shape\"],\"editor.inspector.field.innerRadius.1fkmg0j\":[\"Inner radius\"],\"editor.inspector.field.insulated.iwziey\":[\"Insulated\"],\"editor.inspector.field.insulationR.663ufx\":[\"Insulation r\"],\"editor.inspector.field.lanternHeight.oz7zpm\":[\"Lantern height\"],\"editor.inspector.field.lanternTopScale.hkmpp2\":[\"Lantern top scale\"],\"editor.inspector.field.ledgerDepth.7av8k9\":[\"Ledger depth\"],\"editor.inspector.field.ledgerHeight.zo2oif\":[\"Ledger height\"],\"editor.inspector.field.ledgerVerticalOffset.1athbgt\":[\"Ledger vertical offset\"],\"editor.inspector.field.leftEndCondition.1syqlaw\":[\"Left end condition\"],\"editor.inspector.field.leftOverhang.1ydl9ma\":[\"Left overhang\"],\"editor.inspector.field.length.1kpdcfp\":[\"Length\"],\"editor.inspector.field.liquidDiameter.w3pk7c\":[\"Liquid diameter\"],\"editor.inspector.field.louverCount.lhw2gr\":[\"Louver count\"],\"editor.inspector.field.lowBeamInset.1g7yw5l\":[\"Low beam inset\"],\"editor.inspector.field.lowEdgeHeight.47mae1\":[\"Low edge height\"],\"editor.inspector.field.lowOverhang.t4nz1h\":[\"Low overhang\"],\"editor.inspector.field.mansardSteepHeightRatio.1hbb8rq\":[\"Mansard steep height ratio\"],\"editor.inspector.field.mansardSteepWidthRatio.6xk6vd\":[\"Mansard steep width ratio\"],\"editor.inspector.field.matchHostRoofMaterial.s56zwj\":[\"Match host roof material\"],\"editor.inspector.field.matchHostRoofStructure.bojht3\":[\"Match host roof structure\"],\"editor.inspector.field.mount.h80tay\":[\"Mount\"],\"editor.inspector.field.mountingType.1xvin1e\":[\"Mounting type\"],\"editor.inspector.field.neckHeight.19ym8n1\":[\"Neck height\"],\"editor.inspector.field.opacity.1pmpfd2\":[\"Opacity\"],\"editor.inspector.field.openingAngle.brhi7c\":[\"Opening angle\"],\"editor.inspector.field.openingOffset.wpoqt6\":[\"Opening offset\"],\"editor.inspector.field.openingSide.18l2iwe\":[\"Opening side\"],\"editor.inspector.field.operationState.1lwrz07\":[\"Operation state\"],\"editor.inspector.field.outletShape.lgpqhx\":[\"Outlet shape\"],\"editor.inspector.field.overhang.b5ljpv\":[\"Overhang\"],\"editor.inspector.field.panelHeight.1kf31vk\":[\"Panel height\"],\"editor.inspector.field.panelWidth.946581\":[\"Panel width\"],\"editor.inspector.field.patternAlignment.jd1ht0\":[\"Pattern alignment\"],\"editor.inspector.field.patternCount.1r37pve\":[\"Pattern count\"],\"editor.inspector.field.patternDistribution.fmam51\":[\"Pattern distribution\"],\"editor.inspector.field.patternRemainder.i47p5y\":[\"Pattern remainder\"],\"editor.inspector.field.picketProfile.1xgzuyc\":[\"Picket profile\"],\"editor.inspector.field.picketRailCount.atgt6w\":[\"Picket rail count\"],\"editor.inspector.field.picketSpacing.p8d98w\":[\"Picket spacing\"],\"editor.inspector.field.picketTop.1tskplu\":[\"Picket top\"],\"editor.inspector.field.picketTopClearance.16eq1k0\":[\"Picket top clearance\"],\"editor.inspector.field.picketVariation.f7vl1c\":[\"Picket variation\"],\"editor.inspector.field.picketWidth.3fr3rh\":[\"Picket width\"],\"editor.inspector.field.pipeMaterial.1ggc2wy\":[\"Pipe material\"],\"editor.inspector.field.pitch.xe6ekx\":[\"Pitch\"],\"editor.inspector.field.position.1quewx6\":[\"Position\"],\"editor.inspector.field.postBracing.crq8nn\":[\"Post bracing\"],\"editor.inspector.field.postCap.1xcvxrv\":[\"Post cap\"],\"editor.inspector.field.postCount.22r56c\":[\"Post count\"],\"editor.inspector.field.postDepth.1ko6j1m\":[\"Post depth\"],\"editor.inspector.field.postInset.le36li\":[\"Post inset\"],\"editor.inspector.field.postLayoutMode.16jre62\":[\"Post layout mode\"],\"editor.inspector.field.postSize.1jodxek\":[\"Post size\"],\"editor.inspector.field.postSpacing.voppfi\":[\"Post spacing\"],\"editor.inspector.field.postThrough.1abgu4a\":[\"Post through\"],\"editor.inspector.field.postWidth.rk26fz\":[\"Post width\"],\"editor.inspector.field.profile.1kwlci6\":[\"Profile\"],\"editor.inspector.field.projection.jhtig0\":[\"Projection\"],\"editor.inspector.field.purlinHeight.eo9cjm\":[\"Purlin height\"],\"editor.inspector.field.purlinSpacing.4gbzb2\":[\"Purlin spacing\"],\"editor.inspector.field.purlinWidth.r6tu3j\":[\"Purlin width\"],\"editor.inspector.field.rafterEndInset.14c0wy5\":[\"Rafter end inset\"],\"editor.inspector.field.rafterHeight.1e1n99w\":[\"Rafter height\"],\"editor.inspector.field.rafterSpacing.19subc0\":[\"Rafter spacing\"],\"editor.inspector.field.rafterWidth.eksj4t\":[\"Rafter width\"],\"editor.inspector.field.railingHeight.6wf4bm\":[\"Railing height\"],\"editor.inspector.field.railingMode.ov7xpu\":[\"Railing mode\"],\"editor.inspector.field.railingStyle.1nyd2u\":[\"Railing style\"],\"editor.inspector.field.resizeLock.b2hbr0\":[\"Resize lock\"],\"editor.inspector.field.returnDiameter.166ozhe\":[\"Return diameter\"],\"editor.inspector.field.returnHeight.b9sq6y\":[\"Return height\"],\"editor.inspector.field.returnShape.1e3a27i\":[\"Return shape\"],\"editor.inspector.field.returnWidth.z9r3h3\":[\"Return width\"],\"editor.inspector.field.rightEndCondition.tjlw3l\":[\"Right end condition\"],\"editor.inspector.field.rightOverhang.yqbeft\":[\"Right overhang\"],\"editor.inspector.field.roofHeight.1uhy6qy\":[\"Roof height\"],\"editor.inspector.field.roofStyle.pawqfy\":[\"Roof style\"],\"editor.inspector.field.roofThickness.1xlr3rx\":[\"Roof thickness\"],\"editor.inspector.field.roofType.1m9kv5h\":[\"Roof type\"],\"editor.inspector.field.rotation.ly2rj\":[\"Rotation\"],\"editor.inspector.field.rows.1ba1neg\":[\"Rows\"],\"editor.inspector.field.scale.b6rpxt\":[\"Scale\"],\"editor.inspector.field.seamDetail.197za48\":[\"Seam detail\"],\"editor.inspector.field.segmentType.l667sy\":[\"Segment type\"],\"editor.inspector.field.shaftStyle.1i5ampa\":[\"Shaft style\"],\"editor.inspector.field.shaftWallThickness.bduslr\":[\"Shaft wall thickness\"],\"editor.inspector.field.shape.iq9t52\":[\"Shape\"],\"editor.inspector.field.shape2.fi0nx8\":[\"Shape2\"],\"editor.inspector.field.shedHighSide.bzzddg\":[\"Shed high side\"],\"editor.inspector.field.shingleThickness.1hn5wxr\":[\"Shingle thickness\"],\"editor.inspector.field.shoulderExtent.11j8uiv\":[\"Shoulder extent\"],\"editor.inspector.field.shoulderHeight.xyqn6g\":[\"Shoulder height\"],\"editor.inspector.field.shoulderStyle.1hkcv2w\":[\"Shoulder style\"],\"editor.inspector.field.showCenterColumn.1uggg45\":[\"Show center column\"],\"editor.inspector.field.showInfill.33rv7u\":[\"Show infill\"],\"editor.inspector.field.showStepSupports.1kg8h8m\":[\"Show step supports\"],\"editor.inspector.field.sideFlashing.cv744o\":[\"Side flashing\"],\"editor.inspector.field.size.1a4x3zw\":[\"Size\"],\"editor.inspector.field.skylightType.1a5vj3i\":[\"Skylight type\"],\"editor.inspector.field.slabOpeningMode.g4f730\":[\"Slab opening mode\"],\"editor.inspector.field.slatGap.slmk93\":[\"Slat gap\"],\"editor.inspector.field.slideDirection.1527q13\":[\"Slide direction\"],\"editor.inspector.field.span.1bmmuw1\":[\"Span\"],\"editor.inspector.field.speed.2w2xwg\":[\"Speed\"],\"editor.inspector.field.spinSpeed.1ozx746\":[\"Spin speed\"],\"editor.inspector.field.stairType.1u05e02\":[\"Stair type\"],\"editor.inspector.field.standoff.4wv2g0\":[\"Standoff\"],\"editor.inspector.field.standoffHeight.1qdnff\":[\"Standoff height\"],\"editor.inspector.field.startPost.1ood9kv\":[\"Start post\"],\"editor.inspector.field.stepCount.nzorrw\":[\"Step count\"],\"editor.inspector.field.strapSpacing.1cyt3dc\":[\"Strap spacing\"],\"editor.inspector.field.strapStyle.fh1oy4\":[\"Strap style\"],\"editor.inspector.field.style.nf3vcm\":[\"Style\"],\"editor.inspector.field.suctionDiameter.nutxtj\":[\"Suction diameter\"],\"editor.inspector.field.supplyDiameter.h5ldh5\":[\"Supply diameter\"],\"editor.inspector.field.supplyHeight.zy6ru1\":[\"Supply height\"],\"editor.inspector.field.supplyShape.k39wnb\":[\"Supply shape\"],\"editor.inspector.field.supplyWidth.g66axi\":[\"Supply width\"],\"editor.inspector.field.supportOffset.1w4cocr\":[\"Support offset\"],\"editor.inspector.field.sweepAngle.ucmmlw\":[\"Sweep angle\"],\"editor.inspector.field.system.13qbhrw\":[\"System\"],\"editor.inspector.field.terminal.1501knl\":[\"Terminal\"],\"editor.inspector.field.terminalType.1ne8lgb\":[\"Terminal type\"],\"editor.inspector.field.thickness.1l8hu45\":[\"Thickness\"],\"editor.inspector.field.tiltAngle.1312yw1\":[\"Tilt angle\"],\"editor.inspector.field.toLevelId.8i5qx7\":[\"To level id\"],\"editor.inspector.field.topLandingDepth.ki7gk2\":[\"Top landing depth\"],\"editor.inspector.field.topLandingMode.zk0h7e\":[\"Top landing mode\"],\"editor.inspector.field.topRailHeight.fscisv\":[\"Top rail height\"],\"editor.inspector.field.transitionMode.f3evgt\":[\"Transition mode\"],\"editor.inspector.field.transitionWidth.rtgz3m\":[\"Transition width\"],\"editor.inspector.field.vaneCount.55vtye\":[\"Vane count\"],\"editor.inspector.field.wallHeight.18vmh5c\":[\"Wall height\"],\"editor.inspector.field.wallShell.vqsyhv\":[\"Wall shell\"],\"editor.inspector.field.wallSkirtHeight.1uea2ad\":[\"Wall skirt height\"],\"editor.inspector.field.wallThickness.17wn4iv\":[\"Wall thickness\"],\"editor.inspector.field.width.gfxz9b\":[\"Width\"],\"editor.inspector.field.width2.1nqawvr\":[\"Width2\"],\"editor.inspector.field.withBack.1ay8nus\":[\"With back\"],\"editor.inspector.field.withBottom.1x1z1vo\":[\"With bottom\"],\"editor.inspector.field.withSides.uq066x\":[\"With sides\"],\"editor.inspector.group.advanced.qwfkor\":[\"Advanced\"],\"editor.inspector.group.air.15jotdj\":[\"Air\"],\"editor.inspector.group.appearance.dli9wf\":[\"Appearance\"],\"editor.inspector.group.body.ign4wl\":[\"Body\"],\"editor.inspector.group.cab.o4z1ox\":[\"Cab\"],\"editor.inspector.group.cabinet.rzvymr\":[\"Cabinet\"],\"editor.inspector.group.cap.s0thej\":[\"Cap\"],\"editor.inspector.group.collar.1am2iqo\":[\"Collar\"],\"editor.inspector.group.connection.2r1h4p\":[\"Connection\"],\"editor.inspector.group.connections.ai4w8u\":[\"Connections\"],\"editor.inspector.group.construction.j6p9kc\":[\"Construction\"],\"editor.inspector.group.cricket.6iwg8g\":[\"Cricket\"],\"editor.inspector.group.curb.5xi2ol\":[\"Curb\"],\"editor.inspector.group.curvePoints.ztdcnn\":[\"Curve points\"],\"editor.inspector.group.dimensions.pewa8g\":[\"Dimensions\"],\"editor.inspector.group.doors.18w2kpa\":[\"Doors\"],\"editor.inspector.group.dormer.1fjs5tq\":[\"Dormer\"],\"editor.inspector.group.dormerRoof.u9uy2g\":[\"Dormer roof\"],\"editor.inspector.group.drainage.7easy0\":[\"Drainage\"],\"editor.inspector.group.elevation.4jbp44\":[\"Elevation\"],\"editor.inspector.group.endCaps.4fsb5b\":[\"End caps\"],\"editor.inspector.group.equipment.1xtq0ev\":[\"Equipment\"],\"editor.inspector.group.face.1531fr0\":[\"Face\"],\"editor.inspector.group.fitting.171z1r4\":[\"Fitting\"],\"editor.inspector.group.flues.nq6pye\":[\"Flues\"],\"editor.inspector.group.footprint.g8q2pg\":[\"Footprint\"],\"editor.inspector.group.frame.q7q9fq\":[\"Frame\"],\"editor.inspector.group.geometry.a21os1\":[\"Geometry\"],\"editor.inspector.group.grid.d8gwxd\":[\"Grid\"],\"editor.inspector.group.hangers.agnfzn\":[\"Hangers\"],\"editor.inspector.group.hardware.1nygwm1\":[\"Hardware\"],\"editor.inspector.group.hungWall.1ftr1gz\":[\"Hung wall\"],\"editor.inspector.group.insulation.1kyo5e7\":[\"Insulation\"],\"editor.inspector.group.lantern.1h7o2xb\":[\"Lantern\"],\"editor.inspector.group.levels.1s6yaru\":[\"Levels\"],\"editor.inspector.group.line.166mv5z\":[\"Line\"],\"editor.inspector.group.lines.12bghr0\":[\"Lines\"],\"editor.inspector.group.motion.1be7sjr\":[\"Motion\"],\"editor.inspector.group.mounting.feutd2\":[\"Mounting\"],\"editor.inspector.group.opening.dfet3\":[\"Opening\"],\"editor.inspector.group.panelDimensions.b59qe6\":[\"Panel dimensions\"],\"editor.inspector.group.patternDistribution.fmam51\":[\"Pattern distribution\"],\"editor.inspector.group.placement.13lcvts\":[\"Placement\"],\"editor.inspector.group.position.1quewx6\":[\"Position\"],\"editor.inspector.group.profile.1kwlci6\":[\"Profile\"],\"editor.inspector.group.railing.19a5bnr\":[\"Railing\"],\"editor.inspector.group.return.1klrjrz\":[\"Return\"],\"editor.inspector.group.settings.ktdb8o\":[\"Settings\"],\"editor.inspector.group.shaft.mad9cv\":[\"Shaft\"],\"editor.inspector.group.shape.iq9t52\":[\"Shape\"],\"editor.inspector.group.shoulder.1nyfx25\":[\"Shoulder\"],\"editor.inspector.group.size.1a4x3zw\":[\"Size\"],\"editor.inspector.group.structure.gezdra\":[\"Structure\"],\"editor.inspector.group.style.nf3vcm\":[\"Style\"],\"editor.inspector.group.supply.1oy2zwu\":[\"Supply\"],\"editor.inspector.group.surface.wtamqk\":[\"Surface\"],\"editor.inspector.group.terminal.1501knl\":[\"Terminal\"],\"editor.inspector.group.topology.18t8dyc\":[\"Topology\"],\"editor.inspector.group.transform.1c55ayj\":[\"Transform\"],\"editor.inspector.group.trap.1qc7vhe\":[\"Trap\"],\"editor.inspector.group.type.1m2zofh\":[\"Type\"],\"editor.inspector.option.abs.132mz8b\":[\"Abs\"],\"editor.inspector.option.accessPanel.13sptb5\":[\"Access panel\"],\"editor.inspector.option.airHandler.1crnyg3\":[\"Air handler\"],\"editor.inspector.option.alternating.5jashi\":[\"Alternating\"],\"editor.inspector.option.arched.14188w0\":[\"Arched\"],\"editor.inspector.option.auto.540b06\":[\"Auto\"],\"editor.inspector.option.automatic.r122ro\":[\"Automatic\"],\"editor.inspector.option.back.1hzmxtu\":[\"Back\"],\"editor.inspector.option.balusters.1j3w6ng\":[\"Balusters\"],\"editor.inspector.option.band.160umau\":[\"Band\"],\"editor.inspector.option.basePlate.17fcg8k\":[\"Base plate\"],\"editor.inspector.option.boards.u14hig\":[\"Boards\"],\"editor.inspector.option.bookshelf.ls0zqk\":[\"Bookshelf\"],\"editor.inspector.option.both.mkyb42\":[\"Both\"],\"editor.inspector.option.bottom.9mpruy\":[\"Bottom\"],\"editor.inspector.option.box.1nbjcuq\":[\"Box\"],\"editor.inspector.option.break.17spnvc\":[\"Break\"],\"editor.inspector.option.butterfly.pvv0g4\":[\"Butterfly\"],\"editor.inspector.option.cable.fp50zq\":[\"Cable\"],\"editor.inspector.option.castIron.1q2z4qk\":[\"Cast iron\"],\"editor.inspector.option.ceiling.1t2zaqo\":[\"Ceiling\"],\"editor.inspector.option.center.kzdxno\":[\"Center\"],\"editor.inspector.option.centerOpening.bn1rvw\":[\"Center opening\"],\"editor.inspector.option.cleanout.lww8bm\":[\"Cleanout\"],\"editor.inspector.option.concretePad.m1zdij\":[\"Concrete pad\"],\"editor.inspector.option.condenser.a74nps\":[\"Condenser\"],\"editor.inspector.option.corbeled.bovs4v\":[\"Corbeled\"],\"editor.inspector.option.count.1qoi4r8\":[\"Count\"],\"editor.inspector.option.coupling.111aap2\":[\"Coupling\"],\"editor.inspector.option.coveringSpecific.1h9fo8w\":[\"Covering specific\"],\"editor.inspector.option.cross.1elubzv\":[\"Cross\"],\"editor.inspector.option.cubby.ivq4qc\":[\"Cubby\"],\"editor.inspector.option.curved.6noz1s\":[\"Curved\"],\"editor.inspector.option.cylinder.eol6vj\":[\"Cylinder\"],\"editor.inspector.option.damper.wgri80\":[\"Damper\"],\"editor.inspector.option.destination.gfzm1r\":[\"Destination\"],\"editor.inspector.option.diffuser.1igj0jh\":[\"Diffuser\"],\"editor.inspector.option.dogEar.xhtvjt\":[\"Dog ear\"],\"editor.inspector.option.dome.1c87e56\":[\"Dome\"],\"editor.inspector.option.double.13h5354\":[\"Double\"],\"editor.inspector.option.ductBoard.lc3o4t\":[\"Duct board\"],\"editor.inspector.option.dutch.1djx429\":[\"Dutch\"],\"editor.inspector.option.elbow.1flup74\":[\"Elbow\"],\"editor.inspector.option.end.1llf32i\":[\"End\"],\"editor.inspector.option.endCap.1p4v4na\":[\"End cap\"],\"editor.inspector.option.equalFit.11vw52c\":[\"Equal fit\"],\"editor.inspector.option.fixedCount.1o3dznu\":[\"Fixed count\"],\"editor.inspector.option.fixedSpacing.hl4dr4\":[\"Fixed spacing\"],\"editor.inspector.option.flat.ly0bei\":[\"Flat\"],\"editor.inspector.option.flex.ktpamq\":[\"Flex\"],\"editor.inspector.option.floating.1oadpln\":[\"Floating\"],\"editor.inspector.option.floor.q9caf1\":[\"Floor\"],\"editor.inspector.option.flush.olwwu5\":[\"Flush\"],\"editor.inspector.option.front.uhugl4\":[\"Front\"],\"editor.inspector.option.furnace.w0mzpf\":[\"Furnace\"],\"editor.inspector.option.gable.1ekyx6\":[\"Gable\"],\"editor.inspector.option.gambrel.1yo90d9\":[\"Gambrel\"],\"editor.inspector.option.generic.1074oey\":[\"Generic\"],\"editor.inspector.option.glass.163tc5n\":[\"Glass\"],\"editor.inspector.option.glassFrame.jp9s3m\":[\"Glass frame\"],\"editor.inspector.option.globe.1avkqoy\":[\"Globe\"],\"editor.inspector.option.grounded.jwoq73\":[\"Grounded\"],\"editor.inspector.option.guard.1m575se\":[\"Guard\"],\"editor.inspector.option.halfRound.m5hob0\":[\"Half round\"],\"editor.inspector.option.hidden.gp48rd\":[\"Hidden\"],\"editor.inspector.option.hip.r0jnjy\":[\"Hip\"],\"editor.inspector.option.horizontal.tyapah\":[\"Horizontal\"],\"editor.inspector.option.include.1a27vnr\":[\"Include\"],\"editor.inspector.option.independentHighBeam.1tn0zvy\":[\"Independent high beam\"],\"editor.inspector.option.industrial.8sd9u\":[\"Industrial\"],\"editor.inspector.option.inline.1uh05lg\":[\"Inline\"],\"editor.inspector.option.integrated.1xd9lv0\":[\"Integrated\"],\"editor.inspector.option.joined.1x2o8ds\":[\"Joined\"],\"editor.inspector.option.kStyle.m8kfwz\":[\"K style\"],\"editor.inspector.option.kickout.vt8s6p\":[\"Kickout\"],\"editor.inspector.option.knee.186hcui\":[\"Knee\"],\"editor.inspector.option.landing.tyqarg\":[\"Landing\"],\"editor.inspector.option.lantern.1h7o2xb\":[\"Lantern\"],\"editor.inspector.option.leave.1xf0sbk\":[\"Leave\"],\"editor.inspector.option.left.14n06mo\":[\"Left\"],\"editor.inspector.option.level.i4lhtp\":[\"Level\"],\"editor.inspector.option.lowProfile.1h9lgzg\":[\"Low profile\"],\"editor.inspector.option.mansard.dwzck9\":[\"Mansard\"],\"editor.inspector.option.manual.1wu7r43\":[\"Manual\"],\"editor.inspector.option.maximumSpacing.n1fzl4\":[\"Maximum spacing\"],\"editor.inspector.option.metal.1az9fgc\":[\"Metal\"],\"editor.inspector.option.metalPanel.1xdz476\":[\"Metal panel\"],\"editor.inspector.option.minimal.fwmiwg\":[\"Minimal\"],\"editor.inspector.option.mono.oszvd6\":[\"Mono\"],\"editor.inspector.option.none.deku7v\":[\"None\"],\"editor.inspector.option.omit.okcs7w\":[\"Omit\"],\"editor.inspector.option.open.n6hn1l\":[\"Open\"],\"editor.inspector.option.openRack.mvt1x0\":[\"Open rack\"],\"editor.inspector.option.opening.dfet3\":[\"Opening\"],\"editor.inspector.option.oval.1jiqzxx\":[\"Oval\"],\"editor.inspector.option.picket.zyeoy1\":[\"Picket\"],\"editor.inspector.option.pointed.effedk\":[\"Pointed\"],\"editor.inspector.option.postAndRail.lajk2u\":[\"Post and rail\"],\"editor.inspector.option.preserveHighEdge.y6ywf0\":[\"Preserve high edge\"],\"editor.inspector.option.preserveLowEdge.196m5pk\":[\"Preserve low edge\"],\"editor.inspector.option.preservePitch.1uq973r\":[\"Preserve pitch\"],\"editor.inspector.option.privacy.1th4hrb\":[\"Privacy\"],\"editor.inspector.option.purlins.od124c\":[\"Purlins\"],\"editor.inspector.option.pvc.1oc4u2w\":[\"Pvc\"],\"editor.inspector.option.pyramid.17ucsqv\":[\"Pyramid\"],\"editor.inspector.option.rafters.16tzbby\":[\"Rafters\"],\"editor.inspector.option.rail.39zqc9\":[\"Rail\"],\"editor.inspector.option.raised.4kyx9h\":[\"Raised\"],\"editor.inspector.option.rect.tpfvaf\":[\"Rect\"],\"editor.inspector.option.reducer.18byt67\":[\"Reducer\"],\"editor.inspector.option.return.1klrjrz\":[\"Return\"],\"editor.inspector.option.returnGrille.r28ncm\":[\"Return grille\"],\"editor.inspector.option.right.8humpx\":[\"Right\"],\"editor.inspector.option.round.1vx0wyj\":[\"Round\"],\"editor.inspector.option.rounded.199nwpm\":[\"Rounded\"],\"editor.inspector.option.sanitaryTee.oro3d4\":[\"Sanitary tee\"],\"editor.inspector.option.scalloped.1eltba0\":[\"Scalloped\"],\"editor.inspector.option.scoop.qy5zdv\":[\"Scoop\"],\"editor.inspector.option.segmentedPanel.5r3hwl\":[\"Segmented panel\"],\"editor.inspector.option.shed.6t994v\":[\"Shed\"],\"editor.inspector.option.sheetMetal.l164lv\":[\"Sheet metal\"],\"editor.inspector.option.shingle.pfmynh\":[\"Shingle\"],\"editor.inspector.option.shingled.1nm9inf\":[\"Shingled\"],\"editor.inspector.option.simple.12mfu73\":[\"Simple\"],\"editor.inspector.option.single.1uzxurt\":[\"Single\"],\"editor.inspector.option.singleLeft.1atdh02\":[\"Single left\"],\"editor.inspector.option.singleRight.1axbu67\":[\"Single right\"],\"editor.inspector.option.slantBox.1ta99vy\":[\"Slant box\"],\"editor.inspector.option.slat.1p9njbz\":[\"Slat\"],\"editor.inspector.option.sliding.cd0jox\":[\"Sliding\"],\"editor.inspector.option.slope.1bn2qw\":[\"Slope\"],\"editor.inspector.option.sloped.lo4sh0\":[\"Sloped\"],\"editor.inspector.option.solid.o4uy6s\":[\"Solid\"],\"editor.inspector.option.solidPanel.2yaw62\":[\"Solid panel\"],\"editor.inspector.option.spiral.91hcl4\":[\"Spiral\"],\"editor.inspector.option.splash.1uke4cm\":[\"Splash\"],\"editor.inspector.option.spread.1g1zysu\":[\"Spread\"],\"editor.inspector.option.square.1xl1lk6\":[\"Square\"],\"editor.inspector.option.stair.1xf86e\":[\"Stair\"],\"editor.inspector.option.standard.vuco\":[\"Standard\"],\"editor.inspector.option.start.30xvgf\":[\"Start\"],\"editor.inspector.option.step.jtn9kf\":[\"Step\"],\"editor.inspector.option.stepped.naxzh0\":[\"Stepped\"],\"editor.inspector.option.straight.lxqbfr\":[\"Straight\"],\"editor.inspector.option.strap.14q0ps9\":[\"Strap\"],\"editor.inspector.option.supply.1oy2zwu\":[\"Supply\"],\"editor.inspector.option.supplyRegister.1lzdbbn\":[\"Supply register\"],\"editor.inspector.option.tapered.3y5blo\":[\"Tapered\"],\"editor.inspector.option.targetSpacing.14uibc9\":[\"Target spacing\"],\"editor.inspector.option.tee.7d6tjp\":[\"Tee\"],\"editor.inspector.option.tilted.6n0u5j\":[\"Tilted\"],\"editor.inspector.option.top.2nypcs\":[\"Top\"],\"editor.inspector.option.transition.1rziz9e\":[\"Transition\"],\"editor.inspector.option.vent.xda5j2\":[\"Vent\"],\"editor.inspector.option.walkOn.1n5sg9l\":[\"Walk on\"],\"editor.inspector.option.wall.blj585\":[\"Wall\"],\"editor.inspector.option.wallAbutment.lza941\":[\"Wall abutment\"],\"editor.inspector.option.wallLedger.5m8ana\":[\"Wall ledger\"],\"editor.inspector.option.wallShelf.yiozfl\":[\"Wall shelf\"],\"editor.inspector.option.waste.3xrp79\":[\"Waste\"],\"editor.inspector.option.wye.14vupc8\":[\"Wye\"],\"editor.layers.collapse\":[\"Collapse\"],\"editor.layers.defaultName.ceiling\":[\"Ceiling (\",[\"area\"],\")\"],\"editor.layers.defaultName.collection\":[\"Collection\"],\"editor.layers.defaultName.flight\":[\"Flight (\",[\"width\"],\"×\",[\"length\"],\"m)\"],\"editor.layers.defaultName.landing\":[\"Landing (\",[\"width\"],\"×\",[\"length\"],\"m)\"],\"editor.layers.defaultName.node\":[\"Node\"],\"editor.layers.defaultName.roof\":[\"Roof (\",[\"count\",\"plural\",{\"one\":[\"#\",\" segment\"],\"other\":[\"#\",\" segments\"]}],\")\"],\"editor.layers.defaultName.roofSegment\":[[\"roofType\",\"select\",{\"hip\":[\"Hip\"],\"gable\":[\"Gable\"],\"shed\":[\"Shed\"],\"gambrel\":[\"Gambrel\"],\"dutch\":[\"Dutch\"],\"mansard\":[\"Mansard\"],\"flat\":[\"Flat\"],\"conical\":[\"Conical\"],\"barrel\":[\"Barrel\"],\"other\":[\"Roof\"]}],\" (\",[\"width\"],\"×\",[\"depth\"],\"m)\"],\"editor.layers.defaultName.room\":[\"Room (\",[\"area\"],\")\"],\"editor.layers.defaultName.slab\":[\"Slab (\",[\"area\"],\")\"],\"editor.layers.defaultName.staircase\":[\"Staircase (\",[\"count\",\"plural\",{\"one\":[\"#\",\" segment\"],\"other\":[\"#\",\" segments\"]}],\")\"],\"editor.layers.defaultName.zone\":[\"Zone (\",[\"area\"],\")\"],\"editor.layers.expand\":[\"Expand\"],\"editor.layers.levelOptions\":[[\"levelName\"],\" options\"],\"editor.layers.rename\":[\"Rename\"],\"editor.layers.spaceColour\":[[\"role\",\"select\",{\"room\":[\"Room colour\"],\"other\":[\"Zone colour\"]}]],\"editor.layers.unitColour\":[\"Unit colour\"],\"editor.levelDuplicateDialog.cancel\":[\"Cancel\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromLevel\":[\"Choose what to copy from \",[\"levelName\"],\".\"],\"editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel\":[\"Choose what to copy from this level.\"],\"editor.levelDuplicateDialog.duplicate\":[\"Duplicate\"],\"editor.levelDuplicateDialog.duplicateLevel\":[\"Duplicate Level\"],\"editor.levelDuplicateDialog.everything\":[\"Everything\"],\"editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout\":[\"Structure, finishes, and placed items, without guide references.\"],\"editor.levelDuplicateDialog.structureFurniture\":[\"Structure + furniture\"],\"editor.levelDuplicateDialog.structureMaterials\":[\"Structure + materials\"],\"editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences\":[\"Structure, materials, furniture, and references.\"],\"editor.levelDuplicateDialog.structureOnly\":[\"Structure only\"],\"editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd\":[\"Structure with the current material and finish assignments.\"],\"editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd\":[\"Walls, slabs, roofs, stairs, windows, and doors without finishes.\"],\"editor.levelWallCopyPrompt.copy\":[\"Copy\"],\"editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel\":[\"Copy exterior walls from the level \",[\"direction\"],\"?\"],\"editor.levelWallCopyPrompt.noThanks\":[\"No thanks\"],\"editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace\":[[\"sourceName\"],\"’s outside walls, in place, so this level lines up with it.\"],\"editor.lib.buildingFoundation.basementExcavation\":[\"Cut \",[\"depth\"],\" into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.\"],\"editor.lib.buildingFoundation.basementTopOverTheGround\":[\"The basement wall’s top stands \",[\"distance\"],\" over the high side of the ground; the foundation is set for \",[\"asked\"],\" over it.\"],\"editor.lib.buildingFoundation.basementTopUnderTheGround\":[\"The basement wall’s top stands \",[\"distance\"],\" under the high side of the ground; the foundation is set for \",[\"asked\"],\" over it.\"],\"editor.lib.buildingFoundation.crawlSpaceDugOut\":[\"Crawl space: dug out up to \",[\"dug\"],\" for \",[\"clearance\"],\" under the joists (IRC R317.1).\"],\"editor.lib.buildingFoundation.crippleWalls\":[\"Past \",[\"stem\"],\" of stem out of the ground the stem drops to \",[\"least\"],\" over grade: \",[\"cripples\",\"plural\",{\"one\":[\"#\",\" cripple wall\"],\"other\":[\"#\",\" cripple walls\"]}],\" over it, up to \",[\"tallest\"],\" tall.\"],\"editor.lib.buildingFoundation.daylightWalls\":[\"The ground falls \",[\"fall\"],\" across the house: the downhill walls stand up to \",[\"exposed\"],\" out of it.\"],\"editor.lib.buildingFoundation.deepFill\":[\"That is \",[\"fill\"],\" of fill. A raised floor on a stem wall suits a floor this high.\"],\"editor.lib.buildingFoundation.deepFillOnASlope\":[\"That is \",[\"fill\"],\" of fill on the low side. A raised floor on a stem wall suits this slope.\"],\"editor.lib.buildingFoundation.floorOverTheGround\":[\"The floor stands \",[\"distance\"],\" over the high side of the ground; the foundation is set for \",[\"asked\"],\" over it.\"],\"editor.lib.buildingFoundation.floorUnderTheGround\":[\"The floor stands \",[\"distance\"],\" under the high side of the ground; the foundation is set for \",[\"asked\"],\" over it.\"],\"editor.lib.buildingFoundation.footingSteps\":[\"The ground falls \",[\"fall\"],\" across the house: the footing steps \",[\"steps\",\"plural\",{\"one\":[\"#\",\" time\"],\"other\":[\"#\",\" times\"]}],\" along its steepest side, \",[\"riser\"],\" a step.\"],\"editor.lib.buildingFoundation.footingStepsUpTo\":[\"The ground falls \",[\"fall\"],\" across the house: the footing steps \",[\"steps\",\"plural\",{\"one\":[\"#\",\" time\"],\"other\":[\"#\",\" times\"]}],\" along its steepest side, up to \",[\"riser\"],\" a step.\"],\"editor.lib.buildingFoundation.footingTooNarrow\":[\"The footing is \",[\"width\"],\" wide: \",[\"storeys\",\"plural\",{\"one\":[\"one storey asks\"],\"other\":[\"#\",\" storeys ask\"]}],\" \",[\"asked\"],\" on 1500 psf soil (IRC Table R403.1(1)).\"],\"editor.lib.buildingFoundation.footingTooShallow\":[\"The footing’s bottom is \",[\"depth\"],\" under grade: keep it \",[\"least\"],\" at least, or the frost line (IRC R403.1.4).\"],\"editor.lib.buildingFoundation.gradingCut\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: cut \",[\"cut\"],\".\"],\"editor.lib.buildingFoundation.gradingCutOnASlope\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: cut \",[\"cut\"],\" on the high side.\"],\"editor.lib.buildingFoundation.gradingFilled\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: filled \",[\"fill\"],\".\"],\"editor.lib.buildingFoundation.gradingFilledAndCut\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: filled \",[\"fill\"],\", cut \",[\"cut\"],\".\"],\"editor.lib.buildingFoundation.gradingFilledAndCutOnASlope\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: filled up to \",[\"fill\"],\" on the low side, cut \",[\"cut\"],\" on the high side.\"],\"editor.lib.buildingFoundation.gradingFilledOnASlope\":[\"The ground is held \",[\"least\"],\" under the slab’s top on every side: filled up to \",[\"fill\"],\" on the low side.\"],\"editor.lib.buildingFoundation.groundCutRoundTheStem\":[\"The ground is cut \",[\"cut\"],\" on the high side to hold the stem wall’s top \",[\"least\"],\" over it (IRC R317.1). Raising the floor cuts less.\"],\"editor.lib.buildingFoundation.raiseItOnAStemWall\":[\"Raise it on a stem wall\"],\"editor.lib.buildingFoundation.raiseTheFloor\":[\"Raise the floor\"],\"editor.lib.buildingFoundation.seatBasementWallTop\":[\"Seat the basement wall’s top \",[\"asked\"],\" over the high side of the ground\"],\"editor.lib.buildingFoundation.seatFloorTop\":[\"Seat the floor’s top \",[\"asked\"],\" over the high side of the ground\"],\"editor.lib.buildingFoundation.seatGroundFloor\":[\"Seat the ground floor \",[\"asked\"],\" over grade\"],\"editor.lib.buildingFoundation.seatItAt\":[\"Seat it at \",[\"asked\"]],\"editor.lib.buildingFoundation.seatRaisedFloorTop\":[\"Seat the raised floor’s top \",[\"asked\"],\" over the high side of the ground\"],\"editor.lib.buildingFoundation.seatSlabTop\":[\"Seat the slab’s top \",[\"asked\"],\" over the high side of the ground\"],\"editor.lib.buildingFoundation.setIt\":[\"Set it \",[\"height\"]],\"editor.lib.buildingFoundation.setItDeep\":[\"Set it \",[\"depth\"],\" deep\"],\"editor.lib.buildingFoundation.sillTooNearTheGround\":[\"The sill and the floor framing sit \",[\"height\"],\" over the ground: keep them \",[\"least\"],\" off it at least (IRC R317.1).\"],\"editor.lib.buildingFoundation.slabEdgeSteps\":[\"The ground falls \",[\"fall\"],\" across the house: the slab edge steps \",[\"steps\",\"plural\",{\"one\":[\"#\",\" time\"],\"other\":[\"#\",\" times\"]}],\" along its steepest side, \",[\"riser\"],\" a step.\"],\"editor.lib.buildingFoundation.slabEdgeStepsUpTo\":[\"The ground falls \",[\"fall\"],\" across the house: the slab edge steps \",[\"steps\",\"plural\",{\"one\":[\"#\",\" time\"],\"other\":[\"#\",\" times\"]}],\" along its steepest side, up to \",[\"riser\"],\" a step.\"],\"editor.lib.buildingFoundation.slabEdgeTooShallow\":[\"The slab edge’s bottom is \",[\"depth\"],\" under grade: keep it \",[\"least\"],\" at least, or the frost line (IRC R403.1.4).\"],\"editor.lib.buildingFoundation.widenItTo\":[\"Widen it to \",[\"width\"]],\"editor.lib.contextualHelp.addOrRemoveObjectsFromThe\":[\"Add or remove objects from the selection\"],\"editor.lib.contextualHelp.adjustOneSideSCornerRadius\":[\"Adjust one side’s corner radius\"],\"editor.lib.contextualHelp.clearTheSelectionOrClickOutside\":[\"Clear the selection (or click outside)\"],\"editor.lib.contextualHelp.clickAHandleDotToShow\":[\"Click a handle dot to show move arrows\"],\"editor.lib.contextualHelp.clickOrDragTheSelectionTo\":[\"Click or drag the selection to move it as one\"],\"editor.lib.contextualHelp.clickTheHandleDotToShow\":[\"Click the handle dot to show move + rotate handles\"],\"editor.lib.contextualHelp.detachTheJointWhileDraggingAn\":[\"Detach the joint while dragging an arrow\"],\"editor.lib.contextualHelp.dragLeftOrRightToRotate\":[\"Drag left or right to rotate selected object\"],\"editor.lib.contextualHelp.dragSelectedMovableObject\":[\"Drag selected movable object\"],\"editor.lib.contextualHelp.groupSelectionSessionOnly\":[\"Group selection (session only)\"],\"editor.lib.contextualHelp.holdToRotateFreely\":[\"Hold to rotate freely\"],\"editor.lib.contextualHelp.rotate45\":[\"Rotate ±45°\"],\"editor.lib.contextualHelp.rotateTheSelection45\":[\"Rotate the selection ±45°\"],\"editor.lib.contextualHelp.rotatingFreely\":[\"Rotating freely (no angle step)\"],\"editor.lib.contextualHelp.switchTheRotationAxisYX\":[\"Switch the rotation axis (Y → X → Z)\"],\"editor.lib.contextualHelp.ungroupSessionSelection\":[\"Ungroup session selection\"],\"editor.lib.continuation.continuous\":[\"Continuous\"],\"editor.lib.continuation.continuousCanopy\":[\"Continuous canopy\"],\"editor.lib.continuation.continuousRun\":[\"Continuous run\"],\"editor.lib.continuation.curvedFence\":[\"Curved fence\"],\"editor.lib.continuation.freehandFence\":[\"Freehand fence\"],\"editor.lib.continuation.placeMultiple\":[\"Place multiple\"],\"editor.lib.continuation.placeOnce\":[\"Place once\"],\"editor.lib.continuation.polygonRoom\":[\"Polygon room\"],\"editor.lib.continuation.rectangleRoom\":[\"Rectangle room\"],\"editor.lib.continuation.singleCabinet\":[\"Single cabinet\"],\"editor.lib.continuation.singleCanopy\":[\"Single canopy\"],\"editor.lib.continuation.singleFence\":[\"Single fence\"],\"editor.lib.continuation.walls\":[\"Walls\"],\"editor.lib.hudTitle.cutOpening\":[\"Cut opening\"],\"editor.lib.hudTitle.divideRoom\":[\"Divide room\"],\"editor.lib.hudTitle.editKind\":[\"Edit \",[\"kind\"]],\"editor.lib.hudTitle.erase\":[\"Erase\"],\"editor.lib.hudTitle.levelTerrain\":[\"Level terrain\"],\"editor.lib.hudTitle.lowerTerrain\":[\"Lower terrain\"],\"editor.lib.hudTitle.mezzanine\":[\"Mezzanine\"],\"editor.lib.hudTitle.moveKind\":[\"Move \",[\"kind\"]],\"editor.lib.hudTitle.moveRoom\":[\"Move room\"],\"editor.lib.hudTitle.moveSelection\":[\"Move selection\"],\"editor.lib.hudTitle.paint\":[\"Paint\"],\"editor.lib.hudTitle.paintARectangle\":[\"Paint a rectangle\"],\"editor.lib.hudTitle.paintAShape\":[\"Paint a shape\"],\"editor.lib.hudTitle.pickMaterial\":[\"Pick material\"],\"editor.lib.hudTitle.raiseTerrain\":[\"Raise terrain\"],\"editor.lib.hudTitle.resize\":[\"Resize\"],\"editor.lib.hudTitle.rotate\":[\"Rotate\"],\"editor.lib.hudTitle.select\":[\"Select\"],\"editor.lib.hudTitle.smartMeasure\":[\"Smart measure\"],\"editor.lib.hudTitle.smoothTerrain\":[\"Smooth terrain\"],\"editor.lib.hudTitle.splitKind\":[\"Split \",[\"kind\"]],\"editor.lib.hudTitle.terrace\":[\"Terrace\"],\"editor.lib.toolShortcuts.wallsTooltip\":[[\"title\"],\" (\",[\"exterior\"],\" exterior, \",[\"interior\"],\" interior)\"],\"editor.lib.wallDrawVariant.polygonRoom\":[\"Polygon room\"],\"editor.lib.wallDrawVariant.rectangleRoom\":[\"Rectangle room\"],\"editor.lib.wallDrawVariant.walls\":[\"Walls\"],\"editor.mechanismAction\":[[\"action\",\"select\",{\"close\":[\"Close\"],\"open\":[\"Open\"],\"stop\":[\"Stop\"],\"other\":[\"Play\"]}]],\"editor.nodeActionMenu.curve\":[\"Curve\"],\"editor.nodeActionMenu.cutOut\":[\"Cut out\"],\"editor.nodeActionMenu.delete\":[\"Delete\"],\"editor.nodeActionMenu.duplicate\":[\"Duplicate\"],\"editor.nodeActionMenu.editMesh\":[\"Edit mesh\"],\"editor.nodeActionMenu.findInCatalog\":[\"Find in catalog\"],\"editor.nodeActionMenu.groupSelection\":[\"Group selection\"],\"editor.nodeActionMenu.move\":[\"Move\"],\"editor.nodeActionMenu.rotateLeft\":[\"Rotate left\"],\"editor.nodeActionMenu.rotateRight\":[\"Rotate right\"],\"editor.nodeActionMenu.ungroupSelection\":[\"Ungroup selection\"],\"editor.nodeDisplay.ceiling\":[\"Ceiling\"],\"editor.nodeDisplay.column\":[\"Column\"],\"editor.nodeDisplay.door\":[\"Door\"],\"editor.nodeDisplay.elevator\":[\"Elevator\"],\"editor.nodeDisplay.fence\":[\"Fence\"],\"editor.nodeDisplay.guide\":[\"Guide image\"],\"editor.nodeDisplay.item\":[\"Item\"],\"editor.nodeDisplay.roof\":[\"Roof\"],\"editor.nodeDisplay.roofSegment\":[\"Roof segment\"],\"editor.nodeDisplay.room\":[\"Room\"],\"editor.nodeDisplay.scan\":[\"3D Scan\"],\"editor.nodeDisplay.selection\":[\"Selection\"],\"editor.nodeDisplay.slab\":[\"Slab\"],\"editor.nodeDisplay.stair\":[\"Stair\"],\"editor.nodeDisplay.stairSegment\":[\"Stair segment\"],\"editor.nodeDisplay.wall\":[\"Wall\"],\"editor.nodeDisplay.window\":[\"Window\"],\"editor.nodeDisplay.zone\":[\"Zone\"],\"editor.panels.buildingFoundationSection.atTheHighSideOfThe\":[\"At the high side of the ground, to scale\"],\"editor.panels.buildingFoundationSection.atTheHighSideOfThe2\":[\"At the high side of the ground, to scale; the dashed line is the grade\"],\"editor.panels.buildingFoundationSection.auto\":[\"Auto\"],\"editor.panels.buildingFoundationSection.basementSlab\":[\"Basement slab\"],\"editor.panels.buildingFoundationSection.basementWall\":[\"Basement wall\"],\"editor.panels.buildingFoundationSection.basementWallHint\":[\"Poured concrete: 8 in for most basements (IRC Table R404.1.2(1)).\"],\"editor.panels.buildingFoundationSection.basementWallItsOwn\":[\"Basement wall (its own)\"],\"editor.panels.buildingFoundationSection.basementWallThickness\":[\"Basement wall \",[\"thickness\"]],\"editor.panels.buildingFoundationSection.basementWallTopOverGrade\":[\"Basement wall top over grade\"],\"editor.panels.buildingFoundationSection.basementWallTopOverGradeHint\":[\"Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement.\"],\"editor.panels.buildingFoundationSection.carpet\":[\"Carpet\"],\"editor.panels.buildingFoundationSection.ceiling\":[\"Ceiling\"],\"editor.panels.buildingFoundationSection.ceramicTile\":[\"Ceramic tile\"],\"editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA\":[\"Close the outside walls into a loop to draw the foundation under them.\"],\"editor.panels.buildingFoundationSection.crawlSpaceUnderJoists\":[\"Crawl space under joists\"],\"editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint\":[\"Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it.\"],\"editor.panels.buildingFoundationSection.custom\":[\"Custom\"],\"editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor\":[\"Delete the basement and it stands on its raised floor again.\"],\"editor.panels.buildingFoundationSection.deleteTheBasementSlab\":[\"Delete the basement and it stands on its slab again.\"],\"editor.panels.buildingFoundationSection.depthDeep\":[[\"depth\"],\" deep\"],\"editor.panels.buildingFoundationSection.depthDown\":[[\"depth\"],\" down\"],\"editor.panels.buildingFoundationSection.depthIJoist\":[[\"depth\"],\" I-joist\"],\"editor.panels.buildingFoundationSection.drawTheFoundation\":[\"Draw the foundation\"],\"editor.panels.buildingFoundationSection.drywall\":[\"Drywall\"],\"editor.panels.buildingFoundationSection.drywallCeiling\":[\"Drywall ceiling\"],\"editor.panels.buildingFoundationSection.edgeUnderGrade\":[\"Edge under grade\"],\"editor.panels.buildingFoundationSection.edgeUnderGradeHint\":[\"IRC R403.1.4: at least 12 in, or the frost line.\"],\"editor.panels.buildingFoundationSection.edgeWidth\":[\"Edge width\"],\"editor.panels.buildingFoundationSection.edgeWidthWide\":[\"Edge \",[\"width\"],\" wide\"],\"editor.panels.buildingFoundationSection.engineeredWood\":[\"Engineered wood\"],\"editor.panels.buildingFoundationSection.finish\":[\"Finish\"],\"editor.panels.buildingFoundationSection.finishFloorOverGrade\":[\"Finish floor over grade\"],\"editor.panels.buildingFoundationSection.finishFloorOverGradeHint\":[\"Measured over the highest grade under the outside walls. The foundation grows down the slope from there.\"],\"editor.panels.buildingFoundationSection.floorFraming\":[\"Floor framing\"],\"editor.panels.buildingFoundationSection.floorFramingAndSill\":[\"Floor framing and sill \",[\"depth\"]],\"editor.panels.buildingFoundationSection.floorFramingHint\":[\"Floor top down to the stem: flooring, subfloor, joists, sill.\"],\"editor.panels.buildingFoundationSection.floorFramingSillIncluded\":[\"Floor framing \",[\"depth\"],\", sill \",[\"sill\"],\" included\"],\"editor.panels.buildingFoundationSection.flooring\":[\"Flooring\"],\"editor.panels.buildingFoundationSection.footingThickness\":[\"Footing thickness\"],\"editor.panels.buildingFoundationSection.footingUnderGrade\":[\"Footing under grade\"],\"editor.panels.buildingFoundationSection.footingUnderGradeHint\":[\"IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line.\"],\"editor.panels.buildingFoundationSection.footingWidth\":[\"Footing width\"],\"editor.panels.buildingFoundationSection.footingWidthHint\":[\"Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys.\"],\"editor.panels.buildingFoundationSection.footingWidthThickness\":[\"Footing \",[\"width\"],\" × \",[\"thickness\"]],\"editor.panels.buildingFoundationSection.foundation\":[\"Foundation\"],\"editor.panels.buildingFoundationSection.gradeBeamDepth\":[\"Grade beam depth\"],\"editor.panels.buildingFoundationSection.gradeBeamOnPiles\":[\"Grade beam on piles\"],\"editor.panels.buildingFoundationSection.gradeBeamWidth\":[\"Grade beam width\"],\"editor.panels.buildingFoundationSection.gradeBeamWidthDepth\":[\"Grade beam \",[\"width\"],\" × \",[\"depth\"]],\"editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder\":[\"Half the edge’s depth under the slab, 6 to 12 in\"],\"editor.panels.buildingFoundationSection.hardwood\":[\"Hardwood\"],\"editor.panels.buildingFoundationSection.heightCrawl\":[[\"height\"],\" crawl\"],\"editor.panels.buildingFoundationSection.heightFinishFloor\":[[\"height\"],\" FF\"],\"editor.panels.buildingFoundationSection.heightTop\":[[\"height\"],\" top\"],\"editor.panels.buildingFoundationSection.iJoists\":[\"I-joists\"],\"editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill\":[\"Its layers make \",[\"depth\"],\" with the sill; the framing follows them once a layer is set.\"],\"editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe\":[\"Its walls are poured under the house’s outside walls.\"],\"editor.panels.buildingFoundationSection.itsWallsAreTheBasementS\":[\"Its walls are the basement’s own, on their footing.\"],\"editor.panels.buildingFoundationSection.joists\":[\"Joists\"],\"editor.panels.buildingFoundationSection.kind\":[\"Kind\"],\"editor.panels.buildingFoundationSection.labelAuto\":[[\"label\"],\" (auto)\"],\"editor.panels.buildingFoundationSection.laminate\":[\"Laminate\"],\"editor.panels.buildingFoundationSection.leastDepthUnderGrade\":[\"Least depth under grade\"],\"editor.panels.buildingFoundationSection.leastDepthUnderGradeHint\":[\"The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further.\"],\"editor.panels.buildingFoundationSection.leastStepRun\":[\"Least step run\"],\"editor.panels.buildingFoundationSection.leastStepRunHint\":[\"IRC R403.1.5: the top of a footing is level; it steps where the ground falls.\"],\"editor.panels.buildingFoundationSection.lumber\":[\"Lumber\"],\"editor.panels.buildingFoundationSection.nameTheMaterial\":[\"Name the material\"],\"editor.panels.buildingFoundationSection.off\":[\"Off\"],\"editor.panels.buildingFoundationSection.osb\":[\"OSB\"],\"editor.panels.buildingFoundationSection.osbSubfloor\":[\"OSB subfloor\"],\"editor.panels.buildingFoundationSection.panel\":[\"Panel\"],\"editor.panels.buildingFoundationSection.pileDiameter\":[\"Pile diameter\"],\"editor.panels.buildingFoundationSection.pileDiameterHint\":[\"Piles are sized by the engineer.\"],\"editor.panels.buildingFoundationSection.pileDiameterSpacingDeep\":[\"Pile \",[\"diameter\"],\" @ \",[\"spacing\"],\", \",[\"length\"],\" deep\"],\"editor.panels.buildingFoundationSection.pileLength\":[\"Pile length\"],\"editor.panels.buildingFoundationSection.pileSpacing\":[\"Pile spacing\"],\"editor.panels.buildingFoundationSection.plaster\":[\"Plaster\"],\"editor.panels.buildingFoundationSection.plasterCeiling\":[\"Plaster ceiling\"],\"editor.panels.buildingFoundationSection.plywood\":[\"Plywood\"],\"editor.panels.buildingFoundationSection.plywoodSubfloor\":[\"Plywood subfloor\"],\"editor.panels.buildingFoundationSection.preset\":[\"Preset\"],\"editor.panels.buildingFoundationSection.raisedFloor\":[\"Raised floor\"],\"editor.panels.buildingFoundationSection.sectionBasement\":[\"Foundation section: basement, its wall's top \",[\"top\"],\" over grade, its floor \",[\"floor\"],\" under it\"],\"editor.panels.buildingFoundationSection.sectionRaisedFloor\":[\"Foundation section: raised floor on a stem wall, finish floor \",[\"floor\"],\" over grade, crawl space \",[\"crawl\"],\" under the joists\"],\"editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles\":[\"Foundation section: raised floor on a stem wall on piles, finish floor \",[\"floor\"],\" over grade, crawl space \",[\"crawl\"],\" under the joists\"],\"editor.panels.buildingFoundationSection.sectionSlabOnGrade\":[\"Foundation section: slab on grade, finish floor \",[\"floor\"],\" over grade\"],\"editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles\":[\"Foundation section: slab on grade on piles, finish floor \",[\"floor\"],\" over grade\"],\"editor.panels.buildingFoundationSection.sill\":[\"Sill\"],\"editor.panels.buildingFoundationSection.size\":[\"Size\"],\"editor.panels.buildingFoundationSection.sizeJoists\":[[\"size\"],\" joists\"],\"editor.panels.buildingFoundationSection.slab\":[\"Slab\"],\"editor.panels.buildingFoundationSection.slabHint\":[\"IRC R506.1 asks 3½ in at least.\"],\"editor.panels.buildingFoundationSection.slabOnGrade\":[\"Slab on grade\"],\"editor.panels.buildingFoundationSection.slabThickness\":[\"Slab \",[\"thickness\"]],\"editor.panels.buildingFoundationSection.slabTopOverGrade\":[\"Slab top over grade\"],\"editor.panels.buildingFoundationSection.slabTopOverGradeHint\":[\"The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in).\"],\"editor.panels.buildingFoundationSection.spacing\":[\"Spacing\"],\"editor.panels.buildingFoundationSection.spacingOnCentre\":[[\"spacing\"],\" o.c.\"],\"editor.panels.buildingFoundationSection.stemTopOverGrade\":[\"Stem top over grade\"],\"editor.panels.buildingFoundationSection.stemTopOverGradeHint\":[\"IRC R317.1 keeps wood framing 8 in off the ground.\"],\"editor.panels.buildingFoundationSection.stemWall\":[\"Stem wall\"],\"editor.panels.buildingFoundationSection.stemWallAndFooting\":[\"Stem wall and footing\"],\"editor.panels.buildingFoundationSection.stemWallHint\":[\"Table R404.1.2: 6 in for one storey, 8 in for more.\"],\"editor.panels.buildingFoundationSection.stemWallThickness\":[\"Stem wall \",[\"thickness\"]],\"editor.panels.buildingFoundationSection.stepDownTheSlope\":[\"Step down the slope\"],\"editor.panels.buildingFoundationSection.stepHeight\":[\"Step height\"],\"editor.panels.buildingFoundationSection.subfloorSheathing\":[\"Subfloor sheathing\"],\"editor.panels.buildingFoundationSection.tallestStemBeforeStepping\":[\"Tallest stem before stepping\"],\"editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint\":[\"A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it.\"],\"editor.panels.buildingFoundationSection.taper\":[\"Taper\"],\"editor.panels.buildingFoundationSection.taperAt45\":[\"Taper \",[\"taper\"],\" at 45°\"],\"editor.panels.buildingFoundationSection.taperHint\":[\"The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider.\"],\"editor.panels.buildingFoundationSection.theHouseStandsOnABasement\":[\"The house stands on a basement: its walls are the foundation walls, so only their footing is drawn.\"],\"editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement\":[\"The house stands on its basement, cut into the ground: the ground round it is left as it lies.\"],\"editor.panels.buildingFoundationSection.thickness\":[\"Thickness\"],\"editor.panels.buildingFoundationSection.titleMaterial\":[[\"title\"],\" material\"],\"editor.panels.buildingFoundationSection.turnedDownEdge\":[\"Turned-down edge\"],\"editor.panels.buildingFoundationSection.vinylPlank\":[\"Vinyl plank\"],\"editor.panels.buildingFoundationSection.woodPlank\":[\"Wood plank\"],\"editor.panels.buildingFoundationSection.woodPlankCeiling\":[\"Wood plank ceiling\"],\"editor.panels.collections.collectionsPopover.cancel\":[\"Cancel\"],\"editor.panels.collections.collectionsPopover.collectionName\":[\"Collection name…\"],\"editor.panels.collections.collectionsPopover.collections\":[\"Collections\"],\"editor.panels.collections.collectionsPopover.delete\":[\"Delete\"],\"editor.panels.collections.collectionsPopover.deleteName\":[\"Delete \\\"\",[\"name\"],\"\\\"?\"],\"editor.panels.collections.collectionsPopover.new\":[\"New\"],\"editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo\":[\"No collections yet. Create one to group elements together.\"],\"editor.panels.collections.collectionsPopover.rename\":[\"Rename\"],\"editor.panels.deckBoardsFields.alongHouse\":[\"Along house\"],\"editor.panels.deckBoardsFields.boards\":[\"Boards\"],\"editor.panels.deckBoardsFields.boardsSummary\":[[\"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.\"],\"editor.panels.deckBoardsFields.deckBoards\":[\"Deck boards\"],\"editor.panels.deckBoardsFields.diagonal\":[\"Diagonal\"],\"editor.panels.deckBoardsFields.gap\":[\"Gap\"],\"editor.panels.deckBoardsFields.outFromHouse\":[\"Out from house\"],\"editor.panels.deckBoardsFields.run\":[\"Run\"],\"editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe\":[\"A thicker floor grows upward: the rooms below keep their height.\"],\"editor.panels.floorFoundationPanel.buildUpFollows\":[\"Its layers make \",[\"thickness\"],\"; the thickness follows them on the first change.\"],\"editor.panels.floorFoundationPanel.edgeFinish\":[\"Edge finish\"],\"editor.panels.floorFoundationPanel.floor\":[\"Floor\"],\"editor.panels.floorFoundationPanel.floorBuildUp\":[\"Floor build-up\"],\"editor.panels.floorFoundationPanel.floorPlate\":[\"Floor plate\"],\"editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn\":[\"Floor plate \",[\"thicknessLabel\"],\" thick, resting on the walls below\"],\"editor.panels.floorFoundationPanel.floorThicknessValue\":[\"Floor thickness \",[\"thickness\"]],\"editor.panels.floorFoundationPanel.floorTopAbove\":[\"Floor top +\",[\"height\"]],\"editor.panels.floorFoundationPanel.floorTopValue\":[\"Floor top \",[\"height\"]],\"editor.panels.floorFoundationPanel.foundationFinish\":[\"Foundation finish\"],\"editor.panels.floorFoundationPanel.foundationHeight\":[\"Foundation height\"],\"editor.panels.floorFoundationPanel.kicker\":[\"Floor plate\"],\"editor.panels.floorFoundationPanel.kickerLevel\":[\"Floor plate · \",[\"level\"]],\"editor.panels.floorFoundationPanel.kickerRestsOn\":[\"Floor plate · \",[\"level\"],\" · rests on \",[\"below\"]],\"editor.panels.floorFoundationPanel.lowerTogether\":[[\"count\",\"plural\",{\"2\":[\"Lower both\"],\"other\":[\"Lower all \",[\"number\"]]}]],\"editor.panels.floorFoundationPanel.notToScale\":[\"Not to scale\"],\"editor.panels.floorFoundationPanel.onTheGround\":[\"On the ground\"],\"editor.panels.floorFoundationPanel.raiseTogether\":[[\"count\",\"plural\",{\"2\":[\"Raise both\"],\"other\":[\"Raise all \",[\"number\"]]}]],\"editor.panels.floorFoundationPanel.raisedOnAFoundation\":[\"Raised on a foundation\"],\"editor.panels.floorFoundationPanel.section.floorPlate\":[\"Floor plate · \",[\"thickness\"]],\"editor.panels.floorFoundationPanel.section.floorTop\":[\"Floor top\"],\"editor.panels.floorFoundationPanel.section.foundation\":[\"Foundation\"],\"editor.panels.floorFoundationPanel.section.foundationKind\":[\"Foundation · \",[\"foundation\"]],\"editor.panels.floorFoundationPanel.section.ground\":[\"Ground\"],\"editor.panels.floorFoundationPanel.section.layer\":[[\"layer\"],\" · \",[\"size\"]],\"editor.panels.floorFoundationPanel.section.slab\":[\"Slab · \",[\"thickness\"]],\"editor.panels.floorFoundationPanel.section.wallsBelow\":[\"Walls below\"],\"editor.panels.floorFoundationPanel.sectionLayerSize\":[[\"layer\"],\" \",[\"size\"]],\"editor.panels.floorFoundationPanel.sectionLayers\":[\"Floor plate \",[\"thickness\"],\" thick: \",[\"layers\"],\"; resting on the walls below\"],\"editor.panels.floorFoundationPanel.sectionOnGround\":[\"Slab \",[\"thickness\"],\" thick on the ground, floor top \",[\"top\"],\" above the ground\"],\"editor.panels.floorFoundationPanel.sectionRaised\":[\"Slab \",[\"thickness\"],\" thick on a \",[\"foundation\"],\" foundation, floor top \",[\"top\"],\" above the ground\"],\"editor.panels.floorFoundationPanel.slab\":[\"Slab\"],\"editor.panels.floorFoundationPanel.slabThickness\":[\"Slab thickness\"],\"editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll\":[\"The floor's outside edge, all around the footprint.\"],\"editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround\":[\"The foundation stands on the ground; the slab sits on it.\"],\"editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation\":[\"The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it.\"],\"editor.panels.floorFoundationPanel.underTheFloor\":[\"Under the floor\"],\"editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt\":[\"A hatch in the mezzanine: it cuts only the mezzanine’s floor.\"],\"editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow\":[\"Also cut the ceiling below\"],\"editor.panels.floorOpeningPanel.alsoCutTheFloorAbove\":[\"Also cut the floor above\"],\"editor.panels.floorOpeningPanel.areaDragCorners\":[[\"area\"],\" · Drag its corners to change the shape.\"],\"editor.panels.floorOpeningPanel.areaOwned\":[[\"area\"],\" · Shape follows its owner.\"],\"editor.panels.floorOpeningPanel.areaStairResize\":[[\"area\"],\" · Drag its sides or corners to resize it; it moves with the stair.\"],\"editor.panels.floorOpeningPanel.delete\":[\"Delete\"],\"editor.panels.floorOpeningPanel.done\":[\"Done\"],\"editor.panels.floorOpeningPanel.kicker\":[\"Opening\"],\"editor.panels.floorOpeningPanel.kickerLevel\":[\"Opening · \",[\"level\"]],\"editor.panels.floorOpeningPanel.kickerRoom\":[\"Opening · in \",[\"room\"]],\"editor.panels.floorOpeningPanel.kickerRoomLevel\":[\"Opening · in \",[\"room\"],\" · \",[\"level\"]],\"editor.panels.floorOpeningPanel.oneVoidWith\":[\"On \",[\"level\"],\", as one void with this opening.\"],\"editor.panels.floorOpeningPanel.ownedByElevator\":[\"Owned by Elevator\"],\"editor.panels.floorOpeningPanel.ownedByStaircase\":[\"Owned by Staircase\"],\"editor.panels.floorOpeningPanel.room\":[\"Room\"],\"editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis\":[\"There is no floor above this storey.\"],\"editor.panels.floorOpeningPanel.thereIsNoStoreyBelow\":[\"There is no storey below.\"],\"editor.panels.floorOpeningPanel.title\":[[\"source\",\"select\",{\"stair\":[\"Stair opening\"],\"elevator\":[\"Elevator opening\"],\"ceiling\":[\"Ceiling opening\"],\"other\":[\"Floor opening\"]}]],\"editor.panels.foundationViewPanel.aRaisedFloorOnAStem\":[\"A raised floor on a stem wall of its own.\"],\"editor.panels.foundationViewPanel.aSlabOfItsOwn\":[\"A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round.\"],\"editor.panels.foundationViewPanel.clickItsCornersClickTheFirst\":[\"Click its corners; click the first again, or press Enter, to close it.\"],\"editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA\":[\"Close the outside walls into a loop first, or draw one\"],\"editor.panels.foundationViewPanel.deleteThisOutline\":[\"Delete this outline\"],\"editor.panels.foundationViewPanel.draw\":[\"Draw\"],\"editor.panels.foundationViewPanel.editItMyself\":[\"Edit it myself\"],\"editor.panels.foundationViewPanel.followTheWalls\":[\"Follow the walls\"],\"editor.panels.foundationViewPanel.footingsDrawn\":[[\"footings\",\"plural\",{\"one\":[\"#\",\" footing\"],\"other\":[\"#\",\" footings\"]}],\" drawn: click one in the plan to size or move it.\"],\"editor.panels.foundationViewPanel.foundation\":[\"Foundation\"],\"editor.panels.foundationViewPanel.houseFoundation\":[\"House foundation\"],\"editor.panels.foundationViewPanel.howFarItsTopSitsUnder\":[\"How far its top sits under the house’s floor: a porch slab steps down from the door\"],\"editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd\":[\"Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be.\"],\"editor.panels.foundationViewPanel.outline\":[\"Outline\"],\"editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors\":[\"Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts.\"],\"editor.panels.foundationViewPanel.raisedFloor\":[\"Raised floor\"],\"editor.panels.foundationViewPanel.reTraceFromWalls\":[\"Re-trace from walls\"],\"editor.panels.foundationViewPanel.setTheOutlineBackToThe\":[\"Set the outline back to the outside faces of the walls, keeping it your own\"],\"editor.panels.foundationViewPanel.slab\":[\"Slab\"],\"editor.panels.foundationViewPanel.theHouses\":[\"The house’s\"],\"editor.panels.foundationViewPanel.theHousesOwnFoundation\":[\"The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline.\"],\"editor.panels.foundationViewPanel.thisOutline\":[\"This outline\"],\"editor.panels.foundationViewPanel.topUnderTheFloor\":[\"Top under the floor\"],\"editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt\":[\"Traced from the outside walls: it moves when they do.\"],\"editor.panels.mergeRefusal.alreadyJoined\":[\"One of them already wraps another corner. Unwrap it first.\"],\"editor.panels.mergeRefusal.between\":[[\"kind\",\"select\",{\"item\":[\"There is an item between them.\"],\"door\":[\"There is a door between them.\"],\"window\":[\"There is a window between them.\"],\"shelf\":[\"There is a shelf between them.\"],\"other\":[\"There is something between them.\"]}]],\"editor.panels.mergeRefusal.cannotMerge\":[\"These windows cannot be merged.\"],\"editor.panels.mergeRefusal.cornerAngle\":[\"Their walls meet at too sharp or too flat an angle for a corner window.\"],\"editor.panels.mergeRefusal.heightExceedsWall\":[\"The window selected first is taller than the other wall. Select the other one first.\"],\"editor.panels.mergeRefusal.notAdjacent\":[\"They are too far apart. Windows merge with at most \",[\"reach\"],\" between them.\"],\"editor.panels.mergeRefusal.notAtCorner\":[\"One of them is too far from the corner. A window joins one within \",[\"reach\"],\" of it.\"],\"editor.panels.mergeRefusal.notOnWall\":[\"A roof or dormer window cannot join another window.\"],\"editor.panels.mergeRefusal.overlap\":[[\"kind\",\"select\",{\"item\":[\"Sliding them to the corner would overlap an item.\"],\"door\":[\"Sliding them to the corner would overlap a door.\"],\"window\":[\"Sliding them to the corner would overlap a window.\"],\"shelf\":[\"Sliding them to the corner would overlap a shelf.\"],\"other\":[\"Sliding them to the corner would overlap something.\"]}]],\"editor.panels.mergeRefusal.wallsNotMeeting\":[\"Their walls do not meet at a corner.\"],\"editor.panels.mixed\":[\"Mixed\"],\"editor.panels.mobilePanelSheet.close\":[\"Close\"],\"editor.panels.mobileSelectionBar.delete\":[\"Delete\"],\"editor.panels.mobileSelectionBar.duplicate\":[\"Duplicate\"],\"editor.panels.mobileSelectionBar.editProperties\":[\"Edit properties\"],\"editor.panels.mobileSelectionBar.editResolvedlabel\":[\"Edit \",[\"resolvedLabel\"]],\"editor.panels.mobileSelectionBar.move\":[\"Move\"],\"editor.panels.multiHeightMode.currentlyCurrentlabel\":[\"Currently \",[\"currentLabel\"]],\"editor.panels.multiHeightMode.customHeight\":[\"Custom height\"],\"editor.panels.multiHeightMode.followsLevel\":[\"Follows level\"],\"editor.panels.multiHeightMode.height\":[\"Height\"],\"editor.panels.multiHeightMode.top\":[\"Top\"],\"editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects\":[[\"label\"],\" (session only). Plain click reselects all members. Not saved with the project.\"],\"editor.panels.multiSelectionPanel.delete\":[\"Delete\"],\"editor.panels.multiSelectionPanel.duplicate\":[\"Duplicate\"],\"editor.panels.multiSelectionPanel.group\":[\"Group\"],\"editor.panels.multiSelectionPanel.groupCtrlCmdG\":[\"Group (Ctrl/Cmd+G)\"],\"editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects\":[[\"label\"],\" (session only). Plain click reselects all members. Not saved with the project.\"],\"editor.panels.multiSelectionPanel.lengthSelected\":[[\"length\"],\" selected\"],\"editor.panels.multiSelectionPanel.mergeWindows\":[\"Merge windows\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow\":[\"Merge windows into a corner window: both take the height, sill and type of the one you selected first\"],\"editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth\":[\"Merge windows into one across both: it keeps the type and style of the one you selected first\"],\"editor.panels.multiSelectionPanel.selectBuilding\":[\"Select building\"],\"editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove\":[\"Select the whole building to move all its levels\"],\"editor.panels.multiSelectionPanel.ungroup\":[\"Ungroup\"],\"editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG\":[\"Ungroup (Ctrl/Cmd+Shift+G)\"],\"editor.panels.multiSelectionPanel.unwrapTheCorner\":[\"Unwrap the corner\"],\"editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay\":[\"Unwrap the corner: both windows stay, each framed at the corner\"],\"editor.panels.panelManager.foundation\":[\"Foundation\"],\"editor.panels.panelManager.selection\":[\"Selection\"],\"editor.panels.panelWrapper.back\":[\"Back\"],\"editor.panels.panelWrapper.close\":[\"Close\"],\"editor.panels.panelWrapper.closeTitle\":[\"Close \",[\"title\"]],\"editor.panels.panelWrapper.collapsePanel\":[\"Collapse panel\"],\"editor.panels.panelWrapper.collections\":[\"Collections\"],\"editor.panels.panelWrapper.expandPanel\":[\"Expand panel\"],\"editor.panels.panelWrapper.manageCollections\":[\"Manage collections…\"],\"editor.panels.panelWrapper.openTitle\":[\"Open \",[\"title\"]],\"editor.panels.panelWrapper.reset\":[\"Reset\"],\"editor.panels.panelWrapper.titleHitAnErrorAndWas\":[\"“\",[\"title\"],\"” hit an error and was unloaded for this session.\"],\"editor.panels.parametricFieldControl.mixed\":[\"Mixed\"],\"editor.panels.parametricInspector.actions\":[\"Actions\"],\"editor.panels.parametricInspector.delete\":[\"Delete\"],\"editor.panels.parametricInspector.deleteWithContents\":[\"Delete with contents\"],\"editor.panels.parametricInspector.move\":[\"Move\"],\"editor.panels.referencePanel.cancel\":[\"Cancel\"],\"editor.panels.referencePanel.capture\":[\"Capture\"],\"editor.panels.referencePanel.center\":[\"Center\"],\"editor.panels.referencePanel.clearScale\":[\"Clear Scale\"],\"editor.panels.referencePanel.clickBothEndsOfAKnown\":[\"Click both ends of a known distance on the plan, then type its real length.\"],\"editor.panels.referencePanel.delete\":[\"Delete\"],\"editor.panels.referencePanel.drawALineOverAKnown\":[\"Draw a line over a known dimension on the plan, then type its real length to scale the image exactly.\"],\"editor.panels.referencePanel.editScale\":[\"Edit Scale\"],\"editor.panels.referencePanel.guideImage\":[\"Guide Image\"],\"editor.panels.referencePanel.hide\":[\"Hide\"],\"editor.panels.referencePanel.hideScale\":[\"Hide Scale\"],\"editor.panels.referencePanel.image\":[\"Image\"],\"editor.panels.referencePanel.lock\":[\"Lock\"],\"editor.panels.referencePanel.move\":[\"Move\"],\"editor.panels.referencePanel.opacity\":[\"Opacity\"],\"editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage\":[\"Overlay image unavailable. Replace the image to restore it.\"],\"editor.panels.referencePanel.pos\":[\"pos\"],\"editor.panels.referencePanel.position\":[\"Position\"],\"editor.panels.referencePanel.quickActions\":[\"Quick Actions\"],\"editor.panels.referencePanel.referenceScale\":[\"Reference Scale\"],\"editor.panels.referencePanel.replace\":[\"Replace\"],\"editor.panels.referencePanel.replacing\":[\"Replacing...\"],\"editor.panels.referencePanel.resetImageScale\":[\"Reset Image Scale\"],\"editor.panels.referencePanel.resetRotation\":[\"Reset Rotation\"],\"editor.panels.referencePanel.rot\":[\"rot\"],\"editor.panels.referencePanel.rotation\":[\"Rotation\"],\"editor.panels.referencePanel.scale\":[\"scale\"],\"editor.panels.referencePanel.scaleOpacity\":[\"Scale & Opacity\"],\"editor.panels.referencePanel.scaled\":[\"Scaled · \",[\"scale\"]],\"editor.panels.referencePanel.scaledHidden\":[\"Scaled (hidden) · \",[\"scale\"]],\"editor.panels.referencePanel.setScale\":[\"Set Scale\"],\"editor.panels.referencePanel.show\":[\"Show\"],\"editor.panels.referencePanel.showScale\":[\"Show Scale\"],\"editor.panels.referencePanel.uncalibrated\":[\"Uncalibrated\"],\"editor.panels.referencePanel.unlock\":[\"Unlock\"],\"editor.panels.referencePanel.xyz\":[\"XYZ\"],\"editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness\":[\"A drawn slab: set its thickness and finishes here. It has no footprint height or foundation.\"],\"editor.panels.roomConstructionRows.addMezzanine\":[\"+ Add mezzanine\"],\"editor.panels.roomConstructionRows.atFloorLevel\":[\"At floor level\"],\"editor.panels.roomConstructionRows.beam\":[\"Beam\"],\"editor.panels.roomConstructionRows.beamSize\":[\"Beam size\"],\"editor.panels.roomConstructionRows.builtOn\":[\"Built on\"],\"editor.panels.roomConstructionRows.builtWith\":[\"Built with\"],\"editor.panels.roomConstructionRows.ceiling\":[\"Ceiling\"],\"editor.panels.roomConstructionRows.ceilingAt\":[\"At \",[\"height\"]],\"editor.panels.roomConstructionRows.closeWithWalls\":[\"Close with walls\"],\"editor.panels.roomConstructionRows.drawnFloor\":[[\"ambiguous\",\"select\",{\"yes\":[\"Floor is several drawn slabs, mostly \",[\"name\"]],\"other\":[\"Floor is a drawn slab: \",[\"name\"]]}],[\"shared\",\"plural\",{\"0\":[],\"one\":[\" (shared with \",[\"sharedCount\"],\" room)\"],\"other\":[\" (shared with \",[\"sharedCount\"],\" rooms)\"]}]],\"editor.panels.roomConstructionRows.drawnFloorSwitchHint\":[\"This floor is a drawn slab: select it to edit or delete it.\"],\"editor.panels.roomConstructionRows.drawnSlab\":[\"Drawn slab\"],\"editor.panels.roomConstructionRows.drawnSlabAt\":[\"Drawn slab at \",[\"height\"]],\"editor.panels.roomConstructionRows.editShape\":[\"Edit shape\"],\"editor.panels.roomConstructionRows.flatAtWallHeight\":[\"Flat, at wall height\"],\"editor.panels.roomConstructionRows.floor\":[\"Floor\"],\"editor.panels.roomConstructionRows.floorDown\":[[\"distance\"],\" down\"],\"editor.panels.roomConstructionRows.floorThickness\":[\"Floor thickness\"],\"editor.panels.roomConstructionRows.floorUp\":[[\"distance\"],\" up\"],\"editor.panels.roomConstructionRows.framingAutoHint\":[[\"posts\",\"plural\",{\"one\":[[\"postCount\"],\" post\"],\"other\":[[\"postCount\"],\" posts\"]}],\" and \",[\"beams\",\"plural\",{\"one\":[[\"beamCount\"],\" beam\"],\"other\":[[\"beamCount\"],\" beams\"]}],\", a post on each outside corner, kept with \",[\"room\",\"select\",{\"deck\":[\"the deck\"],\"patio\":[\"the patio\"],\"other\":[\"the porch\"]}],\".\"],\"editor.panels.roomConstructionRows.framingAutomatic\":[\"Automatic\"],\"editor.panels.roomConstructionRows.framingManual\":[\"Manual\"],\"editor.panels.roomConstructionRows.framingManualHint\":[\"Kept as you edited them. Automatic rebuilds them from these settings.\"],\"editor.panels.roomConstructionRows.framingOff\":[\"Off\"],\"editor.panels.roomConstructionRows.framingOffHint\":[\"None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over \",[\"room\",\"select\",{\"deck\":[\"the deck\"],\"patio\":[\"the patio\"],\"other\":[\"the porch\"]}],\".\"],\"editor.panels.roomConstructionRows.handDrawnCeiling\":[\"Hand-drawn ceiling\"],\"editor.panels.roomConstructionRows.height\":[\"Height\"],\"editor.panels.roomConstructionRows.includeFloor\":[\"Include floor\"],\"editor.panels.roomConstructionRows.includePart\":[[\"part\",\"select\",{\"floor\":[\"Include floor\"],\"walls\":[\"Include walls\"],\"other\":[\"Include ceiling\"]}]],\"editor.panels.roomConstructionRows.includeWalls\":[\"Include walls\"],\"editor.panels.roomConstructionRows.keepOutsideDimensions\":[\"Keep outside dimensions\"],\"editor.panels.roomConstructionRows.lowered\":[\"Lowered \",[\"lift\"]],\"editor.panels.roomConstructionRows.newFloor\":[\"+ New floor\"],\"editor.panels.roomConstructionRows.noFloor\":[\"No floor\"],\"editor.panels.roomConstructionRows.notIncluded\":[\"Not included\"],\"editor.panels.roomConstructionRows.nothingToChange\":[\"Nothing to change.\"],\"editor.panels.roomConstructionRows.onTheFoundation\":[\"On the foundation\"],\"editor.panels.roomConstructionRows.onTheGround\":[\"On the ground\"],\"editor.panels.roomConstructionRows.onTheWallsBelow\":[\"On the walls below\"],\"editor.panels.roomConstructionRows.openSides\":[[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" open side\"],\"other\":[[\"openSideCount\"],\" open sides\"]}]],\"editor.panels.roomConstructionRows.partialCovered\":[\"Partial · \",[\"coverage\",\"number\",\"percent\"],\" covered\"],\"editor.panels.roomConstructionRows.partialWalls\":[\"Partial · \",[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" wall\"],\"other\":[[\"wallCount\"],\" walls\"]}],\", \",[\"openSides\",\"plural\",{\"one\":[[\"openSideCount\"],\" open side\"],\"other\":[[\"openSideCount\"],\" open sides\"]}]],\"editor.panels.roomConstructionRows.postSize\":[\"Post size\"],\"editor.panels.roomConstructionRows.postSpacingMax\":[\"Post spacing (max)\"],\"editor.panels.roomConstructionRows.postsAndBeam\":[\"Posts and beam\"],\"editor.panels.roomConstructionRows.raised\":[\"Raised \",[\"lift\"]],\"editor.panels.roomConstructionRows.raisedFloor\":[\"Raised floor\"],\"editor.panels.roomConstructionRows.replace\":[\"Replace\"],\"editor.panels.roomConstructionRows.roomType\":[\"Room type\"],\"editor.panels.roomConstructionRows.roomTypeDeck\":[\"Deck\"],\"editor.panels.roomConstructionRows.roomTypeDeckHint\":[\"Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling.\"],\"editor.panels.roomConstructionRows.roomTypePatio\":[\"Patio\"],\"editor.panels.roomConstructionRows.roomTypePatioHint\":[\"Outdoors and open: a concrete slab 1½ in down from the house, no ceiling.\"],\"editor.panels.roomConstructionRows.roomTypePorch\":[\"Porch\"],\"editor.panels.roomConstructionRows.roomTypePorchHint\":[\"Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s.\"],\"editor.panels.roomConstructionRows.roomTypeRoom\":[\"Room\"],\"editor.panels.roomConstructionRows.roomTypeRoomHint\":[\"An ordinary room inside the house.\"],\"editor.panels.roomConstructionRows.select\":[\"Select\"],\"editor.panels.roomConstructionRows.shape\":[\"Shape\"],\"editor.panels.roomConstructionRows.sharedWallsNote\":[\"Turning walls off keeps the ones shared with \",[\"rooms\"],\".\"],\"editor.panels.roomConstructionRows.sitsOnPreset\":[\"Sits on: \",[\"name\"],\" · \",[\"preset\"]],\"editor.panels.roomConstructionRows.slab\":[\"Slab\"],\"editor.panels.roomConstructionRows.theNextRoom\":[\"the next room\"],\"editor.panels.roomConstructionRows.useExisting\":[\"Use existing\"],\"editor.panels.roomConstructionRows.wallCount\":[[\"walls\",\"plural\",{\"one\":[[\"wallCount\"],\" wall\"],\"other\":[[\"wallCount\"],\" walls\"]}]],\"editor.panels.roomConstructionRows.walls\":[\"Walls\"],\"editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding\":[\"Walls thicken inward, so the building's outer size stays the same.\"],\"editor.panels.roomOpeningsField.cutOpening\":[\"Cut opening\"],\"editor.panels.roomOpeningsField.done\":[\"Done\"],\"editor.panels.roomOpeningsField.editFloor\":[\"Edit floor\"],\"editor.panels.roomOpeningsField.elevatorOpening\":[\"Elevator opening\"],\"editor.panels.roomOpeningsField.noOpeningsYetCutOneFor\":[\"No openings yet. Cut one for a stair void or a hatch.\"],\"editor.panels.roomOpeningsField.openingNumber\":[\"Opening \",[\"number\"]],\"editor.panels.roomOpeningsField.openings\":[[\"count\",\"plural\",{\"0\":[\"No openings\"],\"one\":[[\"number\"],\" opening\"],\"other\":[[\"number\"],\" openings\"]}]],\"editor.panels.roomOpeningsField.stairOpening\":[\"Stair opening\"],\"editor.panels.roomPanel.addStairs\":[\"Add stairs\"],\"editor.panels.roomPanel.addToAUnit\":[\"Add to a unit\"],\"editor.panels.roomPanel.area\":[\"Area\"],\"editor.panels.roomPanel.browse\":[\"Browse\"],\"editor.panels.roomPanel.builtWith\":[\"Built with\"],\"editor.panels.roomPanel.ceiling\":[\"Ceiling\"],\"editor.panels.roomPanel.deleteRoom\":[\"Delete room\"],\"editor.panels.roomPanel.editShape\":[\"Edit shape\"],\"editor.panels.roomPanel.floor\":[\"Floor\"],\"editor.panels.roomPanel.headroom\":[[\"height\"],\" headroom\"],\"editor.panels.roomPanel.height\":[\"Height\"],\"editor.panels.roomPanel.hideCode\":[\"Hide code\"],\"editor.panels.roomPanel.kickerLevel\":[\"Room · \",[\"level\"]],\"editor.panels.roomPanel.kickerMezzanineIn\":[\"Mezzanine · in \",[\"room\"]],\"editor.panels.roomPanel.kickerNotEnclosed\":[\"Room · not enclosed\"],\"editor.panels.roomPanel.loading\":[\"Loading…\"],\"editor.panels.roomPanel.mezzanine\":[\"Mezzanine\"],\"editor.panels.roomPanel.mezzanineName\":[\"Mezzanine name\"],\"editor.panels.roomPanel.mezzanineNames\":[\"Mezzanine names\"],\"editor.panels.roomPanel.newUnit\":[\"New unit\"],\"editor.panels.roomPanel.none\":[\"None\"],\"editor.panels.roomPanel.notEnoughHeadroom\":[\"Not enough headroom\"],\"editor.panels.roomPanel.openRoomArea\":[[\"area\"],\" · Close its walls to build its floor and ceiling.\"],\"editor.panels.roomPanel.openingCounts\":[[\"doors\",\"plural\",{\"one\":[[\"doorCount\"],\" door\"],\"other\":[[\"doorCount\"],\" doors\"]}],\" · \",[\"windows\",\"plural\",{\"one\":[[\"windowCount\"],\" window\"],\"other\":[[\"windowCount\"],\" windows\"]}]],\"editor.panels.roomPanel.overTheMezzanine\":[\"Over the mezzanine\"],\"editor.panels.roomPanel.perimeter\":[\"Perimeter\"],\"editor.panels.roomPanel.pick\":[\"Pick\"],\"editor.panels.roomPanel.pickingItAddsANumber\":[\"Picking it adds a number\"],\"editor.panels.roomPanel.remove\":[\"Remove\"],\"editor.panels.roomPanel.removeFromUnit\":[\"Remove from unit\"],\"editor.panels.roomPanel.removeFromUnitNamed\":[\"Remove from \",[\"unit\"]],\"editor.panels.roomPanel.rename\":[\"Rename\"],\"editor.panels.roomPanel.renameNamed\":[[\"name\"],\" · Rename\"],\"editor.panels.roomPanel.revert\":[\"Revert\"],\"editor.panels.roomPanel.room\":[\"Room\"],\"editor.panels.roomPanel.roomColourUsedInThePlan\":[\"Room colour — used in the plan and labels\"],\"editor.panels.roomPanel.roomName\":[\"Room name\"],\"editor.panels.roomPanel.roomNames\":[\"Room names\"],\"editor.panels.roomPanel.scriptedFinish\":[\"Scripted finish\"],\"editor.panels.roomPanel.shape\":[\"Shape\"],\"editor.panels.roomPanel.showMezzanineNames\":[\"Show mezzanine names\"],\"editor.panels.roomPanel.showRoomNames\":[\"Show room names\"],\"editor.panels.roomPanel.stairs\":[\"Stairs\"],\"editor.panels.roomPanel.unit\":[\"Unit\"],\"editor.panels.roomPanel.use\":[\"Use\"],\"editor.panels.roomPanel.useTyped\":[[\"use\"],\" “\",[\"name\"],\"”\"],\"editor.panels.roomPanel.usedOnThisLevel\":[[\"used\"],\" on this level\"],\"editor.panels.roomPanel.viewCode\":[\"View code\"],\"editor.panels.scriptUnreadable\":[\"The script could not be read.\"],\"editor.panels.shapeChoice.polygon\":[\"Polygon\"],\"editor.panels.shapeChoice.rectangle\":[\"Rectangle\"],\"editor.panels.wallRegionList.deleteRegion\":[\"Delete region\"],\"editor.panels.wallRegionList.paintRegions\":[\"Paint regions\"],\"editor.proceduralItems.inspector.adjustDesign\":[\"Adjust design\"],\"editor.proceduralItems.inspector.back\":[\"Back\"],\"editor.proceduralItems.inspector.editParts\":[\"Edit parts\"],\"editor.proceduralItems.inspector.invalidAttachment\":[\"Invalid attachment\"],\"editor.proceduralItems.inspector.invalidEdit\":[\"Invalid edit\"],\"editor.proceduralItems.inspector.invalidPlacement\":[\"Invalid placement\"],\"editor.proceduralItems.inspector.invalidPosition\":[\"Invalid position\"],\"editor.proceduralItems.inspector.invalidReset\":[\"Invalid reset\"],\"editor.proceduralItems.inspector.pos\":[\"pos\"],\"editor.proceduralItems.inspector.position\":[\"Position\"],\"editor.proceduralItems.inspector.resetThisPart\":[\"Reset this part\"],\"editor.reloadEditor\":[\"Reload editor\"],\"editor.riserDiagramPanel.close\":[\"Close riser diagram\"],\"editor.riserDiagramPanel.dwvPlumbingIsometric\":[\"DWV plumbing isometric\"],\"editor.riserDiagramPanel.dwvRiserDiagram\":[\"DWV riser diagram\"],\"editor.riserDiagramPanel.noDrainWasteOrVentPipes\":[\"No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram.\"],\"editor.riserDiagramPanel.riserDiagram\":[\"Riser Diagram\"],\"editor.riserDiagramPanel.trap\":[[\"diameter\"],\"\\\" P-trap\"],\"editor.riserDiagramPanel.vent\":[\"Vent\"],\"editor.riserDiagramPanel.ventTermination\":[[\"diameter\"],\"\\\" VTR\"],\"editor.riserDiagramPanel.waste\":[\"Waste\"],\"editor.room.builtOn.above\":[[\"current\"],\" · \",[\"distance\"],\" above \",[\"shared\"]],\"editor.room.builtOn.below\":[[\"current\"],\" · \",[\"distance\"],\" below \",[\"shared\"]],\"editor.room.builtOn.levelWith\":[[\"current\"],\" · level with \",[\"shared\"]],\"editor.roomHandles.cannotReadInput\":[\"Cannot read \\\"\",[\"input\"],\"\\\"\"],\"editor.roomHandles.dimensionValue\":[\"Dimension value\"],\"editor.roomHandles.floor\":[\"Floor\"],\"editor.roomHandles.typeASizeToMoveThe\":[\"Type a size to move the wall\"],\"editor.roomLabel.room\":[\"Room\"],\"editor.roomLabel.zone\":[\"Zone\"],\"editor.roomName.attic\":[\"Attic\"],\"editor.roomName.balcony\":[\"Balcony\"],\"editor.roomName.basement\":[\"Basement\"],\"editor.roomName.bathroom\":[\"Bathroom\"],\"editor.roomName.bedroom\":[\"Bedroom\"],\"editor.roomName.boilerRoom\":[\"Boiler room\"],\"editor.roomName.carport\":[\"Carport\"],\"editor.roomName.cellar\":[\"Cellar\"],\"editor.roomName.closet\":[\"Closet\"],\"editor.roomName.corridor\":[\"Corridor\"],\"editor.roomName.courtyard\":[\"Courtyard\"],\"editor.roomName.deck\":[\"Deck\"],\"editor.roomName.den\":[\"Den\"],\"editor.roomName.diningRoom\":[\"Dining room\"],\"editor.roomName.dressingRoom\":[\"Dressing room\"],\"editor.roomName.enSuite\":[\"En-suite\"],\"editor.roomName.entrance\":[\"Entrance\"],\"editor.roomName.familyRoom\":[\"Family room\"],\"editor.roomName.gallery\":[\"Gallery\"],\"editor.roomName.gamesRoom\":[\"Games room\"],\"editor.roomName.garage\":[\"Garage\"],\"editor.roomName.garden\":[\"Garden\"],\"editor.roomName.gardenShed\":[\"Garden shed\"],\"editor.roomName.guestRoom\":[\"Guest room\"],\"editor.roomName.gym\":[\"Gym\"],\"editor.roomName.hallway\":[\"Hallway\"],\"editor.roomName.homeCinema\":[\"Home cinema\"],\"editor.roomName.kidsRoom\":[\"Kids' room\"],\"editor.roomName.kitchen\":[\"Kitchen\"],\"editor.roomName.kitchenette\":[\"Kitchenette\"],\"editor.roomName.landing\":[\"Landing\"],\"editor.roomName.laundry\":[\"Laundry\"],\"editor.roomName.library\":[\"Library\"],\"editor.roomName.livingRoom\":[\"Living room\"],\"editor.roomName.loft\":[\"Loft\"],\"editor.roomName.lounge\":[\"Lounge\"],\"editor.roomName.mezzanine\":[\"Mezzanine\"],\"editor.roomName.mudroom\":[\"Mudroom\"],\"editor.roomName.nursery\":[\"Nursery\"],\"editor.roomName.office\":[\"Office\"],\"editor.roomName.pantry\":[\"Pantry\"],\"editor.roomName.patio\":[\"Patio\"],\"editor.roomName.playroom\":[\"Playroom\"],\"editor.roomName.poolArea\":[\"Pool area\"],\"editor.roomName.porch\":[\"Porch\"],\"editor.roomName.powderRoom\":[\"Powder room\"],\"editor.roomName.primaryBedroom\":[\"Primary bedroom\"],\"editor.roomName.readingNook\":[\"Reading nook\"],\"editor.roomName.section.bathrooms\":[\"Bathrooms\"],\"editor.roomName.section.circulation\":[\"Circulation\"],\"editor.roomName.section.living\":[\"Living\"],\"editor.roomName.section.mezzanine\":[\"Mezzanine\"],\"editor.roomName.section.outdoor\":[\"Outdoor\"],\"editor.roomName.section.sleeping\":[\"Sleeping\"],\"editor.roomName.section.utility\":[\"Utility & storage\"],\"editor.roomName.section.work\":[\"Work & hobbies\"],\"editor.roomName.showerRoom\":[\"Shower room\"],\"editor.roomName.stairwell\":[\"Stairwell\"],\"editor.roomName.storage\":[\"Storage\"],\"editor.roomName.studio\":[\"Studio\"],\"editor.roomName.study\":[\"Study\"],\"editor.roomName.sunroom\":[\"Sunroom\"],\"editor.roomName.terrace\":[\"Terrace\"],\"editor.roomName.toilet\":[\"Toilet\"],\"editor.roomName.utilityRoom\":[\"Utility room\"],\"editor.roomName.veranda\":[\"Veranda\"],\"editor.roomName.walkInCloset\":[\"Walk-in closet\"],\"editor.roomName.workshop\":[\"Workshop\"],\"editor.sidebar.iconRail.settings\":[\"Settings\"],\"editor.sidebar.iconRail.site\":[\"Site\"],\"editor.sidebar.panels.itemsPanel.all\":[\"All\"],\"editor.sidebar.panels.itemsPanel.community\":[\"Community\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.all\":[\"All\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.community\":[\"Community\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.library\":[\"Library\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.mine\":[\"Mine\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch\":[\"No results for “\",[\"search\"],\"”\"],\"editor.sidebar.panels.itemsPanel.functionTreePanel.search\":[\"Search...\"],\"editor.sidebar.panels.itemsPanel.library\":[\"Library\"],\"editor.sidebar.panels.itemsPanel.mine\":[\"Mine\"],\"editor.sidebar.panels.itemsPanel.noResultsForSearch\":[\"No results for “\",[\"search\"],\"”\"],\"editor.sidebar.panels.itemsPanel.search\":[\"Search...\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools\":[\"Build Tools\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera\":[\"Camera\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation\":[\"Direct Manipulation\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools\":[\"Drawing Tools\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation\":[\"Editor Navigation\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement\":[\"Item Placement\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts\":[\"Keyboard shortcuts\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory\":[\"Modes & History\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection\":[\"Selection\"],\"editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints\":[\"Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely.\"],\"editor.sidebar.panels.sitePanel.addLevel\":[\"Add level\"],\"editor.sidebar.panels.sitePanel.addPoint\":[\"Add point\"],\"editor.sidebar.panels.sitePanel.area\":[\"Area:\"],\"editor.sidebar.panels.sitePanel.baseElevation\":[\"Base elevation\"],\"editor.sidebar.panels.sitePanel.building\":[\"Building\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel\":[\"Add new level\"],\"editor.sidebar.panels.sitePanel.buildingTreeNode.building\":[\"Building\"],\"editor.sidebar.panels.sitePanel.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.sitePanel.capture\":[\"Capture\"],\"editor.sidebar.panels.sitePanel.clearSelection\":[\"Clear selection\"],\"editor.sidebar.panels.sitePanel.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo\":[\"Close walls around a space to make a room.\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour\":[\"Collection colour\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.collections\":[\"Collections\"],\"editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection\":[\"Delete collection\"],\"editor.sidebar.panels.sitePanel.delete\":[\"Delete\"],\"editor.sidebar.panels.sitePanel.deleteLevel\":[\"Delete level\"],\"editor.sidebar.panels.sitePanel.drawAZone\":[\"Draw a zone\"],\"editor.sidebar.panels.sitePanel.duplicate\":[\"Duplicate\"],\"editor.sidebar.panels.sitePanel.duplicateLevel\":[\"Duplicate level\"],\"editor.sidebar.panels.sitePanel.duplicateLevelWithOptions\":[\"Duplicate level with options\"],\"editor.sidebar.panels.sitePanel.duplicateWithOptions\":[\"Duplicate with options...\"],\"editor.sidebar.panels.sitePanel.elements\":[\"Elements\"],\"editor.sidebar.panels.sitePanel.guide\":[\"Guide\"],\"editor.sidebar.panels.sitePanel.hide\":[\"Hide\"],\"editor.sidebar.panels.sitePanel.level\":[\"Level\"],\"editor.sidebar.panels.sitePanel.newUnit\":[\"New unit\"],\"editor.sidebar.panels.sitePanel.noBuildingsYet\":[\"No buildings yet\"],\"editor.sidebar.panels.sitePanel.noElementsOnThisLevel\":[\"No elements on this level\"],\"editor.sidebar.panels.sitePanel.noLevelsYet\":[\"No levels yet\"],\"editor.sidebar.panels.sitePanel.objectsSelected\":[[\"count\",\"plural\",{\"one\":[\"#\",\" object selected\"],\"other\":[\"#\",\" objects selected\"]}]],\"editor.sidebar.panels.sitePanel.otherZones\":[\"Other zones\"],\"editor.sidebar.panels.sitePanel.perimeter\":[\"Perimeter:\"],\"editor.sidebar.panels.sitePanel.propertyLine\":[\"Property Line\"],\"editor.sidebar.panels.sitePanel.rooms\":[\"Rooms\"],\"editor.sidebar.panels.sitePanel.selectALevelToViewContent\":[\"Select a level to view content\"],\"editor.sidebar.panels.sitePanel.show\":[\"Show\"],\"editor.sidebar.panels.sitePanel.site\":[\"Site\"],\"editor.sidebar.panels.sitePanel.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted\":[\"The ground level cannot be deleted\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.hide\":[\"Hide\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.show\":[\"Show\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit\":[\"Delete unit\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.focused\":[\"Focused\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit\":[\"Remove from unit\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.room\":[\"Room\"],\"editor.sidebar.panels.sitePanel.unitTreeNode.zone\":[\"Zone\"],\"editor.sidebar.panels.sitePanel.units\":[\"Units\"],\"editor.sidebar.panels.sitePanel.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.sitePanel.uploadScanFloorplan\":[\"Upload scan/floorplan\"],\"editor.sidebar.panels.sitePanel.uploadingProgress\":[[\"assetType\",\"select\",{\"guide\":[\"Uploading guide... \",[\"progress\"],\"%\"],\"other\":[\"Uploading scan... \",[\"progress\"],\"%\"]}]],\"editor.sidebar.panels.sitePanel.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname\":[\"In \",[\"unitName\"]],\"editor.sidebar.panels.zonePanel.actions\":[\"Actions\"],\"editor.sidebar.panels.zonePanel.addOne\":[\"Add one\"],\"editor.sidebar.panels.zonePanel.cameraSnapshot\":[\"Camera snapshot\"],\"editor.sidebar.panels.zonePanel.clearSnapshot\":[\"Clear snapshot\"],\"editor.sidebar.panels.zonePanel.delete\":[\"Delete\"],\"editor.sidebar.panels.zonePanel.deleteWithContents\":[\"Delete with contents\"],\"editor.sidebar.panels.zonePanel.noZonesOnThisLevel\":[\"No zones on this level.\"],\"editor.sidebar.panels.zonePanel.saveToCatalog\":[\"Save to catalog\"],\"editor.sidebar.panels.zonePanel.selectALevelToViewAnd\":[\"Select a level to view and create zones\"],\"editor.sidebar.panels.zonePanel.takeSnapshot\":[\"Take snapshot\"],\"editor.sidebar.panels.zonePanel.updateSnapshot\":[\"Update snapshot\"],\"editor.sidebar.panels.zonePanel.viewSnapshot\":[\"View snapshot\"],\"editor.sidebar.usePluginPanels.labelPluginCrashed\":[\"\\\"\",[\"label\"],\"\\\" plugin crashed\"],\"editor.sidebar.usePluginPanels.loading\":[\"Loading…\"],\"editor.sidebar.usePluginPanels.plugins\":[\"Plugins\"],\"editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd\":[\"This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again.\"],\"editor.snapTarget.ceiling\":[\"Ceiling attachment\"],\"editor.snapTarget.roof\":[\"Roof attachment\"],\"editor.snapTarget.wall\":[\"Wall attachment\"],\"editor.theEditorSceneFailedToRender\":[\"The editor scene failed to render\"],\"editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i\":[\"A curtain wall takes no corner window\"],\"editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5\":[\"A curved wall takes no corner window\"],\"editor.toolHint.addACornerClickTheFirst.o4r67t\":[\"Add a corner · click the first to close\"],\"editor.toolHint.addAWallEachOneStarts.4zkf12\":[\"Add a wall · each one starts where the last ended\"],\"editor.toolHint.anotherOpeningIsInTheWay.11l9dd0\":[\"Another opening is in the way at the corner\"],\"editor.toolHint.autoHangers.10554ka\":[\"Auto hangers\"],\"editor.toolHint.autoHangersClickOrPressH.rwa3ka\":[\"Auto hangers - click or press H to toggle\"],\"editor.toolHint.autoHangersOff.7yx01x\":[\"Auto hangers: Off\"],\"editor.toolHint.autoHangersOn.10b1umh\":[\"Auto hangers: On\"],\"editor.toolHint.bypassSnapping.w5lmdv\":[\"Bypass snapping\"],\"editor.toolHint.cancel.ew9em3\":[\"Cancel\"],\"editor.toolHint.cancelMove.lvp31c\":[\"Cancel move\"],\"editor.toolHint.cancelRunExit.zqjf3h\":[\"Cancel run / exit\"],\"editor.toolHint.cancelStartPoint.1xbq2lh\":[\"Cancel start point\"],\"editor.toolHint.closeTheOutline.f6cvzi\":[\"Close the outline\"],\"editor.toolHint.cornerWindow.1avkzpo\":[\"Corner window\"],\"editor.toolHint.cuts.1y0axps\":[\"Cuts\"],\"editor.toolHint.cutsValue.ouzhvb\":[\"Cuts: \",[\"value\"]],\"editor.toolHint.cutsValueEven.1ngsz3\":[\"Cuts: \",[\"value\"],\", even\"],\"editor.toolHint.cycleMonoGableButterfly.1poga3m\":[\"Cycle mono / gable / butterfly\"],\"editor.toolHint.cycleSnappingMode.1ct0gn6\":[\"Cycle snapping mode\"],\"editor.toolHint.doorStyleClickOrPressL.33pe65\":[\"Door style — click or press L to cycle\"],\"editor.toolHint.doorTypeClickOrPressO.1hhh8gt\":[\"Door type — click or press O to cycle\"],\"editor.toolHint.dropDownspoutFromOutlet.1k4h1m4\":[\"Drop downspout from outlet\"],\"editor.toolHint.ductDiameterDownUp.1wdz5vq\":[\"Duct diameter down / up\"],\"editor.toolHint.endRunCancel.59dh2w\":[\"End run / cancel\"],\"editor.toolHint.endTheRun.1c1raig\":[\"End the run\"],\"editor.toolHint.exit.engmg5\":[\"Exit\"],\"editor.toolHint.exitDrawing.9noxyb\":[\"Exit drawing\"],\"editor.toolHint.finishAndContinue.1gpfsks\":[\"Finish and continue\"],\"editor.toolHint.finishCeiling.1c1lfel\":[\"Finish ceiling\"],\"editor.toolHint.finishGridAxis.1wvrfhr\":[\"Finish grid axis\"],\"editor.toolHint.finishMeasurement.1e4gv16\":[\"Finish measurement\"],\"editor.toolHint.finishMultiPointWitnesses.8wzskz\":[\"Finish multi-point witnesses\"],\"editor.toolHint.finishSlab.xmnm8e\":[\"Finish slab\"],\"editor.toolHint.finishTheRun.ayliww\":[\"Finish the run\"],\"editor.toolHint.flipSide.172xku9\":[\"Flip side\"],\"editor.toolHint.followTraceALineset.mgquwe\":[\"Follow: trace a lineset\"],\"editor.toolHint.forcePlace.1hq1nr\":[\"Force place\"],\"editor.toolHint.freeAngle.1ptblho\":[\"Free angle\"],\"editor.toolHint.freePlace.1s1uusw\":[\"Free place\"],\"editor.toolHint.freePlacement.1nztdac\":[\"Free placement\"],\"editor.toolHint.goVerticalClickToPlace.dg8g6m\":[\"Go vertical ↕, click to place\"],\"editor.toolHint.highlightOutlet.65hoq8\":[\"Highlight outlet\"],\"editor.toolHint.joinAsACornerWindow.jvdp4j\":[\"Join as a corner window\"],\"editor.toolHint.key.altDrag.ai3wn9\":[\"Alt + drag\"],\"editor.toolHint.key.backspace.qy6qx8\":[\"Backspace\"],\"editor.toolHint.key.click.m8sun\":[\"Click\"],\"editor.toolHint.key.clickAgain.190ws9j\":[\"Click again\"],\"editor.toolHint.key.command.1j2mkia\":[\"Command\"],\"editor.toolHint.key.control.10fbl8e\":[\"Control\"],\"editor.toolHint.key.delete.oay2cq\":[\"Delete\"],\"editor.toolHint.key.doubleClick.vlma6o\":[\"Double click\"],\"editor.toolHint.key.drag.8pwtpf\":[\"Drag\"],\"editor.toolHint.key.enter.1ctpui5\":[\"Enter\"],\"editor.toolHint.key.hover.6xw79p\":[\"Hover\"],\"editor.toolHint.key.hoverADuctEnd.19axr3b\":[\"Hover a duct end\"],\"editor.toolHint.key.hoverAPipeEnd.1jbum5f\":[\"Hover a pipe end\"],\"editor.toolHint.key.hoverGutter.1dwcsno\":[\"Hover gutter\"],\"editor.toolHint.key.leftClick.s4gwfc\":[\"Left click\"],\"editor.toolHint.key.middleClick.1jbaan4\":[\"Middle click\"],\"editor.toolHint.key.rShiftR.13bnobh\":[\"R / Shift+R\"],\"editor.toolHint.key.rightClick.1ovld0h\":[\"Right click\"],\"editor.toolHint.key.room.1f3jkpm\":[\"Room\"],\"editor.toolHint.key.scroll.ai6wr0\":[\"Scroll\"],\"editor.toolHint.key.scrollWheel.18j0ix7\":[\"Scroll wheel\"],\"editor.toolHint.key.space.1hrh82t\":[\"Space\"],\"editor.toolHint.key.tab.1xsal0s\":[\"Tab\"],\"editor.toolHint.mountFloorCeilingWall.32dxpv\":[\"Mount: floor / ceiling / wall\"],\"editor.toolHint.noRoomForTheMatchingWindow.1755948\":[\"No room for the matching window on the other wall\"],\"editor.toolHint.noWallMeetsHere.19n99yc\":[\"No wall meets here\"],\"editor.toolHint.notARectangleDrawThisRoof.100pa5g\":[\"Not a rectangle: draw this roof instead\"],\"editor.toolHint.numberOfCutsScrollOrClick.1kqe84b\":[\"Number of cuts — scroll or click to change\"],\"editor.toolHint.pickWitnessPoint.k3hgh1\":[\"Pick witness point\"],\"editor.toolHint.pipeSizeDownUp.wp04og\":[\"Pipe size down / up\"],\"editor.toolHint.placeACornerOfTheFoundation.1o1ze9j\":[\"Place a corner of the foundation\"],\"editor.toolHint.placeAndContinue.qfwkrq\":[\"Place and continue\"],\"editor.toolHint.placeBlock.1aylwrx\":[\"Place block\"],\"editor.toolHint.placeBoxVentOnRoof.kf88e5\":[\"Place box vent on roof\"],\"editor.toolHint.placeCabinet.b0ouyk\":[\"Place cabinet\"],\"editor.toolHint.placeCanopyOrSetTheNext.12l7zf8\":[\"Place canopy or set the next run point\"],\"editor.toolHint.placeChimneyOnRoof.19dbqu\":[\"Place chimney on roof\"],\"editor.toolHint.placeColumn.1w2vq86\":[\"Place column\"],\"editor.toolHint.placeCupolaOnRoof.ofx3bn\":[\"Place cupola on roof\"],\"editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj\":[\"Place dimension line when needed\"],\"editor.toolHint.placeDoorOnWall.1jwadf\":[\"Place door on wall\"],\"editor.toolHint.placeDormerOnRoof.1w518a6\":[\"Place dormer on roof\"],\"editor.toolHint.placeElevator.1f65zva\":[\"Place elevator\"],\"editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn\":[\"Place eyebrow vent on roof\"],\"editor.toolHint.placeFitting.1pmx3vn\":[\"Place fitting\"],\"editor.toolHint.placeGutterOnRoofEave.llwzf9\":[\"Place gutter on roof eave\"],\"editor.toolHint.placeItLockedTo45.16e4csk\":[\"Place it (locked to 45°)\"],\"editor.toolHint.placeItem.teynf\":[\"Place item\"],\"editor.toolHint.placeMeasurementPoint.bsatli\":[\"Place measurement point\"],\"editor.toolHint.placeOnAFence.1dptstr\":[\"Place on a fence\"],\"editor.toolHint.placeRegister.1uebycd\":[\"Place register\"],\"editor.toolHint.placeRidgeVentOnRoof.2tpd8r\":[\"Place ridge vent on roof\"],\"editor.toolHint.placeShelf.y4l3iw\":[\"Place shelf\"],\"editor.toolHint.placeSkylightOnRoof.e215pe\":[\"Place skylight on roof\"],\"editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn\":[\"Place solar panel array on roof\"],\"editor.toolHint.placeSpawnPoint.205o25\":[\"Place spawn point\"],\"editor.toolHint.placeStairs.zwxxu2\":[\"Place stairs\"],\"editor.toolHint.placeTrap.1gxemn3\":[\"Place trap\"],\"editor.toolHint.placeTurbineVentOnRoof.1iqeluf\":[\"Place turbine vent on roof\"],\"editor.toolHint.placeUnit.8zkx4q\":[\"Place unit\"],\"editor.toolHint.placeWindowOnWall.1k3qnfb\":[\"Place window on wall\"],\"editor.toolHint.placeWithoutTheCornerSnap.1nifqew\":[\"Place without the corner snap\"],\"editor.toolHint.placement.13lcvts\":[\"Placement\"],\"editor.toolHint.placementAuto.1caa52d\":[\"Placement: Auto\"],\"editor.toolHint.placementGround.1ruz145\":[\"Placement: Ground\"],\"editor.toolHint.placementRoof.renkw8\":[\"Placement: Roof\"],\"editor.toolHint.placementSurfaceClickOrPressP.w56h19\":[\"Placement surface - click or press P to cycle\"],\"editor.toolHint.placementType.1mdpl60\":[\"Placement type\"],\"editor.toolHint.placementTypeClickOrPressI.10cgxh2\":[\"Placement type — click or press I to toggle\"],\"editor.toolHint.pointInsideARoomPorchOr.58w64l\":[\"Point inside a room, porch or deck\"],\"editor.toolHint.removeLastPoint.2dmdcx\":[\"Remove last point\"],\"editor.toolHint.removeLastWitness.1b6vvzw\":[\"Remove last witness\"],\"editor.toolHint.ridgeDirection.5m1369\":[\"Ridge direction\"],\"editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk\":[\"Ridge direction - click or press R to turn it 90°\"],\"editor.toolHint.ridgeLongSide.ew0euv\":[\"Ridge: Long side\"],\"editor.toolHint.ridgeShortSide.gye8pb\":[\"Ridge: Short side\"],\"editor.toolHint.rotate.1thqa0a\":[\"Rotate\"],\"editor.toolHint.rotate45.9zq68u\":[\"Rotate ±45°\"],\"editor.toolHint.rotate45FloorCeiling.m9t57d\":[\"Rotate ±45° (floor / ceiling)\"],\"editor.toolHint.rotateGhost15.rsa52c\":[\"Rotate ghost ±15°\"],\"editor.toolHint.rotateOrFlipTheRunSide.t5ior7\":[\"Rotate or flip the run side\"],\"editor.toolHint.rotateSpawnPoint.1opcuu7\":[\"Rotate spawn point\"],\"editor.toolHint.roundRectangularOval.e4nh6x\":[\"Round / rectangular / oval\"],\"editor.toolHint.setBeamStartEndTheNext.di3bzj\":[\"Set beam start / end; the next beam starts there\"],\"editor.toolHint.setFenceStartEnd.1phbwsq\":[\"Set fence start / end\"],\"editor.toolHint.setFootingStartEndOrPlace.nnv1av\":[\"Set footing start / end, or place a pad\"],\"editor.toolHint.setOneCornerThenTheOpposite.8tsl49\":[\"Set one corner, then the opposite\"],\"editor.toolHint.setRoofFootprint.p50n0o\":[\"Set roof footprint\"],\"editor.toolHint.slopeLevel.ftqp67\":[\"Slope / level\"],\"editor.toolHint.snapOntoTheRun.86yhmp\":[\"Snap onto the run\"],\"editor.toolHint.splitAtTheMarks.x5tpdd\":[\"Split at the marks\"],\"editor.toolHint.startGridAxis.k92yza\":[\"Start grid axis\"],\"editor.toolHint.startLineset.23q8dh\":[\"Start lineset\"],\"editor.toolHint.startLiquidLine.1rlxsgd\":[\"Start liquid line\"],\"editor.toolHint.startRun.r6fc5u\":[\"Start run\"],\"editor.toolHint.startSegment.rolym6\":[\"Start segment\"],\"editor.toolHint.stepBackOrCancel.zbfdkn\":[\"Step back or cancel\"],\"editor.toolHint.style.nf3vcm\":[\"Style\"],\"editor.toolHint.styleBarn.k9n1x\":[\"Style: Barn\"],\"editor.toolHint.styleCasement.ziklec\":[\"Style: Casement\"],\"editor.toolHint.styleCraftsman.52e6a9\":[\"Style: Craftsman\"],\"editor.toolHint.styleDoubleHung.1lg8pkg\":[\"Style: Double-hung\"],\"editor.toolHint.styleFrench.1ue3j64\":[\"Style: French\"],\"editor.toolHint.styleGlass.qudnb4\":[\"Style: Glass\"],\"editor.toolHint.styleGrid.1vuq4po\":[\"Style: Grid\"],\"editor.toolHint.styleHalfLouvered.13b1few\":[\"Style: Half-louvered\"],\"editor.toolHint.styleHorizontalBands.12ethxk\":[\"Style: Horizontal bands\"],\"editor.toolHint.styleModern.1fh8hhn\":[\"Style: Modern\"],\"editor.toolHint.stylePanel.922jze\":[\"Style: Panel\"],\"editor.toolHint.stylePaneledGlass.17a17en\":[\"Style: Paneled glass\"],\"editor.toolHint.styleShaker.49z17k\":[\"Style: Shaker\"],\"editor.toolHint.styleSingle.6f3cm0\":[\"Style: Single\"],\"editor.toolHint.styleSixPanel.u49laj\":[\"Style: Six-panel\"],\"editor.toolHint.styleSliding.q81s0i\":[\"Style: Sliding\"],\"editor.toolHint.styleTallGrid.1eotg3x\":[\"Style: Tall grid\"],\"editor.toolHint.styleTransom.kb9bh6\":[\"Style: Transom\"],\"editor.toolHint.styleTripleHung.pxnqi3\":[\"Style: Triple-hung\"],\"editor.toolHint.styleWideGrid.12n27et\":[\"Style: Wide grid\"],\"editor.toolHint.switchRotationAxisYXZ.15lohm6\":[\"Switch rotation axis (Y → X → Z)\"],\"editor.toolHint.theWallsMeetAtTooSharp.kh3wi3\":[\"The walls meet at too sharp or too flat an angle\"],\"editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4\":[\"The window waiting there is taller than this wall\"],\"editor.toolHint.threeOrMoreWallsMeetHere.rgifif\":[\"Three or more walls meet here\"],\"editor.toolHint.traceCeilingOutline.1b639wv\":[\"Trace ceiling outline\"],\"editor.toolHint.traceSlabOutline.z0j2lc\":[\"Trace slab outline\"],\"editor.toolHint.type.1m2zofh\":[\"Type\"],\"editor.toolHint.typeAwning.1cj59al\":[\"Type: Awning\"],\"editor.toolHint.typeBarn.sbnygq\":[\"Type: Barn\"],\"editor.toolHint.typeBay.1200hc1\":[\"Type: Bay\"],\"editor.toolHint.typeBow.14s2t9p\":[\"Type: Bow\"],\"editor.toolHint.typeCabinet.1r68je5\":[\"Type: Cabinet\"],\"editor.toolHint.typeCasement.176uo9j\":[\"Type: Casement\"],\"editor.toolHint.typeDouble.af7w4i\":[\"Type: Double\"],\"editor.toolHint.typeDoubleHung.t482fu\":[\"Type: Double Hung\"],\"editor.toolHint.typeFixed.v1cjez\":[\"Type: Fixed\"],\"editor.toolHint.typeFolding.sxxyng\":[\"Type: Folding\"],\"editor.toolHint.typeFrench.1y57z1b\":[\"Type: French\"],\"editor.toolHint.typeHinged.1xrcfju\":[\"Type: Hinged\"],\"editor.toolHint.typeIsland.ufr3ce\":[\"Type: Island\"],\"editor.toolHint.typeLouvered.oxl1cz\":[\"Type: Louvered\"],\"editor.toolHint.typePocket.hc65x9\":[\"Type: Pocket\"],\"editor.toolHint.typeSingleHung.p53pyl\":[\"Type: Single Hung\"],\"editor.toolHint.typeSliding.nwmr3f\":[\"Type: Sliding\"],\"editor.toolHint.verticalStackClickToPlace.ijcn4q\":[\"Vertical stack ↕, click to place\"],\"editor.toolHint.wasteVent.1hvi3vj\":[\"Waste / vent\"],\"editor.toolHint.windowStyleClickOrPressL.ah7qm9\":[\"Window style — click or press L to cycle\"],\"editor.toolHint.windowTypeClickOrPressO.wv4x35\":[\"Window type — click or press O to cycle\"],\"editor.tools.distribution.distributionRunTool.lengthField\":[\"Length: \",[\"field\"],\" m\"],\"editor.tools.distribution.distributionRunTool.tabDirection\":[\"Tab: direction\"],\"editor.tools.distribution.distributionRunTool.typeALength\":[\"type a length\"],\"editor.views.plugins\":[\"Plugins\"],\"editor.views.view2d\":[\"2D\"],\"editor.views.view3d\":[\"3D\"],\"editor.views.viewBar.auto\":[\"Auto\"],\"editor.views.viewBar.browsePlugins\":[\"Browse plugins\"],\"editor.views.viewBar.closeLabel\":[\"Close \",[\"label\"]],\"editor.views.viewBar.closeSplit\":[\"Close split (\\\\)\"],\"editor.views.viewBar.ground\":[\"Ground\"],\"editor.views.viewBar.groundSolid\":[[\"standing\",\"select\",{\"partly\":[\"Partly below grade · ground solid\"],\"below\":[\"Below grade · ground solid\"],\"other\":[\"Ground solid\"]}]],\"editor.views.viewBar.groundVeiled\":[[\"standing\",\"select\",{\"partly\":[\"Partly below grade · ground veiled\"],\"below\":[\"Below grade · ground veiled\"],\"other\":[\"Ground veiled\"]}]],\"editor.views.viewBar.keepTheGroundSolid\":[\"Keep the ground solid\"],\"editor.views.viewBar.moreViews\":[\"More views\"],\"editor.views.viewBar.paneViews\":[\"Pane \",[\"pane\"],\" views\"],\"editor.views.viewBar.pinLabel\":[\"Pin \",[\"label\"]],\"editor.views.viewBar.pinToTheViewBar\":[\"Pin to the view bar\"],\"editor.views.viewBar.solid\":[\"Solid\"],\"editor.views.viewBar.split\":[\"Split (\\\\)\"],\"editor.views.viewBar.split2\":[\"Split\"],\"editor.views.viewBar.swapPanes\":[\"Swap panes\"],\"editor.views.viewBar.unpinFromTheViewBar\":[\"Unpin from the view bar\"],\"editor.views.viewBar.unpinLabel\":[\"Unpin \",[\"label\"]],\"editor.views.viewBar.veilTheGroundOverALevel\":[\"Veil the ground over a level below grade\"],\"editor.views.viewBar.views\":[\"Views\"],\"editor.youCanRetryTheSceneOr\":[\"You can retry the scene or return home without reloading the whole app shell.\"],\"node.beam.description\":[\"A framing beam drawn end to end: a porch header, a deck beam, a girder.\"],\"node.beam.label\":[\"Beam\"],\"node.beam.panel.actions\":[\"Actions\"],\"node.beam.panel.addPosts\":[\"Add posts\"],\"node.beam.panel.beam\":[\"Beam\"],\"node.beam.panel.beamSize\":[\"Beam \",[\"size\"]],\"node.beam.panel.delete\":[\"Delete\"],\"node.beam.panel.depth\":[\"Depth\"],\"node.beam.panel.dropped\":[\"Dropped\"],\"node.beam.panel.flush\":[\"Flush\"],\"node.beam.panel.followTheCeilingPlane\":[\"Follow the ceiling plane\"],\"node.beam.panel.lengthLongItsUnderside\":[[\"length\"],\" long; its underside \",[\"underside\"],\" over the floor, the ceiling plane \",[\"plane\"],\".\"],\"node.beam.panel.material\":[\"Material\"],\"node.beam.panel.move\":[\"Move\"],\"node.beam.panel.n1Ply\":[\"1 ply\"],\"node.beam.panel.placement\":[\"Placement\"],\"node.beam.panel.pliesPlies\":[[\"plies\"],\" plies\"],\"node.beam.panel.size\":[\"Size\"],\"node.beam.panel.top\":[\"Top\"],\"node.beam.panel.topElevation\":[\"Top elevation\"],\"node.beam.panel.width\":[\"Width\"],\"node.block.description\":[\"A topology-backed solid edited directly in the canvas.\"],\"node.block.label\":[\"Block\"],\"node.boxVent.description\":[\"Small louvered exhaust vent that sits on a roof slope.\"],\"node.boxVent.label\":[\"Box Vent\"],\"node.boxVent.panel.actions\":[\"Actions\"],\"node.boxVent.panel.baseFlange\":[\"Base Flange\"],\"node.boxVent.panel.baseHeight\":[\"Base Height\"],\"node.boxVent.panel.baseInset\":[\"Base Inset\"],\"node.boxVent.panel.box\":[\"Box\"],\"node.boxVent.panel.boxVent\":[\"Box Vent\"],\"node.boxVent.panel.cap\":[\"Cap\"],\"node.boxVent.panel.capHeight\":[\"Cap Height\"],\"node.boxVent.panel.cornerBevel\":[\"Corner Bevel\"],\"node.boxVent.panel.delete\":[\"Delete\"],\"node.boxVent.panel.depth\":[\"Depth\"],\"node.boxVent.panel.dimensions\":[\"Dimensions\"],\"node.boxVent.panel.dome\":[\"Dome\"],\"node.boxVent.panel.domeCurvature\":[\"Dome Curvature\"],\"node.boxVent.panel.duplicate\":[\"Duplicate\"],\"node.boxVent.panel.gapHeight\":[\"Gap Height\"],\"node.boxVent.panel.height\":[\"Height\"],\"node.boxVent.panel.hoodOverhang\":[\"Hood Overhang\"],\"node.boxVent.panel.move\":[\"Move\"],\"node.boxVent.panel.position\":[\"Position\"],\"node.boxVent.panel.rotation\":[\"Rotation\"],\"node.boxVent.panel.style\":[\"Style\"],\"node.boxVent.panel.topTaper\":[\"Top Taper\"],\"node.boxVent.panel.width\":[\"Width\"],\"node.building.description\":[\"A building container holding one or more levels.\"],\"node.building.label\":[\"Building\"],\"node.cabinet.compartmentCard.baskets\":[\"Baskets\"],\"node.cabinet.compartmentCard.bottom\":[\"Bottom\"],\"node.cabinet.compartmentCard.bottomFreezer\":[\"Bottom Freezer\"],\"node.cabinet.compartmentCard.bowls\":[\"Bowls\"],\"node.cabinet.compartmentCard.burnersOn\":[\"Burners on\"],\"node.cabinet.compartmentCard.chimney\":[\"Chimney\"],\"node.cabinet.compartmentCard.decrease\":[\"Decrease \",[\"label\"]],\"node.cabinet.compartmentCard.door\":[\"Door\"],\"node.cabinet.compartmentCard.double\":[\"Double\"],\"node.cabinet.compartmentCard.drawer\":[\"Drawer\"],\"node.cabinet.compartmentCard.drawers\":[\"Drawers\"],\"node.cabinet.compartmentCard.flipHinge\":[\"Flip hinge\"],\"node.cabinet.compartmentCard.flipTheDoorHingeToThe\":[\"Flip the door hinge to the opposite side\"],\"node.cabinet.compartmentCard.fridge\":[\"Fridge\"],\"node.cabinet.compartmentCard.gas\":[\"Gas\"],\"node.cabinet.compartmentCard.glass\":[\"Glass\"],\"node.cabinet.compartmentCard.height\":[\"Height\"],\"node.cabinet.compartmentCard.hob\":[\"Hob\"],\"node.cabinet.compartmentCard.increase\":[\"Increase \",[\"label\"]],\"node.cabinet.compartmentCard.induction\":[\"Induction\"],\"node.cabinet.compartmentCard.layout\":[\"Layout\"],\"node.cabinet.compartmentCard.left\":[\"Left\"],\"node.cabinet.compartmentCard.micro\":[\"Micro\"],\"node.cabinet.compartmentCard.moveDown\":[\"Move down\"],\"node.cabinet.compartmentCard.moveUp\":[\"Move up\"],\"node.cabinet.compartmentCard.oven\":[\"Oven\"],\"node.cabinet.compartmentCard.pullout\":[\"Pullout\"],\"node.cabinet.compartmentCard.rack\":[\"Rack\"],\"node.cabinet.compartmentCard.removeCompartment\":[\"Remove compartment\"],\"node.cabinet.compartmentCard.right\":[\"Right\"],\"node.cabinet.compartmentCard.shelf\":[\"Shelf\"],\"node.cabinet.compartmentCard.shelves\":[\"Shelves\"],\"node.cabinet.compartmentCard.shelvesInside\":[\"Shelves inside\"],\"node.cabinet.compartmentCard.single\":[\"Single\"],\"node.cabinet.compartmentCard.sink\":[\"Sink\"],\"node.cabinet.compartmentCard.style\":[\"Style\"],\"node.cabinet.compartmentCard.surface\":[\"Surface\"],\"node.cabinet.compartmentCard.top\":[\"Top\"],\"node.cabinet.compartmentCard.topFreezer\":[\"Top Freezer\"],\"node.cabinet.compartmentCard.topGrate\":[\"Top grate\"],\"node.cabinet.compartmentCard.tray\":[\"Tray\"],\"node.cabinet.compartmentCard.type\":[\"Type\"],\"node.cabinet.compartmentCard.washer\":[\"Washer\"],\"node.cabinet.compartmentCard.wire\":[\"Wire\"],\"node.cabinet.description\":[\"A configurable parametric base cabinet.\"],\"node.cabinet.essentialControls.bottomFreezer\":[\"Bottom freezer\"],\"node.cabinet.essentialControls.cooktopType\":[\"Cooktop type\"],\"node.cabinet.essentialControls.doorDirection\":[\"Door direction\"],\"node.cabinet.essentialControls.double\":[\"Double\"],\"node.cabinet.essentialControls.fridgeType\":[\"Fridge type\"],\"node.cabinet.essentialControls.gas\":[\"Gas\"],\"node.cabinet.essentialControls.glass\":[\"Glass\"],\"node.cabinet.essentialControls.induction\":[\"Induction\"],\"node.cabinet.essentialControls.itemOptions\":[\"Item options\"],\"node.cabinet.essentialControls.leftHinge\":[\"Left hinge\"],\"node.cabinet.essentialControls.rightHinge\":[\"Right hinge\"],\"node.cabinet.essentialControls.single\":[\"Single\"],\"node.cabinet.essentialControls.sinkLayout\":[\"Sink layout\"],\"node.cabinet.essentialControls.topFreezer\":[\"Top freezer\"],\"node.cabinet.kitchenCollection.chooseLabel\":[\"Choose \",[\"label\"]],\"node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge\":[\"No kitchen items match. Try “fridge”, “sink”, or “cabinet”.\"],\"node.cabinet.kitchenCollection.searchFridgeSinkCabinets\":[\"Search fridge, sink, cabinets…\"],\"node.cabinet.kitchenCollection.searchKitchenItems\":[\"Search kitchen items\"],\"node.cabinet.kitchenGroup.appliances\":[\"Appliances & fixtures\"],\"node.cabinet.kitchenGroup.cabinets\":[\"Cabinets\"],\"node.cabinet.kitchenModuleActions.addBeside\":[\"Add beside\"],\"node.cabinet.kitchenModuleActions.addBesideSelectedItem\":[\"Add beside selected item\"],\"node.cabinet.kitchenModuleActions.addLabel\":[\"Add \",[\"label\"]],\"node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis\":[\"An upper cabinet attached to this item will be removed to make room.\"],\"node.cabinet.kitchenModuleActions.cabinet\":[\"Cabinet\"],\"node.cabinet.kitchenModuleActions.cancel\":[\"Cancel\"],\"node.cabinet.kitchenModuleActions.cannotFit\":[\"This \",[\"width\"],\" item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again.\"],\"node.cabinet.kitchenModuleActions.insertsBeside\":[[\"side\",\"select\",{\"left\":[\"Inserts on the left of the selected item.\"],\"other\":[\"Inserts on the right of the selected item.\"]}]],\"node.cabinet.kitchenModuleActions.kitchenChanged\":[\"The kitchen changed. Select an item again to refresh the preview.\"],\"node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs\":[\"Kitchen run preview, highlighted item is the proposed change\"],\"node.cabinet.kitchenModuleActions.leftSide\":[\"Left side\"],\"node.cabinet.kitchenModuleActions.moved\":[[\"name\"],\": moves \",[\"distance\"]],\"node.cabinet.kitchenModuleActions.previewItem\":[[\"name\"],\": \",[\"width\"]],\"node.cabinet.kitchenModuleActions.replaceSelectedItem\":[\"Replace selected item\"],\"node.cabinet.kitchenModuleActions.replaceWith\":[\"Replace with…\"],\"node.cabinet.kitchenModuleActions.replaceWithLabel\":[\"Replace with \",[\"label\"]],\"node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem\":[\"Replaces only the selected item.\"],\"node.cabinet.kitchenModuleActions.resized\":[[\"name\"],\": \",[\"before\"],\" → \",[\"after\"]],\"node.cabinet.kitchenModuleActions.rightSide\":[\"Right side\"],\"node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen\":[\"Unable to update this kitchen.\"],\"node.cabinet.label\":[\"Modular Cabinet\"],\"node.cabinet.panel.aboveCeiling\":[\"Finished height extends \",[\"overflow\"],\" mm above the ceiling.\"],\"node.cabinet.panel.addChimney\":[\"Add chimney\"],\"node.cabinet.panel.addCompartment\":[\"Add compartment\"],\"node.cabinet.panel.addWallCabinet\":[\"Add wall cabinet\"],\"node.cabinet.panel.advanced\":[\"Advanced\"],\"node.cabinet.panel.appliance\":[\"Appliance\"],\"node.cabinet.panel.applianceFront\":[\"Appliance front\"],\"node.cabinet.panel.auto\":[\"Auto\"],\"node.cabinet.panel.bar\":[\"Bar\"],\"node.cabinet.panel.baseCabinet\":[\"Base Cabinet\"],\"node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview\":[\"Cabinet construction, compartments, and opening preview.\"],\"node.cabinet.panel.cabinetPanel\":[\"Cabinet panel\"],\"node.cabinet.panel.cabinetType\":[\"Cabinet Type\"],\"node.cabinet.panel.center\":[\"Center\"],\"node.cabinet.panel.close\":[\"Close\"],\"node.cabinet.panel.closeCabinet\":[\"Close cabinet\"],\"node.cabinet.panel.commonWallCabinetHeightsUseThe\":[\"Common wall-cabinet heights. Use the slider below for a custom height.\"],\"node.cabinet.panel.compartments\":[\"Compartments\"],\"node.cabinet.panel.cutout\":[\"Cutout\"],\"node.cabinet.panel.depth\":[\"Depth\"],\"node.cabinet.panel.dimensions\":[\"Dimensions\"],\"node.cabinet.panel.editKitchenRun\":[\"Edit kitchen run\"],\"node.cabinet.panel.fillToCeiling\":[\"Fill to ceiling\"],\"node.cabinet.panel.finish\":[\"Finish\"],\"node.cabinet.panel.frontConstruction\":[\"Front construction\"],\"node.cabinet.panel.handles\":[\"Handles\"],\"node.cabinet.panel.height\":[\"Height\"],\"node.cabinet.panel.heightPreset\":[\"Height preset\"],\"node.cabinet.panel.hole\":[\"Hole\"],\"node.cabinet.panel.inset\":[\"Inset\"],\"node.cabinet.panel.kitchen\":[\"Kitchen\"],\"node.cabinet.panel.kitchenRun\":[\"Kitchen run\"],\"node.cabinet.panel.knob\":[\"Knob\"],\"node.cabinet.panel.mounting\":[\"Mounting\"],\"node.cabinet.panel.none\":[\"None\"],\"node.cabinet.panel.open\":[\"Open\"],\"node.cabinet.panel.openAnimation\":[\"Open Animation\"],\"node.cabinet.panel.openCabinet\":[\"Open cabinet\"],\"node.cabinet.panel.overlay\":[\"Overlay\"],\"node.cabinet.panel.panelReady\":[\"Panel-ready\"],\"node.cabinet.panel.planningChecks\":[\"Planning checks\"],\"node.cabinet.panel.play\":[\"Play\"],\"node.cabinet.panel.playAnimation\":[\"Play animation\"],\"node.cabinet.panel.position\":[\"Position\"],\"node.cabinet.panel.raisedArch\":[\"Raised Arch\"],\"node.cabinet.panel.removeWallCabinet\":[\"Remove wall cabinet\"],\"node.cabinet.panel.replaceWith\":[\"Replace with…\"],\"node.cabinet.panel.revealGap\":[\"Reveal gap\"],\"node.cabinet.panel.shaker\":[\"Shaker\"],\"node.cabinet.panel.sharedCountertopPlinthAndFinishes\":[\"Shared countertop, plinth, and finishes\"],\"node.cabinet.panel.slab\":[\"Slab\"],\"node.cabinet.panel.solidMatchingFrontBetweenTheCabinet\":[\"Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler.\"],\"node.cabinet.panel.standardWidth\":[\"Standard width\"],\"node.cabinet.panel.stop\":[\"Stop\"],\"node.cabinet.panel.stopAnimation\":[\"Stop animation\"],\"node.cabinet.panel.style\":[\"Style\"],\"node.cabinet.panel.tallCabinet\":[\"Tall Cabinet\"],\"node.cabinet.panel.top\":[\"Top\"],\"node.cabinet.panel.topCabinet\":[\"Top Cabinet\"],\"node.cabinet.panel.topCeiling\":[\"Top / Ceiling\"],\"node.cabinet.panel.topDepth\":[\"Top depth\"],\"node.cabinet.panel.topHeight\":[\"Top height\"],\"node.cabinet.panel.trimSoffit\":[\"Trim / Soffit\"],\"node.cabinet.panel.usesTheCabinetFrontStyleReveal\":[\"Uses the cabinet front style, reveal, handle, and front material settings below.\"],\"node.cabinet.panel.wallFiller\":[\"Wall filler\"],\"node.cabinet.panel.width\":[\"Width\"],\"node.cabinet.preset.baseCabinet\":[\"Base cabinet\"],\"node.cabinet.preset.dishwasher\":[\"Dishwasher\"],\"node.cabinet.preset.drawerCabinet\":[\"Drawer cabinet\"],\"node.cabinet.preset.fridge\":[\"Fridge\"],\"node.cabinet.preset.gasCooktop\":[\"Gas cooktop\"],\"node.cabinet.preset.inductionCooktop\":[\"Induction cooktop\"],\"node.cabinet.preset.ovenTower\":[\"Oven Tower\"],\"node.cabinet.preset.sinkCabinet\":[\"Sink cabinet\"],\"node.cabinet.preset.tallPantry\":[\"Tall Pantry\"],\"node.cabinet.preset.wallCabinet\":[\"Wall cabinet\"],\"node.cabinet.runPanel.addLeft\":[\"Add left\"],\"node.cabinet.runPanel.addRight\":[\"Add right\"],\"node.cabinet.runPanel.alignCabinetsHandles\":[\"Align cabinets & handles\"],\"node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop\":[\"Applies depth, carcass, plinth, and countertop thickness to this run.\"],\"node.cabinet.runPanel.arrayCreate\":[[\"count\",\"plural\",{\"one\":[\"Create \",[\"number\"],\" module copy\"],\"other\":[\"Create \",[\"number\"],\" module copies\"]}]],\"node.cabinet.runPanel.arrayCreateNamed\":[[\"count\",\"plural\",{\"one\":[\"Create \",[\"number\"],\" \",[\"name\"],\" copy\"],\"other\":[\"Create \",[\"number\"],\" \",[\"name\"],\" copies\"]}]],\"node.cabinet.runPanel.arrayInvalidOptions\":[\"Choose a copy count from 1 to 20 and spacing from 0 to 2 m\"],\"node.cabinet.runPanel.arrayNoRoom\":[\"There is not enough room in this run for the requested array\"],\"node.cabinet.runPanel.arrayNoSource\":[\"Choose a standard or appliance module as the source\"],\"node.cabinet.runPanel.auto\":[\"Auto\"],\"node.cabinet.runPanel.back\":[\"Back\"],\"node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping\":[\"Balances standard base cabinets while keeping appliance and corner-filler widths fixed.\"],\"node.cabinet.runPanel.bar\":[\"Bar\"],\"node.cabinet.runPanel.barCounter\":[\"Bar counter\"],\"node.cabinet.runPanel.barDepth\":[\"Bar depth\"],\"node.cabinet.runPanel.barHeight\":[\"Bar height\"],\"node.cabinet.runPanel.carcassHeight\":[\"Carcass height\"],\"node.cabinet.runPanel.center\":[\"Center\"],\"node.cabinet.runPanel.commonWallCabinetHeightsUseThe\":[\"Common wall-cabinet heights. Use the slider below for a custom height.\"],\"node.cabinet.runPanel.copies\":[\"Copies\"],\"node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure\":[\"Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space.\"],\"node.cabinet.runPanel.cornerFillersCannotBeUsedAs\":[\"Corner fillers cannot be used as array sources\"],\"node.cabinet.runPanel.countertopDepth\":[\"Countertop depth\"],\"node.cabinet.runPanel.countertopHeight\":[\"Countertop height\"],\"node.cabinet.runPanel.createArray\":[\"Create array\"],\"node.cabinet.runPanel.cutout\":[\"Cutout\"],\"node.cabinet.runPanel.deleteModule\":[\"Delete \",[\"name\"]],\"node.cabinet.runPanel.depth\":[\"Depth\"],\"node.cabinet.runPanel.direction\":[\"Direction\"],\"node.cabinet.runPanel.duplicateAlongRun\":[\"Duplicate along run\"],\"node.cabinet.runPanel.equalizeAll\":[\"Equalize all resizeable standard base cabinets in this run\"],\"node.cabinet.runPanel.equalizeAlreadyEqual\":[\"The resizeable cabinet widths are already equal\"],\"node.cabinet.runPanel.equalizeNeedsTwo\":[\"At least two standard base cabinets are required\"],\"node.cabinet.runPanel.equalizeNoFit\":[\"The available run width cannot satisfy the cabinet width limits\"],\"node.cabinet.runPanel.equalizeWidths\":[\"Equalize widths\"],\"node.cabinet.runPanel.fillerPlanSide\":[[\"side\",\"select\",{\"left\":[\"Left: \",[\"width\"],\" mm\"],\"other\":[\"Right: \",[\"width\"],\" mm\"]}]],\"node.cabinet.runPanel.finishAlign\":[\"Finish & align\"],\"node.cabinet.runPanel.finishedBack\":[\"Finished back\"],\"node.cabinet.runPanel.finishedExposedEnds\":[\"Finished exposed ends\"],\"node.cabinet.runPanel.fitWallFillers\":[\"Fit wall fillers\"],\"node.cabinet.runPanel.fits50150MmGapsAt\":[\"Fits 50–150 mm gaps at run ends beside perpendicular walls.\"],\"node.cabinet.runPanel.fronts\":[\"Fronts\"],\"node.cabinet.runPanel.handles\":[\"Handles\"],\"node.cabinet.runPanel.heightPreset\":[\"Height preset\"],\"node.cabinet.runPanel.hole\":[\"Hole\"],\"node.cabinet.runPanel.inset\":[\"Inset\"],\"node.cabinet.runPanel.islandBar\":[\"Island & Bar\"],\"node.cabinet.runPanel.kitchenRun\":[\"Kitchen run\"],\"node.cabinet.runPanel.knob\":[\"Knob\"],\"node.cabinet.runPanel.left\":[\"Left\"],\"node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd\":[\"Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments.\"],\"node.cabinet.runPanel.metricProfile\":[\"Metric · 600 mm\"],\"node.cabinet.runPanel.moduleNumber\":[\"Module \",[\"number\"]],\"node.cabinet.runPanel.modules\":[\"Modules\"],\"node.cabinet.runPanel.mounting\":[\"Mounting\"],\"node.cabinet.runPanel.noEligibleModule\":[\"No eligible module\"],\"node.cabinet.runPanel.none\":[\"None\"],\"node.cabinet.runPanel.overlay\":[\"Overlay\"],\"node.cabinet.runPanel.plinthHeight\":[\"Plinth height\"],\"node.cabinet.runPanel.position\":[\"Position\"],\"node.cabinet.runPanel.raisedArch\":[\"Raised Arch\"],\"node.cabinet.runPanel.revealGap\":[\"Reveal gap\"],\"node.cabinet.runPanel.right\":[\"Right\"],\"node.cabinet.runPanel.seatingOverhang\":[\"Seating overhang\"],\"node.cabinet.runPanel.shaker\":[\"Shaker\"],\"node.cabinet.runPanel.sharedPlinthCountertop\":[\"Shared Plinth & Countertop\"],\"node.cabinet.runPanel.showCountertop\":[\"Show countertop\"],\"node.cabinet.runPanel.showPlinth\":[\"Show plinth\"],\"node.cabinet.runPanel.slab\":[\"Slab\"],\"node.cabinet.runPanel.sourceModule\":[\"Source module\"],\"node.cabinet.runPanel.spacing\":[\"Spacing\"],\"node.cabinet.runPanel.standardDimensions\":[\"Standard dimensions\"],\"node.cabinet.runPanel.style\":[\"Style\"],\"node.cabinet.runPanel.summaryCompartment\":[[\"type\",\"select\",{\"shelf\":[\"Shelf\"],\"drawer\":[\"Drawer\"],\"door\":[\"Door\"],\"oven\":[\"Oven\"],\"microwave\":[\"Microwave\"],\"dishwasher\":[\"Dishwasher\"],\"sink\":[\"Sink\"],\"cooktop_gas\":[\"Gas cooktop\"],\"cooktop_induction\":[\"Induction cooktop\"],\"pull_out_pantry\":[\"Pull-out pantry\"],\"fridge_single\":[\"Fridge\"],\"fridge_double\":[\"Double fridge\"],\"fridge_top_freezer\":[\"Top-freezer fridge\"],\"fridge_bottom_freezer\":[\"Bottom-freezer fridge\"],\"hood_pyramid\":[\"Pyramid hood\"],\"hood_curved_glass\":[\"Curved glass hood\"],\"other\":[[\"raw\"]]}]],\"node.cabinet.runPanel.summaryCompartments\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" compartment\"],\"other\":[[\"number\"],\" compartments\"]}]],\"node.cabinet.runPanel.summaryEmpty\":[\"Empty\"],\"node.cabinet.runPanel.summaryTall\":[\"Tall cabinet\"],\"node.cabinet.runPanel.top\":[\"Top\"],\"node.cabinet.runPanel.unableToFinish\":[\"Unable to finish this kitchen run.\"],\"node.cabinet.runPanel.usProfile\":[\"US · 24 in\"],\"node.cabinet.runPanel.useAsArraySource\":[\"Use \",[\"name\"],\" as array source\"],\"node.cabinet.runPanel.useThisModuleAsTheArray\":[\"Use this module as the array source\"],\"node.cabinet.runPanel.waterfallEnds\":[\"Waterfall ends\"],\"node.cabinetModule.description\":[\"An editable module inside a modular cabinet run.\"],\"node.cabinetModule.label\":[\"Cabinet Module\"],\"node.ceiling.buildUpSection.bottomChord\":[\"Bottom chord\"],\"node.ceiling.buildUpSection.ceilingJoists\":[\"Ceiling joists\"],\"node.ceiling.buildUpSection.decking\":[\"Decking\"],\"node.ceiling.buildUpSection.finish\":[\"Finish\"],\"node.ceiling.buildUpSection.framing\":[\"Framing\"],\"node.ceiling.buildUpSection.joists\":[\"Joists\"],\"node.ceiling.buildUpSection.layers\":[\"Layers\"],\"node.ceiling.buildUpSection.none\":[\"None\"],\"node.ceiling.buildUpSection.preset\":[\"Preset\"],\"node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside\":[\"The height is the finished underside; the layers rise from it.\"],\"node.ceiling.buildUpSection.thickness\":[\"Thickness\"],\"node.ceiling.buildUpSection.trussBottomChords\":[\"Truss bottom chords\"],\"node.ceiling.description\":[\"A polygon-bounded ceiling surface that hosts ceiling-mounted items.\"],\"node.ceiling.label\":[\"Ceiling\"],\"node.ceiling.panel.addHole\":[\"Add Hole\"],\"node.ceiling.panel.area\":[\"Area\"],\"node.ceiling.panel.auto\":[\"Auto\"],\"node.ceiling.panel.ceiling\":[\"Ceiling\"],\"node.ceiling.panel.customHeight\":[\"Custom height\"],\"node.ceiling.panel.cutOpening\":[\"Cut opening\"],\"node.ceiling.panel.done\":[\"Done\"],\"node.ceiling.panel.followsLevel\":[\"Follows level\"],\"node.ceiling.panel.height\":[\"Height\"],\"node.ceiling.panel.high30m\":[\"High (3.0m)\"],\"node.ceiling.panel.high90\":[\"High (9'0\\\")\"],\"node.ceiling.panel.holes\":[\"Holes\"],\"node.ceiling.panel.info\":[\"Info\"],\"node.ceiling.panel.limitedByLevel\":[\"Limited by the level to \",[\"height\"],\" — raise the level height for a taller ceiling.\"],\"node.ceiling.panel.low24m\":[\"Low (2.4m)\"],\"node.ceiling.panel.low80\":[\"Low (8'0\\\")\"],\"node.ceiling.panel.move\":[\"Move\"],\"node.ceiling.panel.noHoles\":[\"No holes\"],\"node.ceiling.panel.standard25m\":[\"Standard (2.5m)\"],\"node.ceiling.panel.standard86\":[\"Standard (8'6\\\")\"],\"node.ceiling.panel.tallerThanLevel\":[\"Taller than this level (\",[\"available\"],\" available). Raise the level height first.\"],\"node.chimney.description\":[\"Vertical masonry stack on a roof segment.\"],\"node.chimney.label\":[\"Chimney\"],\"node.chimney.panel.aboveRidge\":[\"Above Ridge\"],\"node.chimney.panel.actions\":[\"Actions\"],\"node.chimney.panel.back\":[\"Back\"],\"node.chimney.panel.bands\":[\"Bands\"],\"node.chimney.panel.cap\":[\"Cap\"],\"node.chimney.panel.chimney\":[\"Chimney\"],\"node.chimney.panel.chimneyType\":[\"Chimney Type\"],\"node.chimney.panel.corbeled\":[\"Corbeled\"],\"node.chimney.panel.cornerBevel\":[\"Corner Bevel\"],\"node.chimney.panel.count\":[\"Count\"],\"node.chimney.panel.cricket\":[\"Cricket\"],\"node.chimney.panel.cutoutOffset\":[\"Cutout Offset\"],\"node.chimney.panel.delete\":[\"Delete\"],\"node.chimney.panel.depth\":[\"Depth\"],\"node.chimney.panel.diameter\":[\"Diameter\"],\"node.chimney.panel.double\":[\"Double\"],\"node.chimney.panel.extent\":[\"Extent\"],\"node.chimney.panel.flat\":[\"Flat\"],\"node.chimney.panel.flues\":[\"Flues\"],\"node.chimney.panel.footprint\":[\"Footprint\"],\"node.chimney.panel.front\":[\"Front\"],\"node.chimney.panel.height\":[\"Height\"],\"node.chimney.panel.hollowDepth\":[\"Hollow Depth\"],\"node.chimney.panel.length\":[\"Length\"],\"node.chimney.panel.none\":[\"None\"],\"node.chimney.panel.offset\":[\"Offset\"],\"node.chimney.panel.overhang\":[\"Overhang\"],\"node.chimney.panel.panels\":[\"Panels\"],\"node.chimney.panel.position\":[\"Position\"],\"node.chimney.panel.rectangular\":[\"Rectangular\"],\"node.chimney.panel.rotation\":[\"Rotation\"],\"node.chimney.panel.round\":[\"Round\"],\"node.chimney.panel.shoulder\":[\"Shoulder\"],\"node.chimney.panel.sideMargin\":[\"Side Margin\"],\"node.chimney.panel.simple\":[\"Simple\"],\"node.chimney.panel.single\":[\"Single\"],\"node.chimney.panel.sloped\":[\"Sloped\"],\"node.chimney.panel.spacing\":[\"Spacing\"],\"node.chimney.panel.square\":[\"Square\"],\"node.chimney.panel.stepped\":[\"Stepped\"],\"node.chimney.panel.style\":[\"Style\"],\"node.chimney.panel.tapered\":[\"Tapered\"],\"node.chimney.panel.thickness\":[\"Thickness\"],\"node.chimney.panel.topOffset\":[\"Top Offset\"],\"node.chimney.panel.wallThickness\":[\"Wall Thickness\"],\"node.chimney.panel.width\":[\"Width\"],\"node.chimney.preset.brick\":[\"Brick\"],\"node.chimney.preset.modern\":[\"Modern\"],\"node.chimney.preset.round\":[\"Round\"],\"node.column.description\":[\"A parametric column with configurable cross-section, base, and capital.\"],\"node.column.label\":[\"Column\"],\"node.column.panel.aFrame\":[\"A-Frame\"],\"node.column.panel.actions\":[\"Actions\"],\"node.column.panel.applyPreset\":[\"Apply preset...\"],\"node.column.panel.applyProportion\":[\"Apply proportion...\"],\"node.column.panel.bottomDepth\":[\"Bottom Depth\"],\"node.column.panel.bottomHeight\":[\"Bottom Height\"],\"node.column.panel.bottomSpread\":[\"Bottom Spread\"],\"node.column.panel.bottomStepSpread\":[\"Bottom Step Spread\"],\"node.column.panel.bottomTiers\":[\"Bottom Tiers\"],\"node.column.panel.bottomWidth\":[\"Bottom Width\"],\"node.column.panel.boxFrame\":[\"Box Frame\"],\"node.column.panel.braceDepth\":[\"Brace Depth\"],\"node.column.panel.braceWidth\":[\"Brace Width\"],\"node.column.panel.bulge\":[\"Bulge\"],\"node.column.panel.bulged\":[\"Bulged\"],\"node.column.panel.column\":[\"Column\"],\"node.column.panel.connectorPlates\":[\"Connector Plates\"],\"node.column.panel.delete\":[\"Delete\"],\"node.column.panel.depth\":[\"Depth\"],\"node.column.panel.dimensions\":[\"Dimensions\"],\"node.column.panel.edgeSoftness\":[\"Edge Softness\"],\"node.column.panel.endWidth\":[\"End Width\"],\"node.column.panel.ends\":[\"Ends\"],\"node.column.panel.forkSpread\":[\"Fork Spread\"],\"node.column.panel.heavy\":[\"Heavy\"],\"node.column.panel.height\":[\"Height\"],\"node.column.panel.heightAndFootprintAreControlledBy\":[\"Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to.\"],\"node.column.panel.hourglass\":[\"Hourglass\"],\"node.column.panel.kBrace\":[\"K Brace\"],\"node.column.panel.move\":[\"Move\"],\"node.column.panel.neckWidth\":[\"Neck Width\"],\"node.column.panel.noBottom\":[\"No Bottom\"],\"node.column.panel.noTop\":[\"No Top\"],\"node.column.panel.plinthThickness\":[\"Plinth Thickness\"],\"node.column.panel.portalFrame\":[\"Portal Frame\"],\"node.column.panel.preset\":[\"Preset\"],\"node.column.panel.rectangular\":[\"Rectangular\"],\"node.column.panel.ringPairs\":[\"Ring Pairs\"],\"node.column.panel.ringSpread\":[\"Ring Spread\"],\"node.column.panel.ringThickness\":[\"Ring Thickness\"],\"node.column.panel.round\":[\"Round\"],\"node.column.panel.roundBandWidth\":[\"Round Band Width\"],\"node.column.panel.roundedBottom\":[\"Rounded Bottom\"],\"node.column.panel.roundedTop\":[\"Rounded Top\"],\"node.column.panel.segmentTwist\":[\"Segment Twist\"],\"node.column.panel.shaft\":[\"Shaft\"],\"node.column.panel.shaftCornerRadius\":[\"Shaft Corner Radius\"],\"node.column.panel.shaftWidth\":[\"Shaft Width\"],\"node.column.panel.shape\":[\"Shape\"],\"node.column.panel.shortStout\":[\"Short / Stout\"],\"node.column.panel.simpleBlockBottom\":[\"Simple Block Bottom\"],\"node.column.panel.simpleTop\":[\"Simple Top\"],\"node.column.panel.singleStrut\":[\"Single Strut\"],\"node.column.panel.slender\":[\"Slender\"],\"node.column.panel.square\":[\"Square\"],\"node.column.panel.squarePlinthBottom\":[\"Square Plinth Bottom\"],\"node.column.panel.standard\":[\"Standard\"],\"node.column.panel.steppedBottom\":[\"Stepped Bottom\"],\"node.column.panel.steppedTop\":[\"Stepped Top\"],\"node.column.panel.straight\":[\"Straight\"],\"node.column.panel.taper\":[\"Taper\"],\"node.column.panel.tapered\":[\"Tapered\"],\"node.column.panel.topDepth\":[\"Top Depth\"],\"node.column.panel.topHeight\":[\"Top Height\"],\"node.column.panel.topSpread\":[\"Top Spread\"],\"node.column.panel.topStepSpread\":[\"Top Step Spread\"],\"node.column.panel.topTiers\":[\"Top Tiers\"],\"node.column.panel.topWidth\":[\"Top Width\"],\"node.column.panel.transform\":[\"Transform\"],\"node.column.panel.trestle\":[\"Trestle\"],\"node.column.panel.tripod\":[\"Tripod\"],\"node.column.panel.twistSegments\":[\"Twist Segments\"],\"node.column.panel.vSupport\":[\"V Support\"],\"node.column.panel.vertical\":[\"Vertical\"],\"node.column.panel.waist\":[\"Waist\"],\"node.column.panel.width\":[\"Width\"],\"node.column.panel.xBrace\":[\"X Brace\"],\"node.column.panel.ySupport\":[\"Y Support\"],\"node.column.panel.yaw\":[\"Yaw\"],\"node.column.postSection.aSizeDeckPostStandsNo\":[\"A \",[\"size\"],\" deck post stands no taller than \",[\"limit\"],\" (IRC Table R507.4, by species and the area it carries): check it, or use the next size up.\"],\"node.column.postSection.custom\":[\"Custom\"],\"node.column.postSection.fitToTheBeamOverIt\":[\"Fit to the beam over it (\",[\"height\"],\")\"],\"node.column.postSection.fitToTheCeilingPlane\":[\"Fit to the ceiling plane (\",[\"height\"],\")\"],\"node.column.postSection.height\":[\"Height\"],\"node.column.postSection.keepItsShapeAndStyleIt\":[\"Keep its shape and style it as a column\"],\"node.column.postSection.makeItAColumn\":[\"Make it a column\"],\"node.column.postSection.post\":[\"Post\"],\"node.column.postSection.treated\":[\"Treated\"],\"node.column.postSection.untreated\":[\"Untreated\"],\"node.column.preset.aFrameSupport\":[\"A-Frame Support\"],\"node.column.preset.basicPillar\":[\"Straight Round\"],\"node.column.preset.boxFrameSupport\":[\"Box Frame\"],\"node.column.preset.bulgedPillar\":[\"Soft Bulged\"],\"node.column.preset.hourglassPillar\":[\"Hourglass\"],\"node.column.preset.kBraceSupport\":[\"K Brace\"],\"node.column.preset.portalFrameSupport\":[\"Portal Frame\"],\"node.column.preset.singleStrutSupport\":[\"Single Strut\"],\"node.column.preset.squarePillar\":[\"Square Block\"],\"node.column.preset.taperedPillar\":[\"Tapered Round\"],\"node.column.preset.trestleSupport\":[\"Trestle Frame\"],\"node.column.preset.tripodSupport\":[\"Tripod Support\"],\"node.column.preset.vFrameSupport\":[\"V Support\"],\"node.column.preset.xBraceSupport\":[\"X Brace\"],\"node.column.preset.yFrameSupport\":[\"Y Support\"],\"node.constructionDimension.description\":[\"Associative linear, curved, circular, angular, or coordinate plan dimension.\"],\"node.constructionDimension.label\":[\"Construction Dimension\"],\"node.cornerRadius.bottomLeft\":[\"Bottom Left\"],\"node.cornerRadius.bottomRight\":[\"Bottom Right\"],\"node.cornerRadius.topLeft\":[\"Top Left\"],\"node.cornerRadius.topRight\":[\"Top Right\"],\"node.cupola.description\":[\"Louvered roof lantern with a dome or pyramid cap and optional finial.\"],\"node.cupola.label\":[\"Cupola\"],\"node.cupola.panel.actions\":[\"Actions\"],\"node.cupola.panel.cupola\":[\"Cupola\"],\"node.cupola.panel.delete\":[\"Delete\"],\"node.cupola.panel.depth\":[\"Depth\"],\"node.cupola.panel.dimensions\":[\"Dimensions\"],\"node.cupola.panel.dome\":[\"Dome\"],\"node.cupola.panel.duplicate\":[\"Duplicate\"],\"node.cupola.panel.finial\":[\"Finial\"],\"node.cupola.panel.height\":[\"Height\"],\"node.cupola.panel.move\":[\"Move\"],\"node.cupola.panel.noFinial\":[\"No Finial\"],\"node.cupola.panel.position\":[\"Position\"],\"node.cupola.panel.pyramid\":[\"Pyramid\"],\"node.cupola.panel.rotation\":[\"Rotation\"],\"node.cupola.panel.style\":[\"Style\"],\"node.cupola.panel.width\":[\"Width\"],\"node.cutout.autoElevator\":[\"Auto elevator cutout\"],\"node.cutout.autoStair\":[\"Auto stair cutout\"],\"node.cutout.opening\":[\"Opening\"],\"node.cutout.room\":[\"Room cutout\"],\"node.door.actions.swapHingeSide\":[\"Swap hinge side\"],\"node.door.actions.swapSwingSide\":[\"Swap swing side\"],\"node.door.description\":[\"A door cut into a wall. Animated open/close state.\"],\"node.door.label\":[\"Door\"],\"node.door.panel.actions\":[\"Actions\"],\"node.door.panel.addSegment\":[\"+ Add Segment\"],\"node.door.panel.all\":[\"All\"],\"node.door.panel.arch\":[\"Arch\"],\"node.door.panel.archHeight\":[\"Arch Height\"],\"node.door.panel.auto\":[\"Auto\"],\"node.door.panel.autoFollowsTheWall\":[\"Auto follows the wall\"],\"node.door.panel.barHeight\":[\"Bar Height\"],\"node.door.panel.columns\":[\"Columns\"],\"node.door.panel.contentPadding\":[\"Content Padding\"],\"node.door.panel.cornerRadius\":[\"Corner Radius\"],\"node.door.panel.customTheSegmentsMatchNoStyle\":[\"Custom: the segments match no style.\"],\"node.door.panel.delete\":[\"Delete\"],\"node.door.panel.depth\":[\"Depth\"],\"node.door.panel.dimensions\":[\"Dimensions\"],\"node.door.panel.direction\":[\"Direction\"],\"node.door.panel.divider\":[\"Divider\"],\"node.door.panel.door\":[\"Door\"],\"node.door.panel.doorCloser\":[\"Door Closer\"],\"node.door.panel.duplicate\":[\"Duplicate\"],\"node.door.panel.empty\":[\"Empty\"],\"node.door.panel.enableHandle\":[\"Enable Handle\"],\"node.door.panel.enableThreshold\":[\"Enable Threshold\"],\"node.door.panel.flipSide\":[\"Flip Side\"],\"node.door.panel.fold\":[\"Fold\"],\"node.door.panel.frame\":[\"Frame\"],\"node.door.panel.frameDepthAuto\":[\"Depth (auto)\"],\"node.door.panel.garage\":[\"Garage\"],\"node.door.panel.glass\":[\"Glass\"],\"node.door.panel.handle\":[\"Handle\"],\"node.door.panel.handleSide\":[\"Handle Side\"],\"node.door.panel.hardware\":[\"Hardware\"],\"node.door.panel.height\":[\"Height\"],\"node.door.panel.hingesSide\":[\"Hinges Side\"],\"node.door.panel.horizontal\":[\"Horizontal\"],\"node.door.panel.individual\":[\"Individual\"],\"node.door.panel.inset\":[\"Inset\"],\"node.door.panel.inward\":[\"Inward\"],\"node.door.panel.left\":[\"Left\"],\"node.door.panel.move\":[\"Move\"],\"node.door.panel.open\":[\"Open\"],\"node.door.panel.opening\":[\"Opening\"],\"node.door.panel.openingShape\":[\"Opening Shape\"],\"node.door.panel.outward\":[\"Outward\"],\"node.door.panel.panel\":[\"Panel\"],\"node.door.panel.panels\":[\"Panels\"],\"node.door.panel.panicBar\":[\"Panic Bar\"],\"node.door.panel.pocket\":[\"Pocket\"],\"node.door.panel.position\":[\"Position\"],\"node.door.panel.rail\":[\"Rail\"],\"node.door.panel.rect\":[\"Rect\"],\"node.door.panel.remove\":[\"- Remove\"],\"node.door.panel.revealRadius\":[\"Reveal Radius\"],\"node.door.panel.right\":[\"Right\"],\"node.door.panel.rounded\":[\"Rounded\"],\"node.door.panel.segments\":[\"Segments\"],\"node.door.panel.sizeIsLimitedToTheWall\":[\"Size is limited to the wall, including clearance for the opening frame.\"],\"node.door.panel.slide\":[\"Slide\"],\"node.door.panel.style\":[\"Style\"],\"node.door.panel.swing\":[\"Swing\"],\"node.door.panel.thickness\":[\"Thickness\"],\"node.door.panel.threshold\":[\"Threshold\"],\"node.door.panel.topShape\":[\"Top Shape\"],\"node.door.panel.type\":[\"Type\"],\"node.door.panel.vertical\":[\"Vertical\"],\"node.door.panel.wall\":[\"wall\"],\"node.door.panel.width\":[\"Width\"],\"node.door.style.barn\":[\"Barn\"],\"node.door.style.craftsman\":[\"Craftsman\"],\"node.door.style.french\":[\"French\"],\"node.door.style.glass\":[\"Glass\"],\"node.door.style.halfLouvered\":[\"Half-louvered\"],\"node.door.style.modern\":[\"Modern\"],\"node.door.style.panel\":[\"Panel\"],\"node.door.style.paneledGlass\":[\"Paneled glass\"],\"node.door.style.shaker\":[\"Shaker\"],\"node.door.style.sixPanel\":[\"Six-panel\"],\"node.door.type.barn\":[\"Barn\"],\"node.door.type.double\":[\"Double\"],\"node.door.type.folding\":[\"Folding\"],\"node.door.type.french\":[\"French\"],\"node.door.type.garageRollup\":[\"Roll-up\"],\"node.door.type.garageSectional\":[\"Sectional\"],\"node.door.type.garageTiltup\":[\"Tilt-up\"],\"node.door.type.hinged\":[\"Hinged\"],\"node.door.type.pocket\":[\"Pocket\"],\"node.door.type.sliding\":[\"Sliding\"],\"node.dormer.description\":[\"House-shaped protrusion on a roof segment.\"],\"node.dormer.label\":[\"Dormer\"],\"node.dormer.panel.depth\":[\"Depth\"],\"node.dormer.panel.dimensions\":[\"Dimensions\"],\"node.dormer.panel.dormer\":[\"Dormer\"],\"node.dormer.panel.dutch\":[\"Dutch\"],\"node.dormer.panel.flat\":[\"Flat\"],\"node.dormer.panel.gable\":[\"Gable\"],\"node.dormer.panel.gambrel\":[\"Gambrel\"],\"node.dormer.panel.hip\":[\"Hip\"],\"node.dormer.panel.mansard\":[\"Mansard\"],\"node.dormer.panel.pitchDirection\":[\"Pitch Direction\"],\"node.dormer.panel.pitchRise\":[\"Pitch Rise\"],\"node.dormer.panel.riseBack\":[\"Rise Back\"],\"node.dormer.panel.riseFront\":[\"Rise Front\"],\"node.dormer.panel.roofHeight\":[\"Roof Height\"],\"node.dormer.panel.roofType\":[\"Roof Type\"],\"node.dormer.panel.section\":[\"Section\"],\"node.dormer.panel.shed\":[\"Shed\"],\"node.dormer.panel.wallHeight\":[\"Wall Height\"],\"node.dormer.panel.width\":[\"Width\"],\"node.dormer.panel.windows\":[\"Windows\"],\"node.dormer.panelActionsSection.actions\":[\"Actions\"],\"node.dormer.panelActionsSection.delete\":[\"Delete\"],\"node.dormer.panelActionsSection.duplicate\":[\"Duplicate\"],\"node.dormer.panelActionsSection.move\":[\"Move\"],\"node.dormer.panelPositionSection.position\":[\"Position\"],\"node.dormer.panelPositionSection.rotation\":[\"Rotation\"],\"node.dormer.panelWindowsSection.addWindow\":[\"Add Window\"],\"node.dormer.panelWindowsSection.editNamed\":[\"Edit \",[\"name\"]],\"node.dormer.panelWindowsSection.editWindow\":[\"Edit window\"],\"node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd\":[\"Increase the dormer width to add another window.\"],\"node.dormer.panelWindowsSection.move\":[\"Move\"],\"node.dormer.panelWindowsSection.moveNamed\":[\"Move \",[\"name\"]],\"node.dormer.panelWindowsSection.moveWindow\":[\"Move window\"],\"node.dormer.panelWindowsSection.noWindows\":[\"No windows\"],\"node.dormer.panelWindowsSection.windowNumber\":[\"Window \",[\"number\"]],\"node.dormer.panelWindowsSection.windowSummary\":[[\"face\",\"select\",{\"back\":[\"Back\"],\"left\":[\"Left\"],\"right\":[\"Right\"],\"other\":[\"Front\"]}],\" · \",[\"width\"],\" × \",[\"height\"],\" m\"],\"node.dormer.panelWindowsSection.windowsLength\":[\"Windows (\",[\"length\"],\")\"],\"node.downspout.description\":[\"Vertical drop pipe from a gutter outlet to the ground.\"],\"node.downspout.inspectorEditors.position\":[\"Position\"],\"node.downspout.label\":[\"Downspout\"],\"node.ductFitting.description\":[\"Elbow, tee, reducer, or square-to-round transition connecting duct runs.\"],\"node.ductFitting.inspectorEditors.swapWH\":[\"Swap W/H\"],\"node.ductFitting.inspectorEditors.swapWidthAndHeight\":[\"Swap width and height\"],\"node.ductFitting.label\":[\"Duct Fitting\"],\"node.ductFitting.tool.axis\":[\"Axis\"],\"node.ductFitting.tool.axis2\":[\"⌥ axis\"],\"node.ductFitting.tool.hoverADuctFaceThatFits\":[\"Hover a duct face that fits the door\"],\"node.ductFitting.tool.placeAccessDoor\":[\"Place access door\"],\"node.ductFitting.tool.rTRotate\":[\"R/T rotate\"],\"node.ductSegment.description\":[\"HVAC duct run — polyline of round, rect, or flat-oval sections.\"],\"node.ductSegment.label\":[\"Duct\"],\"node.ductTerminal.description\":[\"Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar.\"],\"node.ductTerminal.label\":[\"Register\"],\"node.ductTerminal.tool.mSurface\":[\"M surface\"],\"node.ductTerminal.tool.mount\":[[\"mount\",\"select\",{\"floor\":[\"Mount Floor\"],\"ceiling\":[\"Mount Ceiling\"],\"wall\":[\"Mount Wall\"],\"other\":[\"Mount \",[\"label\"]]}]],\"node.ductTerminal.tool.rTRotate\":[\"R/T rotate\"],\"node.ductTerminal.tool.snappedToDuct\":[\"Snapped to duct\"],\"node.elevator.description\":[\"A multi-level elevator shaft with configurable openings per level.\"],\"node.elevator.label\":[\"Elevator\"],\"node.elevator.panel.access\":[\"Access\"],\"node.elevator.panel.actions\":[\"Actions\"],\"node.elevator.panel.cab\":[\"Cab\"],\"node.elevator.panel.cabHeight\":[\"Cab Height\"],\"node.elevator.panel.centerOpening\":[\"Center opening\"],\"node.elevator.panel.defaultFloor\":[\"Default Floor\"],\"node.elevator.panel.delete\":[\"Delete\"],\"node.elevator.panel.depth\":[\"Depth\"],\"node.elevator.panel.destination\":[\"Destination\"],\"node.elevator.panel.disabled\":[\"Disabled\"],\"node.elevator.panel.doorHeight\":[\"Door Height\"],\"node.elevator.panel.doorTime\":[\"Door Time\"],\"node.elevator.panel.doorType\":[\"Door Type\"],\"node.elevator.panel.doorWidth\":[\"Door Width\"],\"node.elevator.panel.doors\":[\"Doors\"],\"node.elevator.panel.duplicate\":[\"Duplicate\"],\"node.elevator.panel.dwell\":[\"Dwell\"],\"node.elevator.panel.elevator\":[\"Elevator\"],\"node.elevator.panel.from\":[\"From\"],\"node.elevator.panel.glass\":[\"Glass\"],\"node.elevator.panel.glassFrame\":[\"Glass frame\"],\"node.elevator.panel.motion\":[\"Motion\"],\"node.elevator.panel.move\":[\"Move\"],\"node.elevator.panel.openingStyle\":[\"Opening Style\"],\"node.elevator.panel.position\":[\"Position\"],\"node.elevator.panel.rotation\":[\"Rotation\"],\"node.elevator.panel.segmentedPanel\":[\"Segmented panel\"],\"node.elevator.panel.service\":[\"Service\"],\"node.elevator.panel.shaft\":[\"Shaft\"],\"node.elevator.panel.shaftDepth\":[\"Shaft Depth\"],\"node.elevator.panel.shaftStyle\":[\"Shaft Style\"],\"node.elevator.panel.shaftWidth\":[\"Shaft Width\"],\"node.elevator.panel.singleLeft\":[\"Single left\"],\"node.elevator.panel.singleRight\":[\"Single right\"],\"node.elevator.panel.solid\":[\"Solid\"],\"node.elevator.panel.solidPanel\":[\"Solid panel\"],\"node.elevator.panel.speed\":[\"Speed\"],\"node.elevator.panel.stopStoporder\":[\"Stop \",[\"stopOrder\"]],\"node.elevator.panel.to\":[\"To\"],\"node.elevator.panel.wallThickness\":[\"Wall Thickness\"],\"node.elevator.panel.width\":[\"Width\"],\"node.elevator.panel.yaw\":[\"Yaw\"],\"node.eyebrowVent.description\":[\"Low curved lens-shaped roof vent with a louvered front.\"],\"node.eyebrowVent.label\":[\"Eyebrow Vent\"],\"node.eyebrowVent.panel.actions\":[\"Actions\"],\"node.eyebrowVent.panel.backHeight\":[\"Back height\"],\"node.eyebrowVent.panel.delete\":[\"Delete\"],\"node.eyebrowVent.panel.depth\":[\"Depth\"],\"node.eyebrowVent.panel.dimensions\":[\"Dimensions\"],\"node.eyebrowVent.panel.duplicate\":[\"Duplicate\"],\"node.eyebrowVent.panel.eyebrowVent\":[\"Eyebrow Vent\"],\"node.eyebrowVent.panel.halfRound\":[\"Half-round\"],\"node.eyebrowVent.panel.height\":[\"Height\"],\"node.eyebrowVent.panel.louvers\":[\"Louvers\"],\"node.eyebrowVent.panel.move\":[\"Move\"],\"node.eyebrowVent.panel.position\":[\"Position\"],\"node.eyebrowVent.panel.rotation\":[\"Rotation\"],\"node.eyebrowVent.panel.scoop\":[\"Scoop\"],\"node.eyebrowVent.panel.slantBox\":[\"Slant-box\"],\"node.eyebrowVent.panel.style\":[\"Style\"],\"node.eyebrowVent.panel.width\":[\"Width\"],\"node.fence.description\":[\"A straight or curved fence segment with configurable posts and infill.\"],\"node.fence.inspectorEditors.addPointAfter\":[\"Add point after\"],\"node.fence.inspectorEditors.chooseASlabOrShapedSurface\":[\"Choose a slab or shaped surface on this level.\"],\"node.fence.inspectorEditors.chooseASurface\":[\"Choose a surface\"],\"node.fence.inspectorEditors.corner\":[\"Corner\"],\"node.fence.inspectorEditors.curve\":[\"Curve\"],\"node.fence.inspectorEditors.curved\":[\"Curved\"],\"node.fence.inspectorEditors.distributionInfo\":[[\"style\",\"select\",{\"picket\":[\"Distribution controls pickets in each bay between posts.\"],\"horizontal\":[\"Distribution controls posts along the full fence.\"],\"other\":[\"Distribution controls infill pieces between the two end posts.\"]}],\" Spacing is measured between centers. The available width limits the count so pieces do not overlap.\"],\"node.fence.inspectorEditors.editPath\":[\"Edit path\"],\"node.fence.inspectorEditors.extendPath\":[\"Extend path\"],\"node.fence.inspectorEditors.followHighestSurface\":[\"Follow highest surface\"],\"node.fence.inspectorEditors.followSelectedSurface\":[\"Follow selected surface\"],\"node.fence.inspectorEditors.holdStartingHeight\":[\"Hold starting height\"],\"node.fence.inspectorEditors.join\":[\"Join\"],\"node.fence.inspectorEditors.length\":[\"Length\"],\"node.fence.inspectorEditors.pathSummary\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" point\"],\"other\":[[\"number\"],\" points\"]}],\" · \",[\"length\"],\" m\"],\"node.fence.inspectorEditors.picketRailProjection\":[\"Picket rail projection\"],\"node.fence.inspectorEditors.point\":[\"Point\"],\"node.fence.inspectorEditors.removePoint\":[\"Remove point\"],\"node.fence.inspectorEditors.setEachSpanToStraightOr\":[\"Set each span to straight or curved, then drag its points and curve handles in either view.\"],\"node.fence.inspectorEditors.smooth\":[\"Smooth\"],\"node.fence.inspectorEditors.spanToPointNumber\":[\"Span to point \",[\"number\"]],\"node.fence.inspectorEditors.straight\":[\"Straight\"],\"node.fence.inspectorEditors.surface\":[\"Surface\"],\"node.fence.inspectorEditors.surfaceBehavior\":[\"Surface behavior\"],\"node.fence.label\":[\"Fence\"],\"node.fence.moveEndpointTool.detachEndpoint\":[\"Detach endpoint\"],\"node.fence.moveEndpointTool.dragEndpoint\":[\"Drag endpoint\"],\"node.fenceFeature.actions.close\":[\"Close\"],\"node.fenceFeature.actions.open\":[\"Open\"],\"node.fenceFeature.inspector.boardWidth\":[\"Board width\"],\"node.fenceFeature.inspector.bracing\":[\"Bracing\"],\"node.fenceFeature.inspector.distanceFromStart\":[\"Distance from start\"],\"node.fenceFeature.inspector.fenceUnavailable\":[\"Fence unavailable.\"],\"node.fenceFeature.inspector.frameWidth\":[\"Frame width\"],\"node.fenceFeature.inspector.gateHeight\":[\"Gate height\"],\"node.fenceFeature.inspector.gateStyle\":[\"Gate style\"],\"node.fenceFeature.inspector.groundClearance\":[\"Ground clearance\"],\"node.fenceFeature.inspector.hingeSide\":[\"Hinge side\"],\"node.fenceFeature.inspector.hingesAndLatch\":[\"Hinges and latch\"],\"node.fenceFeature.inspector.horizontalBoards\":[\"Horizontal boards\"],\"node.fenceFeature.inspector.infillSpacing\":[\"Infill spacing\"],\"node.fenceFeature.inspector.jambPosts\":[\"Jamb posts\"],\"node.fenceFeature.inspector.keepTheOpeningInsideTheFence\":[\"Keep the opening inside the fence and clear of other gates or passages.\"],\"node.fenceFeature.inspector.leafThickness\":[\"Leaf thickness\"],\"node.fenceFeature.inspector.leaves\":[\"Leaves\"],\"node.fenceFeature.inspector.leftLeafProportion\":[\"Left leaf proportion\"],\"node.fenceFeature.inspector.matchFenceHeight\":[\"Match fence height\"],\"node.fenceFeature.inspector.matchFenceStyle\":[\"Match fence style\"],\"node.fenceFeature.inspector.openRails\":[\"Open rails\"],\"node.fenceFeature.inspector.openingAngle\":[\"Opening angle\"],\"node.fenceFeature.inspector.openingWidth\":[\"Opening width\"],\"node.fenceFeature.inspector.option.cross\":[\"Cross\"],\"node.fenceFeature.inspector.option.diagonal\":[\"Diagonal\"],\"node.fenceFeature.inspector.option.doubleGate\":[\"Double gate\"],\"node.fenceFeature.inspector.option.inward\":[\"Inward\"],\"node.fenceFeature.inspector.option.left\":[\"Left\"],\"node.fenceFeature.inspector.option.none\":[\"None\"],\"node.fenceFeature.inspector.option.outward\":[\"Outward\"],\"node.fenceFeature.inspector.option.right\":[\"Right\"],\"node.fenceFeature.inspector.option.singleGate\":[\"Single gate\"],\"node.fenceFeature.inspector.picket\":[\"Picket\"],\"node.fenceFeature.inspector.postHeight\":[\"Post height\"],\"node.fenceFeature.inspector.removeGate\":[\"Remove gate\"],\"node.fenceFeature.inspector.removeOpening\":[\"Remove opening\"],\"node.fenceFeature.inspector.solidPrivacy\":[\"Solid privacy\"],\"node.fenceFeature.inspector.swingDirection\":[\"Swing direction\"],\"node.fenceFeature.inspector.verticalSlats\":[\"Vertical slats\"],\"node.fenceGate.description\":[\"A gate hosted by a fence.\"],\"node.fenceGate.label\":[\"Gate\"],\"node.fenceOpening.description\":[\"An intentional opening in a fence.\"],\"node.fenceOpening.label\":[\"Open Passage\"],\"node.floorOpening.label\":[\"Floor opening\"],\"node.footing.description\":[\"A concrete strip or pad footing poured under the lowest floor.\"],\"node.footing.label\":[\"Footing\"],\"node.footing.panel.aGradeBeamOnPilesAs\":[\"A grade beam on piles, as the building is.\"],\"node.footing.panel.aPierCarriesTheFloor\":[\"A pier carries the floor.\"],\"node.footing.panel.aPileCapOnOnePile\":[\"A pile cap on one pile, as the building is.\"],\"node.footing.panel.aStemWallCarriesTheFloor\":[\"A stem wall carries the floor.\"],\"node.footing.panel.aThickenedSlabUnderTheFloor\":[\"A thickened slab under the floor slab.\"],\"node.footing.panel.actions\":[\"Actions\"],\"node.footing.panel.atLeast\":[\"At least\"],\"node.footing.panel.bottom\":[\", bottom\"],\"node.footing.panel.clearTheDepth\":[\"Clear the depth\"],\"node.footing.panel.delete\":[\"Delete\"],\"node.footing.panel.depthUnderGrade\":[\"Depth under grade\"],\"node.footing.panel.depthUnderGradeBuildingS\":[\"Depth under grade (building's)\"],\"node.footing.panel.footing\":[\"Footing\"],\"node.footing.panel.ircR4031AsksAFooting\":[\"IRC R403.1 asks a footing at least 12 in wide and 6 in thick.\"],\"node.footing.panel.length\":[\"Length\"],\"node.footing.panel.pad\":[\"Pad\"],\"node.footing.panel.strip\":[\"Strip\"],\"node.footing.panel.theFootingSThicknessUnderThe\":[\"the footing’s thickness under the floor reaches this far.\"],\"node.footing.panel.theGradeBeamReachesThisFar\":[\"the grade beam reaches this far.\"],\"node.footing.panel.theSlabAndTheFootingS\":[\"the slab and the footing’s thickness reach this far.\"],\"node.footing.panel.thickness\":[\"Thickness\"],\"node.footing.panel.top\":[\"Top\"],\"node.footing.panel.useTheBuildingSDepth\":[\"Use the building's depth\"],\"node.footing.panel.width\":[\"Width\"],\"node.foundation.description\":[\"The outline the foundation is poured to: drawn, or traced from the walls and stretched.\"],\"node.foundation.label\":[\"Foundation\"],\"node.guide.description\":[\"A measurement / reference annotation (linear, area, or arc).\"],\"node.guide.label\":[\"Guide\"],\"node.gutter.description\":[\"Rain-water channel running along the eave of a roof segment.\"],\"node.gutter.downspoutsPanel.addDownspout\":[\"Add Downspout\"],\"node.gutter.downspoutsPanel.downspoutNumber\":[\"Downspout \",[\"number\"]],\"node.gutter.downspoutsPanel.downspouts\":[\"Downspouts\"],\"node.gutter.downspoutsPanel.removeDownspout\":[\"Remove downspout\"],\"node.gutter.label\":[\"Gutter\"],\"node.hole.manual\":[\"Manual\"],\"node.holeEditing\":[\"Hole \",[\"number\"],\" (Editing)\"],\"node.holeNumber\":[\"Hole \",[\"number\"]],\"node.holeSummary\":[[\"area\"],\" \",[\"unit\"],\" · \",[\"count\",\"plural\",{\"one\":[[\"points\"],\" pt\"],\"other\":[[\"points\"],\" pts\"]}],\" · \",[\"source\"]],\"node.hvacEquipment.description\":[\"Furnace, air handler, or condenser — duct runs connect to its supply/return collars.\"],\"node.hvacEquipment.label\":[\"HVAC Unit\"],\"node.hvacEquipment.tool.rTRotate\":[\"R/T rotate\"],\"node.importedMesh.description\":[\"Geometry preserved from an imported model when no native Pascal shape exists.\"],\"node.importedMesh.label\":[\"Imported Mesh\"],\"node.item.authoredParams.couldnTRebuildWithTheseValues\":[\"Couldn't rebuild with these values.\"],\"node.item.authoredParams.parameters\":[\"Parameters\"],\"node.item.authoredParams.rebuilding\":[\"Rebuilding…\"],\"node.item.authoredParams.rebuildingInBlender\":[\"Rebuilding in Blender…\"],\"node.item.authoredParams.resetParam\":[\"Reset \",[\"param\"]],\"node.item.authoredParams.resetParameters\":[\"Reset parameters\"],\"node.item.authoredParams.resetToDefault\":[\"Reset to default\"],\"node.item.description\":[\"A catalog-backed item (furniture, fixtures, decorations).\"],\"node.item.label\":[\"Item\"],\"node.item.panel.actions\":[\"Actions\"],\"node.item.panel.delete\":[\"Delete\"],\"node.item.panel.dimensions\":[\"Dimensions\"],\"node.item.panel.duplicate\":[\"Duplicate\"],\"node.item.panel.info\":[\"Info\"],\"node.item.panel.move\":[\"Move\"],\"node.item.panel.pos\":[\"pos\"],\"node.item.panel.position\":[\"Position\"],\"node.item.panel.rot\":[\"rot\"],\"node.item.panel.rotation\":[\"Rotation\"],\"node.item.panel.scale\":[\"Scale\"],\"node.item.panel.scale2\":[\"scale\"],\"node.item.panel.uniformScale\":[\"Uniform Scale\"],\"node.item.panel.xyz\":[\"XYZ\"],\"node.leanToExtension.description\":[\"An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage.\"],\"node.leanToExtension.label\":[\"Canopy\"],\"node.level.description\":[\"A single floor of a building, holding walls / slabs / ceilings / items.\"],\"node.level.label\":[\"Level\"],\"node.lineset.description\":[\"Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil.\"],\"node.lineset.label\":[\"Lineset\"],\"node.liquidLine.description\":[\"Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset.\"],\"node.liquidLine.label\":[\"Liquid Line\"],\"node.liquidLine.tool.clickToTraceThisLinesetRun\":[\"Click to trace this lineset run\"],\"node.liquidLine.tool.followHoverALineset\":[\"Follow: hover a lineset\"],\"node.measurement.description\":[\"A persistent distance, angle, area, perimeter, or volume annotation.\"],\"node.measurement.label\":[\"Measurement\"],\"node.numberedName\":[[\"kind\"],\" \",[\"number\"]],\"node.panel.currently\":[\"Currently \",[\"value\"]],\"node.pipeFitting.description\":[\"DWV joint — elbow bend, 45° wye, or sanitary tee.\"],\"node.pipeFitting.label\":[\"Pipe Fitting\"],\"node.pipeFitting.tool.axis\":[\"Axis\"],\"node.pipeFitting.tool.axis2\":[\"⌥ axis\"],\"node.pipeFitting.tool.rTRotate\":[\"R/T rotate\"],\"node.pipeSegment.description\":[\"Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S.\"],\"node.pipeSegment.label\":[\"DWV Pipe\"],\"node.pipeTrap.description\":[\"DWV P-trap — water seal on the waste line. The trap arm runs to the vent.\"],\"node.pipeTrap.label\":[\"Trap\"],\"node.pipeTrap.tool.diameterTrap\":[[\"diameter\"],\"\\\" Trap\"],\"node.pipeTrap.tool.rTRotate\":[\"R/T rotate\"],\"node.proceduralItem.description\":[\"Experimental editable recipe\"],\"node.proceduralItem.label\":[\"Procedural item\"],\"node.ridgeVent.description\":[\"Ventilation strip running along the ridge of a roof segment.\"],\"node.ridgeVent.label\":[\"Ridge Vent\"],\"node.ridgeVent.panel.actions\":[\"Actions\"],\"node.ridgeVent.panel.delete\":[\"Delete\"],\"node.ridgeVent.panel.dimensions\":[\"Dimensions\"],\"node.ridgeVent.panel.duplicate\":[\"Duplicate\"],\"node.ridgeVent.panel.endCaps\":[\"End Caps\"],\"node.ridgeVent.panel.flanged\":[\"Flanged\"],\"node.ridgeVent.panel.height\":[\"Height\"],\"node.ridgeVent.panel.length\":[\"Length\"],\"node.ridgeVent.panel.move\":[\"Move\"],\"node.ridgeVent.panel.open\":[\"Open\"],\"node.ridgeVent.panel.position\":[\"Position\"],\"node.ridgeVent.panel.ridgeVent\":[\"Ridge Vent\"],\"node.ridgeVent.panel.rotation\":[\"Rotation\"],\"node.ridgeVent.panel.shingled\":[\"Shingled\"],\"node.ridgeVent.panel.standard\":[\"Standard\"],\"node.ridgeVent.panel.style\":[\"Style\"],\"node.ridgeVent.panel.width\":[\"Width\"],\"node.roof.barrelPanel.archBays\":[\"Arch bays\"],\"node.roof.barrelPanel.barrelArch\":[\"Barrel arch\"],\"node.roof.barrelPanel.flatPanesFollowACircularArch\":[\"Flat panes follow a circular arch. Rise is measured from the eaves.\"],\"node.roof.barrelPanel.rise\":[\"Rise\"],\"node.roof.description\":[\"A pitched / hip / gable roof composed of one or more segments.\"],\"node.roof.glazing.face.archBay\":[\"Arch bay \",[\"number\"]],\"node.roof.glazing.face.back\":[\"Back slope\"],\"node.roof.glazing.face.endLeft\":[\"Left end\"],\"node.roof.glazing.face.endRight\":[\"Right end\"],\"node.roof.glazing.face.front\":[\"Front slope\"],\"node.roof.glazing.face.slope\":[\"Shed slope\"],\"node.roof.glazingControls.alignment\":[\"Alignment\"],\"node.roof.glazingControls.center\":[\"Center\"],\"node.roof.glazingControls.chooseALayoutToEditThe\":[\"Choose a layout to edit the grid.\"],\"node.roof.glazingControls.empty\":[\"Empty\"],\"node.roof.glazingControls.end\":[\"End\"],\"node.roof.glazingControls.fixedSpacing\":[\"Fixed spacing\"],\"node.roof.glazingControls.glass\":[\"Glass\"],\"node.roof.glazingControls.layout\":[\"Layout\"],\"node.roof.glazingControls.maxSpacing\":[\"Max spacing\"],\"node.roof.glazingControls.maximumSpacing\":[\"Maximum spacing\"],\"node.roof.glazingControls.mixed\":[\"Mixed\"],\"node.roof.glazingControls.panelCount\":[\"Panel count\"],\"node.roof.glazingControls.panels\":[\"Panels\"],\"node.roof.glazingControls.solid\":[\"Solid\"],\"node.roof.glazingControls.spacing\":[\"Spacing\"],\"node.roof.glazingControls.start\":[\"Start\"],\"node.roof.glazingControls.titleAlignment\":[[\"title\"],\" alignment\"],\"node.roof.glazingPanel.changesApplyToAllLengthRoof\":[\"Changes apply to all \",[\"length\"],\" roof segments.\"],\"node.roof.glazingPanel.chooseOneRoofSystemToEdit\":[\"Choose one roof system to edit settings for all selected segments.\"],\"node.roof.glazingPanel.closeTheRoofEnds\":[\"Close the roof ends\"],\"node.roof.glazingPanel.defaultPanelInfill\":[\"Default panel infill\"],\"node.roof.glazingPanel.dividesEachRoofFaceIntoColumns\":[\"Divides each roof face into columns.\"],\"node.roof.glazingPanel.dividesTheLengthOfEachArch\":[\"Divides the length of each arch bay into columns.\"],\"node.roof.glazingPanel.endInfillFillsTheSpaceAbove\":[\"End infill fills the space above the walls. Edit room walls and ceilings separately.\"],\"node.roof.glazingPanel.frameColor\":[\"Frame color\"],\"node.roof.glazingPanel.frameDepth\":[\"Frame depth\"],\"node.roof.glazingPanel.frameWidth\":[\"Frame width\"],\"node.roof.glazingPanel.framing\":[\"Framing\"],\"node.roof.glazingPanel.glass\":[\"Glass\"],\"node.roof.glazingPanel.glassTint\":[\"Glass tint\"],\"node.roof.glazingPanel.glazed\":[\"Glazed\"],\"node.roof.glazingPanel.glazingIsAvailableForGableStraight\":[\"Glazing is available for gable, straight shed, and barrel roofs.\"],\"node.roof.glazingPanel.gridAcrossTheRoof\":[\"Grid across the roof\"],\"node.roof.glazingPanel.gridAlongTheLength\":[\"Grid along the length\"],\"node.roof.glazingPanel.gridAlongTheSlope\":[\"Grid along the slope\"],\"node.roof.glazingPanel.gridWithinEachArchBay\":[\"Grid within each arch bay\"],\"node.roof.glazingPanel.infill\":[\"Infill\"],\"node.roof.glazingPanel.opacity\":[\"Opacity\"],\"node.roof.glazingPanel.paneThickness\":[\"Pane thickness\"],\"node.roof.glazingPanel.roofSystem\":[\"Roof system\"],\"node.roof.glazingPanel.roughness\":[\"Roughness\"],\"node.roof.glazingPanel.spacingIsMeasuredOnTheSlope\":[\"Spacing is measured on the slope, not its horizontal projection.\"],\"node.roof.glazingPanel.standard\":[\"Standard\"],\"node.roof.glazingPanel.subdividesEachFlatArchBayArch\":[\"Subdivides each flat arch bay. Arch bays controls the divisions around the curve.\"],\"node.roof.glazingPanelEditor.chooseAPanelToOverrideIts\":[\"Choose a panel to override its default infill.\"],\"node.roof.glazingPanelEditor.column2\":[\"Column\"],\"node.roof.glazingPanelEditor.custom\":[\"Custom\"],\"node.roof.glazingPanelEditor.default\":[\"Default\"],\"node.roof.glazingPanelEditor.grid\":[[\"columns\",\"plural\",{\"one\":[[\"columnCount\"],\" column\"],\"other\":[[\"columnCount\"],\" columns\"]}],\" · \",[\"rows\",\"plural\",{\"one\":[[\"rowCount\"],\" row\"],\"other\":[[\"rowCount\"],\" rows\"]}]],\"node.roof.glazingPanelEditor.individualPanels\":[\"Individual panels\"],\"node.roof.glazingPanelEditor.panelInfill\":[\"Panel infill\"],\"node.roof.glazingPanelEditor.resetToDefault\":[\"Reset to default\"],\"node.roof.glazingPanelEditor.roofFace\":[\"Roof face\"],\"node.roof.glazingPanelEditor.row2\":[\"Row\"],\"node.roof.glazingPanelEditor.selectedPanelInfill\":[\"Selected panel infill\"],\"node.roof.label\":[\"Roof\"],\"node.roof.panel.actions\":[\"Actions\"],\"node.roof.panel.addChimney\":[\"Add Chimney\"],\"node.roof.panel.addCupola\":[\"Add Cupola\"],\"node.roof.panel.addDormer\":[\"Add Dormer\"],\"node.roof.panel.addEyebrowVent\":[\"Add Eyebrow Vent\"],\"node.roof.panel.addGutter\":[\"Add Gutter\"],\"node.roof.panel.addSkylight\":[\"Add Skylight\"],\"node.roof.panel.addSolarPanel\":[\"Add Solar Panel\"],\"node.roof.panel.addVent\":[\"Add Vent\"],\"node.roof.panel.box\":[\"Box\"],\"node.roof.panel.boxVent\":[\"box vent\"],\"node.roof.panel.chimney\":[\"chimney\"],\"node.roof.panel.custom\":[\"Custom\"],\"node.roof.panel.delete\":[\"Delete\"],\"node.roof.panel.dormer\":[\"dormer\"],\"node.roof.panel.drawSegment\":[\"Draw Segment\"],\"node.roof.panel.duplicate\":[\"Duplicate\"],\"node.roof.panel.eaveOverhang\":[\"Eave overhang\"],\"node.roof.panel.eaves\":[\"Eaves\"],\"node.roof.panel.elements\":[\"Elements\"],\"node.roof.panel.followsWalls\":[\"Follows walls\"],\"node.roof.panel.gableOverhang\":[\"Gable overhang\"],\"node.roof.panel.gutter\":[\"gutter\"],\"node.roof.panel.level\":[\"Level\"],\"node.roof.panel.move\":[\"Move\"],\"node.roof.panel.moveToLevel\":[\"Move to \",[\"level\"]],\"node.roof.panel.moveToTheRoofLevel\":[\"Move to the roof level\"],\"node.roof.panel.onStoreyWalls\":[\"On \",[\"storey\"],\" with its walls, so it hides that floor's plan in solo and exploded views.\",[\"roofs\",\"plural\",{\"0\":[],\"1\":[],\"other\":[\" The floor's \",[\"roofCount\"],\" roofs move together.\"]}]],\"node.roof.panel.position\":[\"Position\"],\"node.roof.panel.ridge\":[\"Ridge\"],\"node.roof.panel.ridgeVent\":[\"ridge vent\"],\"node.roof.panel.roof\":[\"Roof\"],\"node.roof.panel.rotation\":[\"Rotation\"],\"node.roof.panel.segmentType\":[[\"roofType\",\"select\",{\"hip\":[\"Hip\"],\"gable\":[\"Gable\"],\"shed\":[\"Shed\"],\"gambrel\":[\"Gambrel\"],\"dutch\":[\"Dutch\"],\"mansard\":[\"Mansard\"],\"flat\":[\"Flat\"],\"conical\":[\"Conical\"],\"barrel\":[\"Barrel\"],\"other\":[\"Roof\"]}]],\"node.roof.panel.segments\":[\"Segments\"],\"node.roof.panel.skylight\":[\"skylight\"],\"node.roof.panel.solarPanel\":[\"solar panel\"],\"node.roof.panel.turbine\":[\"Turbine\"],\"node.roof.panel.turbineVent\":[\"turbine vent\"],\"node.roofSegment.buildUpSection.applyToAllPlanesOfThis\":[\"Apply to all planes of this roof\"],\"node.roofSegment.buildUpSection.architecturalShingle\":[\"Architectural shingle\"],\"node.roofSegment.buildUpSection.asphalt3Tab\":[\"Asphalt 3-tab\"],\"node.roofSegment.buildUpSection.clayTile\":[\"Clay tile\"],\"node.roofSegment.buildUpSection.concreteTile\":[\"Concrete tile\"],\"node.roofSegment.buildUpSection.covering\":[\"Covering\"],\"node.roofSegment.buildUpSection.deck\":[\"Deck\"],\"node.roofSegment.buildUpSection.editLayers\":[\"Edit layers\"],\"node.roofSegment.buildUpSection.finishName\":[\"Finish name\"],\"node.roofSegment.buildUpSection.iceAndWaterShield\":[\"Ice and water shield\"],\"node.roofSegment.buildUpSection.insulationName\":[\"Insulation name\"],\"node.roofSegment.buildUpSection.interiorFinish\":[\"Interior finish\"],\"node.roofSegment.buildUpSection.layers\":[\"Layers\"],\"node.roofSegment.buildUpSection.n15Felt\":[\"#15 felt\"],\"node.roofSegment.buildUpSection.n30Felt\":[\"#30 felt\"],\"node.roofSegment.buildUpSection.none\":[\"None\"],\"node.roofSegment.buildUpSection.preset\":[\"Preset\"],\"node.roofSegment.buildUpSection.rafters\":[\"Rafters\"],\"node.roofSegment.buildUpSection.roofing\":[\"Roofing\"],\"node.roofSegment.buildUpSection.roofingName\":[\"Roofing name\"],\"node.roofSegment.buildUpSection.sheathing\":[\"Sheathing\"],\"node.roofSegment.buildUpSection.sheathingName\":[\"Sheathing name\"],\"node.roofSegment.buildUpSection.slate\":[\"Slate\"],\"node.roofSegment.buildUpSection.standingSeamMetal\":[\"Standing-seam metal\"],\"node.roofSegment.buildUpSection.synthetic\":[\"Synthetic\"],\"node.roofSegment.buildUpSection.topChord\":[\"Top chord\"],\"node.roofSegment.buildUpSection.trusses\":[\"Trusses\"],\"node.roofSegment.buildUpSection.underlayment\":[\"Underlayment\"],\"node.roofSegment.buildUpSection.underlaymentName\":[\"Underlayment name\"],\"node.roofSegment.buildUpSection.woodShake\":[\"Wood shake\"],\"node.roofSegment.description\":[\"A single pitched plane of a parent roof.\"],\"node.roofSegment.label\":[\"Roof Segment\"],\"node.roofSegment.panel.actions\":[\"Actions\"],\"node.roofSegment.panel.angle\":[\"Angle\"],\"node.roofSegment.panel.arc\":[\"Arc\"],\"node.roofSegment.panel.autoGutters\":[\"Auto gutters\"],\"node.roofSegment.panel.autoRidgeVent\":[\"Auto ridge vent\"],\"node.roofSegment.panel.barrel\":[\"Barrel\"],\"node.roofSegment.panel.clippedVersion\":[\"Clipped version\"],\"node.roofSegment.panel.conical\":[\"Conical\"],\"node.roofSegment.panel.conicalShape\":[\"Conical Shape\"],\"node.roofSegment.panel.deckThick\":[\"Deck Thick.\"],\"node.roofSegment.panel.delete\":[\"Delete\"],\"node.roofSegment.panel.depth\":[\"Depth\"],\"node.roofSegment.panel.diameter\":[\"Diameter\"],\"node.roofSegment.panel.doneEditing\":[\"Done editing\"],\"node.roofSegment.panel.drainage\":[\"Drainage\"],\"node.roofSegment.panel.duplicate\":[\"Duplicate\"],\"node.roofSegment.panel.dutch\":[\"Dutch\"],\"node.roofSegment.panel.eaveCut\":[\"Eave cut\"],\"node.roofSegment.panel.eaveOverhang\":[\"Eave overhang\"],\"node.roofSegment.panel.editFootprint\":[\"Edit footprint\"],\"node.roofSegment.panel.endOverhang\":[\"End overhang\"],\"node.roofSegment.panel.fascia\":[\"Fascia\"],\"node.roofSegment.panel.fasciaHeight\":[\"Fascia height\"],\"node.roofSegment.panel.flat\":[\"Flat\"],\"node.roofSegment.panel.footprint\":[\"Footprint\"],\"node.roofSegment.panel.gable\":[\"Gable\"],\"node.roofSegment.panel.gableOverhang\":[\"Gable overhang\"],\"node.roofSegment.panel.gambrel\":[\"Gambrel\"],\"node.roofSegment.panel.hip\":[\"Hip\"],\"node.roofSegment.panel.kinkDepth\":[\"Kink Depth\"],\"node.roofSegment.panel.kinkHeight\":[\"Kink Height\"],\"node.roofSegment.panel.length\":[\"Length\"],\"node.roofSegment.panel.mansard\":[\"Mansard\"],\"node.roofSegment.panel.move\":[\"Move\"],\"node.roofSegment.panel.pitch\":[\"Pitch\"],\"node.roofSegment.panel.plumb\":[\"Plumb\"],\"node.roofSegment.panel.plumbLevel\":[\"Plumb + level\"],\"node.roofSegment.panel.position\":[\"Position\"],\"node.roofSegment.panel.reset\":[\"Reset\"],\"node.roofSegment.panel.roofSegment\":[\"Roof Segment\"],\"node.roofSegment.panel.roofType\":[\"Roof Type\"],\"node.roofSegment.panel.rotation\":[\"Rotation\"],\"node.roofSegment.panel.shape\":[\"Shape\"],\"node.roofSegment.panel.shed\":[\"Shed\"],\"node.roofSegment.panel.shingleThick\":[\"Shingle Thick.\"],\"node.roofSegment.panel.span\":[\"Span\"],\"node.roofSegment.panel.square\":[\"Square\"],\"node.roofSegment.panel.startAngle\":[\"Start Angle\"],\"node.roofSegment.panel.structure\":[\"Structure\"],\"node.roofSegment.panel.topRakeLength\":[\"Top Rake Length\"],\"node.roofSegment.panel.topRakeThick\":[\"Top Rake Thick.\"],\"node.roofSegment.panel.trim\":[\"Trim\"],\"node.roofSegment.panel.waistHeight\":[\"Waist Height\"],\"node.roofSegment.panel.waistLength\":[\"Waist Length\"],\"node.roofSegment.panel.waistWidth\":[\"Waist Width\"],\"node.roofSegment.panel.wall\":[\"Wall\"],\"node.roofSegment.panel.wallHeight\":[\"Wall Height\"],\"node.roofSegment.panel.wallThick\":[\"Wall Thick.\"],\"node.roofSegment.panel.width\":[\"Width\"],\"node.scan.description\":[\"A captured session with optional mesh, motion, media, and sensor data.\"],\"node.scan.label\":[\"Capture\"],\"node.segmentNumber\":[\"Segment \",[\"number\"]],\"node.separator.label\":[\"Separator\"],\"node.shared.buildUpControls.batt\":[\"Batt\"],\"node.shared.buildUpControls.blown\":[\"Blown\"],\"node.shared.buildUpControls.drywall\":[\"Drywall\"],\"node.shared.buildUpControls.drywallThickness\":[\"Drywall thickness\"],\"node.shared.buildUpControls.insulation\":[\"Insulation\"],\"node.shared.buildUpControls.insulationThickness\":[\"Insulation thickness\"],\"node.shared.buildUpControls.labelThickness\":[[\"label\"],\" thickness\"],\"node.shared.buildUpControls.none\":[\"None\"],\"node.shared.buildUpControls.osb\":[\"OSB\"],\"node.shared.buildUpControls.plaster\":[\"Plaster\"],\"node.shared.buildUpControls.plywood\":[\"Plywood\"],\"node.shared.buildUpControls.rigidBoard\":[\"Rigid board\"],\"node.shared.buildUpControls.spacing\":[\"Spacing\"],\"node.shared.buildUpControls.spacingOC\":[[\"spacing\"],\" o.c.\"],\"node.shared.buildUpControls.woodPlank\":[\"Wood plank\"],\"node.shared.itemInteractionActions.turnLightOff\":[\"Turn light off\"],\"node.shared.itemInteractionActions.turnLightOn\":[\"Turn light on\"],\"node.shared.openingCasingSection.casing\":[\"Casing\"],\"node.shared.openingCasingSection.width\":[\"Width\"],\"node.shared.runHangerInspector.automaticSupport\":[\"Automatic support\"],\"node.shared.runHangerInspector.hangerOnSegment\":[\"Hanger \",[\"number\"],\" · segment \",[\"segment\"]],\"node.shared.runHangerInspector.individualHangers\":[\"Individual hangers\"],\"node.shared.runHangerInspector.missingSupport\":[\"Missing support\"],\"node.shared.runHangerInspector.noSupportWithinReachMoveThe\":[\"No support within reach. Move the hanger or choose another host.\"],\"node.shared.runHangerInspector.positionLabel\":[\"Hanger \",[\"number\"],\" position in metres\"],\"node.shared.runHangerInspector.positionM\":[\"Position (m)\"],\"node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath\":[\"Positions are measured along each path segment. Changing spacing regenerates the numbered slots.\"],\"node.shared.runHangerInspector.revealSupport\":[\"Reveal support\"],\"node.shared.runHangerInspector.skip\":[\"Skip\"],\"node.shared.runHangerInspector.supportLabel\":[\"Hanger \",[\"number\"],\" support\"],\"node.shared.systemCheckPanel.aboutTheseChecks\":[\"About these checks\"],\"node.shared.systemCheckPanel.allFindings\":[\"All findings\"],\"node.shared.systemCheckPanel.allSystems\":[\"All systems\"],\"node.shared.systemCheckPanel.connections\":[\"Connections\"],\"node.shared.systemCheckPanel.drainage\":[\"Drainage\"],\"node.shared.systemCheckPanel.error\":[\"Error\"],\"node.shared.systemCheckPanel.errors\":[\"Errors\"],\"node.shared.systemCheckPanel.filterSystemChecks\":[\"Filter system checks\"],\"node.shared.systemCheckPanel.hangers\":[\"Hangers\"],\"node.shared.systemCheckPanel.intersections\":[\"Intersections\"],\"node.shared.systemCheckPanel.locate\":[\"Locate \",[\"name\"],\" (\",[\"id\"],\")\"],\"node.shared.systemCheckPanel.missingItem\":[\"Missing item\"],\"node.shared.systemCheckPanel.noFindings\":[\"No findings\"],\"node.shared.systemCheckPanel.noFindingsInThisCategory\":[\"No findings in this category\"],\"node.shared.systemCheckPanel.openEnds\":[\"Open ends\"],\"node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay\":[\"Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances.\"],\"node.shared.systemCheckPanel.selectedConnectedSystem\":[\"Selected connected system\"],\"node.shared.systemCheckPanel.selectedRuns\":[\"Selected: \",[\"count\",\"plural\",{\"one\":[[\"number\"],\" run\"],\"other\":[[\"number\"],\" runs\"]}],\" · \",[\"length\"],\" m\"],\"node.shared.systemCheckPanel.separateBranches\":[\"Separate branches\"],\"node.shared.systemCheckPanel.showAllFindings\":[\"Show all findings\"],\"node.shared.systemCheckPanel.systemChecks\":[\"System checks\"],\"node.shared.systemCheckPanel.systemFinding\":[\"System finding\"],\"node.shared.systemCheckPanel.systemFindings\":[\"System findings\"],\"node.shared.systemCheckPanel.warning\":[\"Warning\"],\"node.shared.systemCheckPanel.warnings\":[\"Warnings\"],\"node.shelf.description\":[\"A configurable shelving unit. Items host on each row.\"],\"node.shelf.label\":[\"Shelf\"],\"node.site.description\":[\"The top-level container holding buildings, zones, and the property boundary.\"],\"node.site.label\":[\"Site\"],\"node.site.panel.address\":[\"Address\"],\"node.site.panel.apn\":[\"APN\"],\"node.site.panel.autoMostNorthFacingEdge\":[\"Auto — most north-facing edge\"],\"node.site.panel.contourIntervalSitePlanAnd3d\":[\"Contour interval (site plan and 3D)\"],\"node.site.panel.county\":[\"County\"],\"node.site.panel.eGRs60\":[\"e.g. RS-60\"],\"node.site.panel.eGTampaLdc27156\":[\"e.g. Tampa LDC §27-156\"],\"node.site.panel.every12In\":[\"every 12 in\"],\"node.site.panel.every24In\":[\"every 24 in\"],\"node.site.panel.every6In\":[\"every 6 in\"],\"node.site.panel.findParcel\":[\"Find parcel\"],\"node.site.panel.finding\":[\"Finding…\"],\"node.site.panel.frontStreetEdge\":[\"Front (street) edge\"],\"node.site.panel.lookingUpTheParcel\":[\"Looking up the parcel and the streets around it…\"],\"node.site.panel.lotArea\":[\"Lot area\"],\"node.site.panel.n1200WCassStTampaFl\":[\"1200 W Cass St, Tampa, FL\"],\"node.site.panel.noSurveyedLinesContoursAreDrawn\":[\"No surveyed lines — contours are drawn from the sculpted ground.\"],\"node.site.panel.none\":[\"none\"],\"node.site.panel.northRotation\":[\"North rotation\"],\"node.site.panel.orientation\":[\"Orientation\"],\"node.site.panel.parcel\":[\"Parcel\"],\"node.site.panel.parcelLookupFailed\":[\"Parcel lookup failed.\"],\"node.site.panel.projectLook\":[\"Project look\"],\"node.site.panel.saveCurrentLook\":[\"Save current look\"],\"node.site.panel.saveTheCurrentLightingAndDisplay\":[\"Save the current lighting and display as this project's default. Personal display changes stay in this browser.\"],\"node.site.panel.setbacks\":[\"Setbacks\"],\"node.site.panel.showGround\":[\"Show ground\"],\"node.site.panel.showTheContourLinesIn3d\":[\"Show the contour lines in 3D\"],\"node.site.panel.side\":[\"side\"],\"node.site.panel.site\":[\"Site\"],\"node.site.panel.source\":[\"Source\"],\"node.site.panel.sourceCitation\":[\"Source / citation\"],\"node.site.panel.streetAddress\":[\"Street address\"],\"node.site.panel.survey\":[\"survey\"],\"node.site.panel.surveyedContours\":[[\"count\",\"plural\",{\"one\":[[\"lineCount\"],\" surveyed line\"],\"other\":[[\"lineCount\"],\" surveyed lines\"]}],\" at \",[\"interval\"],\" ft (\",[\"datum\"],\") — \",[\"source\"]],\"node.site.panel.terrain\":[\"Terrain\"],\"node.site.panel.typeAnAddressFirst\":[\"Type an address first.\"],\"node.site.panel.useProjectLook\":[\"Use project look\"],\"node.site.panel.zone\":[\"Zone\"],\"node.site.setback.front\":[\"Front\"],\"node.site.setback.leftOverride\":[\"Left (override)\"],\"node.site.setback.rear\":[\"Rear\"],\"node.site.setback.rightOverride\":[\"Right (override)\"],\"node.site.setback.side\":[\"Side\"],\"node.skylight.description\":[\"Framed glass opening on a roof segment.\"],\"node.skylight.label\":[\"Skylight\"],\"node.skylight.panel.actions\":[\"Actions\"],\"node.skylight.panel.alongX\":[\"Along X\"],\"node.skylight.panel.alongZ\":[\"Along Z\"],\"node.skylight.panel.bottom\":[\"Bottom\"],\"node.skylight.panel.curb\":[\"Curb\"],\"node.skylight.panel.cutoutOffset\":[\"Cutout Offset\"],\"node.skylight.panel.delete\":[\"Delete\"],\"node.skylight.panel.depth\":[\"Depth\"],\"node.skylight.panel.dimensions\":[\"Dimensions\"],\"node.skylight.panel.frame\":[\"Frame\"],\"node.skylight.panel.glassThickness\":[\"Glass Thickness\"],\"node.skylight.panel.height\":[\"Height\"],\"node.skylight.panel.lanternHeight\":[\"Lantern Height\"],\"node.skylight.panel.left\":[\"Left\"],\"node.skylight.panel.motor\":[\"Motor\"],\"node.skylight.panel.motorHousing\":[\"Motor Housing\"],\"node.skylight.panel.no\":[\"No\"],\"node.skylight.panel.noMotor\":[\"No Motor\"],\"node.skylight.panel.open\":[\"Open\"],\"node.skylight.panel.openingAngle\":[\"Opening Angle\"],\"node.skylight.panel.position\":[\"Position\"],\"node.skylight.panel.right\":[\"Right\"],\"node.skylight.panel.rotation\":[\"Rotation\"],\"node.skylight.panel.skylight\":[\"Skylight\"],\"node.skylight.panel.thickness\":[\"Thickness\"],\"node.skylight.panel.top\":[\"Top\"],\"node.skylight.panel.topScale\":[\"Top Scale\"],\"node.skylight.panel.trackWidth\":[\"Track Width\"],\"node.skylight.panel.type\":[\"Type\"],\"node.skylight.panel.width\":[\"Width\"],\"node.skylight.panel.yes\":[\"Yes\"],\"node.skylight.type.flat\":[\"Flat roof skylight\"],\"node.skylight.type.lantern\":[\"Roof lantern\"],\"node.skylight.type.opening\":[\"Opening skylight\"],\"node.skylight.type.sliding\":[\"Sliding skylight\"],\"node.skylight.type.walkOn\":[\"Walk-on rooflight\"],\"node.slab.description\":[\"A polygon-bounded floor surface that hosts items on top.\"],\"node.slab.label\":[\"Slab\"],\"node.slab.panel.addHole\":[\"Add Hole\"],\"node.slab.panel.area\":[\"Area\"],\"node.slab.panel.auto\":[\"Auto\"],\"node.slab.panel.base\":[\"Base\"],\"node.slab.panel.cutHatch\":[\"Cut hatch\"],\"node.slab.panel.cutOpening\":[\"Cut opening\"],\"node.slab.panel.depth\":[\"Depth\"],\"node.slab.panel.done\":[\"Done\"],\"node.slab.panel.elevation\":[\"Elevation\"],\"node.slab.panel.extendsThePerimeterDownToTerrain\":[\"Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged.\"],\"node.slab.panel.fixed\":[\"Fixed\"],\"node.slab.panel.floor\":[\"Floor\"],\"node.slab.panel.followsTerrain\":[\"Follows terrain\"],\"node.slab.panel.foundation\":[\"Foundation\"],\"node.slab.panel.holes\":[\"Holes\"],\"node.slab.panel.info\":[\"Info\"],\"node.slab.panel.move\":[\"Move\"],\"node.slab.panel.noHoles\":[\"No holes\"],\"node.slab.panel.rim\":[\"Rim\"],\"node.slab.panel.slab\":[\"Slab\"],\"node.slab.panel.standard2\":[\"Standard (2\\\")\"],\"node.slab.panel.standard5cm\":[\"Standard (5cm)\"],\"node.slab.panel.sunken15cm\":[\"Sunken (15cm)\"],\"node.slab.panel.sunken6\":[\"Sunken (6\\\")\"],\"node.slab.panel.surface\":[\"Surface\"],\"node.slab.panel.thick15cm\":[\"Thick (15cm)\"],\"node.slab.panel.thick6\":[\"Thick (6\\\")\"],\"node.slab.panel.thickness\":[\"Thickness\"],\"node.slab.panel.thin1\":[\"Thin (1\\\")\"],\"node.slab.panel.thin2cm\":[\"Thin (2cm)\"],\"node.solarPanel.description\":[\"Grid of photovoltaic panels mounted on a roof segment.\"],\"node.solarPanel.label\":[\"Solar Panel\"],\"node.solarPanel.panel.actions\":[\"Actions\"],\"node.solarPanel.panel.array\":[\"Array\"],\"node.solarPanel.panel.autoFitToRoof\":[\"Auto-fit to roof\"],\"node.solarPanel.panel.columns\":[\"Columns\"],\"node.solarPanel.panel.customDimensionsDonTMatchAny\":[\"Custom — dimensions don't match any preset\"],\"node.solarPanel.panel.delete\":[\"Delete\"],\"node.solarPanel.panel.flipOrientation\":[\"Flip orientation\"],\"node.solarPanel.panel.flush\":[\"Flush\"],\"node.solarPanel.panel.frameDepth\":[\"Frame depth\"],\"node.solarPanel.panel.frameThickness\":[\"Frame thickness\"],\"node.solarPanel.panel.gapX\":[\"Gap X\"],\"node.solarPanel.panel.gapY\":[\"Gap Y\"],\"node.solarPanel.panel.height\":[\"Height\"],\"node.solarPanel.panel.mounting\":[\"Mounting\"],\"node.solarPanel.panel.panel\":[\"Panel\"],\"node.solarPanel.panel.preset\":[\"Preset\"],\"node.solarPanel.panel.rows\":[\"Rows\"],\"node.solarPanel.panel.setbacksTooLarge\":[\"Setbacks too large to fit a panel.\"],\"node.solarPanel.panel.solarPanel\":[\"Solar Panel\"],\"node.solarPanel.panel.standoff\":[\"Standoff\"],\"node.solarPanel.panel.tiltAngle\":[\"Tilt angle\"],\"node.solarPanel.panel.tilted\":[\"Tilted\"],\"node.solarPanel.panel.width\":[\"Width\"],\"node.solarPanel.preset.compact\":[\"Compact\"],\"node.solarPanel.preset.frameless\":[\"Frameless\"],\"node.solarPanel.preset.residential\":[\"Residential\"],\"node.solarPanel.preset.residentialLarge\":[\"Residential Large\"],\"node.spawn.description\":[\"Player or camera origin within a level. One per level.\"],\"node.spawn.label\":[\"Spawn Point\"],\"node.stair.constructionControls.body\":[\"Body\"],\"node.stair.constructionControls.centerStringer\":[\"Center stringer\"],\"node.stair.constructionControls.closedRisers\":[\"Closed risers\"],\"node.stair.constructionControls.fromStructure\":[\"From structure\"],\"node.stair.constructionControls.inherit\":[\"Inherit\"],\"node.stair.constructionControls.nosing\":[\"Nosing\"],\"node.stair.constructionControls.open\":[\"Open\"],\"node.stair.constructionControls.original\":[\"Original\"],\"node.stair.constructionControls.riserThickness\":[\"Riser thickness\"],\"node.stair.constructionControls.sideStringers\":[\"Side stringers\"],\"node.stair.constructionControls.solid\":[\"Solid\"],\"node.stair.constructionControls.stringerDepth\":[\"Stringer depth\"],\"node.stair.constructionControls.stringerWidth\":[\"Stringer width\"],\"node.stair.constructionControls.treadFinish\":[\"Tread finish\"],\"node.stair.constructionControls.treadThickness\":[\"Tread thickness\"],\"node.stair.constructionControls.waist\":[\"Waist\"],\"node.stair.constructionControls.waistThickness\":[\"Waist thickness\"],\"node.stair.description\":[\"A stair composed of one or more flights with configurable treads, risers, railings.\"],\"node.stair.diagnostic.arrivalMismatch\":[\"The flight chain does not reach the declared destination.\"],\"node.stair.diagnostic.comfortTarget\":[\"Two risers plus going is outside the 0.60–0.65 m design range.\"],\"node.stair.diagnostic.goingTarget\":[\"Going is below the design target.\"],\"node.stair.diagnostic.headroomTarget\":[\"Overhead surfaces leave less than the target headroom.\"],\"node.stair.diagnostic.headroomUnresolved\":[\"Detailed headroom could not be evaluated: check riser counts and the surface query budget.\"],\"node.stair.diagnostic.invalidFlight\":[\"Flight dimensions and integer riser count must be positive.\"],\"node.stair.diagnostic.invalidRise\":[\"The destination must be above the stair base.\"],\"node.stair.diagnostic.landingOverlap\":[\"Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle.\"],\"node.stair.diagnostic.narrowWinderEnd\":[\"The narrow end of a winder tread is below 0.10 m; check the intended stair use.\"],\"node.stair.diagnostic.nonuniformRisers\":[\"Flight riser heights differ. Use uniform-riser repair only if this is a proposed design.\"],\"node.stair.diagnostic.raisedLanding\":[\"A landing has a stored rise; inspect its transition before repair.\"],\"node.stair.diagnostic.riserTarget\":[\"Riser height exceeds the design target.\"],\"node.stair.label\":[\"Stair\"],\"node.stair.layout.straight\":[\"Straight\"],\"node.stair.layout.turn\":[[\"layout\"],\" turn\"],\"node.stair.panel.actions\":[\"Actions\"],\"node.stair.panel.addFlight\":[\"Add flight\"],\"node.stair.panel.addLanding\":[\"Add landing\"],\"node.stair.panel.applyLabelLayout\":[\"Apply \",[\"label\"],\" layout\"],\"node.stair.panel.autoCutout\":[\"Auto Cutout\"],\"node.stair.panel.balusters\":[\"Balusters\"],\"node.stair.panel.boards\":[\"Boards\"],\"node.stair.panel.both\":[\"Both\"],\"node.stair.panel.cable\":[\"Cable\"],\"node.stair.panel.centerColumn\":[\"Center Column\"],\"node.stair.panel.clockwise\":[\"Clockwise\"],\"node.stair.panel.connectedTo\":[\"Connected to \",[\"surface\"],\". Move the stair to connect to another edge.\"],\"node.stair.panel.connectedToSurface\":[\"Connected to a surface. Move the stair to connect to another edge.\"],\"node.stair.panel.construction\":[\"Construction\"],\"node.stair.panel.counterclockwise\":[\"Counterclockwise\"],\"node.stair.panel.curved\":[\"Curved\"],\"node.stair.panel.customRise\":[\"Custom rise\"],\"node.stair.panel.deck\":[\"Deck\"],\"node.stair.panel.delete\":[\"Delete\"],\"node.stair.panel.designTargets\":[\"Design targets\"],\"node.stair.panel.destination\":[\"Destination\"],\"node.stair.panel.dragNearADeckPatioConcrete\":[\"Drag near a deck, patio, concrete slab, or landing edge to fit the stair.\"],\"node.stair.panel.duplicate\":[\"Duplicate\"],\"node.stair.panel.equalAngle\":[\"Equal angle\"],\"node.stair.panel.equalGoing\":[\"Equal going\"],\"node.stair.panel.fitRisersAndGoing\":[\"Fit risers and going\"],\"node.stair.panel.fitToFloor\":[\"Fit To Floor\"],\"node.stair.panel.fitUniformRisers\":[\"Fit uniform risers\"],\"node.stair.panel.flightMeasurements\":[\"Flight \",[\"number\"],\": \",[\"count\",\"plural\",{\"one\":[[\"riserCount\"],\" riser\"],\"other\":[[\"riserCount\"],\" risers\"]}],\" · R \",[\"riser\"],\" · T \",[\"going\"],\" · \",[\"slope\"]],\"node.stair.panel.floorsCeilingsAndStairsOnly\":[\"Floors, ceilings and stairs only.\"],\"node.stair.panel.followsDeck\":[\"Follows deck\"],\"node.stair.panel.followsLevel\":[\"Follows level\"],\"node.stair.panel.fromLevel\":[\"From Level\"],\"node.stair.panel.geometry\":[\"Geometry\"],\"node.stair.panel.glass\":[\"Glass\"],\"node.stair.panel.headroom\":[\"Headroom:\"],\"node.stair.panel.height\":[\"Height\"],\"node.stair.panel.innerGap\":[\"Inner gap\"],\"node.stair.panel.innerRadius\":[\"Inner Radius\"],\"node.stair.panel.integrated\":[\"Integrated\"],\"node.stair.panel.landing\":[\"Landing\"],\"node.stair.panel.layout\":[\"Layout\"],\"node.stair.panel.left\":[\"Left\"],\"node.stair.panel.leftTurn\":[\"Left turn\"],\"node.stair.panel.measurements\":[\"Measurements\"],\"node.stair.panel.metal\":[\"Metal\"],\"node.stair.panel.move\":[\"Move\"],\"node.stair.panel.movingThisStairKeepsItsCurrent\":[\"Moving this stair keeps its current rise, steps, and run.\"],\"node.stair.panel.noOverheadSurface\":[\"No overhead surface\"],\"node.stair.panel.none\":[\"None\"],\"node.stair.panel.notEvaluated\":[\"Not evaluated\"],\"node.stair.panel.opening\":[\"Opening\"],\"node.stair.panel.openingOffset\":[\"Opening Offset\"],\"node.stair.panel.position\":[\"Position\"],\"node.stair.panel.postRail\":[\"Post & rail\"],\"node.stair.panel.railing\":[\"Railing\"],\"node.stair.panel.right\":[\"Right\"],\"node.stair.panel.rightTurn\":[\"Right turn\"],\"node.stair.panel.rise\":[\"Rise\"],\"node.stair.panel.rotation\":[\"Rotation\"],\"node.stair.panel.segmentType\":[[\"type\",\"select\",{\"landing\":[\"Landing\"],\"other\":[\"Stair\"]}]],\"node.stair.panel.segments\":[\"Segments\"],\"node.stair.panel.sizing\":[\"Sizing\"],\"node.stair.panel.snapToNearbySurfaces\":[\"Snap to nearby surfaces\"],\"node.stair.panel.spiral\":[\"Spiral\"],\"node.stair.panel.staircase\":[\"Staircase\"],\"node.stair.panel.stepSupports\":[\"Step Supports\"],\"node.stair.panel.steps\":[\"Steps\"],\"node.stair.panel.straight\":[\"Straight\"],\"node.stair.panel.style\":[\"Style\"],\"node.stair.panel.surfaceConnection\":[\"Surface connection\"],\"node.stair.panel.sweep\":[\"Sweep\"],\"node.stair.panel.targetGoing\":[\"Target going\"],\"node.stair.panel.targetHeadroom\":[\"Target headroom\"],\"node.stair.panel.targetMaxRiser\":[\"Target max riser\"],\"node.stair.panel.targetMinimumGoing\":[\"Target minimum going\"],\"node.stair.panel.thickness\":[\"Thickness\"],\"node.stair.panel.to\":[\"To\"],\"node.stair.panel.topLanding\":[\"Top Landing\"],\"node.stair.panel.type\":[\"Type\"],\"node.stair.panel.unavailable\":[\"Unavailable\"],\"node.stair.panel.walkingLineOffset\":[\"Walking line offset\"],\"node.stair.panel.width\":[\"Width\"],\"node.stair.panel.winder\":[\"Winder\"],\"node.stair.railingControls.both\":[\"Both\"],\"node.stair.railingControls.bottomExtension\":[\"Bottom sloped extension\"],\"node.stair.railingControls.continuous\":[\"Continuous\"],\"node.stair.railingControls.floor\":[\"Floor\"],\"node.stair.railingControls.handrailDiameter\":[\"Handrail diameter\"],\"node.stair.railingControls.handrailHeight\":[\"Handrail height\"],\"node.stair.railingControls.handrailInset\":[\"Handrail inset\"],\"node.stair.railingControls.left\":[\"Left\"],\"node.stair.railingControls.noReturn\":[\"No return\"],\"node.stair.railingControls.none\":[\"None\"],\"node.stair.railingControls.originalLayout\":[\"Original layout\"],\"node.stair.railingControls.postFacing\":[\"Post-facing\"],\"node.stair.railingControls.returnLength\":[\"Return length\"],\"node.stair.railingControls.right\":[\"Right\"],\"node.stair.railingControls.separateHandrail\":[\"Separate handrail\"],\"node.stair.railingControls.setTheLengthToReachThe\":[\"Set the length to reach the wall or post.\"],\"node.stair.railingControls.topExtension\":[\"Top horizontal extension\"],\"node.stair.railingControls.wallFacing\":[\"Wall-facing\"],\"node.stair.structureControls.aSolidPouredFlightOnA\":[\"A solid poured flight on a footing; risers closed, no stringers.\"],\"node.stair.structureControls.autoAutocount\":[\"Auto (\",[\"autoCount\"],\")\"],\"node.stair.structureControls.autoClosed\":[\"Auto (closed)\"],\"node.stair.structureControls.autoOpen\":[\"Auto (open)\"],\"node.stair.structureControls.autoPressureTreated\":[\"Auto (pressure-treated)\"],\"node.stair.structureControls.autoTreads\":[\"Auto (\",[\"treads\"],\")\"],\"node.stair.structureControls.autoUntreated\":[\"Auto (untreated)\"],\"node.stair.structureControls.closed1x8Boards\":[\"Closed (1x8 boards)\"],\"node.stair.structureControls.concrete\":[\"Concrete\"],\"node.stair.structureControls.countSteelChannels\":[[\"count\",\"plural\",{\"one\":[\"#\",\" steel channel\"],\"other\":[\"#\",\" steel channels\"]}]],\"node.stair.structureControls.countSteelChannelsAtSpacing\":[[\"count\",\"plural\",{\"one\":[\"#\",\" steel channel at \",[\"spacing\"],\" o.c.\"],\"other\":[\"#\",\" steel channels at \",[\"spacing\"],\" o.c.\"]}]],\"node.stair.structureControls.countStringers\":[[\"count\",\"plural\",{\"one\":[\"#\",\" stringer\"],\"other\":[\"#\",\" stringers\"]}]],\"node.stair.structureControls.countStringersAtSpacing\":[[\"count\",\"plural\",{\"one\":[\"#\",\" stringer at \",[\"spacing\"],\" o.c.\"],\"other\":[\"#\",\" stringers at \",[\"spacing\"],\" o.c.\"]}]],\"node.stair.structureControls.cut\":[\"Cut\"],\"node.stair.structureControls.flightLayout\":[\"Flight \",[\"number\"],\": \",[\"layout\"]],\"node.stair.structureControls.housedSides\":[\"Housed sides\"],\"node.stair.structureControls.inchesInOC\":[[\"inches\"],\" in o.c.\"],\"node.stair.structureControls.layoutAuto\":[[\"layout\"],\" (auto)\"],\"node.stair.structureControls.lumber\":[\"Lumber\"],\"node.stair.structureControls.maxSpacing\":[\"Max spacing\"],\"node.stair.structureControls.nosing\":[\"Nosing\"],\"node.stair.structureControls.oneStock\":[\"One \",[\"stock\"]],\"node.stair.structureControls.open\":[\"Open\"],\"node.stair.structureControls.originalBody\":[\"Original body\"],\"node.stair.structureControls.plywoodFinish\":[\"Plywood + finish\"],\"node.stair.structureControls.pressureTreated\":[\"Pressure-treated\"],\"node.stair.structureControls.risers\":[\"Risers\"],\"node.stair.structureControls.steelStringers\":[\"Steel stringers\"],\"node.stair.structureControls.stringerCount\":[\"Stringer count\"],\"node.stair.structureControls.stringerType\":[\"Stringer type\"],\"node.stair.structureControls.stringers\":[\"Stringers\"],\"node.stair.structureControls.structure\":[\"Structure\"],\"node.stair.structureControls.treads\":[\"Treads\"],\"node.stair.structureControls.twoStock\":[\"Two \",[\"stock\"]],\"node.stair.structureControls.untreated\":[\"Untreated\"],\"node.stair.structureControls.woodStringers\":[\"Wood stringers\"],\"node.stairSegment.description\":[\"A single flight of a parent stair.\"],\"node.stairSegment.label\":[\"Stair Segment\"],\"node.stairSegment.panel.actions\":[\"Actions\"],\"node.stairSegment.panel.attachment\":[\"Attachment\"],\"node.stairSegment.panel.delete\":[\"Delete\"],\"node.stairSegment.panel.dimensions\":[\"Dimensions\"],\"node.stairSegment.panel.duplicate\":[\"Duplicate\"],\"node.stairSegment.panel.editingSwitchesTheStairToCustom\":[\"Editing switches the stair to Custom rise\"],\"node.stairSegment.panel.equalAngle\":[\"Equal angle\"],\"node.stairSegment.panel.equalGoing\":[\"Equal going\"],\"node.stairSegment.panel.fillToFloor\":[\"Fill to floor\"],\"node.stairSegment.panel.flight\":[\"Flight\"],\"node.stairSegment.panel.flightShape\":[\"Flight shape\"],\"node.stairSegment.panel.front\":[\"Front\"],\"node.stairSegment.panel.height\":[\"Height\"],\"node.stairSegment.panel.innerGap\":[\"Inner gap\"],\"node.stairSegment.panel.landing\":[\"Landing\"],\"node.stairSegment.panel.left\":[\"Left\"],\"node.stairSegment.panel.leftTurn\":[\"Left turn\"],\"node.stairSegment.panel.length\":[\"Length\"],\"node.stairSegment.panel.move\":[\"Move\"],\"node.stairSegment.panel.position\":[\"Position\"],\"node.stairSegment.panel.right\":[\"Right\"],\"node.stairSegment.panel.rightTurn\":[\"Right turn\"],\"node.stairSegment.panel.rotation\":[\"Rotation\"],\"node.stairSegment.panel.stairSegment\":[\"Stair Segment\"],\"node.stairSegment.panel.steps\":[\"Steps\"],\"node.stairSegment.panel.straight\":[\"Straight\"],\"node.stairSegment.panel.structure\":[\"Structure\"],\"node.stairSegment.panel.thickness\":[\"Thickness\"],\"node.stairSegment.panel.type\":[\"Type\"],\"node.stairSegment.panel.walkingLineOffset\":[\"Walking line offset\"],\"node.stairSegment.panel.width\":[\"Width\"],\"node.stairSegment.panel.winder\":[\"Winder\"],\"node.structuralGrid.description\":[\"Persistent construction grid axis with identification bubbles.\"],\"node.structuralGrid.label\":[\"Structural Grid\"],\"node.turbineVent.description\":[\"Wind-driven spinning whirlybird exhaust vent for a roof slope.\"],\"node.turbineVent.label\":[\"Turbine Vent\"],\"node.turbineVent.panel.actions\":[\"Actions\"],\"node.turbineVent.panel.cylinder\":[\"Cylinder\"],\"node.turbineVent.panel.delete\":[\"Delete\"],\"node.turbineVent.panel.diameter\":[\"Diameter\"],\"node.turbineVent.panel.dimensions\":[\"Dimensions\"],\"node.turbineVent.panel.duplicate\":[\"Duplicate\"],\"node.turbineVent.panel.globe\":[\"Globe\"],\"node.turbineVent.panel.height\":[\"Height\"],\"node.turbineVent.panel.motion\":[\"Motion\"],\"node.turbineVent.panel.move\":[\"Move\"],\"node.turbineVent.panel.neckHeight\":[\"Neck Height\"],\"node.turbineVent.panel.pause\":[\"Pause\"],\"node.turbineVent.panel.play\":[\"Play\"],\"node.turbineVent.panel.position\":[\"Position\"],\"node.turbineVent.panel.rotation\":[\"Rotation\"],\"node.turbineVent.panel.spinSpeed\":[\"Spin Speed\"],\"node.turbineVent.panel.style\":[\"Style\"],\"node.turbineVent.panel.turbineVent\":[\"Turbine Vent\"],\"node.turbineVent.panel.vanes\":[\"Vanes\"],\"node.unit.description\":[\"A named group of zones forming one apartment, hotel room or commercial lot.\"],\"node.unit.kind.apartment\":[\"Apartment\"],\"node.unit.kind.commercial\":[\"Commercial\"],\"node.unit.kind.common\":[\"Common\"],\"node.unit.kind.hotelRoom\":[\"Hotel room\"],\"node.unit.label\":[\"Unit\"],\"node.unit.panel.memberRoom\":[\"Room\"],\"node.unit.panel.memberZone\":[\"Zone\"],\"node.unit.panel.noZonesAssigned\":[\"No zones assigned yet.\"],\"node.unit.panel.nonAdjacentLevels\":[\"Members sit on levels that are not adjacent.\"],\"node.unit.panel.removeMember\":[\"Remove \",[\"name\"],\" from unit\"],\"node.unit.panel.removeZone\":[\"Remove zone from unit\"],\"node.unit.unitPanel.actions\":[\"Actions\"],\"node.unit.unitPanel.color\":[\"Color\"],\"node.unit.unitPanel.delete\":[\"Delete\"],\"node.unit.unitPanel.focus\":[\"Focus\"],\"node.unit.unitPanel.grossArea\":[\"Gross area\"],\"node.unit.unitPanel.kind\":[\"Kind\"],\"node.unit.unitPanel.levels\":[\"Levels\"],\"node.unit.unitPanel.members\":[\"Members\"],\"node.unit.unitPanel.name\":[\"Name\"],\"node.unit.unitPanel.noRoomsYetPickThisUnit\":[\"No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused.\"],\"node.unit.unitPanel.report\":[\"Report\"],\"node.unit.unitPanel.unit\":[\"Unit\"],\"node.wall.actions.dimensionsToTheExteriorFacesOutside\":[\"Dimensions to the exterior faces (outside size). Click for both\"],\"node.wall.actions.dimensionsToTheInteriorAndExterior\":[\"Dimensions to the interior and exterior faces. Click for interior only\"],\"node.wall.actions.dimensionsToTheInteriorFacesRoom\":[\"Dimensions to the interior faces (room size). Click for exterior only\"],\"node.wall.actions.id\":[\"ID\"],\"node.wall.actions.idOd\":[\"ID+OD\"],\"node.wall.actions.mergeWalls\":[\"Merge walls\"],\"node.wall.actions.od\":[\"OD\"],\"node.wall.actions.splitWall\":[\"Split wall\"],\"node.wall.actions.theseWallsCannotBeMerged\":[\"These walls cannot be merged.\"],\"node.wall.curtain.cell\":[\"Column \",[\"column\"],\", row \",[\"row\"],\": \",[\"type\",\"select\",{\"glass\":[\"glass\"],\"solid\":[\"solid\"],\"other\":[\"empty\"]}]],\"node.wall.curtain.columnSpacing\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" column\"],\"other\":[[\"number\"],\" columns\"]}],\" · \",[\"spacing\"],\" grid spacing\"],\"node.wall.curtain.noPanels\":[\"No panels\"],\"node.wall.curtain.rowSpacing\":[[\"count\",\"plural\",{\"one\":[[\"number\"],\" row\"],\"other\":[[\"number\"],\" rows\"]}],\" · \",[\"spacing\"],\" grid spacing\"],\"node.wall.curtainWallPanel.applyTint\":[\"Apply tint\"],\"node.wall.curtainWallPanel.borderOpeningFrame\":[\"Border / opening frame\"],\"node.wall.curtainWallPanel.bottom\":[\"Bottom\"],\"node.wall.curtainWallPanel.bottomRow\":[\"Bottom row\"],\"node.wall.curtainWallPanel.cancelTint\":[\"Cancel tint\"],\"node.wall.curtainWallPanel.center\":[\"Center\"],\"node.wall.curtainWallPanel.chooseACellOrEnterIts\":[\"Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward.\"],\"node.wall.curtainWallPanel.clearHiddenOverrides\":[\"Clear hidden overrides\"],\"node.wall.curtainWallPanel.column\":[\"Column\"],\"node.wall.curtainWallPanel.columnLayout\":[\"Column layout\"],\"node.wall.curtainWallPanel.columnRow\":[\"Column \",[\"column\"],\", row \",[\"row\"]],\"node.wall.curtainWallPanel.construction\":[\"Construction\"],\"node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong\":[\"Continuous mullions and transoms assembled along the wall.\"],\"node.wall.curtainWallPanel.curtainWallPanelSelection\":[\"Curtain wall panel selection\"],\"node.wall.curtainWallPanel.customInfill\":[\"Custom infill\"],\"node.wall.curtainWallPanel.defaultPanel\":[\"Default panel\"],\"node.wall.curtainWallPanel.editIndividualPanels\":[\"Edit individual panels\"],\"node.wall.curtainWallPanel.effectiveThicknessLimited\":[\"Effective thickness: \",[\"thickness\"],\", limited by frame depth.\"],\"node.wall.curtainWallPanel.empty\":[\"Empty\"],\"node.wall.curtainWallPanel.enablePanelEditing\":[\"Enable panel editing\"],\"node.wall.curtainWallPanel.end\":[\"End\"],\"node.wall.curtainWallPanel.fixedSpacing\":[\"Fixed spacing\"],\"node.wall.curtainWallPanel.followingWallDefaults\":[\"Following wall defaults\"],\"node.wall.curtainWallPanel.frameDimensions\":[\"Frame dimensions\"],\"node.wall.curtainWallPanel.framing\":[\"Framing\"],\"node.wall.curtainWallPanel.fullyCapped\":[\"Fully capped\"],\"node.wall.curtainWallPanel.glass\":[\"Glass\"],\"node.wall.curtainWallPanel.glassAppearance\":[\"Glass appearance\"],\"node.wall.curtainWallPanel.glassAppearanceIsControlledByThe\":[\"Glass appearance is controlled by the paint tool.\"],\"node.wall.curtainWallPanel.glassTint\":[\"Glass tint\"],\"node.wall.curtainWallPanel.glazingGap\":[\"Glazing gap\"],\"node.wall.curtainWallPanel.gridLimitedTo32PanelsPer\":[\"Grid limited to 32 panels per axis. Spacing expands to fit this wall.\"],\"node.wall.curtainWallPanel.hiddenCustomPanelCount\":[[\"length\",\"plural\",{\"one\":[[\"number\"],\" custom panel is outside this grid. It returns if the grid expands.\"],\"other\":[[\"number\"],\" custom panels are outside this grid. They return if the grid expands.\"]}]],\"node.wall.curtainWallPanel.horizontalCaps\":[\"Horizontal caps\"],\"node.wall.curtainWallPanel.horizontalFrameWidth\":[\"Horizontal frame width\"],\"node.wall.curtainWallPanel.layout\":[\"Layout\"],\"node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness\":[\"Low roughness is smooth; high roughness gives a matte surface in rendered shading.\"],\"node.wall.curtainWallPanel.maximumSpacing\":[\"Maximum spacing\"],\"node.wall.curtainWallPanel.moduleJointWidth\":[\"Module joint width\"],\"node.wall.curtainWallPanel.none\":[\"None\"],\"node.wall.curtainWallPanel.opacity\":[\"Opacity\"],\"node.wall.curtainWallPanel.opaqueBand\":[\"Opaque band\"],\"node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow\":[\"Opaque bands replace a full row with solid panels. Apply their finish with the paint tool.\"],\"node.wall.curtainWallPanel.panelCount\":[\"Panel count\"],\"node.wall.curtainWallPanel.panelInfill\":[\"Panel infill\"],\"node.wall.curtainWallPanel.panelThickness\":[\"Panel thickness\"],\"node.wall.curtainWallPanel.panels\":[\"Panels\"],\"node.wall.curtainWallPanel.resetThisPanel\":[\"Reset this panel\"],\"node.wall.curtainWallPanel.row2\":[\"Row\"],\"node.wall.curtainWallPanel.rowLayout\":[\"Row layout\"],\"node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween\":[\"Separate framed modules with joints between units.\"],\"node.wall.curtainWallPanel.solid\":[\"Solid\"],\"node.wall.curtainWallPanel.spacing\":[\"Spacing\"],\"node.wall.curtainWallPanel.start\":[\"Start\"],\"node.wall.curtainWallPanel.stickBuilt\":[\"Stick-built\"],\"node.wall.curtainWallPanel.structuralGlazing\":[\"Structural glazing\"],\"node.wall.curtainWallPanel.surfaceRoughness\":[\"Surface roughness\"],\"node.wall.curtainWallPanel.top\":[\"Top\"],\"node.wall.curtainWallPanel.topRow\":[\"Top row\"],\"node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing\":[\"Uncapped members sit behind the glazing.\"],\"node.wall.curtainWallPanel.unitized\":[\"Unitized\"],\"node.wall.curtainWallPanel.useGlassSettingsInstead\":[\"Use glass settings instead\"],\"node.wall.curtainWallPanel.useTheDoorOrWindowTool\":[\"Use the Door or Window tool to add an opening through the wall.\"],\"node.wall.curtainWallPanel.verticalCaps\":[\"Vertical caps\"],\"node.wall.curtainWallPanel.verticalFrameWidth\":[\"Vertical frame width\"],\"node.wall.curtainWallPanel.wallSystem\":[\"Wall system\"],\"node.wall.description\":[\"A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items.\"],\"node.wall.label\":[\"Wall\"],\"node.wall.panel.actions\":[\"Actions\"],\"node.wall.panel.assembly\":[\"Assembly\"],\"node.wall.panel.auto\":[\"Auto\"],\"node.wall.panel.both\":[\"Both\"],\"node.wall.panel.bottom\":[\"Bottom\"],\"node.wall.panel.brickVeneer\":[\"Brick veneer\"],\"node.wall.panel.chairRail\":[\"Chair rail\"],\"node.wall.panel.cmu\":[\"CMU\"],\"node.wall.panel.colonial\":[\"Colonial\"],\"node.wall.panel.cove\":[\"Cove\"],\"node.wall.panel.craft\":[\"Craft\"],\"node.wall.panel.crownMolding\":[\"Crown molding\"],\"node.wall.panel.curtainWall\":[\"Curtain wall\"],\"node.wall.panel.curve\":[\"Curve\"],\"node.wall.panel.custom\":[\"Custom\"],\"node.wall.panel.customHeight\":[\"Custom height\"],\"node.wall.panel.depth\":[\"Depth\"],\"node.wall.panel.dimensions\":[\"Dimensions\"],\"node.wall.panel.drywall\":[\"Drywall\"],\"node.wall.panel.extendsDownwardToMeetTheTerrain\":[\"Extends downward to meet the terrain. Height and top stay unchanged.\"],\"node.wall.panel.exterior\":[\"Exterior\"],\"node.wall.panel.fiberCement\":[\"Fiber cement\"],\"node.wall.panel.fillToTerrain\":[\"Fill to terrain\"],\"node.wall.panel.flat\":[\"Flat\"],\"node.wall.panel.followsLevel\":[\"Follows level\"],\"node.wall.panel.frameDepth\":[\"Frame depth\"],\"node.wall.panel.framersDefault\":[\"Framer's default\"],\"node.wall.panel.framing\":[\"Framing\"],\"node.wall.panel.gypsum\":[\"Gypsum\"],\"node.wall.panel.height\":[\"Height\"],\"node.wall.panel.hideCode\":[\"Hide code\"],\"node.wall.panel.icf\":[\"ICF\"],\"node.wall.panel.interior\":[\"Interior\"],\"node.wall.panel.lapSiding\":[\"Lap siding\"],\"node.wall.panel.layerThicknessesFollowThe2021Irc\":[\"Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction.\"],\"node.wall.panel.layered\":[\"Layered\"],\"node.wall.panel.length\":[\"Length\"],\"node.wall.panel.loading\":[\"Loading…\"],\"node.wall.panel.modern\":[\"Modern\"],\"node.wall.panel.none\":[\"None\"],\"node.wall.panel.noneSingleLayer\":[\"None (single layer)\"],\"node.wall.panel.offset\":[\"Offset\"],\"node.wall.panel.ogee\":[\"Ogee\"],\"node.wall.panel.osb\":[\"OSB\"],\"node.wall.panel.partitionTheInteriorFinishIsApplied\":[\"Partition: the interior finish is applied to both faces.\"],\"node.wall.panel.picture\":[\"Picture\"],\"node.wall.panel.plaster\":[\"Plaster\"],\"node.wall.panel.plywood\":[\"Plywood\"],\"node.wall.panel.preset\":[\"Preset\"],\"node.wall.panel.railing\":[\"Railing\"],\"node.wall.panel.reference\":[\"Reference\"],\"node.wall.panel.remove\":[\"Remove\"],\"node.wall.panel.round\":[\"Round\"],\"node.wall.panel.scriptedFinish\":[\"Scripted finish\"],\"node.wall.panel.sheathing\":[\"Sheathing\"],\"node.wall.panel.shoe\":[\"Shoe\"],\"node.wall.panel.sideA\":[\"Side A\"],\"node.wall.panel.sideB\":[\"Side B\"],\"node.wall.panel.sideFace\":[\"Side \",[\"face\"]],\"node.wall.panel.skirting\":[\"Skirting\"],\"node.wall.panel.standard\":[\"Standard\"],\"node.wall.panel.steelStuds\":[\"Steel studs\"],\"node.wall.panel.step\":[\"Step\"],\"node.wall.panel.stoneVeneer\":[\"Stone veneer\"],\"node.wall.panel.stucco\":[\"Stucco\"],\"node.wall.panel.studs\":[\"Studs\"],\"node.wall.panel.thickness\":[\"Thickness\"],\"node.wall.panel.thicknessFromAssembly\":[[\"thickness\"],\" · from assembly\"],\"node.wall.panel.thisProject\":[\"This project\"],\"node.wall.panel.top\":[\"Top\"],\"node.wall.panel.totalThickness\":[\"Total thickness\"],\"node.wall.panel.unverifiedPresetnote\":[\"Unverified: \",[\"presetNote\"]],\"node.wall.panel.viewCode\":[\"View code\"],\"node.wall.panel.wall\":[\"Wall\"],\"node.wall.panel.wallType\":[\"Wall type\"],\"node.wall.panel.whichFaceIsOutsideIsUndetermined\":[\"Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B.\"],\"node.wall.panel.woodStuds\":[\"Wood studs\"],\"node.wall.railingWallPanel.aGuardSOpeningsMustNot\":[\"A guard's openings must not pass a 4 in sphere (IRC R312.1.3).\"],\"node.wall.railingWallPanel.height\":[\"Height\"],\"node.wall.railingWallPanel.infill\":[\"Infill\"],\"node.wall.railingWallPanel.n2x6Cap\":[\"2x6 cap\"],\"node.wall.railingWallPanel.n36In\":[\"36 in\"],\"node.wall.railingWallPanel.n42In\":[\"42 in\"],\"node.wall.railingWallPanel.posts\":[\"Posts\"],\"node.wall.railingWallPanel.railing\":[\"Railing\"],\"node.wall.railingWallPanel.style\":[\"Style\"],\"node.wall.reference.center\":[\"Center\"],\"node.wall.reference.inside\":[\"Inside face\"],\"node.wall.reference.left\":[\"Left\"],\"node.wall.reference.outside\":[\"Outside face\"],\"node.wall.reference.right\":[\"Right\"],\"node.wall.trim.hide\":[[\"trim\",\"select\",{\"skirting\":[\"Hide skirting\"],\"crown\":[\"Hide crown molding\"],\"other\":[\"Hide chair rail\"]}]],\"node.wall.trim.show\":[[\"trim\",\"select\",{\"skirting\":[\"Show skirting\"],\"crown\":[\"Show crown molding\"],\"other\":[\"Show chair rail\"]}]],\"node.window.description\":[\"A window cut into a wall. Animated open/close for opening windows.\"],\"node.window.label\":[\"Window\"],\"node.window.operation.raise\":[\"Raise\"],\"node.window.operation.slats\":[\"Slats\"],\"node.window.operation.slide\":[\"Slide\"],\"node.window.operation.swing\":[\"Swing\"],\"node.window.operation.tilt\":[\"Tilt\"],\"node.window.panel.actions\":[\"Actions\"],\"node.window.panel.all\":[\"All\"],\"node.window.panel.arch\":[\"Arch\"],\"node.window.panel.archHeight\":[\"Arch Height\"],\"node.window.panel.auto\":[\"Auto\"],\"node.window.panel.autoFollowsTheWall\":[\"Auto follows the wall\"],\"node.window.panel.colWidths\":[\"Col Widths\"],\"node.window.panel.columns\":[\"Columns\"],\"node.window.panel.corner\":[\"Corner\"],\"node.window.panel.cornerPost\":[\"Corner post\"],\"node.window.panel.cornerRadius\":[\"Corner Radius\"],\"node.window.panel.customThePanesMatchNoStyle\":[\"Custom: the panes match no style.\"],\"node.window.panel.delete\":[\"Delete\"],\"node.window.panel.depth\":[\"Depth\"],\"node.window.panel.dimensions\":[\"Dimensions\"],\"node.window.panel.divider\":[\"Divider\"],\"node.window.panel.down\":[\"Down\"],\"node.window.panel.duplicate\":[\"Duplicate\"],\"node.window.panel.enableSill\":[\"Enable Sill\"],\"node.window.panel.flipSide\":[\"Flip Side\"],\"node.window.panel.frame\":[\"Frame\"],\"node.window.panel.frameDepthAuto\":[\"Depth (auto)\"],\"node.window.panel.french\":[\"French\"],\"node.window.panel.fusedGlass\":[\"Fused glass\"],\"node.window.panel.grid\":[\"Grid\"],\"node.window.panel.height\":[\"Height\"],\"node.window.panel.individual\":[\"Individual\"],\"node.window.panel.left\":[\"Left\"],\"node.window.panel.move\":[\"Move\"],\"node.window.panel.opening\":[\"Opening\"],\"node.window.panel.openingShape\":[\"Opening Shape\"],\"node.window.panel.otherSide\":[\"Other side\"],\"node.window.panel.pos\":[\"pos\"],\"node.window.panel.position\":[\"Position\"],\"node.window.panel.rect\":[\"Rect\"],\"node.window.panel.revealRadius\":[\"Reveal Radius\"],\"node.window.panel.right\":[\"Right\"],\"node.window.panel.rounded\":[\"Rounded\"],\"node.window.panel.rowHeights\":[\"Row Heights\"],\"node.window.panel.rows\":[\"Rows\"],\"node.window.panel.sill\":[\"Sill\"],\"node.window.panel.single\":[\"Single\"],\"node.window.panel.sizeIsLimitedToTheWall\":[\"Size is limited to the wall, including clearance for the opening frame.\"],\"node.window.panel.style\":[\"Style\"],\"node.window.panel.thickness\":[\"Thickness\"],\"node.window.panel.thisSide\":[\"This side\"],\"node.window.panel.topShape\":[\"Top Shape\"],\"node.window.panel.type\":[\"Type\"],\"node.window.panel.unwrap\":[\"Unwrap\"],\"node.window.panel.up\":[\"Up\"],\"node.window.panel.width\":[\"Width\"],\"node.window.panel.window\":[\"Window\"],\"node.window.panel.windowType\":[\"Window Type\"],\"node.window.panel.wrapTheCorner\":[\"Wrap the corner\"],\"node.window.panel.wrapsTheCornerWithNamed\":[\"Wraps the corner with \",[\"partner\"]],\"node.window.panel.wrapsTheCornerWithPartner\":[\"Wraps the corner with its partner\"],\"node.window.style.casement\":[\"Casement\"],\"node.window.style.doubleHung\":[\"Double-hung\"],\"node.window.style.grid\":[\"Grid\"],\"node.window.style.horizontalBands\":[\"Horizontal bands\"],\"node.window.style.picture\":[\"Picture\"],\"node.window.style.single\":[\"Single\"],\"node.window.style.sliding\":[\"Sliding\"],\"node.window.style.tallGrid\":[\"Tall grid\"],\"node.window.style.transom\":[\"Transom\"],\"node.window.style.tripleHung\":[\"Triple-hung\"],\"node.window.style.wideGrid\":[\"Wide grid\"],\"node.window.type.awning\":[\"Awning\"],\"node.window.type.bay\":[\"Bay\"],\"node.window.type.bow\":[\"Bow\"],\"node.window.type.casement\":[\"Casement\"],\"node.window.type.doubleHung\":[\"Double Hung\"],\"node.window.type.fixed\":[\"Fixed\"],\"node.window.type.hopper\":[\"Hopper\"],\"node.window.type.louvered\":[\"Louvered\"],\"node.window.type.singleHung\":[\"Single Hung\"],\"node.window.type.sliding\":[\"Sliding\"],\"node.zone.description\":[\"A polygonal site zone (lawn, water, paving) with a TSL gradient material.\"],\"node.zone.label\":[\"Zone\"],\"node.zone.quantitiesPanel.enclosedRoom\":[\"Enclosed room\"],\"node.zone.quantitiesPanel.floorSurface\":[\"Floor surface\"],\"node.zone.quantitiesPanel.footprintOnly\":[\"Footprint only\"],\"node.zone.quantitiesPanel.none\":[\"None\"],\"node.zone.quantitiesPanel.notProven\":[\"Not proven\"],\"node.zone.quantitiesPanel.roomQuantities\":[\"Room quantities\"],\"node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions\":[\"Top view with zone edge dimensions\"],\"node.zone.quantitiesPanel.unit\":[\"Unit\"],\"node.zone.quantitiesPanel.volume\":[\"Volume\"],\"node.zone.quantitiesPanel.wallSurface\":[\"Wall surface\"],\"node.zone.quantitiesPanel.zoneBoundaryUnavailable\":[\"Zone boundary unavailable\"],\"node.zone.quantitiesPanel.zoneQuantities\":[\"Zone quantities\"],\"viewer.camera.orbitLeft\":[\"Orbit left\"],\"viewer.camera.orbitRight\":[\"Orbit right\"],\"viewer.camera.topView\":[\"Top view\"],\"viewer.common.off\":[\"Off\"],\"viewer.common.on\":[\"On\"],\"viewer.display.camera\":[\"Camera\"],\"viewer.display.colored\":[\"Colored\"],\"viewer.display.colors\":[\"Colors\"],\"viewer.display.edges.off\":[\"Off\"],\"viewer.display.edges.offDetail\":[\"No edge lines\"],\"viewer.display.edges.soft\":[\"Soft\"],\"viewer.display.edges.softDetail\":[\"Faint outline of major creases\"],\"viewer.display.edges.strong\":[\"Strong\"],\"viewer.display.edges.strongDetail\":[\"Crisp, opaque edge lines\"],\"viewer.display.edges.title\":[\"Edges\"],\"viewer.display.monochrome\":[\"Monochrome\"],\"viewer.display.orthographic\":[\"Orthographic\"],\"viewer.display.perspective\":[\"Perspective\"],\"viewer.display.render.rendered\":[\"Rendered\"],\"viewer.display.render.renderedDetail\":[\"Full ambient occlusion\"],\"viewer.display.render.solid\":[\"Solid\"],\"viewer.display.render.solidDetail\":[\"Flat and fast — no ambient occlusion\"],\"viewer.display.render.title\":[\"Render\"],\"viewer.display.shadows\":[\"Shadows\"],\"viewer.display.title\":[\"Display settings\"],\"viewer.floorplan.alignNorth\":[\"Align view to north\"],\"viewer.floorplan.empty\":[\"No floor plans are available for this scene.\"],\"viewer.floorplan.fit\":[\"Fit floor plan\"],\"viewer.floorplan.floor\":[\"Floor\"],\"viewer.floorplan.floorPlan\":[\"Floor plan\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\" 2D view\"],\"viewer.floorplan.zoomIn\":[\"Zoom in\"],\"viewer.floorplan.zoomOut\":[\"Zoom out\"],\"viewer.header.back\":[\"Back\"],\"viewer.header.levels\":[\"Levels\"],\"viewer.header.rooms\":[\"Rooms\"],\"viewer.header.selection\":[\"Selection\"],\"viewer.header.units\":[\"Units\"],\"viewer.header.untitled\":[\"Untitled\"],\"viewer.header.zones\":[\"Zones\"],\"viewer.levels.exploded\":[\"Exploded\"],\"viewer.levels.label\":[\"Levels: \",[\"mode\"]],\"viewer.levels.manual\":[\"Manual\"],\"viewer.levels.solo\":[\"Solo\"],\"viewer.levels.stacked\":[\"Stacked\"],\"viewer.stage.layout\":[\"Viewer layout\"],\"viewer.stage.split\":[\"Split\"],\"viewer.theme.blueprint\":[\"Blueprint\"],\"viewer.theme.mediterranean\":[\"Mediterranean\"],\"viewer.theme.night\":[\"Night\"],\"viewer.theme.overcast\":[\"Overcast\"],\"viewer.theme.paper\":[\"Paper\"],\"viewer.theme.studio\":[\"Studio\"],\"viewer.theme.sunset\":[\"Sunset\"],\"viewer.theme.title\":[\"Scene theme\"],\"viewer.theme.twilight\":[\"Twilight\"],\"viewer.theme.verdant\":[\"Verdant\"],\"viewer.visibility.guides\":[\"Guides\"],\"viewer.visibility.scans\":[\"Scans\"],\"viewer.visibility.title\":[\"Visibility\"],\"viewer.walkthrough.label\":[\"Walkthrough: \",[\"state\"]],\"viewer.walls.cutaway\":[\"Cutaway\"],\"viewer.walls.fullHeight\":[\"Full height\"],\"viewer.walls.label\":[\"Walls: \",[\"mode\"]],\"viewer.walls.low\":[\"Low\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/i18n/locales/es/messages.po b/packages/editor/src/i18n/locales/es/messages.po index ae396c5438..57a9f67cb6 100644 --- a/packages/editor/src/i18n/locales/es/messages.po +++ b/packages/editor/src/i18n/locales/es/messages.po @@ -13,24551 +13,28858 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" +# English: "Pascal home" # Source-Hash: 8ffb1c5066e53a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pascal home" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/app-home-link.tsx #, fuzzy msgid "app.appShell.pascalHome" msgstr "Inicio de Pascal" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addGate" msgstr "Añadir puerta de valla" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addOpenPassage" msgstr "Añadir paso abierto" +# English: "Add to rooms" # Source-Hash: 9a4d56454dc89966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addToRooms" msgstr "Añadir a las estancias" +# English: "← All build tools" # Source-Hash: a17210912addc85f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "← All build tools" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.allBuildTools" msgstr "← Todas las herramientas de construcción" +# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." # Source-Hash: 6b63404967e1d5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.chooseGateOrOpenPassageThen" msgstr "Elige Puerta de valla o Paso abierto y haz clic en su posición en cualquier valla. Las puertas y aberturas colocadas están bajo su valla en el grafo de escena." +# English: "Click a lineset to lay the line beside it." # Source-Hash: 8fd051e1e1a6c1a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a lineset to lay the line beside it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.clickALinesetToLayThe" msgstr "Haz clic en una línea frigorífica para tender la línea junto a ella." +# English: "Double gate" # Source-Hash: 7308869944020839 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.doubleGate" msgstr "Puerta doble" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.featuresExtensions" msgstr "Elementos y ampliaciones" +# English: "Fence features" # Source-Hash: b18788f1d889c1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence features" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.fenceFeatures" msgstr "Elementos de valla" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.followLineset" msgstr "Seguir la línea frigorífica" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gate" msgstr "Puerta de valla" +# English: "Gate style" # Source-Hash: 54098b5ee574737a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gateStyle" msgstr "Estilo de puerta de valla" +# English: "Horizontal boards" # Source-Hash: 8fec7e969045a8e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.horizontalBoards" msgstr "Tablas horizontales" +# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." # Source-Hash: 09197c4fea516277 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.hoverAFenceToPreviewClick" msgstr "Pasa el cursor por una valla para previsualizar. Haz clic para colocar. Esc cancela. Deja espacio entre aberturas." +# English: "Leaves" # Source-Hash: c390ba248e629b9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leaves" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.leaves" msgstr "Hojas" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.liquidLine" msgstr "Línea de líquido" +# English: "Match fence style" # Source-Hash: c15c1af69ec5c1eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match fence style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.matchFenceStyle" msgstr "Igualar el estilo de la valla" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.mep" msgstr "Instalaciones" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.off" msgstr "Desactivado" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.on" msgstr "Activado" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.opening" msgstr "Abertura" +# English: "Opening width (m)" # Source-Hash: 9514d3c022e68808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening width (m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openingWidthM" msgstr "Ancho de la abertura (m)" +# English: "Open rails" # Source-Hash: 6b108fb1d0eb8f9d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rails" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openRails" msgstr "Barandas abiertas" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.outdoor" msgstr "Exterior" +# English: "Picket" # Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.picket" msgstr "Estacas" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roofType" msgstr "Tipo de cubierta" +# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." # Source-Hash: 0f9daa442776a317 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roomsAlreadyCreateTheirFloorAnd" msgstr "Las estancias ya crean su suelo y su techo. Usa esto para plataformas y estructuras puntuales." +# English: "Search build tools and kitchen items" # Source-Hash: fc79fd0c7730de76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search build tools and kitchen items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchBuildToolsAndKitchenItems" msgstr "Buscar herramientas de construcción y elementos de cocina" +# English: "Search tools, fridge, sink…" # Source-Hash: dc1901f276edca6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search tools, fridge, sink…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchToolsFridgeSink" msgstr "Buscar herramientas, nevera, fregadero…" +# English: "Select a curved wall to match its radius and arc." # Source-Hash: 9558db89023d4f05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a curved wall to match its radius and arc." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.selectACurvedWallToMatch" msgstr "Selecciona un muro curvo para igualar su radio y su arco." +# English: "Single gate" # Source-Hash: d327097d9ac1099f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.singleGate" msgstr "Puerta simple" +# English: "Slab, ceiling, column…" # Source-Hash: 99acd5128c51c8a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab, ceiling, column…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.slabCeilingColumn" msgstr "Forjado, techo, pilar…" +# English: "Solid privacy" # Source-Hash: af6f8885fe0fa4e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid privacy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.solidPrivacy" msgstr "Privacidad total" +# English: "Trace a line alongside an existing lineset (F)." # Source-Hash: e2b1a3bb9630b33b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace a line alongside an existing lineset (F)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.traceALineAlongsideAnExisting" msgstr "Trazar una línea junto a una línea frigorífica existente (F)." +# English: "Vertical slats" # Source-Hash: eba3df14a2de0812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.verticalSlats" msgstr "Lamas verticales" +# English: "All projects" # Source-Hash: 2ee62be78ba76f53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All projects" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.allProjects" msgstr "Todos los proyectos" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.preview" msgstr "Vista previa" +# English: "Project name" # Source-Hash: 9f2cbc9214c8f7b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.projectName" msgstr "Nombre del proyecto" +# English: "Rename project" # Source-Hash: 150e143a84240eee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.renameProject" msgstr "Cambiar nombre del proyecto" +# English: "Share" # Source-Hash: 331e1584943b09b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.share" msgstr "Compartir" +# English: "Add {label}" # Source-Hash: 4b2f676fb4c9ec63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addLabel" msgstr "Añadir {label}" +# English: "Add to kitchen" # Source-Hash: 53f98cc505aacbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addToKitchen" msgstr "Añadir a la cocina" +# English: "Choose an item, then click to place it. Appliances include their cabinetry." # Source-Hash: 923a2e81dcc77852 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose an item, then click to place it. Appliances include their cabinetry." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.chooseAnItemThenClickTo" msgstr "Elige un elemento y haz clic para colocarlo. Los electrodomésticos incluyen su mueble." +# English: "Find in this project · {length}" # Source-Hash: 44156a2c888e8a4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in this project · {length}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findInThisProjectLength" msgstr "Buscar en este proyecto · {length}" +# English: "Find {name}" # Source-Hash: aeda65db67edac21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find {name}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findName" msgstr "Buscar {name}" +# English: "Kitchen · Add" # Source-Hash: c1d847411ba09cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · Add" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupAdd" msgstr "Cocina · Añadir" +# English: "Kitchen · In this project" # Source-Hash: bb3f4f8d3407c409 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · In this project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupInThisProject" msgstr "Cocina · En este proyecto" +# English: "Kitchen cabinet" # Source-Hash: 25ffad31086309f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.kitchenCabinet" msgstr "Mueble de cocina" +# English: "No matching kitchen items placed yet." # Source-Hash: a5af7817a7e8947d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No matching kitchen items placed yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.noMatchingKitchenItemsPlacedYet" msgstr "Todavía no hay elementos de cocina coincidentes." +# English: "Select and show in scene" # Source-Hash: 640155f99c25773b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select and show in scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.selectAndShowInScene" msgstr "Seleccionar y mostrar en la escena" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.ceiling" msgstr "Techo" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.column" msgstr "Pilar" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.door" msgstr "Puerta" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.duct" msgstr "Conducto" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.dwvPipe" msgstr "Tubería de desagüe" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.elevator" msgstr "Ascensor" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.fence" msgstr "Valla" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.hvacUnit" msgstr "Equipo de climatización" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.kitchen" msgstr "Cocina" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.lineset" msgstr "Línea frigorífica" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.liquidLine" msgstr "Línea de líquido" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.mep" msgstr "Instalaciones" -# Source-Hash: 161e5386b939e1bc +# English: "Post" +# Source-Hash: 5cc981e79d737694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/lib/build-palette.ts +#, fuzzy +msgid "app.lib.buildPalette.post" +msgstr "Poste" + # English: "Register" +# Source-Hash: 161e5386b939e1bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.register" msgstr "Rejilla" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.roof" msgstr "Cubierta" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.shelf" msgstr "Estantería" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.slab" msgstr "Forjado" +# English: "Spawn point" # Source-Hash: 95e8af5d2b3ca73d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.spawnPoint" msgstr "Punto de aparición" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.stairs" msgstr "Escalera" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrace" msgstr "Terraza" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrain" msgstr "Terreno" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.wall" msgstr "Muro" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.window" msgstr "Ventana" +# English: "Add Fitting" # Source-Hash: d40c50013043284f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addFitting" msgstr "Añadir accesorio" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addGate" msgstr "Añadir puerta de valla" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addOpenPassage" msgstr "Añadir paso abierto" +# English: "Add Trap" # Source-Hash: 7136b5836de6e69f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addTrap" msgstr "Añadir sifón" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.build" msgstr "Construir" +# English: "Create from" # Source-Hash: a23831beeb35ad3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Create from" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.createFrom" msgstr "Crear a partir de" +# English: "Draw the roof footprint with two corner clicks." # Source-Hash: 8146c309bd8326f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the roof footprint with two corner clicks." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.drawTheRoofFootprintWithTwo" msgstr "Dibuja la planta de la cubierta con dos clics en esquinas." +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.duct" msgstr "Conducto" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.dwvPipe" msgstr "Tubería de desagüe" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.featuresExtensions" msgstr "Elementos y ampliaciones" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.fence" msgstr "Valla" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.followLineset" msgstr "Seguir la línea frigorífica" +# English: "Gates & openings" # Source-Hash: c80972ec078ac551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gates & openings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.gatesOpenings" msgstr "Puertas y aberturas" +# English: "Hover a room to preview its boundary, then click to place." # Source-Hash: 3720347aa16ee777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a room to preview its boundary, then click to place." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.hoverARoomToPreviewIts" msgstr "Pasa el cursor por una estancia para ver su contorno y haz clic para colocar." +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.kitchen" msgstr "Cocina" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.mep" msgstr "Instalaciones" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.modularCabinet" msgstr "Mueble modular" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.off" msgstr "Desactivado" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.on" msgstr "Activado" +# English: "Painting" # Source-Hash: 3fafdc69e7756f19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Painting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.painting" msgstr "Pintura" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roof" msgstr "Cubierta" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roofType" msgstr "Tipo de cubierta" +# English: "{count, plural, one {# tool} other {# tools}}" # Source-Hash: 0c35bee851cc57a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# tool} other {# tools}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.toolCount" msgstr "{count, plural, one {# herramienta} many {# herramientas} other {# herramientas}}" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.conical" msgstr "Cónica" +# English: "Draw" # Source-Hash: b5c56dd8c68de309 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.draw" msgstr "Dibujar" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.dutch" msgstr "Holandesa" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.flat" msgstr "Plana" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gable" msgstr "Dos aguas" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gambrel" msgstr "Holandés de buhardilla" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.hip" msgstr "Cuatro aguas" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.mansard" msgstr "Mansarda" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.room" msgstr "Estancia" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.shed" msgstr "Un agua" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.wall" msgstr "Muro" +# English: "Another session saved first — refresh?" # Source-Hash: 8d17e7556ce142da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Another session saved first — refresh?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.anotherSessionSavedFirstRefresh" msgstr "Otra sesión guardó primero: ¿actualizar?" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.build" msgstr "Construir" +# English: "Dismiss" # Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.dismiss" msgstr "Cerrar" +# English: "Items" # Source-Hash: daebc0755d239c99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.items" msgstr "Elementos" +# English: "Paint" # Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.paint" msgstr "Pintar" +# English: "Reload" # Source-Hash: 854555777b69a4b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.reload" msgstr "Recargar" +# English: "Scene" # Source-Hash: 8dc78e215a97d9fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.scene" msgstr "Escena" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.settings" msgstr "Ajustes" +# English: "Your changes haven't been saved. Reload to pick up the latest version." # Source-Hash: ee255fa26d076dcc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Your changes haven't been saved. Reload to pick up the latest version." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.yourChangesHavenTBeenSaved" msgstr "Tus cambios no se han guardado. Recarga para obtener la versión más reciente." +# English: "Anyone with the link can view this project. They cannot edit it." # Source-Hash: 703e1b4b9b715d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Anyone with the link can view this project. They cannot edit it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.anyoneWithTheLinkCanView" msgstr "Cualquier persona con el enlace puede ver este proyecto, pero no editarlo." +# English: "Copied" # Source-Hash: 07e04484867c17d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copied" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copied" msgstr "Copiado" +# English: "Copy" # Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copy" msgstr "Copiar" +# English: "Could not read the sharing state." # Source-Hash: 8ff50e1a9962a4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not read the sharing state." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotReadTheSharingState" msgstr "No se pudo leer el estado de uso compartido." +# English: "Could not turn off the public link." # Source-Hash: 791e281338a51483 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn off the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOffThePublic" msgstr "No se pudo desactivar el enlace público." +# English: "Could not turn on the public link." # Source-Hash: 2c5ddecfd652ba63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn on the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOnThePublic" msgstr "No se pudo activar el enlace público." +# English: "Embed code" # Source-Hash: 15e49d4da8765318 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedCode" msgstr "Código de inserción" +# English: "Embed link" # Source-Hash: d4523c2d303f99b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedLink" msgstr "Enlace de inserción" +# English: "Hide controls" # Source-Hash: 252f2cbf5e1609a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.hideControls" msgstr "Ocultar controles" +# English: "Link" # Source-Hash: ba77c0610a63bb2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.link" msgstr "Enlace" +# English: "Only the 3D view, no header or toolbar." # Source-Hash: a2560430aaec31d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Only the 3D view, no header or toolbar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.onlyThe3dViewNoHeader" msgstr "Solo la vista 3D, sin encabezado ni barra de herramientas." +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.open" msgstr "Abrir" +# English: "Public link" # Source-Hash: b34faaa5378056c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Public link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.publicLink" msgstr "Enlace público" +# English: "Share project" # Source-Hash: 7525e8007fcab09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.shareProject" msgstr "Compartir proyecto" +# English: "Automatic dimensions" # Source-Hash: 3d3f438d5a49eba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.automaticDimensions" msgstr "Cotas automáticas" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.camera" msgstr "Cámara" -# Source-Hash: e6bce525c47ba3ac +# English: "Ceilings" +# Source-Hash: a03ae72f209e32db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/components/viewer-toolbar.tsx +#, fuzzy +msgid "app.viewerToolbar.ceilings" +msgstr "Techos" + # English: "Clean plan; dimensions appear with selection" +# Source-Hash: e6bce525c47ba3ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cleanPlanDimensionsAppearWithSelection" msgstr "Plano limpio; las cotas aparecen al seleccionar" +# English: "Collapse sidebar" # Source-Hash: 61ce356577cfb04a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.collapseSidebar" msgstr "Contraer barra lateral" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crisp, opaque edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.crispOpaqueEdgeLines" msgstr "Aristas nítidas y opacas" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cutaway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cutaway" msgstr "Corte" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.default" msgstr "Predeterminado" +# English: "Display" # Source-Hash: a313661ef9dd0a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.display" msgstr "Visualización" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.displaySettings" msgstr "Ajustes de visualización" +# English: "Door/window marks" # Source-Hash: 0286962ab8e9cfcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door/window marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.doorWindowMarks" msgstr "Marcas de puertas y ventanas" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edges" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.edges" msgstr "Aristas" +# English: "Enter VR" # Source-Hash: d86126c5d16c6238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.enterVr" msgstr "Entrar en RV" +# English: "Exit VR" # Source-Hash: eb99fb0b382e58cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exitVr" msgstr "Salir de RV" +# English: "Expand sidebar" # Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expandSidebar" msgstr "Expandir barra lateral" +# English: "Expert" # Source-Hash: 08c178ee733ff009 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expert" msgstr "Experto" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exploded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exploded" msgstr "Despiece" +# English: "Face of stud" # Source-Hash: c6f865a366ef5755 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face of stud" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faceOfStud" msgstr "Cara del montante" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Faint outline of major creases" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faintOutlineOfMajorCreases" msgstr "Contorno tenue de los pliegues principales" +# English: "Feet & inches" # Source-Hash: 08ecd5878b08139d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Feet & inches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.feetInches" msgstr "Pies y pulgadas" +# English: "Finished faces" # Source-Hash: 43b8017fa821c46c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finished faces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.finishedFaces" msgstr "Caras acabadas" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat and fast — no ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.flatAndFastNoAmbientOcclusion" msgstr "Plano y rápido: sin oclusión ambiental" +# English: "Floor plan annotations" # Source-Hash: 060a718cdcff61b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanAnnotations" msgstr "Anotaciones del plano de planta" +# English: "Floor plan mode" # Source-Hash: 483171f79dc32ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanMode" msgstr "Modo plano de planta" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullAmbientOcclusion" msgstr "Oclusión ambiental completa" +# English: "Full documentation and annotation controls" # Source-Hash: 91fd3544d9ce16bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full documentation and annotation controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullDocumentationAndAnnotationControls" msgstr "Documentación y anotaciones completas" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullHeight" msgstr "Altura completa" +# English: "Full wall thickness" # Source-Hash: c9838bc98cb35125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullWallThickness" msgstr "Espesor total del muro" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.grid" msgstr "Cuadrícula" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels: {mode}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.levelsMode" msgstr "Plantas: {mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.low" msgstr "Bajo" +# English: "Magnetic snap" # Source-Hash: 32f9a404c0bdce10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Magnetic snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.magneticSnap" msgstr "Ajuste magnético" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manual" msgstr "Manual" +# English: "Manual dimensions" # Source-Hash: 2fb69122dd996c0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manualDimensions" msgstr "Cotas manuales" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.measurements" msgstr "Medidas" +# English: "Meters" # Source-Hash: 0a39470be3b84536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Meters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.meters" msgstr "Metros" +# English: "Millimeters" # Source-Hash: 83cce6c521e71d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Millimeters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.millimeters" msgstr "Milímetros" +# English: "3D measurements" # Source-Hash: b784527bb90b9c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.n3dMeasurements" msgstr "Medidas 3D" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.noEdgeLines" msgstr "Sin aristas" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.off" msgstr "Desactivado" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.on" msgstr "Activado" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orthographic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.orthographic" msgstr "Ortográfica" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perspective" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.perspective" msgstr "Perspectiva" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.preview" msgstr "Vista previa" +# English: "Preview mode" # Source-Hash: 34c20b72a42ca618 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.previewMode" msgstr "Modo de vista previa" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.render" msgstr "Renderizado" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rendered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.rendered" msgstr "Renderizado" +# English: "Room labels" # Source-Hash: 4959d5538efc595c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.roomLabels" msgstr "Etiquetas de estancias" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene theme" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.sceneTheme" msgstr "Tema de la escena" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shadows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.shadows" msgstr "Sombras" +# English: "Single wall axis" # Source-Hash: 59dc62417b44931d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single wall axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.singleWallAxis" msgstr "Eje único del muro" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Soft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.soft" msgstr "Suaves" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solid" msgstr "Sólido" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solo" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solo" msgstr "Aislado" +# English: "Stack" # Source-Hash: 481b7608ce12139b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stack" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stack" msgstr "Apilado" +# English: "Stair annotations" # Source-Hash: 61ad94ca68b0458e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stairAnnotations" msgstr "Anotaciones de escaleras" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strong" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.strong" msgstr "Marcadas" +# English: "Structural core face" # Source-Hash: e1be031e67d95382 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural core face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralCoreFace" msgstr "Cara del núcleo estructural" +# English: "Structural grids & column centers" # Source-Hash: efc50964d7b0ec47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural grids & column centers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralGridsColumnCenters" msgstr "Ejes estructurales y centros de pilares" +# English: "Translucent" # Source-Hash: 5ce4f6b80cd30f86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Translucent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.translucent" msgstr "Translúcido" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.units" msgstr "Unidades" +# English: "Walkthrough" # Source-Hash: faf51e28b748e0d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walkthrough" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.walkthrough" msgstr "Recorrido" +# English: "Wall centerline" # Source-Hash: 62281975e51da7e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall centerline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallCenterline" msgstr "Eje del muro" +# English: "Wall dimensions" # Source-Hash: ff145d7003118ec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallDimensions" msgstr "Cotas de muros" +# English: "Walls: {label}" # Source-Hash: 13969d1d6e310aa4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls: {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallsLabel" msgstr "Muros: {label}" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: human -# English: "Basement" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basement" msgstr "Sótano" +# English: "Basement {number}" # Source-Hash: 14927085ee91ba05 # Review: human -# English: "Basement {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basementNumber" msgstr "Sótano {number}" +# English: "Floor {number}" # Source-Hash: e59074a7b9b3f56e # Review: human -# English: "Floor {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.floorNumber" msgstr "Planta {number}" +# English: "Ground floor" # Source-Hash: cde3efc9b7ddfa41 # Review: human -# English: "Ground floor" #: packages/editor/src/i18n/level-name.ts msgid "core.level.groundFloor" msgstr "Planta baja" +# English: "Orbit Left" # Source-Hash: fdd8b3366021a1cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitLeft" msgstr "Orbitar a la izquierda" +# English: "Orbit Right" # Source-Hash: 4a56359f284a3b17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitRight" msgstr "Orbitar a la derecha" +# English: "Top View" # Source-Hash: f5c12d738240f889 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top View" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.topView" msgstr "Vista superior" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.delete" msgstr "Eliminar" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.select" msgstr "Seleccionar" +# English: "Resume following" # Source-Hash: d9a257653600d5a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resume following" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-pill.tsx #, fuzzy msgid "editor.actionMenu.followPascalPill.resumeFollowing" msgstr "Reanudar seguimiento" +# English: "Follow Pascal: Off" # Source-Hash: 4f073f8051cca3ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.off" msgstr "Seguir a Pascal: desactivado" +# English: "Follow Pascal: On" # Source-Hash: 328ea04bb8cd12f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.on" msgstr "Seguir a Pascal: activado" +# English: "Appliance" # Source-Hash: 7f4dcb90defe1ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appliance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.appliance" msgstr "Electrodoméstico" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.bathroom" msgstr "Baño" +# English: "Furniture" # Source-Hash: ed68e72f5353712b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furniture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.furniture" msgstr "Muebles" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.kitchen" msgstr "Cocina" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.outdoor" msgstr "Exterior" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angle" msgstr "Ángulo" +# English: "Angular dimension" # Source-Hash: 622f566fe1e3d8e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angular dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angularDimension" msgstr "Cota angular" +# English: "Arc length" # Source-Hash: 7e3b0d541355305e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.arcLength" msgstr "Longitud de arco" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.area" msgstr "Área" +# English: "Center mark" # Source-Hash: 88c7750ed51d47a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center mark" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.centerMark" msgstr "Marca de centro" +# English: "Chord dimension" # Source-Hash: 634912dabb092872 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chord dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.chordDimension" msgstr "Cota de cuerda" +# English: "Continuous dimension" # Source-Hash: 3cb090120253f68f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.continuousDimension" msgstr "Cota continua" +# English: "Coordinate dimensions" # Source-Hash: fb91b1d43b8362d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coordinate dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.coordinateDimensions" msgstr "Cotas de coordenadas" +# English: "Diameter dimension" # Source-Hash: 530cb7ea461e8298 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.diameterDimension" msgstr "Cota de diámetro" +# English: "Distance" # Source-Hash: fe7ddb094aa8ff4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Distance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.distance" msgstr "Distancia" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.floorPlan" msgstr "Plano de planta" +# English: "Linear dimension" # Source-Hash: a73539fc472e2e12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Linear dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.linearDimension" msgstr "Cota lineal" +# English: "Measurement options" # Source-Hash: 6bba239f244c3d28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementOptions" msgstr "Opciones de medida" +# English: "Measurement type" # Source-Hash: 2cf5ca9b834ce8d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementType" msgstr "Tipo de medida" +# English: "Measure: {selectedLabel}" # Source-Hash: 0431574a0e6d69d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measure: {selectedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measureSelectedlabel" msgstr "Medir: {selectedLabel}" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.perimeter" msgstr "Perímetro" +# English: "Radius dimension" # Source-Hash: 6c9acc0589bdd4d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Radius dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.radiusDimension" msgstr "Cota de radio" +# English: "Smart" # Source-Hash: 01b0015e4e176124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.smart" msgstr "Inteligente" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.volume" msgstr "Volumen" +# English: "Point and ask" # Source-Hash: 64f3eb5e1936906d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAsk" msgstr "Señalar y preguntar" +# English: "Point and ask (C)" # Source-Hash: d86d31e83f6f0888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask (C)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskC" msgstr "Señalar y preguntar (C)" +# English: "Point and ask works in the 3D view" # Source-Hash: 65622d2ef53c1f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask works in the 3D view" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskWorksInThe" msgstr "Señalar y preguntar funciona en la vista 3D" -# Source-Hash: 4275c05172abd7fb +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.beam" +msgstr "Viga" + # English: "Ceiling" +# Source-Hash: 4275c05172abd7fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ceiling" msgstr "Techo" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.column" msgstr "Pilar" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.door" msgstr "Puerta" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.duct" msgstr "Conducto" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ductFitting" msgstr "Accesorio de conducto" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.dwvPipe" msgstr "Tubería de desagüe" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.elevator" msgstr "Ascensor" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.fence" msgstr "Valla" -# Source-Hash: 7f4d93d5f2083844 +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.footing" +msgstr "Zapata" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.foundation" +msgstr "Cimentación" + # English: "Gable Roof" +# Source-Hash: 7f4d93d5f2083844 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.gableRoof" msgstr "Cubierta a dos aguas" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.hvacUnit" msgstr "Equipo de climatización" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.lineset" msgstr "Línea frigorífica" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.liquidLine" msgstr "Línea de líquido" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.pipeFitting" msgstr "Accesorio de tubería" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.register" msgstr "Rejilla" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.shelf" msgstr "Estantería" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.slab" msgstr "Forjado" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.spawnPoint" msgstr "Punto de aparición" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.stairs" msgstr "Escalera" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.trap" msgstr "Sifón" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.wall" msgstr "Muro" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.window" msgstr "Ventana" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.zone" msgstr "Zona" -# Source-Hash: cdf3f12939ba108c +# English: "Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it." +# Source-Hash: a309ae5ee641f072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels" +msgstr "Contornos discontinuos de los muros de las plantas contiguas, para alinear muros y comprobar una cubierta respecto a los muros que tiene debajo." + # English: "Delete guide image" +# Source-Hash: cdf3f12939ba108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteGuideImage" msgstr "Eliminar imagen guía" +# English: "Delete scan" # Source-Hash: 6add06abbafa8289 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete scan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteScan" msgstr "Eliminar escaneo 3D" +# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" # Source-Hash: 6f5e541cb5fb502f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideCount" msgstr "{count, plural, one {# imagen guía en esta planta} many {# imágenes guía en esta planta} other {# imágenes guía en esta planta}}" +# English: "Guide image {number}" # Source-Hash: 8a832a2f33757bf8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageNumber" msgstr "Imagen guía {number}" +# English: "Guide images" # Source-Hash: 7f3aaccfe8d1af1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImages" msgstr "Imágenes guía" +# English: "Guide image settings" # Source-Hash: 9b1de02505434d74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageSettings" msgstr "Ajustes de imágenes guía" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guides" msgstr "Guías" +# English: "Guides: Hidden" # Source-Hash: ccfb05c78d47d6f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesHidden" msgstr "Guías: ocultas" +# English: "Guides: Visible" # Source-Hash: 6d7ca85c4f389991 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesVisible" msgstr "Guías: visibles" +# English: "Hide guide images" # Source-Hash: 5f6264ac469f57c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideGuideImages" msgstr "Ocultar imágenes guía" +# English: "Hide reference floor" # Source-Hash: cae234c23229c80b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideReferenceFloor" msgstr "Ocultar planta de referencia" +# English: "Hide scans" # Source-Hash: 469a8416de876e0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideScans" msgstr "Ocultar escaneos 3D" +# English: "{count} below" # Source-Hash: 873620c284a5651f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count} below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.levelsBelow" msgstr "{count} por debajo" -# Source-Hash: 22a59ae72d8c9504 +# English: "No level above" +# Source-Hash: 6661c6766fdd1a7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelAbove" +msgstr "Sin planta superior" + +# English: "No level below" +# Source-Hash: e34d9ec372c823ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelBelow" +msgstr "Sin planta inferior" + # English: "No lower floor available." +# Source-Hash: 22a59ae72d8c9504 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.noLowerFloorAvailable" msgstr "No hay ninguna planta inferior." +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.opacity" msgstr "Opacidad" +# English: "Reference floor" # Source-Hash: 8bbd9bc90ac70a4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloor" msgstr "Planta de referencia" +# English: "Reference floor: {selectedLevelName}" # Source-Hash: 45493691a23c7690 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor: {selectedLevelName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSelectedlevelname" msgstr "Planta de referencia: {selectedLevelName}" +# English: "Reference floor settings" # Source-Hash: 7178c7b5a4581367 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSettings" msgstr "Ajustes de la planta de referencia" +# English: "References" # Source-Hash: a52848021f822f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.references" msgstr "Referencias" +# English: "Upload GLB meshes as scan references or blueprint images as guide references." # Source-Hash: e59c9eec7dbd6ba1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload GLB meshes as scan references or blueprint images as guide references." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesEmpty" msgstr "Sube mallas GLB como escaneos 3D de referencia o imágenes de planos como guías." +# English: "Reference settings" # Source-Hash: 3e73d06c3e96efba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceSettings" msgstr "Ajustes de referencias" +# English: "References: Hidden" # Source-Hash: aa0df2c2f00672e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesHidden" msgstr "Referencias: ocultas" +# English: "References: Visible" # Source-Hash: 9976708fc92bfa91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesVisible" msgstr "Referencias: visibles" +# English: "Riser diagram" # Source-Hash: 320a5f1ec012900b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser diagram" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.riserDiagram" msgstr "Esquema de montantes" +# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" # Source-Hash: 357b3d3eaf878a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanCount" msgstr "{count, plural, one {# escaneo 3D en esta planta} many {# escaneos 3D en esta planta} other {# escaneos 3D en esta planta}}" +# English: "Scan {number}" # Source-Hash: f09f12547484c888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanNumber" msgstr "Escaneo 3D {number}" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scans" msgstr "Escaneos 3D" +# English: "Scan settings" # Source-Hash: c9c52015095997e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanSettings" msgstr "Ajustes de escaneos 3D" +# English: "Scans: Hidden" # Source-Hash: 60abc9f1a8aec65c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansHidden" msgstr "Escaneos 3D: ocultos" +# English: "Scans: Visible" # Source-Hash: 2d2ff119a7dd7911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansVisible" msgstr "Escaneos 3D: visibles" +# English: "Show guide images" # Source-Hash: 8211234b205fd9a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showGuideImages" msgstr "Mostrar imágenes guía" +# English: "Show reference floor" # Source-Hash: 7786cae6acb04cfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showReferenceFloor" msgstr "Mostrar planta de referencia" +# English: "Show scans" # Source-Hash: 6066f0b6cff8a910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showScans" msgstr "Mostrar escaneos 3D" -# Source-Hash: def6dff836767bda +# English: "Show walls below and above" +# Source-Hash: c100768fc929ccf4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.showWallsBelowAndAbove" +msgstr "Mostrar muros inferiores y superiores" + # English: "Upload scan or guide image" +# Source-Hash: def6dff836767bda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.uploadScanOrGuideImage" msgstr "Subir escaneo 3D o imagen guía" -# Source-Hash: 6aee0962fc18c0f1 +# English: "Walls above" +# Source-Hash: e9ac75feeb58a5be +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsAbove" +msgstr "Muros superiores" + +# English: "Walls below" +# Source-Hash: 790a0b5c6dbf0e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelow" +msgstr "Muros inferiores" + +# English: "Walls below and above" +# Source-Hash: e74e28937511bce9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAbove" +msgstr "Muros inferiores y superiores" + +# English: "Walls below and above settings" +# Source-Hash: 7c7bd2a996dfd5ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveSettings" +msgstr "Ajustes de muros inferiores y superiores" + +# English: "Walls below and above: shown" +# Source-Hash: 43bc80f62aeab442 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveShown" +msgstr "Muros inferiores y superiores: visibles" + # English: "Back to home" +# Source-Hash: 6aee0962fc18c0f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.backToHome" msgstr "Volver al inicio" +# English: "Advanced" # Source-Hash: f571cb2d06ae01ae # Review: human -# English: "Advanced" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.advanced" msgstr "Avanzado" +# English: "{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: 7afac05841af7b0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.caption" +msgstr "{lead} cada muro toma el tipo exterior, esté donde esté. Esc o doble clic termina el tramo." + +# English: "Exterior wall" +# Source-Hash: 0a7c3607e2011f54 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.label" +msgstr "Muro exterior" + +# English: "Exterior walls: every wall drawn takes the exterior type and cladding" +# Source-Hash: 76fd5dab80b9d731 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.title" +msgstr "Muros exteriores: cada muro dibujado toma el tipo exterior y el revestimiento" + +# English: "walls · floor · ceiling" # Source-Hash: fb40866c2d6db5fb # Review: human -# English: "walls · floor · ceiling" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.hint" msgstr "muros · suelo · techo" +# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." # Source-Hash: 271039bd919dc4c1 # Review: human -# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.idle" msgstr "Elige una forma para empezar. Cada estancia incluye sus muros, suelo y techo." +# English: "{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: f1fddcf6d6b5c32b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.caption" +msgstr "{lead} cada muro toma el tipo interior, esté donde esté. Esc o doble clic termina el tramo." + +# English: "Interior wall" +# Source-Hash: b9e25ba9781c9848 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.label" +msgstr "Muro interior" + +# English: "Interior walls: every wall drawn takes the interior type and paint" +# Source-Hash: 9e4a942fd52a06c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.title" +msgstr "Muros interiores: cada muro dibujado toma el tipo interior y la pintura" + +# English: "{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run." +# Source-Hash: 3f519112d88b3ba6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.caption" +msgstr "{lead} cada línea es un muro invisible: delimita estancias, pero no se construye nada. Cerrada fuera de la casa, una estancia se convierte en porche. Esc o doble clic termina el tramo." + +# English: "Invisible wall" +# Source-Hash: 5b0317bd807d560e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.label" +msgstr "Muro invisible" + +# English: "Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall." +# Source-Hash: 72fbf62dac3603b5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.title" +msgstr "Muros invisibles: divide una estancia o cierra un porche sin construir un muro. Delimitan estancias; nunca cortan un muro." + +# English: "{lead} then the first one again to close. For L-shapes and angled rooms." # Source-Hash: ae2800b6c9e2b6ed # Review: human -# English: "{lead} then the first one again to close. For L-shapes and angled rooms." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.caption" msgstr "{lead} y vuelve a pulsar la primera para cerrar. Para estancias en L o con ángulos." +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: human -# English: "Polygon" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.label" msgstr "Polígono" +# English: "Click each corner," # Source-Hash: 345e3aca3879b5c5 # Review: human -# English: "Click each corner," #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.lead" msgstr "Haz clic en cada esquina" +# English: "Polygon room" # Source-Hash: 8ec6152e139b0e3f # Review: human -# English: "Polygon room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.title" msgstr "Estancia poligonal" +# English: "{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run." +# Source-Hash: 18773160bb84f069 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.caption" +msgstr "{lead} cada línea es una barandilla: una protección que delimita estancias, sin montantes, revestimiento ni zapata. Coloca una puerta en ella para crear un paso. Esc o doble clic termina el tramo." + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.label" +msgstr "Barandilla" + +# English: "Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel." +# Source-Hash: 161fbdfe5f09b81a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.title" +msgstr "Barandillas: una protección de 36 in que se dibuja como los muros, con postes a 6 ft como máximo. Delimita estancias y admite aberturas; el estilo y la altura, en el panel del muro." + +# English: "{lead} on the grid. Walls, floor and ceiling are created together." # Source-Hash: 9b46295eedcbedae # Review: human -# English: "{lead} on the grid. Walls, floor and ceiling are created together." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.caption" msgstr "{lead} en la cuadrícula. Los muros, el suelo y el techo se crean a la vez." +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: human -# English: "Rectangle" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.label" msgstr "Rectángulo" +# English: "Click two corners" # Source-Hash: 060faa48e299d7c9 # Review: human -# English: "Click two corners" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.lead" msgstr "Haz clic en dos esquinas" +# English: "Rectangle room" # Source-Hash: 83a45603ac7f41e8 # Review: human -# English: "Rectangle room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.title" msgstr "Estancia rectangular" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.title" msgstr "Estancias" -# Source-Hash: 8a21a03c4acf396b -# Review: human -# English: "{lead} Close a loop and it becomes a room; leave it open for partitions." +# English: "{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room." +# Source-Hash: ee04555fa3ead992 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.caption" -msgstr "{lead} Cierra el contorno para crear una estancia o déjalo abierto para tabiques." +msgstr "{lead} cada muro empieza donde acabó el anterior. Esc o doble clic termina el tramo; cierra un contorno y se convertirá en una estancia." +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.label" msgstr "Muros" -# Source-Hash: 9843edd68426fa2d -# Review: human -# English: "Draw one wall at a time." +# English: "Click each corner;" +# Source-Hash: ab5c2c822d968ca2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.lead" -msgstr "Dibuja un muro cada vez." +msgstr "Haz clic en cada esquina;" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.title" msgstr "Muros" -# Source-Hash: 1f3650da23603008 +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraControlsHint" -msgstr "Ayuda de controles de cámara" +msgid "editor.buildPanel.wallTypeControls.default" +msgstr "Predeterminado" -# Source-Hash: 101b6a54c377049a +# English: "Delete from this project" +# Source-Hash: 1dba2aacc55e0bb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pan" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.pan" -msgstr "Desplazar" +msgid "editor.buildPanel.wallTypeControls.deleteFromThisProject" +msgstr "Eliminar de este proyecto" -# Source-Hash: 306d409f8a1cf480 +# English: "Duplicate as a new type…" +# Source-Hash: 58a1b780e877ddc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.rotate" -msgstr "Girar" +msgid "editor.buildPanel.wallTypeControls.duplicateAsANewType" +msgstr "Duplicar como tipo nuevo…" -# Source-Hash: 663cecc316eb91b8 +# English: "Edit layers…" +# Source-Hash: 43e7aad9a5f75b6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zoom" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.zoom" -msgstr "Zoom" +msgid "editor.buildPanel.wallTypeControls.editLayers" +msgstr "Editar capas…" -# Source-Hash: 34b7ca7ff46c44a9 +# English: "Edit or duplicate {label}" +# Source-Hash: 640331cc307a8421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label}: {finish}" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.ariaLabel" -msgstr "{label}: {finish}" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateLabel" +msgstr "Editar o duplicar {label}" -# Source-Hash: 4ca0e0e73229540c +# English: "Edit or duplicate this wall type" +# Source-Hash: 8f1ea5a4c16ba810 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.default" -msgstr "Predeterminado" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType" +msgstr "Editar o duplicar este tipo de muro" -# Source-Hash: a2bd204e93e90598 +# English: "New wall type…" +# Source-Hash: 273fcb71b4f9272f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismiss" -msgstr "Cerrar" +msgid "editor.buildPanel.wallTypeControls.newWallType" +msgstr "Nuevo tipo de muro…" -# Source-Hash: 9222f970dce6cc60 +# English: "Presets stay as cited. Duplicate one to change its layers." +# Source-Hash: 079e4005e0093122 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismissCameraControlsHint" -msgstr "Cerrar la ayuda de controles de cámara" +msgid "editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne" +msgstr "Los preajustes se mantienen tal como se citan. Duplica uno para cambiar sus capas." -# Source-Hash: aeac665749d3101d +# English: "Add" +# Source-Hash: b071c943d8da1500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.expandSidebar" -msgstr "Expandir barra lateral" +msgid "editor.buildPanel.wallTypeEditorDialog.add" +msgstr "Añadir" -# Source-Hash: dd86efcab72fc5c7 +# English: "Air space" +# Source-Hash: fcc69acc0fd92288 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis" -msgstr "Eje" +msgid "editor.buildPanel.wallTypeEditorDialog.airSpace" +msgstr "Cámara de aire" -# Source-Hash: 5232a826549f915e +# English: "A wall type holds at most {limit, plural, one {# layer} other {# layers}}." +# Source-Hash: 46fb2832f979d656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⌥ axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis2" -msgstr "⌥ eje" +msgid "editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost" +msgstr "Un tipo de muro admite como máximo {limit, plural, one {# capa} many {# capas} other {# capas}}." -# Source-Hash: 7001600001844c92 +# English: "Brick veneer" +# Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⚠ no equipment" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.noEquipment" -msgstr "⚠ sin equipo" +msgid "editor.buildPanel.wallTypeEditorDialog.brickVeneer" +msgstr "Chapado de ladrillo" -# Source-Hash: eae0194029f96edb +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# register} other {# registers}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.registerCount" -msgstr "{count, plural, one {# rejilla} many {# rejillas} other {# rejillas}}" +msgid "editor.buildPanel.wallTypeEditorDialog.cancel" +msgstr "Cancelar" -# Source-Hash: 8fb180251a321cef +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "R/T rotate" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.rTRotate" -msgstr "R/T girar" +msgid "editor.buildPanel.wallTypeEditorDialog.drywall" +msgstr "Placa de yeso" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 6fb660d4eabe8d6a +# English: "Duplicate wall type" +# Source-Hash: 1f2e15ce909adf0c # Review: required -# English: "{count, plural, one {{number} run} other {{number} runs}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.runCount" -msgstr "{count, plural, one {{number} tramo} many {{number} tramos} other {{number} tramos}}" +msgid "editor.buildPanel.wallTypeEditorDialog.duplicateWallType" +msgstr "Duplicar tipo de muro" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Edit wall type" +# Source-Hash: 832628bc67f36778 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.system" -msgstr "Sistema" +msgid "editor.buildPanel.wallTypeEditorDialog.editWallType" +msgstr "Editar tipo de muro" -# Source-Hash: f0e76aea883d4e48 +# English: "Every wall type has one framing core" +# Source-Hash: 7d3d13d418b64f1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a floor under the roof" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" -msgstr "Añadir una planta bajo la cubierta" +msgid "editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming" +msgstr "Cada tipo de muro tiene un núcleo de entramado" -# Source-Hash: a982ff9b075af829 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level above" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelAbove" -msgstr "Añadir planta encima" +msgid "editor.buildPanel.wallTypeEditorDialog.fiberCement" +msgstr "Fibrocemento" -# Source-Hash: 0db7d221df08fa6b +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level below" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelBelow" -msgstr "Añadir planta debajo" +msgid "editor.buildPanel.wallTypeEditorDialog.finish" +msgstr "Acabado" -# Source-Hash: c0774c185060fc13 +# English: "Framing core" +# Source-Hash: 23306d290f066b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.cancel" -msgstr "Cancelar" +msgid "editor.buildPanel.wallTypeEditorDialog.framingCore" +msgstr "Núcleo de entramado" -# Source-Hash: b26de372b4b0eaac +# English: "{fullReason} Remove one to add another." +# Source-Hash: b50fa488c2bfb502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.delete" -msgstr "Eliminar" +msgid "editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother" +msgstr "{fullReason} Quita una para añadir otra." -# Source-Hash: 8e29375893d63408 +# English: "Gypsum sheathing" +# Source-Hash: e3e25fbbf14e97fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevel" -msgstr "Eliminar planta" +msgid "editor.buildPanel.wallTypeEditorDialog.gypsumSheathing" +msgstr "Tablero de yeso" -# Source-Hash: c3bc69ec52022250 +# English: "Inside" +# Source-Hash: 8a7ff5011275abbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevelConfirm" -msgstr "¿Seguro que quieres eliminar {levelName}? Todos los muros, forjados y objetos de esta planta se eliminarán de forma permanente." +msgid "editor.buildPanel.wallTypeEditorDialog.inside" +msgstr "Interior" -# Source-Hash: 5b2826ddac95cd0c +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to reorder" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.dragToReorder" -msgstr "Arrastra para reordenar" +msgid "editor.buildPanel.wallTypeEditorDialog.insulation" +msgstr "Aislamiento" -# Source-Hash: 933177338f5a03ed +# English: "Lap siding" +# Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateLevel" -msgstr "Duplicar planta" +msgid "editor.buildPanel.wallTypeEditorDialog.lapSiding" +msgstr "Revestimiento solapado" -# Source-Hash: 9614c6825a7c67bf +# English: "Layer" +# Source-Hash: d7ee1cefa20d0e32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateWithOptions" -msgstr "Duplicar con opciones..." +msgid "editor.buildPanel.wallTypeEditorDialog.layer" +msgstr "Capa" -# Source-Hash: 5ec91bb339806413 +# English: "Layer role" +# Source-Hash: bd00fff893ace820 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit unit focus" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.exitUnitFocus" -msgstr "Salir del enfoque de unidad" +msgid "editor.buildPanel.wallTypeEditorDialog.layerRole" +msgstr "Función de la capa" -# Source-Hash: a88c3f8bed58b4fa +# English: "Lining" +# Source-Hash: e5536deeaabfacec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused unit has zones here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" -msgstr "La unidad enfocada tiene zonas aquí" +msgid "editor.buildPanel.wallTypeEditorDialog.lining" +msgstr "Trasdosado" -# Source-Hash: 655055a310fb5d13 +# English: "Lists under Exterior: it has a cladding side." +# Source-Hash: d4e37e79879efa23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorOverGrade" -msgstr "La planta baja está {distance} por encima de la rasante. Asentar la planta baja 8 in sobre la rasante" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA" +msgstr "Aparece en Exterior: tiene una cara con revestimiento." -# Source-Hash: 77548faae4869eec +# English: "Lists under Interior: both faces match." +# Source-Hash: d455f88eb0e9e68e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorUnderGrade" -msgstr "La planta baja está {distance} por debajo de la rasante. Asentar la planta baja 8 in sobre la rasante" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch" +msgstr "Aparece en Interior: ambas caras son iguales." -# Source-Hash: e3763ae2dc62c03f +# English: "List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type." +# Source-Hash: 5454746105949d05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground line: the levels under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" -msgstr "Línea de rasante: las plantas inferiores quedan bajo rasante" +msgid "editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside" +msgstr "Enumera las capas de fuera hacia dentro, alrededor de un núcleo de entramado. Los muros ya dibujados conservan sus capas; los que se dibujen a partir de ahora usarán el tipo guardado." -# Source-Hash: e12035eb47cc61cc +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground · {offset}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundOffset" -msgstr "Rasante · {offset}" +msgid "editor.buildPanel.wallTypeEditorDialog.material" +msgstr "Material" -# Source-Hash: 5661c11383217661 +# English: "Mineral wool board" +# Source-Hash: 75d79fb9a2e8fcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insert level here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.insertLevelHere" -msgstr "Insertar planta aquí" +msgid "editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard" +msgstr "Panel de lana mineral" -# Source-Hash: 0215b36d5a08597a +# English: "Move in" +# Source-Hash: 78acc1bad880ce0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level height" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.levelHeight" -msgstr "Altura de planta" +msgid "editor.buildPanel.wallTypeEditorDialog.moveIn" +msgstr "Mover hacia dentro" -# Source-Hash: 91af436e81bb0875 +# English: "Move out" +# Source-Hash: 2de6ee30b4e608bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paste copied selection" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.pasteCopiedSelection" -msgstr "Pegar la selección copiada" +msgid "editor.buildPanel.wallTypeEditorDialog.moveOut" +msgstr "Mover hacia fuera" -# Source-Hash: 68bc52f53240cbec +# English: "Name" +# Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reorder {levelName}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.reorderLevelname" -msgstr "Reordenar {levelName}" +msgid "editor.buildPanel.wallTypeEditorDialog.name" +msgstr "Nombre" -# Source-Hash: 625d53de72ebc2ef +# English: "New wall type" +# Source-Hash: 4b3edeb00b32607e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seat the building so this floor stands at grade; the floors under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" -msgstr "Asentar el edificio para que esta planta quede a nivel de rasante; las inferiores quedan bajo rasante" +msgid "editor.buildPanel.wallTypeEditorDialog.newWallType" +msgstr "Nuevo tipo de muro" -# Source-Hash: 7ac9c00895c30d76 +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set as ground floor" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.setAsGroundFloor" -msgstr "Establecer como planta baja" +msgid "editor.buildPanel.wallTypeEditorDialog.osb" +msgstr "OSB" -# Source-Hash: d6b381247066a7aa +# English: "Outside" +# Source-Hash: dc4fe6cb415ed882 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" -msgstr "La planta baja no se puede eliminar" +msgid "editor.buildPanel.wallTypeEditorDialog.outside" +msgstr "Exterior" -# Source-Hash: b1122051dce0d7a1 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor stands at grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" -msgstr "Esta planta está a nivel de rasante" +msgid "editor.buildPanel.wallTypeEditorDialog.plaster" +msgstr "Enlucido" -# Source-Hash: 062cc0f1afef0c52 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.unit" -msgstr "Unidad" +msgid "editor.buildPanel.wallTypeEditorDialog.plywood" +msgstr "Contrachapado" -# Source-Hash: 5871b7c20478f4a1 +# English: "Remove layer" +# Source-Hash: ca36e9fd7fba1fce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor area" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.area" -msgstr "Superficie de suelo" +msgid "editor.buildPanel.wallTypeEditorDialog.removeLayer" +msgstr "Quitar capa" -# Source-Hash: 8b2e1435272aa86a +# English: "Rigid foam" +# Source-Hash: 7d8f351c35f89b94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{room} floor" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.room" -msgstr "Suelo de {room}" +msgid "editor.buildPanel.wallTypeEditorDialog.rigidFoam" +msgstr "Espuma rígida" -# Source-Hash: 4731183fec06cbdf +# English: "Save wall type" +# Source-Hash: e8bc984965d5753d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shared floor" -#: packages/editor/src/components/ui/panels/room-construction-rows.tsx -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.shared" -msgstr "Suelo compartido" +msgid "editor.buildPanel.wallTypeEditorDialog.saveWallType" +msgstr "Guardar tipo de muro" -# Source-Hash: c0774c185060fc13 +# English: "Sheathing" +# Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.cancel" -msgstr "Cancelar" +msgid "editor.buildPanel.wallTypeEditorDialog.sheathing" +msgstr "Tablero" -# Source-Hash: 4c6ca3441c74da9e +# English: "Stone veneer" +# Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place building" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.placeBuilding" -msgstr "Colocar edificio" +msgid "editor.buildPanel.wallTypeEditorDialog.stoneVeneer" +msgstr "Chapado de piedra" -# Source-Hash: 306d409f8a1cf480 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.rotate" -msgstr "Girar" +msgid "editor.buildPanel.wallTypeEditorDialog.stucco" +msgstr "Estuco" -# Source-Hash: 832470d3aeee82fd +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel placement" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.cancelPlacement" -msgstr "Cancelar colocación" +msgid "editor.buildPanel.wallTypeEditorDialog.thickness" +msgstr "Espesor" -# Source-Hash: e374fcaee33c3e2c +# English: "Total {thickness}" +# Source-Hash: 72e31eecaeb87447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation — click or press C to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" -msgstr "Continuación: haz clic o pulsa C para cambiar" +msgid "editor.buildPanel.wallTypeEditorDialog.totalThickness" +msgstr "Total {thickness}" -# Source-Hash: b108c7dc2ec2b9de +# English: "Wall type" +# Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation: {label}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationLabel" -msgstr "Continuación: {label}" +msgid "editor.buildPanel.wallTypeEditorDialog.wallType" +msgstr "Tipo de muro" -# Source-Hash: 729d87891cb7dbb1 +# English: "Brick" +# Source-Hash: abff67400a76e5eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to draw fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" -msgstr "Arrastra para dibujar la valla" +msgid "editor.buildPanel.wallTypes.brick" +msgstr "Ladrillo" -# Source-Hash: b52e874718af1916 +# English: "Cladding" +# Source-Hash: ecca209317a2d1cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScope" -msgstr "Borrar: {scope}" +msgid "editor.buildPanel.wallTypes.cladding" +msgstr "Revestimiento" -# Source-Hash: 61f7494abc319e66 +# English: "Drawing: Exterior walls." +# Source-Hash: 5c53959decce5dae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" -msgstr "Alcance del borrado: haz clic o pulsa Mayús para cambiar" +msgid "editor.buildPanel.wallTypes.drawingExterior" +msgstr "Dibujando: muros exteriores." -# Source-Hash: 59993c163d68cc53 +# English: "Drawing: Interior walls." +# Source-Hash: 5206b8695c329b06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" -msgstr "Alcance del borrado: {scope}" +msgid "editor.buildPanel.wallTypes.drawingInterior" +msgstr "Dibujando: muros interiores." -# Source-Hash: a4bdcbc6cab772bf +# English: "Drywall paint" +# Source-Hash: 8a542e796ac8edf0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence type — click or press T to switch between straight, curved and freehand" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" -msgstr "Tipo de valla: haz clic o pulsa T para alternar entre recta, curva y a mano alzada" +msgid "editor.buildPanel.wallTypes.drywallPaint" +msgstr "Pintura sobre placa de yeso" -# Source-Hash: 2a9ed258727078ac +# English: "Exterior" +# Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fewer shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" -msgstr "Menos atajos" +msgid "editor.buildPanel.wallTypes.exterior" +msgstr "Exterior" -# Source-Hash: 698fa6e78af4d4b7 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish curve (or double-click)" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" -msgstr "Terminar la curva (o doble clic)" +msgid "editor.buildPanel.wallTypes.fiberCement" +msgstr "Fibrocemento" -# Source-Hash: 09f6d79edf5cfd16 +# English: "{drawing} Every wall takes the exterior type until you pick a shape or leave the tool." +# Source-Hash: 5badd399f078d485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridLength" -msgstr "Cuadrícula: {length}" +msgid "editor.buildPanel.wallTypes.forcedExterior" +msgstr "{drawing} Cada muro toma el tipo exterior hasta que elijas una forma o salgas de la herramienta." -# Source-Hash: c493b562040c4328 +# English: "{drawing} Every wall takes the interior type until you pick a shape or leave the tool." +# Source-Hash: de2c329c8d03a385 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step — click or tap Ctrl to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" -msgstr "Paso de cuadrícula: haz clic o pulsa Ctrl para cambiar" +msgid "editor.buildPanel.wallTypes.forcedInterior" +msgstr "{drawing} Cada muro toma el tipo interior hasta que elijas una forma o salgas de la herramienta." -# Source-Hash: d36a8c93d81b3a36 +# English: "New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint." +# Source-Hash: b70a5d0948632955 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepLength" -msgstr "Paso de cuadrícula: {length}" +msgid "editor.buildPanel.wallTypes.hint" +msgstr "Los muros nuevos usan el tipo exterior y su revestimiento; los dibujados dentro de una estancia usan el tipo interior. Todas las caras de placa de yeso reciben la pintura." -# Source-Hash: abea993ede7daa7e +# English: "Interior" +# Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a painted surface to erase" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" -msgstr "Pasa el cursor por una superficie pintada para borrarla" +msgid "editor.buildPanel.wallTypes.interior" +msgstr "Interior" -# Source-Hash: fc1ed0ddc0cfcef5 +# English: "Plain wall" +# Source-Hash: 3744d42a0e4ee161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" -msgstr "Pasa el cursor por una superficie para pintarla" +msgid "editor.buildPanel.wallTypes.plainWall" +msgstr "Muro simple" -# Source-Hash: 63dec5de4a337015 +# English: "Siding" +# Source-Hash: 8884a1d20caae02f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to pick its material" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" -msgstr "Pasa el cursor por una superficie para tomar su material" +msgid "editor.buildPanel.wallTypes.siding" +msgstr "Revestimiento de tablas" -# Source-Hash: 7ad6dc1b1f03f970 +# English: "Stone" +# Source-Hash: 44c6466e6907a0b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.moreShortcuts" -msgstr "Más atajos" +msgid "editor.buildPanel.wallTypes.stone" +msgstr "Piedra" -# Source-Hash: 476b8f5f9ee542e6 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScope" -msgstr "Pintar: {scope}" +msgid "editor.buildPanel.wallTypes.stucco" +msgstr "Estuco" -# Source-Hash: 9c09ce4c34122277 +# English: "Camera controls hint" +# Source-Hash: 1f3650da23603008 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" -msgstr "Alcance de la pintura: haz clic o pulsa Mayús para cambiar" +msgid "editor.cameraControlsHint" +msgstr "Ayuda de controles de cámara" -# Source-Hash: 7890aa81687f6654 +# English: "Pan" +# Source-Hash: 101b6a54c377049a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" -msgstr "Alcance de la pintura: {scope}" +msgid "editor.cameraHint.pan" +msgstr "Desplazar" -# Source-Hash: a75b3abc0784b12b +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx -#, fuzzy -msgid "editor.helpers.contextualHelperPanel.pickScope" -msgstr "Tomar: {scope}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.cameraHint.rotate" +msgstr "Girar" -# Source-Hash: 69c1044e6ea87ccf +# English: "Zoom" +# Source-Hash: 663cecc316eb91b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gate on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" -msgstr "Colocar puerta en una valla" +msgid "editor.cameraHint.zoom" +msgstr "Zoom" -# Source-Hash: 3754255b8affc7a0 +# English: "{label}: {finish}" +# Source-Hash: 34b7ca7ff46c44a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place passage on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" -msgstr "Colocar paso en una valla" +msgid "editor.controls.finishSwatch.ariaLabel" +msgstr "{label}: {finish}" -# Source-Hash: a8328c8964e947fa +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a material to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" -msgstr "Elige un material para pintar" +msgid "editor.controls.finishSwatch.default" +msgstr "Predeterminado" -# Source-Hash: 6f95d7116aea5738 +# English: "Dismiss" +# Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Angles" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingAngles" -msgstr "Ajuste: ángulos" +msgid "editor.dismiss" +msgstr "Cerrar" -# Source-Hash: 2d6d8d85d2ebed47 +# English: "Dismiss camera controls hint" +# Source-Hash: 9222f970dce6cc60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Grid" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingGrid" -msgstr "Ajuste: cuadrícula" +msgid "editor.dismissCameraControlsHint" +msgstr "Cerrar la ayuda de controles de cámara" -# Source-Hash: 9096c5f9d8ce0f7c +# English: "Click to edit the foundation’s outline" +# Source-Hash: e314809b6907d9ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Lines" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingLines" -msgstr "Ajuste: líneas" +msgid "editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS" +msgstr "Haz clic para editar el contorno de la cimentación" -# Source-Hash: b663afbf0835cf32 +# English: "Expand sidebar" +# Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping mode — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" -msgstr "Modo de ajuste: haz clic o pulsa Mayús para cambiar" +msgid "editor.expandSidebar" +msgstr "Expandir barra lateral" -# Source-Hash: d00e984fc88f71aa +# English: "Axis" +# Source-Hash: dd86efcab72fc5c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Off" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingOff" -msgstr "Ajuste: desactivado" +msgid "editor.floatingActionMenu.axis" +msgstr "Eje" -# Source-Hash: 6be9d0d312200761 +# English: "⌥ axis" +# Source-Hash: 5232a826549f915e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight continuation is unavailable for curved or freehand fences" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" -msgstr "La continuación recta no está disponible en vallas curvas o a mano alzada" +msgid "editor.floatingActionMenu.axis2" +msgstr "⌥ eje" -# Source-Hash: 0d56b1f0a0493302 +# English: "⚠ no equipment" +# Source-Hash: 7001600001844c92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Continuous" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuous" -msgstr "Recta: continua" +msgid "editor.floatingActionMenu.noEquipment" +msgstr "⚠ sin equipo" -# Source-Hash: c034e85d3e7fd27e +# English: "{count, plural, one {# register} other {# registers}}" +# Source-Hash: eae0194029f96edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight fence continuation — click or press C to toggle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" -msgstr "Continuación de valla recta: haz clic o pulsa C para alternar" +msgid "editor.floatingActionMenu.registerCount" +msgstr "{count, plural, one {# rejilla} many {# rejillas} other {# rejillas}}" -# Source-Hash: e3f679cdb4a3d603 +# English: "R/T rotate" +# Source-Hash: 8fb180251a321cef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Single" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightSingle" -msgstr "Recta: simple" +msgid "editor.floatingActionMenu.rTRotate" +msgstr "R/T girar" -# Source-Hash: 9a56d90d461b4bca +# English: "{count, plural, one {{number} run} other {{number} runs}}" +# Source-Hash: 6fb660d4eabe8d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPoint" -msgstr "Añadir punto" +msgid "editor.floatingActionMenu.runCount" +msgstr "{count, plural, one {{number} tramo} many {{number} tramos} other {{number} tramos}}" -# Source-Hash: c3dc3918c823a5e6 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point · click the first to close" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPointClickTheFirstTo" -msgstr "Añadir punto · haz clic en el primero para cerrar" +msgid "editor.floatingActionMenu.system" +msgstr "Sistema" -# Source-Hash: d8cf72d482d56cd7 +# English: "Add a floor under the roof" +# Source-Hash: f0e76aea883d4e48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brush size" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.brushSize" -msgstr "Tamaño del pincel" +msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" +msgstr "Añadir una planta bajo la cubierta" -# Source-Hash: c0774c185060fc13 +# English: "Add level above" +# Source-Hash: a982ff9b075af829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancel" -msgstr "Cancelar" +msgid "editor.floatingLevelSelector.addLevelAbove" +msgstr "Añadir planta encima" -# Source-Hash: 104fdab7934ec66c +# English: "Add level below" +# Source-Hash: 0db7d221df08fa6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel picking" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelPicking" -msgstr "Cancelar selección" +msgid "editor.floatingLevelSelector.addLevelBelow" +msgstr "Añadir planta debajo" -# Source-Hash: c318cf0ce70b8977 +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel stroke" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelStroke" -msgstr "Cancelar trazo" +msgid "editor.floatingLevelSelector.cancel" +msgstr "Cancelar" -# Source-Hash: 16552c2580bbe8b8 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.done" -msgstr "Hecho" +msgid "editor.floatingLevelSelector.delete" +msgstr "Eliminar" -# Source-Hash: e297db9ca0ce6d31 +# English: "Delete level" +# Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the mezzanine" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheMezzanine" -msgstr "Dibujar el altillo" +msgid "editor.floatingLevelSelector.deleteLevel" +msgstr "Eliminar planta" -# Source-Hash: c8be604718a7a9fd +# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." +# Source-Hash: c3bc69ec52022250 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the opening" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheOpening" -msgstr "Dibujar la abertura" +msgid "editor.floatingLevelSelector.deleteLevelConfirm" +msgstr "¿Seguro que quieres eliminar {levelName}? Todos los muros, forjados y objetos de esta planta se eliminarán de forma permanente." -# Source-Hash: 231386ae6d534163 +# English: "Drag to reorder" +# Source-Hash: 5b2826ddac95cd0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the terrace" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheTerrace" -msgstr "Dibujar la terraza" +msgid "editor.floatingLevelSelector.dragToReorder" +msgstr "Arrastra para reordenar" -# Source-Hash: e092e803eb60af69 +# English: "Duplicate level" +# Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit smart measure" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.exitSmartMeasure" -msgstr "Salir de la medida inteligente" +msgid "editor.floatingLevelSelector.duplicateLevel" +msgstr "Duplicar planta" -# Source-Hash: b3907da0ad234f10 +# English: "Duplicate with options..." +# Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finish" -msgstr "Terminar" +msgid "editor.floatingLevelSelector.duplicateWithOptions" +msgstr "Duplicar con opciones..." -# Source-Hash: f74bd937ae2e3a40 +# English: "Exit unit focus" +# Source-Hash: 5ec91bb339806413 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish at the edge" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finishAtTheEdge" -msgstr "Terminar en el borde" +msgid "editor.floatingLevelSelector.exitUnitFocus" +msgstr "Salir del enfoque de unidad" -# Source-Hash: 21029cf1c45697cc +# English: "Focused unit has zones here" +# Source-Hash: a88c3f8bed58b4fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMove" -msgstr "Movimiento libre" +msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" +msgstr "La unidad enfocada tiene zonas aquí" -# Source-Hash: 3247d8689bed6ff6 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move · slide doors and windows" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" -msgstr "Movimiento libre · deslizar puertas y ventanas" +msgid "editor.floatingLevelSelector.foundation" +msgstr "Cimentación" -# Source-Hash: b5ad765b43e75a5c +# English: "Basement" +# Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to pick a material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.holdToPickAMaterial" -msgstr "Mantén pulsado para tomar un material" +msgid "editor.floatingLevelSelector.foundationBadgeBasement" +msgstr "Sótano" -# Source-Hash: 46b53c5449c98964 +# English: "Basement walls cut into the ground" +# Source-Hash: 96878e51aa1f4aac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inspect surface dimensions" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.inspectSurfaceDimensions" -msgstr "Inspeccionar las dimensiones de la superficie" +msgid "editor.floatingLevelSelector.foundationBadgeBasementTitle" +msgstr "Muros de sótano excavados en el terreno" -# Source-Hash: 63f1f7006ae14177 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint with this material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.paintWithThisMaterial" -msgstr "Pintar con este material" +msgid "editor.floatingLevelSelector.foundationBadgeOff" +msgstr "Desactivada" -# Source-Hash: 188f55cc5afbe34f +# English: "No foundation drawn: click to set one up" +# Source-Hash: 44761dadfc126cbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick target height" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pickTargetHeight" -msgstr "Elegir altura objetivo" +msgid "editor.floatingLevelSelector.foundationBadgeOffTitle" +msgstr "No hay cimentación dibujada: haz clic para configurar una" -# Source-Hash: 2880fc9752eababa +# English: "Piles" +# Source-Hash: f4031d60a7899a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin measurement lens" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pinMeasurementLens" -msgstr "Fijar la lupa de medida" +msgid "editor.floatingLevelSelector.foundationBadgePiles" +msgstr "Pilotes" -# Source-Hash: 722741535b346e09 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.place" -msgstr "Colocar" +msgid "editor.floatingLevelSelector.foundationBadgePilesTitle" +msgstr "Viga de cimentación sobre pilotes" -# Source-Hash: 82010a7fd6350c93 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place inside the room" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.placeInsideTheRoom" -msgstr "Colocar dentro de la estancia" +msgid "editor.floatingLevelSelector.foundationBadgeRaised" +msgstr "Elevado" -# Source-Hash: 8b97f4503edb27b9 +# English: "Raised floor on a stem wall" +# Source-Hash: 69980d5b1f49e272 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Press anywhere, drag to the opposite corner" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" -msgstr "Pulsa en cualquier punto y arrastra hasta la esquina opuesta" +msgid "editor.floatingLevelSelector.foundationBadgeRaisedTitle" +msgstr "Suelo elevado sobre murete de cimentación" -# Source-Hash: 786f0b1c81b2a6c0 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise or lower" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.raiseOrLower" -msgstr "Subir o bajar" +msgid "editor.floatingLevelSelector.foundationBadgeSlab" +msgstr "Forjado" -# Source-Hash: b033f46ae3a0cbb7 +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove a painted part, or reset a surface" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" -msgstr "Quitar una parte pintada o restablecer una superficie" +msgid "editor.floatingLevelSelector.foundationBadgeSlabTitle" +msgstr "Forjado sobre el terreno" -# Source-Hash: 0dc6d121791696f7 +# English: "The ground floor stands {distance} over grade. {action}" +# Source-Hash: 6a00e82fda812178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeLastPoint" -msgstr "Quitar el último punto" +msgid "editor.floatingLevelSelector.groundFloorOverGrade" +msgstr "La planta baja está {distance} sobre rasante. {action}" -# Source-Hash: f85bdac0a1d16603 +# English: "The ground floor stands {distance} under grade. {action}" +# Source-Hash: 61f400e77494df9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.resize" -msgstr "Cambiar tamaño" +msgid "editor.floatingLevelSelector.groundFloorUnderGrade" +msgstr "La planta baja está {distance} bajo rasante. {action}" -# Source-Hash: 8a665ecaeffa9308 +# English: "Ground line: the levels under it are below grade" +# Source-Hash: e3763ae2dc62c03f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotate45" -msgstr "Girar ±45°" +msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" +msgstr "Línea de rasante: las plantas inferiores quedan bajo rasante" -# Source-Hash: 6099ee99f8c38700 +# English: "Ground · {offset}" +# Source-Hash: e12035eb47cc61cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotateTheSelection45" -msgstr "Girar la selección ±45°" +msgid "editor.floatingLevelSelector.groundOffset" +msgstr "Rasante · {offset}" -# Source-Hash: c93ae898b4d59f6f +# English: "Insert level here" +# Source-Hash: 5661c11383217661 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set one corner, then the opposite" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" -msgstr "Fija una esquina y luego la opuesta" +msgid "editor.floatingLevelSelector.insertLevelHere" +msgstr "Insertar planta aquí" -# Source-Hash: c0774c185060fc13 +# English: "Level height" +# Source-Hash: 0215b36d5a08597a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.cancel" -msgstr "Cancelar" +msgid "editor.floatingLevelSelector.levelHeight" +msgstr "Altura de planta" -# Source-Hash: ed7f3d8417a3e074 +# English: "Paste copied selection" +# Source-Hash: 91af436e81bb0875 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.forcePlace" -msgstr "Forzar colocación" +msgid "editor.floatingLevelSelector.pasteCopiedSelection" +msgstr "Pegar la selección copiada" -# Source-Hash: 722741535b346e09 +# English: "Reorder {levelName}" +# Source-Hash: 68bc52f53240cbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.place" -msgstr "Colocar" +msgid "editor.floatingLevelSelector.reorderLevelname" +msgstr "Reordenar {levelName}" -# Source-Hash: 306d409f8a1cf480 +# English: "Seat the building so this floor stands at grade; the floors under it are below grade" +# Source-Hash: 625d53de72ebc2ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.rotate" -msgstr "Girar" +msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" +msgstr "Asentar el edificio para que esta planta quede a nivel de rasante; las inferiores quedan bajo rasante" -# Source-Hash: e9dfc2a178bca5f7 +# English: "Set as ground floor" +# Source-Hash: 7ac9c00895c30d76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guided constraints bypassed" -#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" -msgstr "Restricciones guiadas omitidas" +msgid "editor.floatingLevelSelector.setAsGroundFloor" +msgstr "Establecer como planta baja" -# Source-Hash: c69865ec5d9d1ac9 +# English: "Show the foundation" +# Source-Hash: 46b0734342ab499f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.angle.g10zaw" -msgstr "Ángulo" +msgid "editor.floatingLevelSelector.showTheFoundation" +msgstr "Mostrar la cimentación" -# Source-Hash: 9c562983f6fed685 +# English: "The ground level cannot be deleted" +# Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arm length m" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.armLengthM.1jclqgw" -msgstr "Longitud del brazo m" +msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" +msgstr "La planta baja no se puede eliminar" -# Source-Hash: 38461bc364321749 +# English: "This floor stands at grade" +# Source-Hash: b1122051dce0d7a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment side" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.attachmentSide.6n9s5v" -msgstr "Lado de conexión" +msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" +msgstr "Esta planta está a nivel de rasante" +# English: "Unit" +# Source-Hash: 062cc0f1afef0c52 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.unit" +msgstr "Unidad" + +# English: "Floor area" +# Source-Hash: 5871b7c20478f4a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.area" +msgstr "Superficie de suelo" + +# English: "{room} floor" +# Source-Hash: 8b2e1435272aa86a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.room" +msgstr "Suelo de {room}" + +# English: "Shared floor" +# Source-Hash: 4731183fec06cbdf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.shared" +msgstr "Suelo compartido" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.cancel" +msgstr "Cancelar" + +# English: "Place building" +# Source-Hash: 4c6ca3441c74da9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.placeBuilding" +msgstr "Colocar edificio" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.rotate" +msgstr "Girar" + +# English: "Cancel placement" +# Source-Hash: 832470d3aeee82fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.cancelPlacement" +msgstr "Cancelar colocación" + +# English: "Continuation — click or press C to cycle" +# Source-Hash: e374fcaee33c3e2c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" +msgstr "Continuación: haz clic o pulsa C para cambiar" + +# English: "Continuation: {label}" +# Source-Hash: b108c7dc2ec2b9de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationLabel" +msgstr "Continuación: {label}" + +# English: "Drag to draw fence" +# Source-Hash: 729d87891cb7dbb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" +msgstr "Arrastra para dibujar la valla" + +# English: "Erase: {scope}" +# Source-Hash: b52e874718af1916 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScope" +msgstr "Borrar: {scope}" + +# English: "Erase scope — click or press Shift to cycle" +# Source-Hash: 61f7494abc319e66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" +msgstr "Alcance del borrado: haz clic o pulsa Mayús para cambiar" + +# English: "Erase scope: {scope}" +# Source-Hash: 59993c163d68cc53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" +msgstr "Alcance del borrado: {scope}" + +# English: "Fence type — click or press T to switch between straight, curved and freehand" +# Source-Hash: a4bdcbc6cab772bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" +msgstr "Tipo de valla: haz clic o pulsa T para alternar entre recta, curva y a mano alzada" + +# English: "Fewer shortcuts" +# Source-Hash: 2a9ed258727078ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" +msgstr "Menos atajos" + +# English: "Finish curve (or double-click)" +# Source-Hash: 698fa6e78af4d4b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" +msgstr "Terminar la curva (o doble clic)" + +# English: "Grid: {length}" +# Source-Hash: 09f6d79edf5cfd16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridLength" +msgstr "Cuadrícula: {length}" + +# English: "Grid step — click or tap Ctrl to cycle" +# Source-Hash: c493b562040c4328 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" +msgstr "Paso de cuadrícula: haz clic o pulsa Ctrl para cambiar" + +# English: "Grid step: {length}" +# Source-Hash: d36a8c93d81b3a36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepLength" +msgstr "Paso de cuadrícula: {length}" + +# English: "Hover a painted surface to erase" +# Source-Hash: abea993ede7daa7e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" +msgstr "Pasa el cursor por una superficie pintada para borrarla" + +# English: "Hover a surface to paint" +# Source-Hash: fc1ed0ddc0cfcef5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" +msgstr "Pasa el cursor por una superficie para pintarla" + +# English: "Hover a surface to pick its material" +# Source-Hash: 63dec5de4a337015 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" +msgstr "Pasa el cursor por una superficie para tomar su material" + +# English: "More shortcuts" +# Source-Hash: 7ad6dc1b1f03f970 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.moreShortcuts" +msgstr "Más atajos" + +# English: "Paint: {scope}" +# Source-Hash: 476b8f5f9ee542e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScope" +msgstr "Pintar: {scope}" + +# English: "Paint scope — click or press Shift to cycle" +# Source-Hash: 9c09ce4c34122277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" +msgstr "Alcance de la pintura: haz clic o pulsa Mayús para cambiar" + +# English: "Paint scope: {scope}" +# Source-Hash: 7890aa81687f6654 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" +msgstr "Alcance de la pintura: {scope}" + +# English: "Pick: {scope}" +# Source-Hash: a75b3abc0784b12b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.pickScope" +msgstr "Tomar: {scope}" + +# English: "Place gate on a fence" +# Source-Hash: 69c1044e6ea87ccf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" +msgstr "Colocar puerta en una valla" + +# English: "Place passage on a fence" +# Source-Hash: 3754255b8affc7a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" +msgstr "Colocar paso en una valla" + +# English: "Select a material to paint" +# Source-Hash: a8328c8964e947fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" +msgstr "Elige un material para pintar" + +# English: "Snapping: Angles" +# Source-Hash: 6f95d7116aea5738 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingAngles" +msgstr "Ajuste: ángulos" + +# English: "Snapping: Grid" +# Source-Hash: 2d6d8d85d2ebed47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingGrid" +msgstr "Ajuste: cuadrícula" + +# English: "Snapping: Lines" +# Source-Hash: 9096c5f9d8ce0f7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingLines" +msgstr "Ajuste: líneas" + +# English: "Snapping mode — click or press Shift to cycle" +# Source-Hash: b663afbf0835cf32 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" +msgstr "Modo de ajuste: haz clic o pulsa Mayús para cambiar" + +# English: "Snapping: Off" +# Source-Hash: d00e984fc88f71aa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingOff" +msgstr "Ajuste: desactivado" + +# English: "Straight continuation is unavailable for curved or freehand fences" +# Source-Hash: 6be9d0d312200761 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" +msgstr "La continuación recta no está disponible en vallas curvas o a mano alzada" + +# English: "Straight: Continuous" +# Source-Hash: 0d56b1f0a0493302 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuous" +msgstr "Recta: continua" + +# English: "Straight fence continuation — click or press C to toggle" +# Source-Hash: c034e85d3e7fd27e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" +msgstr "Continuación de valla recta: haz clic o pulsa C para alternar" + +# English: "Straight: Single" +# Source-Hash: e3f679cdb4a3d603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightSingle" +msgstr "Recta: simple" + +# English: "Add point" +# Source-Hash: 9a56d90d461b4bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPoint" +msgstr "Añadir punto" + +# English: "Add point · click the first to close" +# Source-Hash: c3dc3918c823a5e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPointClickTheFirstTo" +msgstr "Añadir punto · haz clic en el primero para cerrar" + +# English: "Brush size" +# Source-Hash: d8cf72d482d56cd7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.brushSize" +msgstr "Tamaño del pincel" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancel" +msgstr "Cancelar" + +# English: "Cancel picking" +# Source-Hash: 104fdab7934ec66c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelPicking" +msgstr "Cancelar selección" + +# English: "Cancel stroke" +# Source-Hash: c318cf0ce70b8977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelStroke" +msgstr "Cancelar trazo" + +# English: "Done" +# Source-Hash: 16552c2580bbe8b8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.done" +msgstr "Hecho" + +# English: "Draw the mezzanine" +# Source-Hash: e297db9ca0ce6d31 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheMezzanine" +msgstr "Dibujar el altillo" + +# English: "Draw the opening" +# Source-Hash: c8be604718a7a9fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheOpening" +msgstr "Dibujar la abertura" + +# English: "Draw the terrace" +# Source-Hash: 231386ae6d534163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheTerrace" +msgstr "Dibujar la terraza" + +# English: "Exit smart measure" +# Source-Hash: e092e803eb60af69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.exitSmartMeasure" +msgstr "Salir de la medida inteligente" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finish" +msgstr "Terminar" + +# English: "Finish at the edge" +# Source-Hash: f74bd937ae2e3a40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finishAtTheEdge" +msgstr "Terminar en el borde" + +# English: "Free move" +# Source-Hash: 21029cf1c45697cc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMove" +msgstr "Movimiento libre" + +# English: "Free move · slide doors and windows" +# Source-Hash: 3247d8689bed6ff6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" +msgstr "Movimiento libre · deslizar puertas y ventanas" + +# English: "Hold to pick a material" +# Source-Hash: b5ad765b43e75a5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.holdToPickAMaterial" +msgstr "Mantén pulsado para tomar un material" + +# English: "Inspect surface dimensions" +# Source-Hash: 46b53c5449c98964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.inspectSurfaceDimensions" +msgstr "Inspeccionar las dimensiones de la superficie" + +# English: "Paint with this material" +# Source-Hash: 63f1f7006ae14177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.paintWithThisMaterial" +msgstr "Pintar con este material" + +# English: "Pick target height" +# Source-Hash: 188f55cc5afbe34f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pickTargetHeight" +msgstr "Elegir altura objetivo" + +# English: "Pin measurement lens" +# Source-Hash: 2880fc9752eababa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pinMeasurementLens" +msgstr "Fijar la lupa de medida" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.place" +msgstr "Colocar" + +# English: "Place inside the room" +# Source-Hash: 82010a7fd6350c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.placeInsideTheRoom" +msgstr "Colocar dentro de la estancia" + +# English: "Press anywhere, drag to the opposite corner" +# Source-Hash: 8b97f4503edb27b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" +msgstr "Pulsa en cualquier punto y arrastra hasta la esquina opuesta" + +# English: "Raise or lower" +# Source-Hash: 786f0b1c81b2a6c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.raiseOrLower" +msgstr "Subir o bajar" + +# English: "Remove a painted part, or reset a surface" +# Source-Hash: b033f46ae3a0cbb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" +msgstr "Quitar una parte pintada o restablecer una superficie" + +# English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeLastPoint" +msgstr "Quitar el último punto" + +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.resize" +msgstr "Cambiar tamaño" + +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotate45" +msgstr "Girar ±45°" + +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotateTheSelection45" +msgstr "Girar la selección ±45°" + +# English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" +msgstr "Fija una esquina y luego la opuesta" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.cancel" +msgstr "Cancelar" + +# English: "Force place" +# Source-Hash: ed7f3d8417a3e074 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.forcePlace" +msgstr "Forzar colocación" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.place" +msgstr "Colocar" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.rotate" +msgstr "Girar" + +# English: "Guided constraints bypassed" +# Source-Hash: e9dfc2a178bca5f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +#, fuzzy +msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" +msgstr "Restricciones guiadas omitidas" + +# English: "Angle" +# Source-Hash: c69865ec5d9d1ac9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.angle.g10zaw" +msgstr "Ángulo" + +# English: "Arm length m" +# Source-Hash: 9c562983f6fed685 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.armLengthM.1jclqgw" +msgstr "Longitud del brazo m" + +# English: "Attachment side" +# Source-Hash: 38461bc364321749 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.attachmentSide.6n9s5v" +msgstr "Lado de conexión" + +# English: "Auto hangers" # Source-Hash: 441124187e7da2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoHangers.10554ka" +msgstr "Colgadores automáticos" + +# English: "Auto miter corners" +# Source-Hash: e20033e4b0045f1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoMiterCorners.zxr5lt" +msgstr "Inglete automático en esquinas" + +# English: "Auto span" +# Source-Hash: 92f3a8b63963daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoSpan.1gas9r6" +msgstr "Luz automática" + +# English: "Back ratio" +# Source-Hash: 8b2dfb9b9137fda0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.backRatio.1ughd99" +msgstr "Proporción trasera" + +# English: "Base height" +# Source-Hash: 75dfae8d9f03fa0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseHeight.1hs4zdf" +msgstr "Altura de la base" + +# English: "Base style" +# Source-Hash: 1cc2da12e8463e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseStyle.24vty5" +msgstr "Estilo de base" + +# English: "Beam height" +# Source-Hash: f9d95c6df06b6223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamHeight.1kq0oh1" +msgstr "Canto de viga" + +# English: "Beam width" +# Source-Hash: a133f8d23b1274dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamWidth.1q1mzbe" +msgstr "Ancho de viga" + +# English: "Body shape" +# Source-Hash: b98c0d0189526335 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bodyShape.arvkuw" +msgstr "Forma del cuerpo" + +# English: "Bracket style" +# Source-Hash: 2251ecab18467631 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bracketStyle.1vvoptm" +msgstr "Estilo de ménsula" + +# English: "Branch angle" +# Source-Hash: 5e9d4a4fcea481ca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.branchAngle.v56zme" +msgstr "Ángulo de derivación" + +# English: "Cab height" +# Source-Hash: dec709f1f9c714ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cabHeight.1yn34mk" +msgstr "Altura de cabina" + +# English: "Canopy form" +# Source-Hash: 87863f8366e1c88a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.canopyForm.1czzggp" +msgstr "Forma de la marquesina" + +# English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cap.s0thej" +msgstr "Remate" + +# English: "Cap overhang" +# Source-Hash: 2f2e49598ec8188e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capOverhang.kxxz9j" +msgstr "Vuelo del remate" + +# English: "Cap shape" +# Source-Hash: 8523c086e95b83e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capShape.17mbxhu" +msgstr "Forma del remate" + +# English: "Cap thickness" +# Source-Hash: acd98600a518dd95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capThickness.u835q9" +msgstr "Espesor del remate" + +# English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.carcassHeight.1aks0zy" +msgstr "Altura del casco" + +# English: "Cleanout style" +# Source-Hash: 827709caa79db37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cleanoutStyle.127zz6z" +msgstr "Estilo de registro" + +# English: "Collar diameter" +# Source-Hash: a549dc98e2a1bd07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarDiameter.1d5k28r" +msgstr "Diámetro del collarín" + +# English: "Collar height" +# Source-Hash: aa0232fc6f3eeadc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarHeight.1dqwsrf" +msgstr "Altura del collarín" + +# English: "Collar shape" +# Source-Hash: cbd8079c3f540a4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarShape.1gacv11" +msgstr "Forma del collarín" + +# English: "Collar width" +# Source-Hash: 557cde7f95616454 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarWidth.qvc9ik" +msgstr "Ancho del collarín" + +# English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.columns.1sy73ks" +msgstr "Columnas" + +# English: "Conical full circle" +# Source-Hash: cd5b2f70924b7c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" +msgstr "Cónica de círculo completo" + +# English: "Connection mode" +# Source-Hash: dc90f3abb935e7e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionMode.1nty478" +msgstr "Modo de conexión" + +# English: "Connection offset" +# Source-Hash: 1a9a49d7135d4f15 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionOffset.2m70s8" +msgstr "Desfase de conexión" + +# English: "Corner bevel" +# Source-Hash: 69be301fcb8030e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cornerBevel.16iniho" +msgstr "Bisel de esquina" + +# English: "Covering type" +# Source-Hash: b997a96a9dc7e450 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.coveringType.95yd3k" +msgstr "Tipo de revestimiento" + +# English: "Cricket height" +# Source-Hash: 03b33160d8baa966 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketHeight.pok31n" +msgstr "Altura del caballete" + +# English: "Cricket length" +# Source-Hash: 21552eee05ccb5d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketLength.i2aht8" +msgstr "Longitud del caballete" + +# English: "Cricket side" +# Source-Hash: c13b707bfdeac855 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketSide.1xnwil7" +msgstr "Lado del caballete" + +# English: "Cricket style" +# Source-Hash: 4ee7089fa1b3e63b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketStyle.10j0lat" +msgstr "Estilo del caballete" + +# English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curb.5xi2ol" +msgstr "Bordillo" + +# English: "Curb height" +# Source-Hash: 8da648df210dbc0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curbHeight.1yn97sg" +msgstr "Altura del bordillo" + +# English: "Curve offset" +# Source-Hash: 0c01b9848f5d9673 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curveOffset.l4xhvr" +msgstr "Desfase de curva" + +# English: "Damper angle" +# Source-Hash: 69179f4caa384585 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.damperAngle.h5fxn7" +msgstr "Ángulo de la compuerta" + +# English: "Deck thickness" +# Source-Hash: 15cff91231e806a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.deckThickness.4jmqze" +msgstr "Espesor de la tarima" + +# English: "Default level id" +# Source-Hash: eff1e7dc0901b9d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.defaultLevelId.xmrjzp" +msgstr "ID de planta predeterminada" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.depth.1lu11u2" +msgstr "Profundidad" + +# English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter.s3robe" +msgstr "Diámetro" + +# English: "Diameter2" +# Source-Hash: dab4100e83e4e6b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter2.pm6cl4" +msgstr "Diámetro 2" + +# English: "Door duration ms" +# Source-Hash: 5747145349432396 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorDurationMs.13nx909" +msgstr "Duración de puerta ms" + +# English: "Door height" +# Source-Hash: 6f4aca26196c221f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorHeight.92r9k4" +msgstr "Altura de puerta" + +# English: "Door panel style" +# Source-Hash: 990ae81752236e3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorPanelStyle.2qog4q" +msgstr "Estilo del panel de puerta" + +# English: "Door style" +# Source-Hash: c94e002123bb1199 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorStyle.gerpos" +msgstr "Estilo de puerta" + +# English: "Door width" +# Source-Hash: 91aeb04b0b7b605e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorWidth.sy732l" +msgstr "Ancho de puerta" + +# English: "Downspout enabled" +# Source-Hash: baad1238f70f79ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutEnabled.1gn00nv" +msgstr "Bajante activada" + +# English: "Downspout position" +# Source-Hash: e288531858c4c44d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutPosition.fxcsop" +msgstr "Posición de la bajante" + +# English: "Duct material" +# Source-Hash: 861d28317325cf61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ductMaterial.i8eonq" +msgstr "Material del conducto" + +# English: "Dutch gablet rake" +# Source-Hash: ad8ab4ae38c3299c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchGabletRake.6paydp" +msgstr "Alero del hastial mixto" + +# English: "Dutch hip height ratio" +# Source-Hash: 913d25cf2c5ff179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" +msgstr "Proporción de altura mixta" + +# English: "Dutch hip width ratio" +# Source-Hash: f4a80233d49f2b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" +msgstr "Proporción de ancho mixta" + +# English: "Dutch top rake thickness" +# Source-Hash: 92021b0e37f9a25a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" +msgstr "Espesor del alero superior" + +# English: "Dutch waist length ratio" +# Source-Hash: d0a2ec13d71ce659 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" +msgstr "Proporción de longitud de cintura" + +# English: "Dwell ms" +# Source-Hash: 993396ccca2cd1d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dwellMs.1lkk0o5" +msgstr "Espera ms" + +# English: "Edge inset" +# Source-Hash: 9cf550eb019bca1b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.edgeInset.1obeimx" +msgstr "Retranqueo del borde" + +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.elevation.4jbp44" +msgstr "Cota" + +# English: "End cap left" +# Source-Hash: 1fc91958e30dbeaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapLeft.19jel4v" +msgstr "Tapa final izquierda" + +# English: "End cap right" +# Source-Hash: 4c9ae62e0ed70635 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapRight.lhse9c" +msgstr "Tapa final derecha" + +# English: "End caps" +# Source-Hash: a9cba06250228a55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCaps.4fsb5b" +msgstr "Tapas finales" + +# English: "End post" +# Source-Hash: ebad74353b5a7610 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endPost.1guwsk0" +msgstr "Poste final" + +# English: "Equipment type" +# Source-Hash: ecbe36df4238439e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.equipmentType.1mc2ywt" +msgstr "Tipo de equipo" + +# English: "Fill to floor" +# Source-Hash: 66f1018b50c2e426 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fillToFloor.1oppnkf" +msgstr "Rellenar hasta el suelo" + +# English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.finial.1ertf4s" +msgstr "Remate" + +# English: "Fitting type" +# Source-Hash: 28cd69e506709f16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fittingType.18mz17s" +msgstr "Tipo de accesorio" + +# English: "Flashing height" +# Source-Hash: 3c1aec5e6fb180cf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingHeight.3e8nn6" +msgstr "Altura del tapajuntas" + +# English: "Flashing projection" +# Source-Hash: 327ba762101db94d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingProjection.hhf0fk" +msgstr "Vuelo del tapajuntas" + +# English: "Flue count" +# Source-Hash: 9473e2ab53370b40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueCount.j3t2ci" +msgstr "Número de conductos" + +# English: "Flue diameter" +# Source-Hash: fd0b65425ed3df8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueDiameter.cjhubg" +msgstr "Diámetro del conducto" + +# English: "Flue height" +# Source-Hash: 07ebe5e3780c624b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueHeight.1dhc4y4" +msgstr "Altura del conducto" + +# English: "Flue shape" +# Source-Hash: ff8319792830d591 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueShape.1ealiy4" +msgstr "Forma del conducto" + +# English: "Flue spacing" +# Source-Hash: 5acf77854a0030ec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueSpacing.exhzco" +msgstr "Separación de conductos" + +# English: "Footing style" +# Source-Hash: 0a64209c0edacaa1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.footingStyle.e3btg6" +msgstr "Estilo de zapata" + +# English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameDepth.1934a5n" +msgstr "Profundidad del marco" + +# English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameThickness.1agxzd8" +msgstr "Espesor del marco" + +# English: "Framing strategy" +# Source-Hash: 9a3495a1e3181b56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.framingStrategy.3jx9qe" +msgstr "Estrategia de entramado" + +# English: "From level id" +# Source-Hash: 49f329aab535fd3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fromLevelId.15ieejs" +msgstr "ID de planta de origen" + +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gableOverhang.2z0obw" +msgstr "Vuelo del hastial" + +# English: "Gambrel lower height ratio" +# Source-Hash: 517caa8af48afb22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" +msgstr "Proporción de altura inferior holandesa" + +# English: "Gambrel lower width ratio" +# Source-Hash: 03ecb8f3b625d49d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" +msgstr "Proporción de ancho inferior holandesa" + +# English: "Gap x" +# Source-Hash: f3ca5555e8c54375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapX.wjxa7" +msgstr "Separación x" + +# English: "Gap y" +# Source-Hash: 2736f7c5dfc7d917 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapY.mkbl8" +msgstr "Separación y" + +# English: "Glass thickness" +# Source-Hash: 5d03ca50d712339b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.glassThickness.s97b0x" +msgstr "Espesor del vidrio" + +# English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.groundClearance.4os7f0" +msgstr "Separación al suelo" + +# English: "Guard infill" +# Source-Hash: 6a22a09b67309545 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.guardInfill.16heiig" +msgstr "Relleno de la barandilla" + +# English: "Gutter enabled" +# Source-Hash: 98dfdac04af3c762 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterEnabled.1oxiltf" +msgstr "Canalón activado" + +# English: "Gutter profile" +# Source-Hash: 4bdc3cdf0fe0ac3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterProfile.1ngzqi3" +msgstr "Perfil del canalón" + +# English: "Gutter size" +# Source-Hash: 76555670cba9bb13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterSize.jgavtz" +msgstr "Tamaño del canalón" + +# English: "Hanger max reach" +# Source-Hash: c14d3aa4a98efa98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerMaxReach.1869epv" +msgstr "Alcance máximo del colgador" + +# English: "Hanger spacing" +# Source-Hash: dfbc357fb556559b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerSpacing.i2b5d7" +msgstr "Separación de colgadores" + +# English: "Hanger style" +# Source-Hash: 3ce5e50c572644ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerStyle.l7v7gb" +msgstr "Estilo de colgador" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height.7puwki" +msgstr "Altura" + +# English: "Height2" +# Source-Hash: d0b836103c795726 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height2.7b3jlc" +msgstr "Altura 2" + +# English: "Height above ridge" +# Source-Hash: 681fd794e601ace5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.heightAboveRidge.1k99brk" +msgstr "Altura sobre la cumbrera" + +# English: "High edge height" +# Source-Hash: b4905e5ec62a170f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highEdgeHeight.1s7v90t" +msgstr "Altura del borde alto" + +# English: "High overhang" +# Source-Hash: c136d5cd7f3ce04a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highOverhang.67w55d" +msgstr "Vuelo alto" + +# English: "High side mode" +# Source-Hash: 8b4cd537cc650dac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highSideMode.1rc1r5j" +msgstr "Modo del lado alto" + +# English: "Hood overhang" +# Source-Hash: c38e4e73ed9779f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hoodOverhang.1q1hcin" +msgstr "Vuelo de la campana" + +# English: "Infill placement" +# Source-Hash: 9884ee52a0e84c0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.infillPlacement.11dxzpo" +msgstr "Posición del relleno" + +# English: "Inlet shape" +# Source-Hash: 37fde9f9290ddc59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.inletShape.ypayh2" +msgstr "Forma de la entrada" + +# English: "Inner radius" +# Source-Hash: d840917007332b94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.innerRadius.1fkmg0j" +msgstr "Radio interior" + +# English: "Insulated" +# Source-Hash: c8ce389856a90ae2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulated.iwziey" +msgstr "Aislado" + +# English: "Insulation r" +# Source-Hash: 043a7a10b6c91f4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulationR.663ufx" +msgstr "Aislamiento R" + +# English: "Lantern height" +# Source-Hash: fe661655ba41b709 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternHeight.oz7zpm" +msgstr "Altura de la linterna" + +# English: "Lantern top scale" +# Source-Hash: ab22022e7d5597af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternTopScale.hkmpp2" +msgstr "Escala superior de la linterna" + +# English: "Ledger depth" +# Source-Hash: 17f854a76ebc033a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerDepth.7av8k9" +msgstr "Profundidad del larguero" + +# English: "Ledger height" +# Source-Hash: 547d3a8a9e3e24f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerHeight.zo2oif" +msgstr "Altura del larguero" + +# English: "Ledger vertical offset" +# Source-Hash: 88603f884243c81e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" +msgstr "Desfase vertical del larguero" + +# English: "Left end condition" +# Source-Hash: 6519f495202b830d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftEndCondition.1syqlaw" +msgstr "Condición del extremo izquierdo" + +# English: "Left overhang" +# Source-Hash: d041b8217c356977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftOverhang.1ydl9ma" +msgstr "Vuelo izquierdo" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.length.1kpdcfp" +msgstr "Longitud" + +# English: "Liquid diameter" +# Source-Hash: d6deaf0ceb9f43af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.liquidDiameter.w3pk7c" +msgstr "Diámetro de líquido" + +# English: "Louver count" +# Source-Hash: 45aca762426392fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.louverCount.lhw2gr" +msgstr "Número de lamas" + +# English: "Low beam inset" +# Source-Hash: c645a86c55eb0075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowBeamInset.1g7yw5l" +msgstr "Retranqueo de la viga baja" + +# English: "Low edge height" +# Source-Hash: 7e7b896682c78c5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowEdgeHeight.47mae1" +msgstr "Altura del borde bajo" + +# English: "Low overhang" +# Source-Hash: 9deb775fc64854b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowOverhang.t4nz1h" +msgstr "Vuelo bajo" + +# English: "Mansard steep height ratio" +# Source-Hash: 9aa621d270fe64b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" +msgstr "Proporción de altura empinada mansarda" + +# English: "Mansard steep width ratio" +# Source-Hash: 7cb03b27c632949b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" +msgstr "Proporción de ancho empinado mansarda" + +# English: "Match host roof material" +# Source-Hash: 95772435857a7424 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" +msgstr "Igualar el material de la cubierta base" + +# English: "Match host roof structure" +# Source-Hash: dc22618e31654bde +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofStructure.bojht3" +msgstr "Igualar la estructura de la cubierta base" + +# English: "Mount" +# Source-Hash: 06b36a09a84fa8b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mount.h80tay" +msgstr "Montaje" + +# English: "Mounting type" +# Source-Hash: a288700f96d2b362 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mountingType.1xvin1e" +msgstr "Tipo de montaje" + +# English: "Neck height" +# Source-Hash: 3087bc9b219af9ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.neckHeight.19ym8n1" +msgstr "Altura del cuello" + +# English: "Opacity" +# Source-Hash: 1c0b8a5503c5daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.opacity.1pmpfd2" +msgstr "Opacidad" + +# English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingAngle.brhi7c" +msgstr "Ángulo de apertura" + +# English: "Opening offset" +# Source-Hash: 1d714b576a057902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingOffset.wpoqt6" +msgstr "Desfase del hueco" + +# English: "Opening side" +# Source-Hash: 55e5629865efd03d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingSide.18l2iwe" +msgstr "Lado de apertura" + +# English: "Operation state" +# Source-Hash: 9824d357692ecb8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.operationState.1lwrz07" +msgstr "Estado de apertura" + +# English: "Outlet shape" +# Source-Hash: 4f1cbc14dcfb8f3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.outletShape.lgpqhx" +msgstr "Forma de la salida" + +# English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.overhang.b5ljpv" +msgstr "Vuelo" + +# English: "Panel height" +# Source-Hash: 50d7e5e370667f63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelHeight.1kf31vk" +msgstr "Altura del panel" + +# English: "Panel width" +# Source-Hash: 068d9e0adabc4578 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelWidth.946581" +msgstr "Ancho del panel" + +# English: "Pattern alignment" +# Source-Hash: aca94a3adc0b0f01 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternAlignment.jd1ht0" +msgstr "Alineación del patrón" + +# English: "Pattern count" +# Source-Hash: 9e9f9408ec2c8f0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternCount.1r37pve" +msgstr "Número de patrones" + +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternDistribution.fmam51" +msgstr "Distribución del patrón" + +# English: "Pattern remainder" +# Source-Hash: 63f833f219b9a592 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternRemainder.i47p5y" +msgstr "Resto del patrón" + +# English: "Picket profile" +# Source-Hash: e0b8f766411329ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketProfile.1xgzuyc" +msgstr "Perfil del listón" + +# English: "Picket rail count" +# Source-Hash: dfac7d68ea16e8a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketRailCount.atgt6w" +msgstr "Número de travesaños" + +# English: "Picket spacing" +# Source-Hash: cd2652254a81d288 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketSpacing.p8d98w" +msgstr "Separación de listones" + +# English: "Picket top" +# Source-Hash: 462f6cb03b23d773 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTop.1tskplu" +msgstr "Remate del listón" + +# English: "Picket top clearance" +# Source-Hash: 0a4dbbe1cf592326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTopClearance.16eq1k0" +msgstr "Holgura superior del listón" + +# English: "Picket variation" +# Source-Hash: c99ccea380ca41bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketVariation.f7vl1c" +msgstr "Variación de listones" + +# English: "Picket width" +# Source-Hash: 798382373625fecd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketWidth.3fr3rh" +msgstr "Ancho del listón" + +# English: "Pipe material" +# Source-Hash: 48ce215c3be034c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pipeMaterial.1ggc2wy" +msgstr "Material de la tubería" + +# English: "Pitch" +# Source-Hash: 75e3449fc1776d09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pitch.xe6ekx" +msgstr "Pendiente" + +# English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.position.1quewx6" +msgstr "Posición" + +# English: "Post bracing" +# Source-Hash: d090baf9f08c2280 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postBracing.crq8nn" +msgstr "Arriostramiento de postes" + +# English: "Post cap" +# Source-Hash: 6467b580eed1d333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCap.1xcvxrv" +msgstr "Remate del poste" + +# English: "Post count" +# Source-Hash: e1d91cdd16cc6593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCount.22r56c" +msgstr "Número de postes" + +# English: "Post depth" +# Source-Hash: d6d8b8653cb43624 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postDepth.1ko6j1m" +msgstr "Profundidad del poste" + +# English: "Post inset" +# Source-Hash: dd071e84e259ee48 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postInset.le36li" +msgstr "Retranqueo del poste" + +# English: "Post layout mode" +# Source-Hash: 88c5d586822e58f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postLayoutMode.16jre62" +msgstr "Modo de distribución de postes" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSize.1jodxek" +msgstr "Tamaño del poste" + +# English: "Post spacing" +# Source-Hash: c8e8490c957e01f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSpacing.voppfi" +msgstr "Separación de postes" + +# English: "Post through" +# Source-Hash: b98cbe8478508d41 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postThrough.1abgu4a" +msgstr "Poste pasante" + +# English: "Post width" +# Source-Hash: d1492df1fbb22c29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postWidth.rk26fz" +msgstr "Ancho del poste" + +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.profile.1kwlci6" +msgstr "Perfil" + +# English: "Projection" +# Source-Hash: d01cd36d3939abab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.projection.jhtig0" +msgstr "Proyección" + +# English: "Purlin height" +# Source-Hash: 800a7a9159519b46 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinHeight.eo9cjm" +msgstr "Altura de correa" + +# English: "Purlin spacing" +# Source-Hash: da4d0d12715d6c33 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinSpacing.4gbzb2" +msgstr "Separación de correas" + +# English: "Purlin width" +# Source-Hash: b4774bcbdf599812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoHangers.10554ka" -msgstr "Colgadores automáticos" +msgid "editor.inspector.field.purlinWidth.r6tu3j" +msgstr "Ancho de correa" -# Source-Hash: e20033e4b0045f1c +# English: "Rafter end inset" +# Source-Hash: 2a4dd54f8a8c15ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto miter corners" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoMiterCorners.zxr5lt" -msgstr "Inglete automático en esquinas" +msgid "editor.inspector.field.rafterEndInset.14c0wy5" +msgstr "Retranqueo del extremo del cabio" -# Source-Hash: 92f3a8b63963daeb +# English: "Rafter height" +# Source-Hash: 5a80a11b97c0e043 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoSpan.1gas9r6" -msgstr "Luz automática" +msgid "editor.inspector.field.rafterHeight.1e1n99w" +msgstr "Altura del cabio" -# Source-Hash: 8b2dfb9b9137fda0 +# English: "Rafter spacing" +# Source-Hash: b6348d554660af5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.backRatio.1ughd99" -msgstr "Proporción trasera" +msgid "editor.inspector.field.rafterSpacing.19subc0" +msgstr "Separación de cabios" -# Source-Hash: 75dfae8d9f03fa0e +# English: "Rafter width" +# Source-Hash: 112c447946c3beaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseHeight.1hs4zdf" -msgstr "Altura de la base" +msgid "editor.inspector.field.rafterWidth.eksj4t" +msgstr "Ancho del cabio" -# Source-Hash: 1cc2da12e8463e85 +# English: "Railing height" +# Source-Hash: 7020d720876a2106 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseStyle.24vty5" -msgstr "Estilo de base" +msgid "editor.inspector.field.railingHeight.6wf4bm" +msgstr "Altura de la barandilla" -# Source-Hash: f9d95c6df06b6223 +# English: "Railing mode" +# Source-Hash: 3e89ce193885b5b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamHeight.1kq0oh1" -msgstr "Canto de viga" +msgid "editor.inspector.field.railingMode.ov7xpu" +msgstr "Modo de barandilla" -# Source-Hash: a133f8d23b1274dc +# English: "Railing style" +# Source-Hash: 174342bb31a86afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamWidth.1q1mzbe" -msgstr "Ancho de viga" +msgid "editor.inspector.field.railingStyle.1nyd2u" +msgstr "Estilo de barandilla" -# Source-Hash: b98c0d0189526335 +# English: "Resize lock" +# Source-Hash: a44eedc895b95474 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bodyShape.arvkuw" -msgstr "Forma del cuerpo" +msgid "editor.inspector.field.resizeLock.b2hbr0" +msgstr "Bloquear proporciones" -# Source-Hash: 2251ecab18467631 +# English: "Return diameter" +# Source-Hash: edd319917231def8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bracket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bracketStyle.1vvoptm" -msgstr "Estilo de ménsula" +msgid "editor.inspector.field.returnDiameter.166ozhe" +msgstr "Diámetro de retorno" -# Source-Hash: 5e9d4a4fcea481ca +# English: "Return height" +# Source-Hash: dafa151b183daa77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Branch angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.branchAngle.v56zme" -msgstr "Ángulo de derivación" +msgid "editor.inspector.field.returnHeight.b9sq6y" +msgstr "Altura de retorno" -# Source-Hash: dec709f1f9c714ee +# English: "Return shape" +# Source-Hash: cd55fd029ad2803b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cabHeight.1yn34mk" -msgstr "Altura de cabina" +msgid "editor.inspector.field.returnShape.1e3a27i" +msgstr "Forma del retorno" -# Source-Hash: 87863f8366e1c88a +# English: "Return width" +# Source-Hash: 6a7c411667124177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy form" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.canopyForm.1czzggp" -msgstr "Forma de la marquesina" +msgid "editor.inspector.field.returnWidth.z9r3h3" +msgstr "Ancho del retorno" -# Source-Hash: d52b53241789ee63 +# English: "Right end condition" +# Source-Hash: 460bd11bfc2e5697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cap.s0thej" -msgstr "Remate" +msgid "editor.inspector.field.rightEndCondition.tjlw3l" +msgstr "Condición del extremo derecho" -# Source-Hash: 2f2e49598ec8188e +# English: "Right overhang" +# Source-Hash: 1ed35f8d27ae5583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capOverhang.kxxz9j" -msgstr "Vuelo del remate" +msgid "editor.inspector.field.rightOverhang.yqbeft" +msgstr "Vuelo derecho" -# Source-Hash: 8523c086e95b83e2 +# English: "Roof height" +# Source-Hash: 39c02aa2d97a62ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capShape.17mbxhu" -msgstr "Forma del remate" +msgid "editor.inspector.field.roofHeight.1uhy6qy" +msgstr "Altura de la cubierta" -# Source-Hash: acd98600a518dd95 +# English: "Roof style" +# Source-Hash: d62f8833d77ca8a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capThickness.u835q9" -msgstr "Espesor del remate" +msgid "editor.inspector.field.roofStyle.pawqfy" +msgstr "Estilo de cubierta" -# Source-Hash: 7af3b69b4b5aeb19 +# English: "Roof thickness" +# Source-Hash: 0ddd561a4126b826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carcass height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.carcassHeight.1aks0zy" -msgstr "Altura del casco" +msgid "editor.inspector.field.roofThickness.1xlr3rx" +msgstr "Espesor de la cubierta" -# Source-Hash: 827709caa79db37f +# English: "Roof type" +# Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cleanoutStyle.127zz6z" -msgstr "Estilo de registro" +msgid "editor.inspector.field.roofType.1m9kv5h" +msgstr "Tipo de cubierta" -# Source-Hash: a549dc98e2a1bd07 +# English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarDiameter.1d5k28r" -msgstr "Diámetro del collarín" +msgid "editor.inspector.field.rotation.ly2rj" +msgstr "Rotación" -# Source-Hash: aa0232fc6f3eeadc +# English: "Rows" +# Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarHeight.1dqwsrf" -msgstr "Altura del collarín" +msgid "editor.inspector.field.rows.1ba1neg" +msgstr "Filas" -# Source-Hash: cbd8079c3f540a4c +# English: "Scale" +# Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarShape.1gacv11" -msgstr "Forma del collarín" +msgid "editor.inspector.field.scale.b6rpxt" +msgstr "Escala" -# Source-Hash: 557cde7f95616454 +# English: "Seam detail" +# Source-Hash: adb6face4bac5245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarWidth.qvc9ik" -msgstr "Ancho del collarín" +msgid "editor.inspector.field.seamDetail.197za48" +msgstr "Detalle de junta" -# Source-Hash: 30edd1c180aac9b4 +# English: "Segment type" +# Source-Hash: 4b2aed9d56cc4dee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.columns.1sy73ks" -msgstr "Columnas" +msgid "editor.inspector.field.segmentType.l667sy" +msgstr "Tipo de segmento" -# Source-Hash: cd5b2f70924b7c2d +# English: "Shaft style" +# Source-Hash: a7794a81141b4de4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical full circle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" -msgstr "Cónica de círculo completo" +msgid "editor.inspector.field.shaftStyle.1i5ampa" +msgstr "Estilo del hueco" -# Source-Hash: dc90f3abb935e7e2 +# English: "Shaft wall thickness" +# Source-Hash: 8eb6438543b18cdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionMode.1nty478" -msgstr "Modo de conexión" +msgid "editor.inspector.field.shaftWallThickness.bduslr" +msgstr "Espesor del muro del hueco" -# Source-Hash: 1a9a49d7135d4f15 +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionOffset.2m70s8" -msgstr "Desfase de conexión" +msgid "editor.inspector.field.shape.iq9t52" +msgstr "Forma" -# Source-Hash: 69be301fcb8030e2 +# English: "Shape2" +# Source-Hash: 44a8ab89c854cad1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner bevel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.shape2.fi0nx8" +msgstr "Forma 2" + +# English: "Shed high side" +# Source-Hash: fcee9712879570ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cornerBevel.16iniho" -msgstr "Bisel de esquina" +msgid "editor.inspector.field.shedHighSide.bzzddg" +msgstr "Lado alto de la cubierta a un agua" -# Source-Hash: b997a96a9dc7e450 +# English: "Shingle thickness" +# Source-Hash: f5a931cd6e97e5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.coveringType.95yd3k" -msgstr "Tipo de revestimiento" +msgid "editor.inspector.field.shingleThickness.1hn5wxr" +msgstr "Espesor de la teja" -# Source-Hash: 03b33160d8baa966 +# English: "Shoulder extent" +# Source-Hash: b14990b3e4222dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketHeight.pok31n" -msgstr "Altura del caballete" +msgid "editor.inspector.field.shoulderExtent.11j8uiv" +msgstr "Extensión del hombro" -# Source-Hash: 21552eee05ccb5d6 +# English: "Shoulder height" +# Source-Hash: 505de343ec5010e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketLength.i2aht8" -msgstr "Longitud del caballete" +msgid "editor.inspector.field.shoulderHeight.xyqn6g" +msgstr "Altura del hombro" -# Source-Hash: c13b707bfdeac855 +# English: "Shoulder style" +# Source-Hash: 58d48c01b7ec1a40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketSide.1xnwil7" -msgstr "Lado del caballete" +msgid "editor.inspector.field.shoulderStyle.1hkcv2w" +msgstr "Estilo del hombro" -# Source-Hash: 4ee7089fa1b3e63b +# English: "Show center column" +# Source-Hash: af84db1026bf2e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketStyle.10j0lat" -msgstr "Estilo del caballete" +msgid "editor.inspector.field.showCenterColumn.1uggg45" +msgstr "Mostrar columna central" -# Source-Hash: b8ff053666ee326b +# English: "Show infill" +# Source-Hash: 80214dc086ebc1b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curb.5xi2ol" -msgstr "Bordillo" +msgid "editor.inspector.field.showInfill.33rv7u" +msgstr "Mostrar relleno" -# Source-Hash: 8da648df210dbc0d +# English: "Show step supports" +# Source-Hash: f02d6cdfb4c6ad08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curbHeight.1yn97sg" -msgstr "Altura del bordillo" +msgid "editor.inspector.field.showStepSupports.1kg8h8m" +msgstr "Mostrar soportes de peldaño" -# Source-Hash: 0c01b9848f5d9673 +# English: "Side flashing" +# Source-Hash: 5d1147c774718b76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curveOffset.l4xhvr" -msgstr "Desfase de curva" +msgid "editor.inspector.field.sideFlashing.cv744o" +msgstr "Tapajuntas lateral" -# Source-Hash: 69179f4caa384585 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.damperAngle.h5fxn7" -msgstr "Ángulo de la compuerta" +msgid "editor.inspector.field.size.1a4x3zw" +msgstr "Tamaño" -# Source-Hash: 15cff91231e806a4 +# English: "Skylight type" +# Source-Hash: fbdbc6c97c75b030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.deckThickness.4jmqze" -msgstr "Espesor de la tarima" +msgid "editor.inspector.field.skylightType.1a5vj3i" +msgstr "Tipo de claraboya" -# Source-Hash: eff1e7dc0901b9d0 +# English: "Slab opening mode" +# Source-Hash: c9a6561cb3c3da82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.defaultLevelId.xmrjzp" -msgstr "ID de planta predeterminada" +msgid "editor.inspector.field.slabOpeningMode.g4f730" +msgstr "Modo de hueco en forjado" -# Source-Hash: 99ac4d03c3061f3a +# English: "Slat gap" +# Source-Hash: ef0efc845ccdba78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.depth.1lu11u2" -msgstr "Profundidad" +msgid "editor.inspector.field.slatGap.slmk93" +msgstr "Separación de lamas" -# Source-Hash: 016d8e70618916d4 +# English: "Slide direction" +# Source-Hash: 4daab7a2ce2ecef3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter.s3robe" -msgstr "Diámetro" +msgid "editor.inspector.field.slideDirection.1527q13" +msgstr "Dirección de deslizamiento" -# Source-Hash: dab4100e83e4e6b3 +# English: "Span" +# Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter2.pm6cl4" -msgstr "Diámetro 2" +msgid "editor.inspector.field.span.1bmmuw1" +msgstr "Luz" -# Source-Hash: 5747145349432396 +# English: "Speed" +# Source-Hash: 3b006be0b1f0493f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door duration ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorDurationMs.13nx909" -msgstr "Duración de puerta ms" +msgid "editor.inspector.field.speed.2w2xwg" +msgstr "Velocidad" -# Source-Hash: 6f4aca26196c221f +# English: "Spin speed" +# Source-Hash: c8cfb8336a240cb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorHeight.92r9k4" -msgstr "Altura de puerta" +msgid "editor.inspector.field.spinSpeed.1ozx746" +msgstr "Velocidad de giro" -# Source-Hash: 990ae81752236e3a +# English: "Stair type" +# Source-Hash: c9f54c3b258961c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door panel style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorPanelStyle.2qog4q" -msgstr "Estilo del panel de puerta" +msgid "editor.inspector.field.stairType.1u05e02" +msgstr "Tipo de escalera" -# Source-Hash: c94e002123bb1199 +# English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorStyle.gerpos" -msgstr "Estilo de puerta" +msgid "editor.inspector.field.standoff.4wv2g0" +msgstr "Separador" -# Source-Hash: 91aeb04b0b7b605e +# English: "Standoff height" +# Source-Hash: 0464d4efa1e80c62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorWidth.sy732l" -msgstr "Ancho de puerta" +msgid "editor.inspector.field.standoffHeight.1qdnff" +msgstr "Altura del separador" -# Source-Hash: baad1238f70f79ea +# English: "Start post" +# Source-Hash: 85da28b0b26b8c2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutEnabled.1gn00nv" -msgstr "Bajante activada" +msgid "editor.inspector.field.startPost.1ood9kv" +msgstr "Poste inicial" -# Source-Hash: e288531858c4c44d +# English: "Step count" +# Source-Hash: d5dc1ba8cca3193d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutPosition.fxcsop" -msgstr "Posición de la bajante" +msgid "editor.inspector.field.stepCount.nzorrw" +msgstr "Número de peldaños" -# Source-Hash: 861d28317325cf61 +# English: "Strap spacing" +# Source-Hash: cadbb9cadd6a5e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ductMaterial.i8eonq" -msgstr "Material del conducto" +msgid "editor.inspector.field.strapSpacing.1cyt3dc" +msgstr "Separación de flejes" -# Source-Hash: ad8ab4ae38c3299c +# English: "Strap style" +# Source-Hash: c017cd56b3ae5865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch gablet rake" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchGabletRake.6paydp" -msgstr "Alero del hastial mixto" +msgid "editor.inspector.field.strapStyle.fh1oy4" +msgstr "Estilo de fleje" -# Source-Hash: 913d25cf2c5ff179 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" -msgstr "Proporción de altura mixta" +msgid "editor.inspector.field.style.nf3vcm" +msgstr "Estilo" -# Source-Hash: f4a80233d49f2b20 +# English: "Suction diameter" +# Source-Hash: 7b3f016989c631a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" -msgstr "Proporción de ancho mixta" +msgid "editor.inspector.field.suctionDiameter.nutxtj" +msgstr "Diámetro de aspiración" -# Source-Hash: 92021b0e37f9a25a +# English: "Supply diameter" +# Source-Hash: 3f1e0fd30e5b1f39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch top rake thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" -msgstr "Espesor del alero superior" +msgid "editor.inspector.field.supplyDiameter.h5ldh5" +msgstr "Diámetro de impulsión" -# Source-Hash: d0a2ec13d71ce659 +# English: "Supply height" +# Source-Hash: 05c9b608538e181d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch waist length ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" -msgstr "Proporción de longitud de cintura" +msgid "editor.inspector.field.supplyHeight.zy6ru1" +msgstr "Altura de impulsión" -# Source-Hash: 993396ccca2cd1d5 +# English: "Supply shape" +# Source-Hash: 3c9400722baa474d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dwell ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.dwellMs.1lkk0o5" -msgstr "Espera ms" +#, fuzzy +msgid "editor.inspector.field.supplyShape.k39wnb" +msgstr "Forma de impulsión" -# Source-Hash: 9cf550eb019bca1b +# English: "Supply width" +# Source-Hash: af7619025438f995 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edge inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.edgeInset.1obeimx" -msgstr "Retranqueo del borde" +msgid "editor.inspector.field.supplyWidth.g66axi" +msgstr "Ancho de impulsión" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Support offset" +# Source-Hash: 617e198cf3f214d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.elevation.4jbp44" -msgstr "Cota" +msgid "editor.inspector.field.supportOffset.1w4cocr" +msgstr "Desfase del soporte" -# Source-Hash: 1fc91958e30dbeaf +# English: "Sweep angle" +# Source-Hash: 2686abc093f3dfb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapLeft.19jel4v" -msgstr "Tapa final izquierda" +msgid "editor.inspector.field.sweepAngle.ucmmlw" +msgstr "Ángulo de barrido" -# Source-Hash: 4c9ae62e0ed70635 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapRight.lhse9c" -msgstr "Tapa final derecha" +msgid "editor.inspector.field.system.13qbhrw" +msgstr "Sistema" -# Source-Hash: a9cba06250228a55 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCaps.4fsb5b" -msgstr "Tapas finales" +msgid "editor.inspector.field.terminal.1501knl" +msgstr "Terminal" -# Source-Hash: ebad74353b5a7610 +# English: "Terminal type" +# Source-Hash: de1777c521046f7d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endPost.1guwsk0" -msgstr "Poste final" +msgid "editor.inspector.field.terminalType.1ne8lgb" +msgstr "Tipo de terminal" -# Source-Hash: ecbe36df4238439e +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.equipmentType.1mc2ywt" -msgstr "Tipo de equipo" +msgid "editor.inspector.field.thickness.1l8hu45" +msgstr "Espesor" -# Source-Hash: 66f1018b50c2e426 +# English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fillToFloor.1oppnkf" -msgstr "Rellenar hasta el suelo" +msgid "editor.inspector.field.tiltAngle.1312yw1" +msgstr "Ángulo de inclinación" -# Source-Hash: 1cfe33235b30b1d8 +# English: "To level id" +# Source-Hash: 7057972ff4c78dc7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.finial.1ertf4s" -msgstr "Remate" +msgid "editor.inspector.field.toLevelId.8i5qx7" +msgstr "ID de planta de destino" -# Source-Hash: 28cd69e506709f16 +# English: "Top landing depth" +# Source-Hash: fa8fc3d5c612e774 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fittingType.18mz17s" -msgstr "Tipo de accesorio" +msgid "editor.inspector.field.topLandingDepth.ki7gk2" +msgstr "Profundidad del descansillo superior" -# Source-Hash: 3c1aec5e6fb180cf +# English: "Top landing mode" +# Source-Hash: 6e4a0b98089ec2cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingHeight.3e8nn6" -msgstr "Altura del tapajuntas" +msgid "editor.inspector.field.topLandingMode.zk0h7e" +msgstr "Modo del descansillo superior" -# Source-Hash: 327ba762101db94d +# English: "Top rail height" +# Source-Hash: c182bb3c8a701085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingProjection.hhf0fk" -msgstr "Vuelo del tapajuntas" +msgid "editor.inspector.field.topRailHeight.fscisv" +msgstr "Altura del travesaño superior" -# Source-Hash: 9473e2ab53370b40 +# English: "Transition mode" +# Source-Hash: 8c5502f1d8087b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueCount.j3t2ci" -msgstr "Número de conductos" +msgid "editor.inspector.field.transitionMode.f3evgt" +msgstr "Modo de transición" -# Source-Hash: fd0b65425ed3df8b +# English: "Transition width" +# Source-Hash: 96eb5997f0aa2dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueDiameter.cjhubg" -msgstr "Diámetro del conducto" +msgid "editor.inspector.field.transitionWidth.rtgz3m" +msgstr "Ancho de transición" -# Source-Hash: 07ebe5e3780c624b +# English: "Vane count" +# Source-Hash: 812d27902509339f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueHeight.1dhc4y4" -msgstr "Altura del conducto" +msgid "editor.inspector.field.vaneCount.55vtye" +msgstr "Número de álabes" -# Source-Hash: ff8319792830d591 +# English: "Wall height" +# Source-Hash: 1e7dcd717c8ac239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueShape.1ealiy4" -msgstr "Forma del conducto" +msgid "editor.inspector.field.wallHeight.18vmh5c" +msgstr "Altura del muro" -# Source-Hash: 5acf77854a0030ec +# English: "Wall shell" +# Source-Hash: f14bdaed6101896a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueSpacing.exhzco" -msgstr "Separación de conductos" +msgid "editor.inspector.field.wallShell.vqsyhv" +msgstr "Envolvente del muro" -# Source-Hash: 0a64209c0edacaa1 +# English: "Wall skirt height" +# Source-Hash: 801268585c9a679b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.footingStyle.e3btg6" -msgstr "Estilo de zapata" +msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" +msgstr "Altura del zócalo" -# Source-Hash: fd413477f1a9e5ed +# English: "Wall thickness" +# Source-Hash: 6b377d0ba1565477 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameDepth.1934a5n" -msgstr "Profundidad del marco" +msgid "editor.inspector.field.wallThickness.17wn4iv" +msgstr "Espesor del muro" -# Source-Hash: 6aa2318fa815e5f2 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameThickness.1agxzd8" -msgstr "Espesor del marco" +msgid "editor.inspector.field.width.gfxz9b" +msgstr "Ancho" -# Source-Hash: 9a3495a1e3181b56 +# English: "Width2" +# Source-Hash: de05070474f589b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing strategy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.framingStrategy.3jx9qe" -msgstr "Estrategia de entramado" +msgid "editor.inspector.field.width2.1nqawvr" +msgstr "Ancho 2" -# Source-Hash: 49f329aab535fd3a +# English: "With back" +# Source-Hash: 6199b641888a83fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fromLevelId.15ieejs" -msgstr "ID de planta de origen" +msgid "editor.inspector.field.withBack.1ay8nus" +msgstr "Con trasera" -# Source-Hash: dbd062efc7f4fd7f +# English: "With bottom" +# Source-Hash: 699934d8b9571b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gableOverhang.2z0obw" -msgstr "Vuelo del hastial" +msgid "editor.inspector.field.withBottom.1x1z1vo" +msgstr "Con base" -# Source-Hash: 517caa8af48afb22 +# English: "With sides" +# Source-Hash: 03987b9aadaab50f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" -msgstr "Proporción de altura inferior holandesa" +msgid "editor.inspector.field.withSides.uq066x" +msgstr "Con laterales" -# Source-Hash: 03ecb8f3b625d49d +# English: "Advanced" +# Source-Hash: f571cb2d06ae01ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" -msgstr "Proporción de ancho inferior holandesa" +msgid "editor.inspector.group.advanced.qwfkor" +msgstr "Avanzado" -# Source-Hash: f3ca5555e8c54375 +# English: "Air" +# Source-Hash: a886619d15bae47e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap x" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapX.wjxa7" -msgstr "Separación x" +msgid "editor.inspector.group.air.15jotdj" +msgstr "Aire" -# Source-Hash: 2736f7c5dfc7d917 +# English: "Appearance" +# Source-Hash: b69fc1f8128958fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap y" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapY.mkbl8" -msgstr "Separación y" +msgid "editor.inspector.group.appearance.dli9wf" +msgstr "Apariencia" -# Source-Hash: 5d03ca50d712339b +# English: "Body" +# Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.glassThickness.s97b0x" -msgstr "Espesor del vidrio" +msgid "editor.inspector.group.body.ign4wl" +msgstr "Cuerpo" -# Source-Hash: 4fc3e60be28f60d5 +# English: "Cab" +# Source-Hash: fdf7108a011bd94b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.groundClearance.4os7f0" -msgstr "Separación al suelo" +msgid "editor.inspector.group.cab.o4z1ox" +msgstr "Cabina" -# Source-Hash: 6a22a09b67309545 +# English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.guardInfill.16heiig" -msgstr "Relleno de la barandilla" +msgid "editor.inspector.group.cabinet.rzvymr" +msgstr "Mueble" -# Source-Hash: 98dfdac04af3c762 +# English: "Cap" +# Source-Hash: d52b53241789ee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterEnabled.1oxiltf" -msgstr "Canalón activado" +msgid "editor.inspector.group.cap.s0thej" +msgstr "Remate" -# Source-Hash: 4bdc3cdf0fe0ac3c +# English: "Collar" +# Source-Hash: 93a545688c917e69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterProfile.1ngzqi3" -msgstr "Perfil del canalón" +msgid "editor.inspector.group.collar.1am2iqo" +msgstr "Collarín" -# Source-Hash: 76555670cba9bb13 +# English: "Connection" +# Source-Hash: 7a9c55b1c5a6e61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterSize.jgavtz" -msgstr "Tamaño del canalón" +msgid "editor.inspector.group.connection.2r1h4p" +msgstr "Conexión" -# Source-Hash: c14d3aa4a98efa98 +# English: "Connections" +# Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger max reach" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerMaxReach.1869epv" -msgstr "Alcance máximo del colgador" +msgid "editor.inspector.group.connections.ai4w8u" +msgstr "Conexiones" -# Source-Hash: dfbc357fb556559b +# English: "Construction" +# Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerSpacing.i2b5d7" -msgstr "Separación de colgadores" +msgid "editor.inspector.group.construction.j6p9kc" +msgstr "Construcción" -# Source-Hash: 3ce5e50c572644ed +# English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerStyle.l7v7gb" -msgstr "Estilo de colgador" +msgid "editor.inspector.group.cricket.6iwg8g" +msgstr "Caballete desviador" -# Source-Hash: 646ef0fbcbc58edf +# English: "Curb" +# Source-Hash: b8ff053666ee326b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height.7puwki" -msgstr "Altura" +msgid "editor.inspector.group.curb.5xi2ol" +msgstr "Bordillo" -# Source-Hash: d0b836103c795726 +# English: "Curve points" +# Source-Hash: 3b9c59e996dcc9c5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height2.7b3jlc" -msgstr "Altura 2" +msgid "editor.inspector.group.curvePoints.ztdcnn" +msgstr "Puntos de curva" -# Source-Hash: 681fd794e601ace5 +# English: "Dimensions" +# Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height above ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.heightAboveRidge.1k99brk" -msgstr "Altura sobre la cumbrera" +msgid "editor.inspector.group.dimensions.pewa8g" +msgstr "Dimensiones" -# Source-Hash: b4905e5ec62a170f +# English: "Doors" +# Source-Hash: 20f60b98de67849b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highEdgeHeight.1s7v90t" -msgstr "Altura del borde alto" +msgid "editor.inspector.group.doors.18w2kpa" +msgstr "Puertas" -# Source-Hash: c136d5cd7f3ce04a +# English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highOverhang.67w55d" -msgstr "Vuelo alto" +msgid "editor.inspector.group.dormer.1fjs5tq" +msgstr "Buhardilla" -# Source-Hash: 8b4cd537cc650dac +# English: "Dormer roof" +# Source-Hash: cda262793b4cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High side mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highSideMode.1rc1r5j" -msgstr "Modo del lado alto" +msgid "editor.inspector.group.dormerRoof.u9uy2g" +msgstr "Cubierta de buhardilla" -# Source-Hash: c38e4e73ed9779f4 +# English: "Drainage" +# Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hood overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hoodOverhang.1q1hcin" -msgstr "Vuelo de la campana" +msgid "editor.inspector.group.drainage.7easy0" +msgstr "Desagüe" -# Source-Hash: 9884ee52a0e84c0d +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.infillPlacement.11dxzpo" -msgstr "Posición del relleno" +msgid "editor.inspector.group.elevation.4jbp44" +msgstr "Cota" -# Source-Hash: 37fde9f9290ddc59 +# English: "End caps" +# Source-Hash: a9cba06250228a55 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.inletShape.ypayh2" -msgstr "Forma de la entrada" +msgid "editor.inspector.group.endCaps.4fsb5b" +msgstr "Tapas finales" -# Source-Hash: d840917007332b94 +# English: "Equipment" +# Source-Hash: 228f2206862e41cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.innerRadius.1fkmg0j" -msgstr "Radio interior" +msgid "editor.inspector.group.equipment.1xtq0ev" +msgstr "Equipo" -# Source-Hash: c8ce389856a90ae2 +# English: "Face" +# Source-Hash: a19276baa3688cbf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulated.iwziey" -msgstr "Aislado" +msgid "editor.inspector.group.face.1531fr0" +msgstr "Cara" -# Source-Hash: 043a7a10b6c91f4f +# English: "Fitting" +# Source-Hash: fb34ee7c59cb84ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation r" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulationR.663ufx" -msgstr "Aislamiento R" +msgid "editor.inspector.group.fitting.171z1r4" +msgstr "Accesorio" -# Source-Hash: fe661655ba41b709 +# English: "Flues" +# Source-Hash: f817c09ef0bb6d69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternHeight.oz7zpm" -msgstr "Altura de la linterna" +msgid "editor.inspector.group.flues.nq6pye" +msgstr "Conductos" -# Source-Hash: ab22022e7d5597af +# English: "Footprint" +# Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern top scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternTopScale.hkmpp2" -msgstr "Escala superior de la linterna" +msgid "editor.inspector.group.footprint.g8q2pg" +msgstr "Huella" -# Source-Hash: 17f854a76ebc033a +# English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerDepth.7av8k9" -msgstr "Profundidad del larguero" +msgid "editor.inspector.group.frame.q7q9fq" +msgstr "Marco" -# Source-Hash: 547d3a8a9e3e24f5 +# English: "Geometry" +# Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerHeight.zo2oif" -msgstr "Altura del larguero" +msgid "editor.inspector.group.geometry.a21os1" +msgstr "Geometría" -# Source-Hash: 88603f884243c81e +# English: "Grid" +# Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger vertical offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" -msgstr "Desfase vertical del larguero" +msgid "editor.inspector.group.grid.d8gwxd" +msgstr "Cuadrícula" -# Source-Hash: 6519f495202b830d +# English: "Hangers" +# Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftEndCondition.1syqlaw" -msgstr "Condición del extremo izquierdo" +msgid "editor.inspector.group.hangers.agnfzn" +msgstr "Colgadores" -# Source-Hash: d041b8217c356977 +# English: "Hardware" +# Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftOverhang.1ydl9ma" -msgstr "Vuelo izquierdo" +msgid "editor.inspector.group.hardware.1nygwm1" +msgstr "Herrajes" -# Source-Hash: 643687bc3adaac76 +# English: "Hung wall" +# Source-Hash: 2a47557eb4a5f766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.length.1kpdcfp" -msgstr "Longitud" +msgid "editor.inspector.group.hungWall.1ftr1gz" +msgstr "Muro colgado" -# Source-Hash: d6deaf0ceb9f43af +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.liquidDiameter.w3pk7c" -msgstr "Diámetro de líquido" +msgid "editor.inspector.group.insulation.1kyo5e7" +msgstr "Aislamiento" -# Source-Hash: 45aca762426392fa +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louver count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.louverCount.lhw2gr" -msgstr "Número de lamas" +msgid "editor.inspector.group.lantern.1h7o2xb" +msgstr "Linterna" -# Source-Hash: c645a86c55eb0075 +# English: "Levels" +# Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low beam inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowBeamInset.1g7yw5l" -msgstr "Retranqueo de la viga baja" +msgid "editor.inspector.group.levels.1s6yaru" +msgstr "Plantas" -# Source-Hash: 7e7b896682c78c5b +# English: "Line" +# Source-Hash: 1a849e52a46d5087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowEdgeHeight.47mae1" -msgstr "Altura del borde bajo" +msgid "editor.inspector.group.line.166mv5z" +msgstr "Línea" -# Source-Hash: 9deb775fc64854b2 +# English: "Lines" +# Source-Hash: 9ab4f7a738ebee13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowOverhang.t4nz1h" -msgstr "Vuelo bajo" +msgid "editor.inspector.group.lines.12bghr0" +msgstr "Líneas" -# Source-Hash: 9aa621d270fe64b1 +# English: "Motion" +# Source-Hash: c0ff16d77ebc9593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" -msgstr "Proporción de altura empinada mansarda" +msgid "editor.inspector.group.motion.1be7sjr" +msgstr "Movimiento" -# Source-Hash: 7cb03b27c632949b +# English: "Mounting" +# Source-Hash: 81d365ccef73068c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" -msgstr "Proporción de ancho empinado mansarda" +msgid "editor.inspector.group.mounting.feutd2" +msgstr "Montaje" -# Source-Hash: 95772435857a7424 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" -msgstr "Igualar el material de la cubierta base" +msgid "editor.inspector.group.opening.dfet3" +msgstr "Abertura" -# Source-Hash: dc22618e31654bde +# English: "Panel dimensions" +# Source-Hash: f46314c54627858d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofStructure.bojht3" -msgstr "Igualar la estructura de la cubierta base" +msgid "editor.inspector.group.panelDimensions.b59qe6" +msgstr "Dimensiones del panel" -# Source-Hash: 06b36a09a84fa8b1 +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mount.h80tay" -msgstr "Montaje" +msgid "editor.inspector.group.patternDistribution.fmam51" +msgstr "Distribución del patrón" -# Source-Hash: a288700f96d2b362 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mountingType.1xvin1e" -msgstr "Tipo de montaje" +msgid "editor.inspector.group.placement.13lcvts" +msgstr "Colocación" -# Source-Hash: 3087bc9b219af9ef +# English: "Position" +# Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Neck height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.neckHeight.19ym8n1" -msgstr "Altura del cuello" +msgid "editor.inspector.group.position.1quewx6" +msgstr "Posición" -# Source-Hash: 1c0b8a5503c5daeb +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.opacity.1pmpfd2" -msgstr "Opacidad" +msgid "editor.inspector.group.profile.1kwlci6" +msgstr "Perfil" -# Source-Hash: 51b7ab6a0a78d90a +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingAngle.brhi7c" -msgstr "Ángulo de apertura" +msgid "editor.inspector.group.railing.19a5bnr" +msgstr "Barandilla" -# Source-Hash: 1d714b576a057902 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingOffset.wpoqt6" -msgstr "Desfase del hueco" +msgid "editor.inspector.group.return.1klrjrz" +msgstr "Retorno" -# Source-Hash: 55e5629865efd03d +# English: "Settings" +# Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingSide.18l2iwe" -msgstr "Lado de apertura" +msgid "editor.inspector.group.settings.ktdb8o" +msgstr "Ajustes" -# Source-Hash: 9824d357692ecb8a +# English: "Shaft" +# Source-Hash: 4fc63ad21de30103 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Operation state" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.operationState.1lwrz07" -msgstr "Estado de apertura" +msgid "editor.inspector.group.shaft.mad9cv" +msgstr "Hueco" -# Source-Hash: 4f1cbc14dcfb8f3b +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.outletShape.lgpqhx" -msgstr "Forma de la salida" +msgid "editor.inspector.group.shape.iq9t52" +msgstr "Forma" -# Source-Hash: 88540def2ce16ac5 +# English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.overhang.b5ljpv" -msgstr "Vuelo" +msgid "editor.inspector.group.shoulder.1nyfx25" +msgstr "Hombro" -# Source-Hash: 50d7e5e370667f63 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelHeight.1kf31vk" -msgstr "Altura del panel" +msgid "editor.inspector.group.size.1a4x3zw" +msgstr "Tamaño" -# Source-Hash: 068d9e0adabc4578 +# English: "Structure" +# Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelWidth.946581" -msgstr "Ancho del panel" +msgid "editor.inspector.group.structure.gezdra" +msgstr "Estructura" -# Source-Hash: aca94a3adc0b0f01 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternAlignment.jd1ht0" -msgstr "Alineación del patrón" +msgid "editor.inspector.group.style.nf3vcm" +msgstr "Estilo" -# Source-Hash: 9e9f9408ec2c8f0b +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternCount.1r37pve" -msgstr "Número de patrones" +msgid "editor.inspector.group.supply.1oy2zwu" +msgstr "Impulsión" -# Source-Hash: 4712c449e9a42d3b +# English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternDistribution.fmam51" -msgstr "Distribución del patrón" +msgid "editor.inspector.group.surface.wtamqk" +msgstr "Superficie" -# Source-Hash: 63f833f219b9a592 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern remainder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternRemainder.i47p5y" -msgstr "Resto del patrón" +msgid "editor.inspector.group.terminal.1501knl" +msgstr "Terminal" -# Source-Hash: e0b8f766411329ff +# English: "Topology" +# Source-Hash: 399fc3e62f491f08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketProfile.1xgzuyc" -msgstr "Perfil del listón" +msgid "editor.inspector.group.topology.18t8dyc" +msgstr "Topología" -# Source-Hash: dfac7d68ea16e8a8 +# English: "Transform" +# Source-Hash: f425799b82e085ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket rail count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketRailCount.atgt6w" -msgstr "Número de travesaños" +msgid "editor.inspector.group.transform.1c55ayj" +msgstr "Transformación" -# Source-Hash: cd2652254a81d288 +# English: "Trap" +# Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketSpacing.p8d98w" -msgstr "Separación de listones" +msgid "editor.inspector.group.trap.1qc7vhe" +msgstr "Sifón" -# Source-Hash: 462f6cb03b23d773 +# English: "Type" +# Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTop.1tskplu" -msgstr "Remate del listón" +msgid "editor.inspector.group.type.1m2zofh" +msgstr "Tipo" -# Source-Hash: 0a4dbbe1cf592326 +# English: "Abs" +# Source-Hash: c8fd17853839516f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTopClearance.16eq1k0" -msgstr "Holgura superior del listón" +msgid "editor.inspector.option.abs.132mz8b" +msgstr "ABS" -# Source-Hash: c99ccea380ca41bf +# English: "Access panel" +# Source-Hash: fd9eb1a16cb6b996 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket variation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketVariation.f7vl1c" -msgstr "Variación de listones" +msgid "editor.inspector.option.accessPanel.13sptb5" +msgstr "Registro de acceso" -# Source-Hash: 798382373625fecd +# English: "Air handler" +# Source-Hash: db8064add8c27a5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketWidth.3fr3rh" -msgstr "Ancho del listón" +msgid "editor.inspector.option.airHandler.1crnyg3" +msgstr "Unidad de tratamiento de aire" -# Source-Hash: 48ce215c3be034c7 +# English: "Alternating" +# Source-Hash: d8234782848be927 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pipeMaterial.1ggc2wy" -msgstr "Material de la tubería" +msgid "editor.inspector.option.alternating.5jashi" +msgstr "Alterno" -# Source-Hash: 75e3449fc1776d09 +# English: "Arched" +# Source-Hash: 63cbda7b09662368 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pitch.xe6ekx" -msgstr "Pendiente" +msgid "editor.inspector.option.arched.14188w0" +msgstr "En arco" -# Source-Hash: 9da0cbfad144d737 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.position.1quewx6" -msgstr "Posición" +msgid "editor.inspector.option.auto.540b06" +msgstr "Auto" -# Source-Hash: d090baf9f08c2280 +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post bracing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postBracing.crq8nn" -msgstr "Arriostramiento de postes" +msgid "editor.inspector.option.automatic.r122ro" +msgstr "Automático" -# Source-Hash: 6467b580eed1d333 +# English: "Back" +# Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCap.1xcvxrv" -msgstr "Remate del poste" +msgid "editor.inspector.option.back.1hzmxtu" +msgstr "Volver" -# Source-Hash: e1d91cdd16cc6593 +# English: "Balusters" +# Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCount.22r56c" -msgstr "Número de postes" +msgid "editor.inspector.option.balusters.1j3w6ng" +msgstr "Balaustres" -# Source-Hash: d6d8b8653cb43624 +# English: "Band" +# Source-Hash: f211248ec8619e4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postDepth.1ko6j1m" -msgstr "Profundidad del poste" +msgid "editor.inspector.option.band.160umau" +msgstr "Banda" -# Source-Hash: dd071e84e259ee48 +# English: "Base plate" +# Source-Hash: 8f9ff05c6a3b08a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postInset.le36li" -msgstr "Retranqueo del poste" +msgid "editor.inspector.option.basePlate.17fcg8k" +msgstr "Placa base" -# Source-Hash: 88c5d586822e58f0 +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post layout mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postLayoutMode.16jre62" -msgstr "Modo de distribución de postes" +msgid "editor.inspector.option.boards.u14hig" +msgstr "Tablas" -# Source-Hash: faa36308c1ecb2a9 +# English: "Bookshelf" +# Source-Hash: c0fee00bdfb09d37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSize.1jodxek" -msgstr "Tamaño del poste" +msgid "editor.inspector.option.bookshelf.ls0zqk" +msgstr "Estantería" -# Source-Hash: c8e8490c957e01f3 +# English: "Both" +# Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSpacing.voppfi" -msgstr "Separación de postes" +msgid "editor.inspector.option.both.mkyb42" +msgstr "Ambos" -# Source-Hash: b98cbe8478508d41 +# English: "Bottom" +# Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post through" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postThrough.1abgu4a" -msgstr "Poste pasante" +msgid "editor.inspector.option.bottom.9mpruy" +msgstr "Inferior" -# Source-Hash: d1492df1fbb22c29 +# English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postWidth.rk26fz" -msgstr "Ancho del poste" +msgid "editor.inspector.option.box.1nbjcuq" +msgstr "Caja" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Break" +# Source-Hash: 2c2815df4f724abe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.profile.1kwlci6" -msgstr "Perfil" +msgid "editor.inspector.option.break.17spnvc" +msgstr "Corte" -# Source-Hash: d01cd36d3939abab +# English: "Butterfly" +# Source-Hash: 0d5a25303d3d8058 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.projection.jhtig0" -msgstr "Proyección" +msgid "editor.inspector.option.butterfly.pvv0g4" +msgstr "Mariposa" -# Source-Hash: 800a7a9159519b46 +# English: "Cable" +# Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinHeight.eo9cjm" -msgstr "Altura de correa" +msgid "editor.inspector.option.cable.fp50zq" +msgstr "Cable" -# Source-Hash: da4d0d12715d6c33 +# English: "Cast iron" +# Source-Hash: 69129e514347672a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinSpacing.4gbzb2" -msgstr "Separación de correas" +msgid "editor.inspector.option.castIron.1q2z4qk" +msgstr "Fundición" -# Source-Hash: b4774bcbdf599812 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinWidth.r6tu3j" -msgstr "Ancho de correa" +msgid "editor.inspector.option.ceiling.1t2zaqo" +msgstr "Techo" -# Source-Hash: 2a4dd54f8a8c15ef +# English: "Center" +# Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter end inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterEndInset.14c0wy5" -msgstr "Retranqueo del extremo del cabio" +msgid "editor.inspector.option.center.kzdxno" +msgstr "Centro" -# Source-Hash: 5a80a11b97c0e043 +# English: "Center opening" +# Source-Hash: 65f410261609060b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterHeight.1e1n99w" -msgstr "Altura del cabio" +msgid "editor.inspector.option.centerOpening.bn1rvw" +msgstr "Apertura central" -# Source-Hash: b6348d554660af5b +# English: "Cleanout" +# Source-Hash: cf5f9756cc866f3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterSpacing.19subc0" -msgstr "Separación de cabios" +msgid "editor.inspector.option.cleanout.lww8bm" +msgstr "Registro de limpieza" -# Source-Hash: 112c447946c3beaa +# English: "Concrete pad" +# Source-Hash: e71d1d949f1d1b41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterWidth.eksj4t" -msgstr "Ancho del cabio" +msgid "editor.inspector.option.concretePad.m1zdij" +msgstr "Losa de hormigón" -# Source-Hash: 7020d720876a2106 +# English: "Condenser" +# Source-Hash: a3804c4f5e6aefbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingHeight.6wf4bm" -msgstr "Altura de la barandilla" +msgid "editor.inspector.option.condenser.a74nps" +msgstr "Condensador" -# Source-Hash: 3e89ce193885b5b4 +# English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingMode.ov7xpu" -msgstr "Modo de barandilla" +msgid "editor.inspector.option.corbeled.bovs4v" +msgstr "Con ménsulas" -# Source-Hash: 174342bb31a86afc +# English: "Count" +# Source-Hash: 8a4beb9a57edc26e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingStyle.1nyd2u" -msgstr "Estilo de barandilla" +msgid "editor.inspector.option.count.1qoi4r8" +msgstr "Cantidad" -# Source-Hash: a44eedc895b95474 +# English: "Coupling" +# Source-Hash: db1f8f141714b363 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.resizeLock.b2hbr0" -msgstr "Bloquear proporciones" +msgid "editor.inspector.option.coupling.111aap2" +msgstr "Manguito" -# Source-Hash: edd319917231def8 +# English: "Covering specific" +# Source-Hash: 7a9a92d2e59a3bbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnDiameter.166ozhe" -msgstr "Diámetro de retorno" +msgid "editor.inspector.option.coveringSpecific.1h9fo8w" +msgstr "Específico del revestimiento" -# Source-Hash: dafa151b183daa77 +# English: "Cross" +# Source-Hash: d500c4b7827d3390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnHeight.b9sq6y" -msgstr "Altura de retorno" +msgid "editor.inspector.option.cross.1elubzv" +msgstr "Cruz" -# Source-Hash: cd55fd029ad2803b +# English: "Cubby" +# Source-Hash: 7ca7f00b3ba531f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnShape.1e3a27i" -msgstr "Forma del retorno" +msgid "editor.inspector.option.cubby.ivq4qc" +msgstr "Hueco" -# Source-Hash: 6a7c411667124177 +# English: "Curved" +# Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnWidth.z9r3h3" -msgstr "Ancho del retorno" +msgid "editor.inspector.option.curved.6noz1s" +msgstr "Curvo" -# Source-Hash: 460bd11bfc2e5697 +# English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightEndCondition.tjlw3l" -msgstr "Condición del extremo derecho" +msgid "editor.inspector.option.cylinder.eol6vj" +msgstr "Cilindro" -# Source-Hash: 1ed35f8d27ae5583 +# English: "Damper" +# Source-Hash: 53f224e2828d5ab7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightOverhang.yqbeft" -msgstr "Vuelo derecho" +msgid "editor.inspector.option.damper.wgri80" +msgstr "Compuerta" -# Source-Hash: 39c02aa2d97a62ba +# English: "Destination" +# Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofHeight.1uhy6qy" -msgstr "Altura de la cubierta" +msgid "editor.inspector.option.destination.gfzm1r" +msgstr "Destino" -# Source-Hash: d62f8833d77ca8a1 +# English: "Diffuser" +# Source-Hash: bfb3561e28d92a39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofStyle.pawqfy" -msgstr "Estilo de cubierta" +msgid "editor.inspector.option.diffuser.1igj0jh" +msgstr "Difusor" -# Source-Hash: 0ddd561a4126b826 +# English: "Dog ear" +# Source-Hash: 6cff941e10ff22b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofThickness.1xlr3rx" -msgstr "Espesor de la cubierta" +msgid "editor.inspector.option.dogEar.xhtvjt" +msgstr "Oreja de perro" -# Source-Hash: 3a08ede9e51d5395 +# English: "Dome" +# Source-Hash: de47e23eb81efe02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofType.1m9kv5h" -msgstr "Tipo de cubierta" +msgid "editor.inspector.option.dome.1c87e56" +msgstr "Cúpula" -# Source-Hash: 01989595ee7c1dc0 +# English: "Double" +# Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rotation.ly2rj" -msgstr "Rotación" +msgid "editor.inspector.option.double.13h5354" +msgstr "Doble" -# Source-Hash: c913b494c3901a17 +# English: "Duct board" +# Source-Hash: 72a84a9762461745 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rows.1ba1neg" -msgstr "Filas" +msgid "editor.inspector.option.ductBoard.lc3o4t" +msgstr "Panel de conducto" -# Source-Hash: 8210f376c0d29acc +# English: "Dutch" +# Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.scale.b6rpxt" -msgstr "Escala" +msgid "editor.inspector.option.dutch.1djx429" +msgstr "Holandesa" -# Source-Hash: adb6face4bac5245 +# English: "Elbow" +# Source-Hash: e6637bddef6aabd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seam detail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.seamDetail.197za48" -msgstr "Detalle de junta" +msgid "editor.inspector.option.elbow.1flup74" +msgstr "Codo" -# Source-Hash: 4b2aed9d56cc4dee +# English: "End" +# Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.segmentType.l667sy" -msgstr "Tipo de segmento" +msgid "editor.inspector.option.end.1llf32i" +msgstr "Final" -# Source-Hash: a7794a81141b4de4 +# English: "End cap" +# Source-Hash: ada9fc2eec2c7d61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftStyle.1i5ampa" -msgstr "Estilo del hueco" +msgid "editor.inspector.option.endCap.1p4v4na" +msgstr "Tapa final" -# Source-Hash: 8eb6438543b18cdb +# English: "Equal fit" +# Source-Hash: 73916b8395208874 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftWallThickness.bduslr" -msgstr "Espesor del muro del hueco" +msgid "editor.inspector.option.equalFit.11vw52c" +msgstr "Ajuste igual" -# Source-Hash: 85ea1d3c8b749680 +# English: "Fixed count" +# Source-Hash: 83579a6e420df2ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape.iq9t52" -msgstr "Forma" +msgid "editor.inspector.option.fixedCount.1o3dznu" +msgstr "Cantidad fija" -# Source-Hash: 44a8ab89c854cad1 +# English: "Fixed spacing" +# Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape2.fi0nx8" -msgstr "Forma 2" +msgid "editor.inspector.option.fixedSpacing.hl4dr4" +msgstr "Separación fija" -# Source-Hash: fcee9712879570ae +# English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed high side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shedHighSide.bzzddg" -msgstr "Lado alto de la cubierta a un agua" +msgid "editor.inspector.option.flat.ly0bei" +msgstr "Plana" -# Source-Hash: f5a931cd6e97e5e2 +# English: "Flex" +# Source-Hash: a9cad0badcc16693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shingleThickness.1hn5wxr" -msgstr "Espesor de la teja" +msgid "editor.inspector.option.flex.ktpamq" +msgstr "Flexible" -# Source-Hash: b14990b3e4222dd3 +# English: "Floating" +# Source-Hash: 7843c5142b7d066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder extent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderExtent.11j8uiv" -msgstr "Extensión del hombro" +msgid "editor.inspector.option.floating.1oadpln" +msgstr "Flotante" -# Source-Hash: 505de343ec5010e6 +# English: "Floor" +# Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderHeight.xyqn6g" -msgstr "Altura del hombro" +msgid "editor.inspector.option.floor.q9caf1" +msgstr "Planta" -# Source-Hash: 58d48c01b7ec1a40 +# English: "Flush" +# Source-Hash: eaaa20f3b603140f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderStyle.1hkcv2w" -msgstr "Estilo del hombro" +msgid "editor.inspector.option.flush.olwwu5" +msgstr "Enrasado" -# Source-Hash: af84db1026bf2e6c +# English: "Front" +# Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show center column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showCenterColumn.1uggg45" -msgstr "Mostrar columna central" +msgid "editor.inspector.option.front.uhugl4" +msgstr "Frente" -# Source-Hash: 80214dc086ebc1b1 +# English: "Furnace" +# Source-Hash: bccfc0565157dae6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showInfill.33rv7u" -msgstr "Mostrar relleno" +msgid "editor.inspector.option.furnace.w0mzpf" +msgstr "Caldera de aire" -# Source-Hash: f02d6cdfb4c6ad08 +# English: "Gable" +# Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show step supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showStepSupports.1kg8h8m" -msgstr "Mostrar soportes de peldaño" +msgid "editor.inspector.option.gable.1ekyx6" +msgstr "Dos aguas" -# Source-Hash: 5d1147c774718b76 +# English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side flashing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sideFlashing.cv744o" -msgstr "Tapajuntas lateral" +msgid "editor.inspector.option.gambrel.1yo90d9" +msgstr "Holandés de buhardilla" -# Source-Hash: 60bb3338e0ad558c +# English: "Generic" +# Source-Hash: 3c4900155c96b139 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.size.1a4x3zw" -msgstr "Tamaño" +msgid "editor.inspector.option.generic.1074oey" +msgstr "Genérico" -# Source-Hash: fbdbc6c97c75b030 +# English: "Glass" +# Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.skylightType.1a5vj3i" -msgstr "Tipo de claraboya" +msgid "editor.inspector.option.glass.163tc5n" +msgstr "Vidrio" -# Source-Hash: c9a6561cb3c3da82 +# English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab opening mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slabOpeningMode.g4f730" -msgstr "Modo de hueco en forjado" +msgid "editor.inspector.option.glassFrame.jp9s3m" +msgstr "Marco de vidrio" -# Source-Hash: ef0efc845ccdba78 +# English: "Globe" +# Source-Hash: 2d4223f80dc3945c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slatGap.slmk93" -msgstr "Separación de lamas" +msgid "editor.inspector.option.globe.1avkqoy" +msgstr "Globo" -# Source-Hash: 4daab7a2ce2ecef3 +# English: "Grounded" +# Source-Hash: 398af4f45ba60c85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slideDirection.1527q13" -msgstr "Dirección de deslizamiento" +msgid "editor.inspector.option.grounded.jwoq73" +msgstr "Apoyado en el suelo" -# Source-Hash: 93b34a5b546664c4 +# English: "Guard" +# Source-Hash: 4972a0e68d69be58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.span.1bmmuw1" -msgstr "Luz" +msgid "editor.inspector.option.guard.1m575se" +msgstr "Barandilla" -# Source-Hash: 3b006be0b1f0493f +# English: "Half round" +# Source-Hash: 67be98efb58a7767 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.speed.2w2xwg" -msgstr "Velocidad" +msgid "editor.inspector.option.halfRound.m5hob0" +msgstr "Media caña" -# Source-Hash: c8cfb8336a240cb1 +# English: "Hidden" +# Source-Hash: 229199467e9cf2db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spin speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.spinSpeed.1ozx746" -msgstr "Velocidad de giro" +msgid "editor.inspector.option.hidden.gp48rd" +msgstr "Oculto" -# Source-Hash: c9f54c3b258961c7 +# English: "Hip" +# Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stairType.1u05e02" -msgstr "Tipo de escalera" +msgid "editor.inspector.option.hip.r0jnjy" +msgstr "Cuatro aguas" -# Source-Hash: 0af6194a8b8d8be6 +# English: "Horizontal" +# Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoff.4wv2g0" -msgstr "Separador" +msgid "editor.inspector.option.horizontal.tyapah" +msgstr "Horizontal" -# Source-Hash: 0464d4efa1e80c62 +# English: "Include" +# Source-Hash: 6a9b4142ab179557 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoffHeight.1qdnff" -msgstr "Altura del separador" +msgid "editor.inspector.option.include.1a27vnr" +msgstr "Incluir" -# Source-Hash: 85da28b0b26b8c2a +# English: "Independent high beam" +# Source-Hash: 8061b426b9d1b5fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.startPost.1ood9kv" -msgstr "Poste inicial" +msgid "editor.inspector.option.independentHighBeam.1tn0zvy" +msgstr "Viga alta independiente" -# Source-Hash: d5dc1ba8cca3193d +# English: "Industrial" +# Source-Hash: 1dc686f2068f976b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stepCount.nzorrw" -msgstr "Número de peldaños" +msgid "editor.inspector.option.industrial.8sd9u" +msgstr "Industrial" -# Source-Hash: cadbb9cadd6a5e6f +# English: "Inline" +# Source-Hash: 0c9b1ec8947ee177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapSpacing.1cyt3dc" -msgstr "Separación de flejes" +msgid "editor.inspector.option.inline.1uh05lg" +msgstr "En línea" -# Source-Hash: c017cd56b3ae5865 +# English: "Integrated" +# Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapStyle.fh1oy4" -msgstr "Estilo de fleje" +msgid "editor.inspector.option.integrated.1xd9lv0" +msgstr "Integrado" -# Source-Hash: 1a79a485d91f3f28 +# English: "Joined" +# Source-Hash: bb197b9219f32603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.style.nf3vcm" -msgstr "Estilo" +msgid "editor.inspector.option.joined.1x2o8ds" +msgstr "Unido" -# Source-Hash: 7b3f016989c631a7 +# English: "Kickout" +# Source-Hash: 0299cc0ee05d17fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Suction diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.suctionDiameter.nutxtj" -msgstr "Diámetro de aspiración" +msgid "editor.inspector.option.kickout.vt8s6p" +msgstr "Desviador" -# Source-Hash: 3f1e0fd30e5b1f39 +# English: "Knee" +# Source-Hash: a0712c8461bb0758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyDiameter.h5ldh5" -msgstr "Diámetro de impulsión" +msgid "editor.inspector.option.knee.186hcui" +msgstr "Rodilla" -# Source-Hash: 05c9b608538e181d +# English: "K style" +# Source-Hash: 083febe9876348ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyHeight.zy6ru1" -msgstr "Altura de impulsión" +msgid "editor.inspector.option.kStyle.m8kfwz" +msgstr "Estilo K" -# Source-Hash: 3c9400722baa474d +# English: "Landing" +# Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyShape.k39wnb" -msgstr "Forma de impulsión" +msgid "editor.inspector.option.landing.tyqarg" +msgstr "Descansillo" -# Source-Hash: af7619025438f995 +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyWidth.g66axi" -msgstr "Ancho de impulsión" +msgid "editor.inspector.option.lantern.1h7o2xb" +msgstr "Linterna" -# Source-Hash: 617e198cf3f214d3 +# English: "Leave" +# Source-Hash: 9477f663422698ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Support offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supportOffset.1w4cocr" -msgstr "Desfase del soporte" +msgid "editor.inspector.option.leave.1xf0sbk" +msgstr "Salir" -# Source-Hash: 2686abc093f3dfb1 +# English: "Left" +# Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sweepAngle.ucmmlw" -msgstr "Ángulo de barrido" +msgid "editor.inspector.option.left.14n06mo" +msgstr "Izquierda" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Level" +# Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.system.13qbhrw" -msgstr "Sistema" +msgid "editor.inspector.option.level.i4lhtp" +msgstr "Planta" -# Source-Hash: ccb3afc48bd9a284 +# English: "Low profile" +# Source-Hash: a281a83a9fef6b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminal.1501knl" -msgstr "Terminal" +msgid "editor.inspector.option.lowProfile.1h9lgzg" +msgstr "Perfil bajo" -# Source-Hash: de1777c521046f7d +# English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminalType.1ne8lgb" -msgstr "Tipo de terminal" +msgid "editor.inspector.option.mansard.dwzck9" +msgstr "Mansarda" -# Source-Hash: fdbb503f0941bd56 +# English: "Manual" +# Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.thickness.1l8hu45" -msgstr "Espesor" +msgid "editor.inspector.option.manual.1wu7r43" +msgstr "Manual" -# Source-Hash: 58cb60c14826f1d0 +# English: "Maximum spacing" +# Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.tiltAngle.1312yw1" -msgstr "Ángulo de inclinación" +msgid "editor.inspector.option.maximumSpacing.n1fzl4" +msgstr "Separación máxima" -# Source-Hash: 7057972ff4c78dc7 +# English: "Metal" +# Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.toLevelId.8i5qx7" -msgstr "ID de planta de destino" +msgid "editor.inspector.option.metal.1az9fgc" +msgstr "Metal" -# Source-Hash: fa8fc3d5c612e774 +# English: "Metal panel" +# Source-Hash: c6185ecb219a9435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingDepth.ki7gk2" -msgstr "Profundidad del descansillo superior" +msgid "editor.inspector.option.metalPanel.1xdz476" +msgstr "Panel metálico" -# Source-Hash: 6e4a0b98089ec2cd +# English: "Minimal" +# Source-Hash: 2fd92342f3986843 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingMode.zk0h7e" -msgstr "Modo del descansillo superior" +msgid "editor.inspector.option.minimal.fwmiwg" +msgstr "Mínimo" -# Source-Hash: c182bb3c8a701085 +# English: "Mono" +# Source-Hash: 5d8a5912056f83ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top rail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topRailHeight.fscisv" -msgstr "Altura del travesaño superior" +msgid "editor.inspector.option.mono.oszvd6" +msgstr "Una vertiente" -# Source-Hash: 8c5502f1d8087b7b +# English: "None" +# Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionMode.f3evgt" -msgstr "Modo de transición" +msgid "editor.inspector.option.none.deku7v" +msgstr "Ninguno" -# Source-Hash: 96eb5997f0aa2dde +# English: "Omit" +# Source-Hash: 7b6e4a6b86ba5112 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionWidth.rtgz3m" -msgstr "Ancho de transición" +msgid "editor.inspector.option.omit.okcs7w" +msgstr "Omitir" -# Source-Hash: 812d27902509339f +# English: "Open" +# Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vane count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.vaneCount.55vtye" -msgstr "Número de álabes" +msgid "editor.inspector.option.open.n6hn1l" +msgstr "Abrir" -# Source-Hash: 1e7dcd717c8ac239 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallHeight.18vmh5c" -msgstr "Altura del muro" +msgid "editor.inspector.option.opening.dfet3" +msgstr "Abertura" -# Source-Hash: f14bdaed6101896a +# English: "Open rack" +# Source-Hash: cc72b9bda6b30b5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallShell.vqsyhv" -msgstr "Envolvente del muro" +msgid "editor.inspector.option.openRack.mvt1x0" +msgstr "Estante abierto" -# Source-Hash: 801268585c9a679b +# English: "Oval" +# Source-Hash: ed6d7b3dd9d5fc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall skirt height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" -msgstr "Altura del zócalo" +msgid "editor.inspector.option.oval.1jiqzxx" +msgstr "Óvalo" -# Source-Hash: 6b377d0ba1565477 +# English: "Picket" +# Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallThickness.17wn4iv" -msgstr "Espesor del muro" +msgid "editor.inspector.option.picket.zyeoy1" +msgstr "Estacas" -# Source-Hash: 3a0ccd6c46877a87 +# English: "Pointed" +# Source-Hash: 180ffb8b75c977aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width.gfxz9b" -msgstr "Ancho" +msgid "editor.inspector.option.pointed.effedk" +msgstr "Puntiagudo" -# Source-Hash: de05070474f589b4 +# English: "Post and rail" +# Source-Hash: fca7919fb0c0669c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width2.1nqawvr" -msgstr "Ancho 2" +msgid "editor.inspector.option.postAndRail.lajk2u" +msgstr "Poste y travesaño" -# Source-Hash: 6199b641888a83fa +# English: "Preserve high edge" +# Source-Hash: a78b28a428b27e20 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBack.1ay8nus" -msgstr "Con trasera" +msgid "editor.inspector.option.preserveHighEdge.y6ywf0" +msgstr "Conservar el borde alto" -# Source-Hash: 699934d8b9571b64 +# English: "Preserve low edge" +# Source-Hash: 325c6f53f6dc7634 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBottom.1x1z1vo" -msgstr "Con base" +msgid "editor.inspector.option.preserveLowEdge.196m5pk" +msgstr "Conservar el borde bajo" -# Source-Hash: 03987b9aadaab50f +# English: "Preserve pitch" +# Source-Hash: 95c0b860d22a62cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With sides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withSides.uq066x" -msgstr "Con laterales" +msgid "editor.inspector.option.preservePitch.1uq973r" +msgstr "Conservar la pendiente" -# Source-Hash: f571cb2d06ae01ae +# English: "Privacy" +# Source-Hash: d4e11841abf22593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Advanced" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.advanced.qwfkor" -msgstr "Avanzado" +msgid "editor.inspector.option.privacy.1th4hrb" +msgstr "Privacidad" -# Source-Hash: a886619d15bae47e +# English: "Purlins" +# Source-Hash: 1a8c46c7712a83b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.air.15jotdj" -msgstr "Aire" +msgid "editor.inspector.option.purlins.od124c" +msgstr "Correas" -# Source-Hash: b69fc1f8128958fc +# English: "Pvc" +# Source-Hash: a9650f6f94de9247 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.appearance.dli9wf" -msgstr "Apariencia" +msgid "editor.inspector.option.pvc.1oc4u2w" +msgstr "PVC" -# Source-Hash: faaa4f6b40001667 +# English: "Pyramid" +# Source-Hash: 47587b825e9a696b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.body.ign4wl" -msgstr "Cuerpo" +msgid "editor.inspector.option.pyramid.17ucsqv" +msgstr "Pirámide" -# Source-Hash: fdf7108a011bd94b +# English: "Rafters" +# Source-Hash: a9cb232546779779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cab.o4z1ox" -msgstr "Cabina" +msgid "editor.inspector.option.rafters.16tzbby" +msgstr "Cabios" -# Source-Hash: 5737c4567e0f6863 +# English: "Rail" +# Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cabinet.rzvymr" -msgstr "Mueble" +msgid "editor.inspector.option.rail.39zqc9" +msgstr "Travesaño" -# Source-Hash: d52b53241789ee63 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cap.s0thej" -msgstr "Remate" +msgid "editor.inspector.option.raised.4kyx9h" +msgstr "Elevado" -# Source-Hash: 93a545688c917e69 +# English: "Rect" +# Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.collar.1am2iqo" -msgstr "Collarín" +msgid "editor.inspector.option.rect.tpfvaf" +msgstr "Rect." -# Source-Hash: 7a9c55b1c5a6e61d +# English: "Reducer" +# Source-Hash: 68206f70689b2340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connection.2r1h4p" -msgstr "Conexión" +msgid "editor.inspector.option.reducer.18byt67" +msgstr "Reductor" -# Source-Hash: d613df280aa13b42 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connections.ai4w8u" -msgstr "Conexiones" +msgid "editor.inspector.option.return.1klrjrz" +msgstr "Retorno" -# Source-Hash: ce1429556f57d7ee +# English: "Return grille" +# Source-Hash: 63b39b1681f74fe4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.construction.j6p9kc" -msgstr "Construcción" +msgid "editor.inspector.option.returnGrille.r28ncm" +msgstr "Rejilla de retorno" -# Source-Hash: f8ba7d73d3006d57 +# English: "Right" +# Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cricket.6iwg8g" -msgstr "Caballete desviador" +msgid "editor.inspector.option.right.8humpx" +msgstr "Derecha" -# Source-Hash: b8ff053666ee326b +# English: "Round" +# Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curb.5xi2ol" -msgstr "Bordillo" +msgid "editor.inspector.option.round.1vx0wyj" +msgstr "Redondo" -# Source-Hash: 3b9c59e996dcc9c5 +# English: "Rounded" +# Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve points" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curvePoints.ztdcnn" -msgstr "Puntos de curva" +msgid "editor.inspector.option.rounded.199nwpm" +msgstr "Redondeado" -# Source-Hash: 12df69f42beb0643 +# English: "Sanitary tee" +# Source-Hash: dc67a1cdb2482f23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dimensions.pewa8g" -msgstr "Dimensiones" +msgid "editor.inspector.option.sanitaryTee.oro3d4" +msgstr "Te sanitaria" -# Source-Hash: 20f60b98de67849b +# English: "Scalloped" +# Source-Hash: beb3fec22c287185 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Doors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.doors.18w2kpa" -msgstr "Puertas" +msgid "editor.inspector.option.scalloped.1eltba0" +msgstr "Festoneado" -# Source-Hash: 6b6f8068bbc4c902 +# English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormer.1fjs5tq" -msgstr "Buhardilla" +msgid "editor.inspector.option.scoop.qy5zdv" +msgstr "Cuchara" -# Source-Hash: cda262793b4cc754 +# English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormerRoof.u9uy2g" -msgstr "Cubierta de buhardilla" +msgid "editor.inspector.option.segmentedPanel.5r3hwl" +msgstr "Panel segmentado" -# Source-Hash: 00a4416e0a33d856 +# English: "Shed" +# Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.drainage.7easy0" -msgstr "Desagüe" +msgid "editor.inspector.option.shed.6t994v" +msgstr "Un agua" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Sheet metal" +# Source-Hash: 7fbc26cd497cfd12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.elevation.4jbp44" -msgstr "Cota" +msgid "editor.inspector.option.sheetMetal.l164lv" +msgstr "Chapa" -# Source-Hash: a9cba06250228a55 +# English: "Shingle" +# Source-Hash: 26e2c261223cbb46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.endCaps.4fsb5b" -msgstr "Tapas finales" +msgid "editor.inspector.option.shingle.pfmynh" +msgstr "Teja asfáltica" -# Source-Hash: 228f2206862e41cf +# English: "Shingled" +# Source-Hash: e9046e9823b400a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.equipment.1xtq0ev" -msgstr "Equipo" +msgid "editor.inspector.option.shingled.1nm9inf" +msgstr "Con tejas" -# Source-Hash: a19276baa3688cbf +# English: "Simple" +# Source-Hash: a05a390ae205fdd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.face.1531fr0" -msgstr "Cara" +msgid "editor.inspector.option.simple.12mfu73" +msgstr "Sencillo" -# Source-Hash: fb34ee7c59cb84ac +# English: "Single" +# Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.fitting.171z1r4" -msgstr "Accesorio" +msgid "editor.inspector.option.single.1uzxurt" +msgstr "Simple" -# Source-Hash: f817c09ef0bb6d69 +# English: "Single left" +# Source-Hash: aee3b2bd78c87d0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flues" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.flues.nq6pye" -msgstr "Conductos" +msgid "editor.inspector.option.singleLeft.1atdh02" +msgstr "Simple izquierda" -# Source-Hash: a3c10560fa3c1612 +# English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.footprint.g8q2pg" -msgstr "Huella" +msgid "editor.inspector.option.singleRight.1axbu67" +msgstr "Simple derecha" -# Source-Hash: ce3a6c03765dbbd8 +# English: "Slant box" +# Source-Hash: 7672d2764445c110 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.frame.q7q9fq" -msgstr "Marco" +msgid "editor.inspector.option.slantBox.1ta99vy" +msgstr "Caja inclinada" -# Source-Hash: 523b9912e283b644 +# English: "Slat" +# Source-Hash: 405e07a8cc2c88e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.geometry.a21os1" -msgstr "Geometría" +msgid "editor.inspector.option.slat.1p9njbz" +msgstr "Lama" -# Source-Hash: 7e4a0a38f694be6b +# English: "Sliding" +# Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.grid.d8gwxd" -msgstr "Cuadrícula" +msgid "editor.inspector.option.sliding.cd0jox" +msgstr "Corredera" -# Source-Hash: 30d8dba84c42040e +# English: "Slope" +# Source-Hash: e6d82e606b8c9301 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hangers.agnfzn" -msgstr "Colgadores" +msgid "editor.inspector.option.slope.1bn2qw" +msgstr "Pendiente" -# Source-Hash: 8c1bc3a078056ff7 +# English: "Sloped" +# Source-Hash: f6998707123fe72d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hardware.1nygwm1" -msgstr "Herrajes" +msgid "editor.inspector.option.sloped.lo4sh0" +msgstr "Inclinado" -# Source-Hash: 2a47557eb4a5f766 +# English: "Solid" +# Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hung wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hungWall.1ftr1gz" -msgstr "Muro colgado" +msgid "editor.inspector.option.solid.o4uy6s" +msgstr "Sólido" -# Source-Hash: 7d6ed24c02ab0c5c +# English: "Solid panel" +# Source-Hash: 8008b6f17089beaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.insulation.1kyo5e7" -msgstr "Aislamiento" +msgid "editor.inspector.option.solidPanel.2yaw62" +msgstr "Panel opaco" -# Source-Hash: d4c6c2495c233bb5 +# English: "Spiral" +# Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lantern.1h7o2xb" -msgstr "Linterna" +msgid "editor.inspector.option.spiral.91hcl4" +msgstr "Helicoidal" -# Source-Hash: cc3d601d59e2e26c +# English: "Splash" +# Source-Hash: beef1cf79388b77b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.levels.1s6yaru" -msgstr "Plantas" +msgid "editor.inspector.option.splash.1uke4cm" +msgstr "Salpicadero" -# Source-Hash: 1a849e52a46d5087 +# English: "Spread" +# Source-Hash: 324677a79ab15e22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.line.166mv5z" -msgstr "Línea" +msgid "editor.inspector.option.spread.1g1zysu" +msgstr "Separación" -# Source-Hash: 9ab4f7a738ebee13 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lines.12bghr0" -msgstr "Líneas" +msgid "editor.inspector.option.square.1xl1lk6" +msgstr "Cuadrado" -# Source-Hash: c0ff16d77ebc9593 +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Motion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.motion.1be7sjr" -msgstr "Movimiento" +msgid "editor.inspector.option.stair.1xf86e" +msgstr "Escalera" -# Source-Hash: 81d365ccef73068c +# English: "Standard" +# Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.mounting.feutd2" -msgstr "Montaje" +msgid "editor.inspector.option.standard.vuco" +msgstr "Estándar" -# Source-Hash: e04298381837252f +# English: "Start" +# Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.opening.dfet3" -msgstr "Abertura" +msgid "editor.inspector.option.start.30xvgf" +msgstr "Inicio" -# Source-Hash: f46314c54627858d +# English: "Step" +# Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.panelDimensions.b59qe6" -msgstr "Dimensiones del panel" +msgid "editor.inspector.option.step.jtn9kf" +msgstr "Escalonado" -# Source-Hash: 4712c449e9a42d3b +# English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.patternDistribution.fmam51" -msgstr "Distribución del patrón" +msgid "editor.inspector.option.stepped.naxzh0" +msgstr "Escalonado" -# Source-Hash: b3be684ac62fbf53 +# English: "Straight" +# Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.placement.13lcvts" -msgstr "Colocación" +msgid "editor.inspector.option.straight.lxqbfr" +msgstr "Recto" -# Source-Hash: 9da0cbfad144d737 +# English: "Strap" +# Source-Hash: b9b45ac5ba1daeee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.position.1quewx6" -msgstr "Posición" +msgid "editor.inspector.option.strap.14q0ps9" +msgstr "Fleje" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.profile.1kwlci6" -msgstr "Perfil" +msgid "editor.inspector.option.supply.1oy2zwu" +msgstr "Impulsión" -# Source-Hash: 240ed9c77627b9a1 +# English: "Supply register" +# Source-Hash: 00e7a2b3b2aa36ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.railing.19a5bnr" -msgstr "Barandilla" +msgid "editor.inspector.option.supplyRegister.1lzdbbn" +msgstr "Rejilla de impulsión" -# Source-Hash: 2e0aa7113b021c67 +# English: "Tapered" +# Source-Hash: f86f1f22230596b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.return.1klrjrz" -msgstr "Retorno" +msgid "editor.inspector.option.tapered.3y5blo" +msgstr "Ahusado" -# Source-Hash: 83909f82446ee9c3 +# English: "Target spacing" +# Source-Hash: 5fed23c2217b83fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.settings.ktdb8o" -msgstr "Ajustes" +msgid "editor.inspector.option.targetSpacing.14uibc9" +msgstr "Separación objetivo" -# Source-Hash: 4fc63ad21de30103 +# English: "Tee" +# Source-Hash: f3ceed7f867f090f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shaft.mad9cv" -msgstr "Hueco" +msgid "editor.inspector.option.tee.7d6tjp" +msgstr "Te" -# Source-Hash: 85ea1d3c8b749680 +# English: "Tilted" +# Source-Hash: 787005b26aec58a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shape.iq9t52" -msgstr "Forma" +msgid "editor.inspector.option.tilted.6n0u5j" +msgstr "Inclinado" -# Source-Hash: 8c6e3985459dacf9 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shoulder.1nyfx25" -msgstr "Hombro" +msgid "editor.inspector.option.top.2nypcs" +msgstr "Superior" -# Source-Hash: 60bb3338e0ad558c +# English: "Transition" +# Source-Hash: e6657000554dfe53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.size.1a4x3zw" -msgstr "Tamaño" +msgid "editor.inspector.option.transition.1rziz9e" +msgstr "Transición" -# Source-Hash: a20c89205a4448d2 +# English: "Vent" +# Source-Hash: 04bc0909a133a7e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.structure.gezdra" -msgstr "Estructura" +msgid "editor.inspector.option.vent.xda5j2" +msgstr "Ventilación" -# Source-Hash: 1a79a485d91f3f28 +# English: "Walk on" +# Source-Hash: 7eee9aff0dd520b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.style.nf3vcm" -msgstr "Estilo" +msgid "editor.inspector.option.walkOn.1n5sg9l" +msgstr "Pisar sobre" -# Source-Hash: b45f382a67d6c0eb +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.supply.1oy2zwu" -msgstr "Impulsión" +msgid "editor.inspector.option.wall.blj585" +msgstr "Muro" -# Source-Hash: 78a454b7ac7a3cd9 +# English: "Wall abutment" +# Source-Hash: f3578f7177ad0054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.surface.wtamqk" -msgstr "Superficie" +msgid "editor.inspector.option.wallAbutment.lza941" +msgstr "Encuentro con el muro" -# Source-Hash: ccb3afc48bd9a284 +# English: "Wall ledger" +# Source-Hash: 8dcf182f91ba3245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.terminal.1501knl" -msgstr "Terminal" +msgid "editor.inspector.option.wallLedger.5m8ana" +msgstr "Larguero de muro" -# Source-Hash: 399fc3e62f491f08 +# English: "Wall shelf" +# Source-Hash: 0adda30be5f17975 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Topology" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.topology.18t8dyc" -msgstr "Topología" +msgid "editor.inspector.option.wallShelf.yiozfl" +msgstr "Estante de pared" -# Source-Hash: f425799b82e085ed +# English: "Waste" +# Source-Hash: 29d8590334492601 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transform" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.transform.1c55ayj" -msgstr "Transformación" +msgid "editor.inspector.option.waste.3xrp79" +msgstr "Desagüe" -# Source-Hash: b778a315e82065a0 +# English: "Wye" +# Source-Hash: 00249876bb37079f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.trap.1qc7vhe" -msgstr "Sifón" +msgid "editor.inspector.option.wye.14vupc8" +msgstr "Y" -# Source-Hash: c44f7ab8bbd24858 +# English: "Collapse" +# Source-Hash: 179b025e25f9a091 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.group.type.1m2zofh" -msgstr "Tipo" +msgid "editor.layers.collapse" +msgstr "Contraer" -# Source-Hash: c8fd17853839516f +# English: "Ceiling ({area})" +# Source-Hash: dbeae4c43f49327f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Abs" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.abs.132mz8b" -msgstr "ABS" +msgid "editor.layers.defaultName.ceiling" +msgstr "Techo ({area})" -# Source-Hash: fd9eb1a16cb6b996 +# English: "Collection" +# Source-Hash: d29165c9d4bfc3cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Access panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.accessPanel.13sptb5" -msgstr "Registro de acceso" +msgid "editor.layers.defaultName.collection" +msgstr "Colección" -# Source-Hash: db8064add8c27a5c +# English: "Flight ({width}×{length}m)" +# Source-Hash: 8f536b859be662a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air handler" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.airHandler.1crnyg3" -msgstr "Unidad de tratamiento de aire" +msgid "editor.layers.defaultName.flight" +msgstr "Tramo ({width}×{length}m)" -# Source-Hash: d8234782848be927 +# English: "Landing ({width}×{length}m)" +# Source-Hash: 46dcbf4548374073 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alternating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.alternating.5jashi" -msgstr "Alterno" +msgid "editor.layers.defaultName.landing" +msgstr "Descansillo ({width}×{length}m)" -# Source-Hash: 63cbda7b09662368 +# English: "Node" +# Source-Hash: 3ea44453fcd08a6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arched" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.arched.14188w0" -msgstr "En arco" +msgid "editor.layers.defaultName.node" +msgstr "Nodo" -# Source-Hash: 05b65242d8c48791 +# English: "Roof ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 93f3a04e15b6fb49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.auto.540b06" -msgstr "Auto" +msgid "editor.layers.defaultName.roof" +msgstr "Cubierta ({count, plural, one {# segmento} many {# segmentos} other {# segmentos}})" -# Source-Hash: d18036b75b2c3e4f +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" +# Source-Hash: a730fab01c172704 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.automatic.r122ro" -msgstr "Automático" +msgid "editor.layers.defaultName.roofSegment" +msgstr "{roofType, select, hip {A cuatro aguas} gable {A dos aguas} shed {A un agua} gambrel {Holandesa} dutch {Mixta} mansard {Mansarda} flat {Plana} conical {Cónica} barrel {Abovedada} other {Cubierta}} ({width}×{depth}m)" -# Source-Hash: 7ceade8f25b58fe9 +# English: "Room ({area})" +# Source-Hash: 54efcb0756c2f2c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.back.1hzmxtu" -msgstr "Volver" +msgid "editor.layers.defaultName.room" +msgstr "Estancia ({area})" -# Source-Hash: 3eda29328f91485d +# English: "Slab ({area})" +# Source-Hash: e38a4b9986a9d21a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.balusters.1j3w6ng" -msgstr "Balaustres" +msgid "editor.layers.defaultName.slab" +msgstr "Forjado ({area})" -# Source-Hash: f211248ec8619e4c +# English: "Staircase ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 57398365d4b9066f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Band" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.band.160umau" -msgstr "Banda" +msgid "editor.layers.defaultName.staircase" +msgstr "Escalera ({count, plural, one {# segmento} many {# segmentos} other {# segmentos}})" -# Source-Hash: 8f9ff05c6a3b08a6 +# English: "Zone ({area})" +# Source-Hash: a17f8fc29aae896e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base plate" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.basePlate.17fcg8k" -msgstr "Placa base" +msgid "editor.layers.defaultName.zone" +msgstr "Zona ({area})" -# Source-Hash: 19ae2360207ecf5a +# English: "Expand" +# Source-Hash: 5cabcabb8bc14809 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.option.boards.u14hig" -msgstr "Tablas" +msgid "editor.layers.expand" +msgstr "Expandir" -# Source-Hash: c0fee00bdfb09d37 +# English: "{levelName} options" +# Source-Hash: 6731e9dd94e8b268 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bookshelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bookshelf.ls0zqk" -msgstr "Estantería" +msgid "editor.layers.levelOptions" +msgstr "Opciones de {levelName}" -# Source-Hash: 69578b7dd7723fa1 +# English: "Rename" +# Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx #, fuzzy -msgid "editor.inspector.option.both.mkyb42" -msgstr "Ambos" +msgid "editor.layers.rename" +msgstr "Cambiar nombre" -# Source-Hash: b3a163b453896bef +# English: "{role, select, room {Room colour} other {Zone colour}}" +# Source-Hash: 21eafa019ad62fea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bottom.9mpruy" -msgstr "Inferior" +msgid "editor.layers.spaceColour" +msgstr "{role, select, room {Color de la estancia} other {Color de la zona}}" -# Source-Hash: e9a89a5e4ac8cd61 +# English: "Unit colour" +# Source-Hash: f5de042416a2c04f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.box.1nbjcuq" -msgstr "Caja" +msgid "editor.layers.unitColour" +msgstr "Color de la unidad" -# Source-Hash: 2c2815df4f724abe +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Break" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.break.17spnvc" -msgstr "Corte" +msgid "editor.levelDuplicateDialog.cancel" +msgstr "Cancelar" -# Source-Hash: 0d5a25303d3d8058 +# English: "Choose what to copy from {levelName}." +# Source-Hash: 07c11e7e4d4681d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Butterfly" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.butterfly.pvv0g4" -msgstr "Mariposa" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" +msgstr "Elige qué copiar de {levelName}." -# Source-Hash: 5e2429ced8144ab4 +# English: "Choose what to copy from this level." +# Source-Hash: 9500d4378a319532 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cable.fp50zq" -msgstr "Cable" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" +msgstr "Elige qué copiar de esta planta." -# Source-Hash: 69129e514347672a +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cast iron" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.castIron.1q2z4qk" -msgstr "Fundición" +msgid "editor.levelDuplicateDialog.duplicate" +msgstr "Duplicar" -# Source-Hash: 4275c05172abd7fb +# English: "Duplicate Level" +# Source-Hash: a56e3eb43e60b45c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.ceiling.1t2zaqo" -msgstr "Techo" +msgid "editor.levelDuplicateDialog.duplicateLevel" +msgstr "Duplicar planta" -# Source-Hash: 5211341ed18f33f6 +# English: "Everything" +# Source-Hash: e4c6ab1ea473a082 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.center.kzdxno" -msgstr "Centro" +msgid "editor.levelDuplicateDialog.everything" +msgstr "Todo" -# Source-Hash: 65f410261609060b +# English: "Structure, finishes, and placed items, without guide references." +# Source-Hash: ac3f119e560388ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.centerOpening.bn1rvw" -msgstr "Apertura central" +msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" +msgstr "Estructura, acabados y elementos colocados, sin referencias de guía." -# Source-Hash: cf5f9756cc866f3e +# English: "Structure + furniture" +# Source-Hash: acbdf968391d5273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cleanout.lww8bm" -msgstr "Registro de limpieza" +msgid "editor.levelDuplicateDialog.structureFurniture" +msgstr "Estructura + mobiliario" -# Source-Hash: e71d1d949f1d1b41 +# English: "Structure + materials" +# Source-Hash: ce682864a140fe75 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Concrete pad" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.concretePad.m1zdij" -msgstr "Losa de hormigón" +msgid "editor.levelDuplicateDialog.structureMaterials" +msgstr "Estructura + materiales" -# Source-Hash: a3804c4f5e6aefbc +# English: "Structure, materials, furniture, and references." +# Source-Hash: 5ee44d53847c5ea2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Condenser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.condenser.a74nps" -msgstr "Condensador" +msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" +msgstr "Estructura, materiales, mobiliario y referencias." -# Source-Hash: 6786ac75cbe9af57 +# English: "Structure only" +# Source-Hash: 95aa1b43dbdb3f02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corbeled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.corbeled.bovs4v" -msgstr "Con ménsulas" +msgid "editor.levelDuplicateDialog.structureOnly" +msgstr "Solo estructura" -# Source-Hash: 8a4beb9a57edc26e +# English: "Structure with the current material and finish assignments." +# Source-Hash: 2c53b46c9ca9fe2f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.count.1qoi4r8" -msgstr "Cantidad" +msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" +msgstr "Estructura con las asignaciones actuales de materiales y acabados." -# Source-Hash: db1f8f141714b363 +# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." +# Source-Hash: 3350e9008eaba15d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coupling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.coupling.111aap2" -msgstr "Manguito" +msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" +msgstr "Muros, forjados, cubiertas, escaleras, ventanas y puertas, sin acabados." -# Source-Hash: 7a9a92d2e59a3bbc +# English: "Copy" +# Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering specific" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.coveringSpecific.1h9fo8w" -msgstr "Específico del revestimiento" +msgid "editor.levelWallCopyPrompt.copy" +msgstr "Copiar" -# Source-Hash: d500c4b7827d3390 +# English: "Copy exterior walls from the level {direction}?" +# Source-Hash: 8ccaf4c98e335cc8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cross" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cross.1elubzv" -msgstr "Cruz" +msgid "editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel" +msgstr "¿Copiar los muros exteriores de la planta {direction, select, below {inferior} above {superior} other {contigua}}?" -# Source-Hash: 7ca7f00b3ba531f4 +# English: "No thanks" +# Source-Hash: f6ad0e813d2945bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cubby" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cubby.ivq4qc" -msgstr "Hueco" +msgid "editor.levelWallCopyPrompt.noThanks" +msgstr "No, gracias" -# Source-Hash: a028e08962c388e0 +# English: "{sourceName}’s outside walls, in place, so this level lines up with it." +# Source-Hash: d2aecc3c47120ad5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.curved.6noz1s" -msgstr "Curvo" +msgid "editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace" +msgstr "Los muros exteriores de {sourceName}, en su sitio, para que esta planta quede alineada con ella." -# Source-Hash: c65f02bcdc3231d6 +# English: "Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies." +# Source-Hash: b6317f13da65f81e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cylinder" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.cylinder.eol6vj" -msgstr "Cilindro" +msgid "editor.lib.buildingFoundation.basementExcavation" +msgstr "Se excava {depth} en el terreno por el lado alto, por dentro de las caras exteriores de los muros; el terreno alrededor de la casa se deja tal como está." -# Source-Hash: 53f224e2828d5ab7 +# English: "The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 16c97d232e4c05dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.damper.wgri80" -msgstr "Compuerta" +msgid "editor.lib.buildingFoundation.basementTopOverTheGround" +msgstr "La cara superior del muro de sótano está {distance} por encima del lado alto del terreno; la cimentación está configurada para {asked} por encima." -# Source-Hash: 748877ae309c313c +# English: "The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 9b781eb9749c9af3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.destination.gfzm1r" -msgstr "Destino" +msgid "editor.lib.buildingFoundation.basementTopUnderTheGround" +msgstr "La cara superior del muro de sótano está {distance} por debajo del lado alto del terreno; la cimentación está configurada para {asked} por encima." -# Source-Hash: bfb3561e28d92a39 +# English: "Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1)." +# Source-Hash: 6903234208cb7eb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diffuser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.diffuser.1igj0jh" -msgstr "Difusor" +msgid "editor.lib.buildingFoundation.crawlSpaceDugOut" +msgstr "Cámara sanitaria: excavada hasta {dug} para dejar {clearance} bajo las viguetas (IRC R317.1)." -# Source-Hash: 6cff941e10ff22b3 +# English: "Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall." +# Source-Hash: fee27efc9440d33c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dog ear" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dogEar.xhtvjt" -msgstr "Oreja de perro" +msgid "editor.lib.buildingFoundation.crippleWalls" +msgstr "Cuando el murete sobresale más de {stem} del terreno, baja a {least} sobre rasante: {cripples, plural, one {# muro enano} many {# muros enanos} other {# muros enanos}} encima, de hasta {tallest} de altura." -# Source-Hash: de47e23eb81efe02 +# English: "The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it." +# Source-Hash: d9f9c3b0c3978923 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dome" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dome.1c87e56" -msgstr "Cúpula" +msgid "editor.lib.buildingFoundation.daylightWalls" +msgstr "El terreno baja {fall} de un lado a otro de la casa: los muros del lado bajo sobresalen hasta {exposed} del terreno." -# Source-Hash: 3cd71355956a1821 +# English: "That is {fill} of fill. A raised floor on a stem wall suits a floor this high." +# Source-Hash: a62040ee19eb7321 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.double.13h5354" -msgstr "Doble" +msgid "editor.lib.buildingFoundation.deepFill" +msgstr "Eso supone {fill} de relleno. Un suelo elevado sobre murete de cimentación es más adecuado para un suelo tan alto." -# Source-Hash: 72a84a9762461745 +# English: "That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope." +# Source-Hash: 7f8966c9c28a5d64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct board" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.ductBoard.lc3o4t" -msgstr "Panel de conducto" +msgid "editor.lib.buildingFoundation.deepFillOnASlope" +msgstr "Eso supone {fill} de relleno en el lado bajo. Un suelo elevado sobre murete de cimentación es más adecuado para esta pendiente." -# Source-Hash: 6949d667043bee0b +# English: "The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: a2434c0de9dae5e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dutch.1djx429" -msgstr "Holandesa" +msgid "editor.lib.buildingFoundation.floorOverTheGround" +msgstr "El suelo está {distance} por encima del lado alto del terreno; la cimentación está configurada para {asked} por encima." -# Source-Hash: e6637bddef6aabd6 +# English: "The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 6dcf21da285e3910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.elbow.1flup74" -msgstr "Codo" +msgid "editor.lib.buildingFoundation.floorUnderTheGround" +msgstr "El suelo está {distance} por debajo del lado alto del terreno; la cimentación está configurada para {asked} por encima." -# Source-Hash: 135874d189176d13 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: b4e4be1da470f91e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.end.1llf32i" -msgstr "Final" +msgid "editor.lib.buildingFoundation.footingSteps" +msgstr "El terreno baja {fall} de un lado a otro de la casa: la zapata se escalona {steps, plural, one {# vez} many {# veces} other {# veces}} a lo largo de su lado más inclinado, {riser} por escalón." -# Source-Hash: ada9fc2eec2c7d61 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: bea524db96134378 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.endCap.1p4v4na" -msgstr "Tapa final" +msgid "editor.lib.buildingFoundation.footingStepsUpTo" +msgstr "El terreno baja {fall} de un lado a otro de la casa: la zapata se escalona {steps, plural, one {# vez} many {# veces} other {# veces}} a lo largo de su lado más inclinado, hasta {riser} por escalón." -# Source-Hash: 73916b8395208874 +# English: "The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1))." +# Source-Hash: 31192a74aa068c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal fit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.equalFit.11vw52c" -msgstr "Ajuste igual" +msgid "editor.lib.buildingFoundation.footingTooNarrow" +msgstr "La zapata tiene {width} de ancho: {storeys, plural, one {una planta exige} many {# plantas exigen} other {# plantas exigen}} {asked} sobre terreno de 1500 psf (tabla R403.1(1) del IRC)." -# Source-Hash: 83579a6e420df2ec +# English: "The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: c9f4ef98e9cd7d9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedCount.1o3dznu" -msgstr "Cantidad fija" +msgid "editor.lib.buildingFoundation.footingTooShallow" +msgstr "La base de la zapata está {depth} bajo rasante: mantenla al menos a {least}, o a la profundidad de helada (IRC R403.1.4)." -# Source-Hash: 4fb65101d0b1a222 +# English: "The ground is held {least} under the slab’s top on every side: cut {cut}." +# Source-Hash: 8e9be841759f6e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedSpacing.hl4dr4" -msgstr "Separación fija" +msgid "editor.lib.buildingFoundation.gradingCut" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: desmonte de {cut}." -# Source-Hash: 9cab4d4c95f63cdd +# English: "The ground is held {least} under the slab’s top on every side: cut {cut} on the high side." +# Source-Hash: 779f127732e5589b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flat.ly0bei" -msgstr "Plana" +msgid "editor.lib.buildingFoundation.gradingCutOnASlope" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: desmonte de {cut} en el lado alto." -# Source-Hash: a9cad0badcc16693 +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}." +# Source-Hash: 954523c1f6ed230c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flex" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flex.ktpamq" -msgstr "Flexible" +msgid "editor.lib.buildingFoundation.gradingFilled" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: relleno de {fill}." -# Source-Hash: 7843c5142b7d066d +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}." +# Source-Hash: b55f69797190bfee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floating.1oadpln" -msgstr "Flotante" +msgid "editor.lib.buildingFoundation.gradingFilledAndCut" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: relleno de {fill}, desmonte de {cut}." -# Source-Hash: d7148fa065bc709b +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side." +# Source-Hash: bae5f3ae42970e8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floor.q9caf1" -msgstr "Planta" +msgid "editor.lib.buildingFoundation.gradingFilledAndCutOnASlope" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: relleno de hasta {fill} en el lado bajo, desmonte de {cut} en el lado alto." -# Source-Hash: eaaa20f3b603140f +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side." +# Source-Hash: 4a06302380f3f85d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flush" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flush.olwwu5" -msgstr "Enrasado" +msgid "editor.lib.buildingFoundation.gradingFilledOnASlope" +msgstr "El terreno se mantiene {least} por debajo de la cara superior del forjado en todo el perímetro: relleno de hasta {fill} en el lado bajo." -# Source-Hash: e360f89d3c0ec456 +# English: "The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less." +# Source-Hash: 64931230719b598b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.front.uhugl4" -msgstr "Frente" +msgid "editor.lib.buildingFoundation.groundCutRoundTheStem" +msgstr "El terreno se rebaja {cut} en el lado alto para mantener la cara superior del murete de cimentación {least} por encima de él (IRC R317.1). Elevar el suelo reduce el desmonte." -# Source-Hash: bccfc0565157dae6 +# English: "Raise it on a stem wall" +# Source-Hash: 3613643e58e4b6d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.furnace.w0mzpf" -msgstr "Caldera de aire" +msgid "editor.lib.buildingFoundation.raiseItOnAStemWall" +msgstr "Elevar sobre un murete de cimentación" -# Source-Hash: deaffa095d126dfe +# English: "Raise the floor" +# Source-Hash: f69b48072be7f6ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gable.1ekyx6" -msgstr "Dos aguas" +msgid "editor.lib.buildingFoundation.raiseTheFloor" +msgstr "Elevar el suelo" -# Source-Hash: 7f53d9ffca39984a +# English: "Seat the basement wall’s top {asked} over the high side of the ground" +# Source-Hash: c1e5c4d64f7952a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gambrel.1yo90d9" -msgstr "Holandés de buhardilla" +msgid "editor.lib.buildingFoundation.seatBasementWallTop" +msgstr "Asentar la cara superior del muro de sótano {asked} por encima del lado alto del terreno" -# Source-Hash: 3c4900155c96b139 +# English: "Seat the floor’s top {asked} over the high side of the ground" +# Source-Hash: 3f82cd72a6654fdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Generic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.generic.1074oey" -msgstr "Genérico" +msgid "editor.lib.buildingFoundation.seatFloorTop" +msgstr "Asentar la cara superior del suelo {asked} por encima del lado alto del terreno" -# Source-Hash: 8ab91094cf650204 +# English: "Seat the ground floor {asked} over grade" +# Source-Hash: b4bbe36aae57a0d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glass.163tc5n" -msgstr "Vidrio" +msgid "editor.lib.buildingFoundation.seatGroundFloor" +msgstr "Asentar la planta baja {asked} sobre rasante" -# Source-Hash: 88810658a2b97bc6 +# English: "Seat it at {asked}" +# Source-Hash: bc5256a4749abb35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass frame" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glassFrame.jp9s3m" -msgstr "Marco de vidrio" +msgid "editor.lib.buildingFoundation.seatItAt" +msgstr "Asentar a {asked}" -# Source-Hash: 2d4223f80dc3945c +# English: "Seat the raised floor’s top {asked} over the high side of the ground" +# Source-Hash: 2becc7a7c8c48a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Globe" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.globe.1avkqoy" -msgstr "Globo" +msgid "editor.lib.buildingFoundation.seatRaisedFloorTop" +msgstr "Asentar la cara superior del suelo elevado {asked} por encima del lado alto del terreno" -# Source-Hash: 398af4f45ba60c85 +# English: "Seat the slab’s top {asked} over the high side of the ground" +# Source-Hash: 88e009e2eb3ec2a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.grounded.jwoq73" -msgstr "Apoyado en el suelo" +msgid "editor.lib.buildingFoundation.seatSlabTop" +msgstr "Asentar la cara superior del forjado {asked} por encima del lado alto del terreno" -# Source-Hash: 4972a0e68d69be58 +# English: "Set it {height}" +# Source-Hash: ccb9193f87264256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.guard.1m575se" -msgstr "Barandilla" +msgid "editor.lib.buildingFoundation.setIt" +msgstr "Fijar a {height}" -# Source-Hash: 67be98efb58a7767 +# English: "Set it {depth} deep" +# Source-Hash: e004c8ca1e6b6117 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.halfRound.m5hob0" -msgstr "Media caña" +msgid "editor.lib.buildingFoundation.setItDeep" +msgstr "Fijar a {depth} de profundidad" -# Source-Hash: 229199467e9cf2db +# English: "The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1)." +# Source-Hash: e884fcda7ee630ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hidden" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hidden.gp48rd" -msgstr "Oculto" +msgid "editor.lib.buildingFoundation.sillTooNearTheGround" +msgstr "El durmiente y el entramado del suelo están {height} sobre el terreno: mantenlos al menos a {least} de él (IRC R317.1)." -# Source-Hash: 9eda0ea8d447066d +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: fe154a985943c7ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hip.r0jnjy" -msgstr "Cuatro aguas" +msgid "editor.lib.buildingFoundation.slabEdgeSteps" +msgstr "El terreno baja {fall} de un lado a otro de la casa: el borde del forjado se escalona {steps, plural, one {# vez} many {# veces} other {# veces}} a lo largo de su lado más inclinado, {riser} por escalón." -# Source-Hash: ac9c79f08ef130a1 +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: ee469b2069a20e5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.horizontal.tyapah" -msgstr "Horizontal" +msgid "editor.lib.buildingFoundation.slabEdgeStepsUpTo" +msgstr "El terreno baja {fall} de un lado a otro de la casa: el borde del forjado se escalona {steps, plural, one {# vez} many {# veces} other {# veces}} a lo largo de su lado más inclinado, hasta {riser} por escalón." -# Source-Hash: 6a9b4142ab179557 +# English: "The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: e45d256982a22d6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.include.1a27vnr" -msgstr "Incluir" +msgid "editor.lib.buildingFoundation.slabEdgeTooShallow" +msgstr "La base del borde del forjado está {depth} bajo rasante: mantenla al menos a {least}, o a la profundidad de helada (IRC R403.1.4)." -# Source-Hash: 8061b426b9d1b5fa +# English: "Widen it to {width}" +# Source-Hash: 70fa99a8b7e7c762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Independent high beam" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.independentHighBeam.1tn0zvy" -msgstr "Viga alta independiente" +msgid "editor.lib.buildingFoundation.widenItTo" +msgstr "Ensanchar a {width}" -# Source-Hash: 1dc686f2068f976b +# English: "Add or remove objects from the selection" +# Source-Hash: 395fd0821b203517 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Industrial" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.industrial.8sd9u" -msgstr "Industrial" +msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" +msgstr "Añadir o quitar objetos de la selección" -# Source-Hash: 0c9b1ec8947ee177 +# English: "Adjust one side’s corner radius" +# Source-Hash: 8428cb0af9c6d6a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inline" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.inline.1uh05lg" -msgstr "En línea" +msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" +msgstr "Ajustar el radio de esquina de un lado" -# Source-Hash: 88d5b9ecb242c650 +# English: "Clear the selection (or click outside)" +# Source-Hash: d63b45c195713993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.integrated.1xd9lv0" -msgstr "Integrado" +msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" +msgstr "Borrar la selección (o hacer clic fuera)" -# Source-Hash: bb197b9219f32603 +# English: "Click a handle dot to show move arrows" +# Source-Hash: 40b1afbeadbd6213 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Joined" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.joined.1x2o8ds" -msgstr "Unido" +msgid "editor.lib.contextualHelp.clickAHandleDotToShow" +msgstr "Haz clic en un punto de control para ver las flechas de movimiento" -# Source-Hash: 0299cc0ee05d17fc +# English: "Click or drag the selection to move it as one" +# Source-Hash: 0b82a35c71549580 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kickout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kickout.vt8s6p" -msgstr "Desviador" +msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" +msgstr "Haz clic o arrastra la selección para moverla en bloque" -# Source-Hash: a0712c8461bb0758 +# English: "Click the handle dot to show move + rotate handles" +# Source-Hash: 73a761f393f69cb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Knee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.knee.186hcui" -msgstr "Rodilla" +msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" +msgstr "Haz clic en el punto de control para ver los controles de mover y girar" -# Source-Hash: 083febe9876348ec +# English: "Detach the joint while dragging an arrow" +# Source-Hash: c0045cef191fee69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "K style" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kStyle.m8kfwz" -msgstr "Estilo K" +msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" +msgstr "Separar la unión al arrastrar una flecha" -# Source-Hash: de9566487b17a73b +# English: "Drag left or right to rotate selected object" +# Source-Hash: a019c8948c104a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.landing.tyqarg" -msgstr "Descansillo" +msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" +msgstr "Arrastra a izquierda o derecha para girar el objeto seleccionado" -# Source-Hash: d4c6c2495c233bb5 +# English: "Drag selected movable object" +# Source-Hash: 7db0364deb12f06e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lantern.1h7o2xb" -msgstr "Linterna" +msgid "editor.lib.contextualHelp.dragSelectedMovableObject" +msgstr "Arrastrar el objeto móvil seleccionado" -# Source-Hash: 9477f663422698ae +# English: "Group selection (session only)" +# Source-Hash: 0f258dd389afdc73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leave" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.leave.1xf0sbk" -msgstr "Salir" +msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" +msgstr "Agrupar la selección (solo esta sesión)" -# Source-Hash: f365c6b6dd9ebab9 +# English: "Hold to rotate freely" +# Source-Hash: 85330d62893a45c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.left.14n06mo" -msgstr "Izquierda" +msgid "editor.lib.contextualHelp.holdToRotateFreely" +msgstr "Mantén pulsado para girar libremente" -# Source-Hash: 0608743fc1736d86 +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.level.i4lhtp" -msgstr "Planta" +msgid "editor.lib.contextualHelp.rotate45" +msgstr "Girar ±45°" -# Source-Hash: a281a83a9fef6b88 +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low profile" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lowProfile.1h9lgzg" -msgstr "Perfil bajo" +msgid "editor.lib.contextualHelp.rotateTheSelection45" +msgstr "Girar la selección ±45°" -# Source-Hash: 7f7bb0e875af3260 +# English: "Rotating freely (no angle step)" +# Source-Hash: 0f6b418b18ff96f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.mansard.dwzck9" -msgstr "Mansarda" +msgid "editor.lib.contextualHelp.rotatingFreely" +msgstr "Giro libre (sin pasos de ángulo)" -# Source-Hash: 387082cd5f118bed +# English: "Switch the rotation axis (Y → X → Z)" +# Source-Hash: 7ed174d6b03563b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.manual.1wu7r43" -msgstr "Manual" +msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" +msgstr "Cambiar el eje de giro (Y → X → Z)" -# Source-Hash: 321ca5101ab61db7 +# English: "Ungroup session selection" +# Source-Hash: d6f2c5cea683de62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.maximumSpacing.n1fzl4" -msgstr "Separación máxima" +msgid "editor.lib.contextualHelp.ungroupSessionSelection" +msgstr "Desagrupar la selección de la sesión" -# Source-Hash: 8e464a8f52254f27 +# English: "Continuous" +# Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metal.1az9fgc" -msgstr "Metal" +msgid "editor.lib.continuation.continuous" +msgstr "Continuo" -# Source-Hash: c6185ecb219a9435 +# English: "Continuous canopy" +# Source-Hash: ebf0eb7af4636a16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metalPanel.1xdz476" -msgstr "Panel metálico" +msgid "editor.lib.continuation.continuousCanopy" +msgstr "Marquesina continua" -# Source-Hash: 2fd92342f3986843 +# English: "Continuous run" +# Source-Hash: 1d8e87cd807e8d40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Minimal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.minimal.fwmiwg" -msgstr "Mínimo" +msgid "editor.lib.continuation.continuousRun" +msgstr "Línea continua" -# Source-Hash: 5d8a5912056f83ce +# English: "Curved fence" +# Source-Hash: 8990683667567c6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mono" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.mono.oszvd6" -msgstr "Una vertiente" +msgid "editor.lib.continuation.curvedFence" +msgstr "Valla curva" -# Source-Hash: f2953d2931e36d0c +# English: "Freehand fence" +# Source-Hash: edf1b9a2ad3ab910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.none.deku7v" -msgstr "Ninguno" +msgid "editor.lib.continuation.freehandFence" +msgstr "Valla a mano alzada" -# Source-Hash: 7b6e4a6b86ba5112 +# English: "Place multiple" +# Source-Hash: 2cdb7c1c7cd7c4fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Omit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.omit.okcs7w" -msgstr "Omitir" +msgid "editor.lib.continuation.placeMultiple" +msgstr "Colocar varios" -# Source-Hash: 3e52f04f92e08070 +# English: "Place once" +# Source-Hash: 90bf945df6f9c979 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.open.n6hn1l" -msgstr "Abrir" +msgid "editor.lib.continuation.placeOnce" +msgstr "Colocar una vez" -# Source-Hash: e04298381837252f +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.opening.dfet3" -msgstr "Abertura" +msgid "editor.lib.continuation.polygonRoom" +msgstr "Estancia poligonal" -# Source-Hash: cc72b9bda6b30b5c +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rack" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.openRack.mvt1x0" -msgstr "Estante abierto" +msgid "editor.lib.continuation.rectangleRoom" +msgstr "Estancia rectangular" -# Source-Hash: ed6d7b3dd9d5fc69 +# English: "Single cabinet" +# Source-Hash: 7fd85d0408141157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Oval" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.oval.1jiqzxx" -msgstr "Óvalo" +msgid "editor.lib.continuation.singleCabinet" +msgstr "Mueble individual" -# Source-Hash: 9295ddc2805b21e2 +# English: "Single canopy" +# Source-Hash: dd1bdbc26165baa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.picket.zyeoy1" -msgstr "Estacas" +msgid "editor.lib.continuation.singleCanopy" +msgstr "Marquesina individual" -# Source-Hash: 180ffb8b75c977aa +# English: "Single fence" +# Source-Hash: 5fb1a69316be983e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pointed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.pointed.effedk" -msgstr "Puntiagudo" +msgid "editor.lib.continuation.singleFence" +msgstr "Valla simple" -# Source-Hash: fca7919fb0c0669c +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post and rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.postAndRail.lajk2u" -msgstr "Poste y travesaño" +msgid "editor.lib.continuation.walls" +msgstr "Muros" -# Source-Hash: a78b28a428b27e20 +# English: "Cut opening" +# Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve high edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveHighEdge.y6ywf0" -msgstr "Conservar el borde alto" +msgid "editor.lib.hudTitle.cutOpening" +msgstr "Abrir hueco" -# Source-Hash: 325c6f53f6dc7634 +# English: "Divide room" +# Source-Hash: 60e8d235f3084a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve low edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveLowEdge.196m5pk" -msgstr "Conservar el borde bajo" +msgid "editor.lib.hudTitle.divideRoom" +msgstr "Dividir estancia" -# Source-Hash: 95c0b860d22a62cb +# English: "Edit {kind}" +# Source-Hash: aa496ec33d21ab03 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve pitch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preservePitch.1uq973r" -msgstr "Conservar la pendiente" +msgid "editor.lib.hudTitle.editKind" +msgstr "Editar {kind}" -# Source-Hash: d4e11841abf22593 +# English: "Erase" +# Source-Hash: f3ea3efa7102844f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Privacy" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.privacy.1th4hrb" -msgstr "Privacidad" +msgid "editor.lib.hudTitle.erase" +msgstr "Borrar" -# Source-Hash: 1a8c46c7712a83b6 +# English: "Level terrain" +# Source-Hash: 3f907e76526472bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlins" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.purlins.od124c" -msgstr "Correas" +msgid "editor.lib.hudTitle.levelTerrain" +msgstr "Nivelar el terreno" -# Source-Hash: a9650f6f94de9247 +# English: "Lower terrain" +# Source-Hash: 4e911c300d74eb47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pvc" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pvc.1oc4u2w" -msgstr "PVC" +msgid "editor.lib.hudTitle.lowerTerrain" +msgstr "Bajar el terreno" -# Source-Hash: 47587b825e9a696b +# English: "Mezzanine" +# Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pyramid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pyramid.17ucsqv" -msgstr "Pirámide" +msgid "editor.lib.hudTitle.mezzanine" +msgstr "Altillo" -# Source-Hash: a9cb232546779779 +# English: "Move {kind}" +# Source-Hash: c48c4129c9ed249a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rafters.16tzbby" -msgstr "Cabios" +msgid "editor.lib.hudTitle.moveKind" +msgstr "Mover {kind}" -# Source-Hash: 947eacd2689daced +# English: "Move room" +# Source-Hash: 89f6b7ca8904c656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rail.39zqc9" -msgstr "Travesaño" +msgid "editor.lib.hudTitle.moveRoom" +msgstr "Mover estancia" -# Source-Hash: bd50bd18e682db41 +# English: "Move selection" +# Source-Hash: 9c707069572fb3db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.raised.4kyx9h" -msgstr "Elevado" +msgid "editor.lib.hudTitle.moveSelection" +msgstr "Mover selección" -# Source-Hash: 3b6f225112035fde +# English: "Paint" +# Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rect.tpfvaf" -msgstr "Rect." +msgid "editor.lib.hudTitle.paint" +msgstr "Pintar" -# Source-Hash: 68206f70689b2340 +# English: "Paint a rectangle" +# Source-Hash: ca197314cb70aa56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reducer" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.reducer.18byt67" -msgstr "Reductor" +msgid "editor.lib.hudTitle.paintARectangle" +msgstr "Pintar un rectángulo" -# Source-Hash: 2e0aa7113b021c67 +# English: "Paint a shape" +# Source-Hash: ea4ce729e17254f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.return.1klrjrz" -msgstr "Retorno" +msgid "editor.lib.hudTitle.paintAShape" +msgstr "Pintar una forma" -# Source-Hash: 63b39b1681f74fe4 +# English: "Pick material" +# Source-Hash: 01359502b07284d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return grille" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.returnGrille.r28ncm" -msgstr "Rejilla de retorno" +msgid "editor.lib.hudTitle.pickMaterial" +msgstr "Tomar material" -# Source-Hash: b34d5ee0f218221d +# English: "Raise terrain" +# Source-Hash: 4066e8a3f60b1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.right.8humpx" -msgstr "Derecha" +msgid "editor.lib.hudTitle.raiseTerrain" +msgstr "Elevar el terreno" -# Source-Hash: ad0457800a1f0eb4 +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.round.1vx0wyj" -msgstr "Redondo" +msgid "editor.lib.hudTitle.resize" +msgstr "Cambiar tamaño" -# Source-Hash: fc826da6c99303e6 +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rounded.199nwpm" -msgstr "Redondeado" +msgid "editor.lib.hudTitle.rotate" +msgstr "Girar" -# Source-Hash: dc67a1cdb2482f23 +# English: "Select" +# Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sanitary tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.sanitaryTee.oro3d4" -msgstr "Te sanitaria" +msgid "editor.lib.hudTitle.select" +msgstr "Seleccionar" -# Source-Hash: beb3fec22c287185 +# English: "Smart measure" +# Source-Hash: ec1f1d68af469867 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scalloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scalloped.1eltba0" -msgstr "Festoneado" +msgid "editor.lib.hudTitle.smartMeasure" +msgstr "Medida inteligente" -# Source-Hash: e16f85e4bebfefb1 +# English: "Smooth terrain" +# Source-Hash: 5ba5f22bd1a69e09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scoop" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scoop.qy5zdv" -msgstr "Cuchara" +msgid "editor.lib.hudTitle.smoothTerrain" +msgstr "Suavizar el terreno" -# Source-Hash: f9a95d8407fafda4 +# English: "Split {kind}" +# Source-Hash: cbb4687261739397 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segmented panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.segmentedPanel.5r3hwl" -msgstr "Panel segmentado" +msgid "editor.lib.hudTitle.splitKind" +msgstr "Dividir {kind}" -# Source-Hash: c8d0df27faa3feaa +# English: "Terrace" +# Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.shed.6t994v" -msgstr "Un agua" +msgid "editor.lib.hudTitle.terrace" +msgstr "Terraza" -# Source-Hash: 7fbc26cd497cfd12 +# English: "{title} ({exterior} exterior, {interior} interior)" +# Source-Hash: 322452fdf0850c8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheet metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/tool-shortcuts.ts #, fuzzy -msgid "editor.inspector.option.sheetMetal.l164lv" -msgstr "Chapa" +msgid "editor.lib.toolShortcuts.wallsTooltip" +msgstr "{title} ({exterior} exteriores, {interior} interiores)" -# Source-Hash: 26e2c261223cbb46 +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingle.pfmynh" -msgstr "Teja asfáltica" +msgid "editor.lib.wallDrawVariant.polygonRoom" +msgstr "Estancia poligonal" -# Source-Hash: e9046e9823b400a0 +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingled.1nm9inf" -msgstr "Con tejas" +msgid "editor.lib.wallDrawVariant.rectangleRoom" +msgstr "Estancia rectangular" -# Source-Hash: a05a390ae205fdd8 +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Simple" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.simple.12mfu73" -msgstr "Sencillo" +msgid "editor.lib.wallDrawVariant.walls" +msgstr "Muros" -# Source-Hash: 62ab2eb3c775da95 +# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" +# Source-Hash: c2626e1fbf89b3c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/registry-action-contributions.tsx #, fuzzy -msgid "editor.inspector.option.single.1uzxurt" -msgstr "Simple" +msgid "editor.mechanismAction" +msgstr "{action, select, close {Cerrar} open {Abrir} stop {Detener} other {Reproducir}}" -# Source-Hash: aee3b2bd78c87d0b +# English: "Curve" +# Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleLeft.1atdh02" -msgstr "Simple izquierda" +msgid "editor.nodeActionMenu.curve" +msgstr "Curva" -# Source-Hash: cc43ffbef4ac7ae0 +# English: "Cut out" +# Source-Hash: 5af05fb6661e4d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleRight.1axbu67" -msgstr "Simple derecha" +msgid "editor.nodeActionMenu.cutOut" +msgstr "Recortar" -# Source-Hash: 7672d2764445c110 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slant box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slantBox.1ta99vy" -msgstr "Caja inclinada" +msgid "editor.nodeActionMenu.delete" +msgstr "Eliminar" -# Source-Hash: 405e07a8cc2c88e1 +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slat.1p9njbz" -msgstr "Lama" +msgid "editor.nodeActionMenu.duplicate" +msgstr "Duplicar" -# Source-Hash: 18cbc73c5e1590aa +# English: "Edit mesh" +# Source-Hash: 94b212bd79c05141 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sliding.cd0jox" -msgstr "Corredera" +msgid "editor.nodeActionMenu.editMesh" +msgstr "Editar malla" -# Source-Hash: e6d82e606b8c9301 +# English: "Find in catalog" +# Source-Hash: ce90cf37224f73cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slope.1bn2qw" -msgstr "Pendiente" +msgid "editor.nodeActionMenu.findInCatalog" +msgstr "Buscar en el catálogo" -# Source-Hash: f6998707123fe72d +# English: "Group selection" +# Source-Hash: d5e550c345e7161c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sloped.lo4sh0" -msgstr "Inclinado" +msgid "editor.nodeActionMenu.groupSelection" +msgstr "Agrupar selección" -# Source-Hash: ef3e765e692faf4c +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solid.o4uy6s" -msgstr "Sólido" +msgid "editor.nodeActionMenu.move" +msgstr "Mover" -# Source-Hash: 8008b6f17089beaf +# English: "Rotate left" +# Source-Hash: 473a9d4463761107 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solidPanel.2yaw62" -msgstr "Panel opaco" +msgid "editor.nodeActionMenu.rotateLeft" +msgstr "Girar a la izquierda" -# Source-Hash: f9a4a870cb3d3c41 +# English: "Rotate right" +# Source-Hash: b1bf4e65c8d8c8ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.spiral.91hcl4" -msgstr "Helicoidal" +msgid "editor.nodeActionMenu.rotateRight" +msgstr "Girar a la derecha" -# Source-Hash: beef1cf79388b77b +# English: "Ungroup selection" +# Source-Hash: 6f4dbbc4a3f58fca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Splash" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.splash.1uke4cm" -msgstr "Salpicadero" +msgid "editor.nodeActionMenu.ungroupSelection" +msgstr "Desagrupar selección" -# Source-Hash: 324677a79ab15e22 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spread" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.spread.1g1zysu" -msgstr "Separación" +msgid "editor.nodeDisplay.ceiling" +msgstr "Techo" -# Source-Hash: 39287346366fc206 +# English: "Column" +# Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Square" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.square.1xl1lk6" -msgstr "Cuadrado" +msgid "editor.nodeDisplay.column" +msgstr "Pilar" -# Source-Hash: 53d7084bc41c1ad6 +# English: "Door" +# Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stair.1xf86e" -msgstr "Escalera" +msgid "editor.nodeDisplay.door" +msgstr "Puerta" -# Source-Hash: d998c212030bc7c2 +# English: "Elevator" +# Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.standard.vuco" -msgstr "Estándar" +msgid "editor.nodeDisplay.elevator" +msgstr "Ascensor" -# Source-Hash: 0a26a1e61c825660 +# English: "Fence" +# Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.start.30xvgf" -msgstr "Inicio" +msgid "editor.nodeDisplay.fence" +msgstr "Valla" -# Source-Hash: ed3989f23a2d185e +# English: "Guide image" +# Source-Hash: 4c3ff8cdc6feda97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.step.jtn9kf" -msgstr "Escalonado" +msgid "editor.nodeDisplay.guide" +msgstr "Imagen guía" -# Source-Hash: 2c6b1cf17a973d67 +# English: "Item" +# Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stepped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stepped.naxzh0" -msgstr "Escalonado" +msgid "editor.nodeDisplay.item" +msgstr "Elemento" -# Source-Hash: 4ee35f740730ae87 +# English: "Roof" +# Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.straight.lxqbfr" -msgstr "Recto" +msgid "editor.nodeDisplay.roof" +msgstr "Cubierta" -# Source-Hash: b9b45ac5ba1daeee +# English: "Roof segment" +# Source-Hash: a130b4eec084c79a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.strap.14q0ps9" -msgstr "Fleje" +msgid "editor.nodeDisplay.roofSegment" +msgstr "Segmento de cubierta" -# Source-Hash: b45f382a67d6c0eb +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supply.1oy2zwu" -msgstr "Impulsión" +msgid "editor.nodeDisplay.room" +msgstr "Estancia" -# Source-Hash: 00e7a2b3b2aa36ac +# English: "3D Scan" +# Source-Hash: b22b2f20cc7cd416 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply register" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supplyRegister.1lzdbbn" -msgstr "Rejilla de impulsión" +msgid "editor.nodeDisplay.scan" +msgstr "Escaneo 3D" -# Source-Hash: f86f1f22230596b7 +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tapered" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tapered.3y5blo" -msgstr "Ahusado" +msgid "editor.nodeDisplay.selection" +msgstr "Selección" -# Source-Hash: 5fed23c2217b83fb +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.targetSpacing.14uibc9" -msgstr "Separación objetivo" +msgid "editor.nodeDisplay.slab" +msgstr "Forjado" -# Source-Hash: f3ceed7f867f090f +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tee.7d6tjp" -msgstr "Te" +msgid "editor.nodeDisplay.stair" +msgstr "Escalera" -# Source-Hash: 787005b26aec58a3 +# English: "Stair segment" +# Source-Hash: 52de428c16bb81e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilted" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tilted.6n0u5j" -msgstr "Inclinado" +msgid "editor.nodeDisplay.stairSegment" +msgstr "Segmento de escalera" -# Source-Hash: 27644f0497fd9849 +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.top.2nypcs" -msgstr "Superior" +msgid "editor.nodeDisplay.wall" +msgstr "Muro" -# Source-Hash: e6657000554dfe53 +# English: "Window" +# Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.transition.1rziz9e" -msgstr "Transición" +msgid "editor.nodeDisplay.window" +msgstr "Ventana" -# Source-Hash: 04bc0909a133a7e9 +# English: "Zone" +# Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vent" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.vent.xda5j2" -msgstr "Ventilación" +msgid "editor.nodeDisplay.zone" +msgstr "Zona" -# Source-Hash: 7eee9aff0dd520b6 +# English: "At the high side of the ground, to scale" +# Source-Hash: 59310bd629d358d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk on" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.walkOn.1n5sg9l" -msgstr "Pisar sobre" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe" +msgstr "En el lado alto del terreno, a escala" -# Source-Hash: 2210e5ee50b74dc9 +# English: "At the high side of the ground, to scale; the dashed line is the grade" +# Source-Hash: 1d006e84523a03c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wall.blj585" -msgstr "Muro" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe2" +msgstr "En el lado alto del terreno, a escala; la línea discontinua es la rasante" -# Source-Hash: f3578f7177ad0054 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall abutment" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallAbutment.lza941" -msgstr "Encuentro con el muro" +msgid "editor.panels.buildingFoundationSection.auto" +msgstr "Auto" -# Source-Hash: 8dcf182f91ba3245 +# English: "Basement slab" +# Source-Hash: ca97295ab420e54a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall ledger" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallLedger.5m8ana" -msgstr "Larguero de muro" +msgid "editor.panels.buildingFoundationSection.basementSlab" +msgstr "Forjado del sótano" -# Source-Hash: 0adda30be5f17975 +# English: "Basement wall" +# Source-Hash: 8691c5ac35304434 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallShelf.yiozfl" -msgstr "Estante de pared" +msgid "editor.panels.buildingFoundationSection.basementWall" +msgstr "Muro de sótano" -# Source-Hash: 29d8590334492601 +# English: "Poured concrete: 8 in for most basements (IRC Table R404.1.2(1))." +# Source-Hash: fecad6f58f5f6e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.waste.3xrp79" -msgstr "Desagüe" +msgid "editor.panels.buildingFoundationSection.basementWallHint" +msgstr "Hormigón in situ: 8 in para la mayoría de los sótanos (tabla R404.1.2(1) del IRC)." -# Source-Hash: 00249876bb37079f +# English: "Basement wall (its own)" +# Source-Hash: 1f0c089c4a7cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wye" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wye.14vupc8" -msgstr "Y" +msgid "editor.panels.buildingFoundationSection.basementWallItsOwn" +msgstr "Muro de sótano (propio)" -# Source-Hash: 179b025e25f9a091 +# English: "Basement wall {thickness}" +# Source-Hash: c672e131662d2766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.collapse" -msgstr "Contraer" +msgid "editor.panels.buildingFoundationSection.basementWallThickness" +msgstr "Muro de sótano {thickness}" -# Source-Hash: dbeae4c43f49327f +# English: "Basement wall top over grade" +# Source-Hash: 29a11cb98f792822 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.ceiling" -msgstr "Techo ({area})" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGrade" +msgstr "Muro de sótano sobre rasante" -# Source-Hash: d29165c9d4bfc3cb +# English: "Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement." +# Source-Hash: 4f96c4f9effefccf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.collection" -msgstr "Colección" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGradeHint" +msgstr "Se mide sobre la rasante más alta a lo largo de la casa: el durmiente y el entramado del suelo se apoyan en el muro, al menos a 8 in del terreno (IRC R317.1). Súbelo más para un semisótano." -# Source-Hash: 8f536b859be662a0 +# English: "Carpet" +# Source-Hash: e653669adc447902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.flight" -msgstr "Tramo ({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.carpet" +msgstr "Moqueta" -# Source-Hash: 46dcbf4548374073 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.landing" -msgstr "Descansillo ({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.ceiling" +msgstr "Techo" -# Source-Hash: 3ea44453fcd08a6e +# English: "Ceramic tile" +# Source-Hash: 299c16d7ad11f5e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Node" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.node" -msgstr "Nodo" +msgid "editor.panels.buildingFoundationSection.ceramicTile" +msgstr "Baldosa cerámica" -# Source-Hash: 93f3a04e15b6fb49 +# English: "Close the outside walls into a loop to draw the foundation under them." +# Source-Hash: 317b5b94671d871a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roof" -msgstr "Cubierta ({count, plural, one {# segmento} many {# segmentos} other {# segmentos}})" +msgid "editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA" +msgstr "Cierra el perímetro de los muros exteriores para dibujar la cimentación bajo ellos." -# Source-Hash: a730fab01c172704 +# English: "Crawl space under joists" +# Source-Hash: 3e736505db2a19c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roofSegment" -msgstr "{roofType, select, hip {A cuatro aguas} gable {A dos aguas} shed {A un agua} gambrel {Holandesa} dutch {Mixta} mansard {Mansarda} flat {Plana} conical {Cónica} barrel {Abovedada} other {Cubierta}} ({width}×{depth}m)" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoists" +msgstr "Cámara sanitaria bajo viguetas" -# Source-Hash: 54efcb0756c2f2c1 +# English: "Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it." +# Source-Hash: 61f898724893d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.room" -msgstr "Estancia ({area})" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint" +msgstr "Se mide desde la cara inferior de las viguetas, que apoyan en el durmiente (IRC R317.1: 18 in hasta el terreno). La cámara sanitaria se excava hasta esa profundidad." -# Source-Hash: e38a4b9986a9d21a +# English: "Custom" +# Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.slab" -msgstr "Forjado ({area})" +msgid "editor.panels.buildingFoundationSection.custom" +msgstr "Personalizado" -# Source-Hash: 57398365d4b9066f +# English: "Delete the basement and it stands on its raised floor again." +# Source-Hash: 8629f9746eac5bfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.staircase" -msgstr "Escalera ({count, plural, one {# segmento} many {# segmentos} other {# segmentos}})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor" +msgstr "Si eliminas el sótano, volverá a apoyarse en su suelo elevado." -# Source-Hash: a17f8fc29aae896e +# English: "Delete the basement and it stands on its slab again." +# Source-Hash: f99636a38ec14785 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.zone" -msgstr "Zona ({area})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementSlab" +msgstr "Si eliminas el sótano, volverá a apoyarse en su forjado." -# Source-Hash: 5cabcabb8bc14809 +# English: "{depth} deep" +# Source-Hash: 09f12189bbbf8cbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.expand" -msgstr "Expandir" +msgid "editor.panels.buildingFoundationSection.depthDeep" +msgstr "{depth} de prof." -# Source-Hash: 6731e9dd94e8b268 +# English: "{depth} down" +# Source-Hash: 164f8aea5d46f232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{levelName} options" -#: packages/editor/src/components/ui/floating-level-selector.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.levelOptions" -msgstr "Opciones de {levelName}" +msgid "editor.panels.buildingFoundationSection.depthDown" +msgstr "{depth} abajo" -# Source-Hash: d6952b4e54d246dd +# English: "{depth} I-joist" +# Source-Hash: a6010b7488316ac1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.rename" -msgstr "Cambiar nombre" +msgid "editor.panels.buildingFoundationSection.depthIJoist" +msgstr "Vigueta en I de {depth}" -# Source-Hash: 21eafa019ad62fea +# English: "Draw the foundation" +# Source-Hash: 4db57e6b9b2a22f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{role, select, room {Room colour} other {Zone colour}}" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx -#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.spaceColour" -msgstr "{role, select, room {Color de la estancia} other {Color de la zona}}" +msgid "editor.panels.buildingFoundationSection.drawTheFoundation" +msgstr "Dibujar la cimentación" -# Source-Hash: f5de042416a2c04f +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit colour" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.unitColour" -msgstr "Color de la unidad" +msgid "editor.panels.buildingFoundationSection.drywall" +msgstr "Placa de yeso" -# Source-Hash: c0774c185060fc13 +# English: "Drywall ceiling" +# Source-Hash: cea6df381e71f25f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.cancel" -msgstr "Cancelar" +msgid "editor.panels.buildingFoundationSection.drywallCeiling" +msgstr "Techo de placa de yeso" -# Source-Hash: 07c11e7e4d4681d6 +# English: "Edge under grade" +# Source-Hash: 027b063ba3959c44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from {levelName}." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" -msgstr "Elige qué copiar de {levelName}." +msgid "editor.panels.buildingFoundationSection.edgeUnderGrade" +msgstr "Borde bajo rasante" -# Source-Hash: 9500d4378a319532 +# English: "IRC R403.1.4: at least 12 in, or the frost line." +# Source-Hash: a114cbbf9fff17fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from this level." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" -msgstr "Elige qué copiar de esta planta." +msgid "editor.panels.buildingFoundationSection.edgeUnderGradeHint" +msgstr "IRC R403.1.4: al menos 12 in, o la profundidad de helada." -# Source-Hash: 34dc96fe77a31e51 +# English: "Edge width" +# Source-Hash: ea863dda5621adfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicate" -msgstr "Duplicar" +msgid "editor.panels.buildingFoundationSection.edgeWidth" +msgstr "Ancho del borde" -# Source-Hash: a56e3eb43e60b45c +# English: "Edge {width} wide" +# Source-Hash: 542f041267191a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate Level" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicateLevel" -msgstr "Duplicar planta" +msgid "editor.panels.buildingFoundationSection.edgeWidthWide" +msgstr "Borde de {width} de ancho" -# Source-Hash: e4c6ab1ea473a082 +# English: "Engineered wood" +# Source-Hash: 5c0c1a6c73eb46e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Everything" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.everything" -msgstr "Todo" +msgid "editor.panels.buildingFoundationSection.engineeredWood" +msgstr "Madera técnica" -# Source-Hash: ac3f119e560388ab +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, finishes, and placed items, without guide references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" -msgstr "Estructura, acabados y elementos colocados, sin referencias de guía." +msgid "editor.panels.buildingFoundationSection.finish" +msgstr "Acabado" -# Source-Hash: acbdf968391d5273 +# English: "Finish floor over grade" +# Source-Hash: b97e6e3652b2ea63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + furniture" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFurniture" -msgstr "Estructura + mobiliario" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGrade" +msgstr "Suelo acabado sobre rasante" -# Source-Hash: ce682864a140fe75 +# English: "Measured over the highest grade under the outside walls. The foundation grows down the slope from there." +# Source-Hash: 326641cd3bd5968a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + materials" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterials" -msgstr "Estructura + materiales" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGradeHint" +msgstr "Se mide sobre la rasante más alta bajo los muros exteriores. A partir de ahí, la cimentación crece pendiente abajo." -# Source-Hash: 5ee44d53847c5ea2 +# English: "Floor framing" +# Source-Hash: bcc3c6e3240bd6da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, materials, furniture, and references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" -msgstr "Estructura, materiales, mobiliario y referencias." +msgid "editor.panels.buildingFoundationSection.floorFraming" +msgstr "Entramado del suelo" -# Source-Hash: 95aa1b43dbdb3f02 +# English: "Floor framing and sill {depth}" +# Source-Hash: 2d7eaba6b2a7787b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure only" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureOnly" -msgstr "Solo estructura" +msgid "editor.panels.buildingFoundationSection.floorFramingAndSill" +msgstr "Entramado del suelo y durmiente {depth}" -# Source-Hash: 2c53b46c9ca9fe2f +# English: "Floor top down to the stem: flooring, subfloor, joists, sill." +# Source-Hash: 60bbbc62b12ee1b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure with the current material and finish assignments." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" -msgstr "Estructura con las asignaciones actuales de materiales y acabados." +msgid "editor.panels.buildingFoundationSection.floorFramingHint" +msgstr "De la cara superior del suelo hasta el murete: pavimento, tablero base, viguetas, durmiente." -# Source-Hash: 3350e9008eaba15d +# English: "Floor framing {depth}, sill {sill} included" +# Source-Hash: b3f975c3aa4056d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" -msgstr "Muros, forjados, cubiertas, escaleras, ventanas y puertas, sin acabados." +msgid "editor.panels.buildingFoundationSection.floorFramingSillIncluded" +msgstr "Entramado del suelo {depth}, durmiente de {sill} incluido" -# Source-Hash: 395fd0821b203517 +# English: "Flooring" +# Source-Hash: 3e6a58401ab2fc64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add or remove objects from the selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" -msgstr "Añadir o quitar objetos de la selección" +msgid "editor.panels.buildingFoundationSection.flooring" +msgstr "Pavimento" -# Source-Hash: 8428cb0af9c6d6a0 +# English: "Footing thickness" +# Source-Hash: e7ca47bb438592ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Adjust one side’s corner radius" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" -msgstr "Ajustar el radio de esquina de un lado" +msgid "editor.panels.buildingFoundationSection.footingThickness" +msgstr "Espesor de la zapata" -# Source-Hash: d63b45c195713993 +# English: "Footing under grade" +# Source-Hash: 3b481ad3c192b6a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear the selection (or click outside)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" -msgstr "Borrar la selección (o hacer clic fuera)" +msgid "editor.panels.buildingFoundationSection.footingUnderGrade" +msgstr "Zapata bajo rasante" -# Source-Hash: 40b1afbeadbd6213 +# English: "IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line." +# Source-Hash: d1ed081aa7773257 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a handle dot to show move arrows" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickAHandleDotToShow" -msgstr "Haz clic en un punto de control para ver las flechas de movimiento" +msgid "editor.panels.buildingFoundationSection.footingUnderGradeHint" +msgstr "IRC R403.1.4: al menos 12 in bajo el terreno contiguo, tanto fuera como en la cámara sanitaria excavada; más abajo, hasta la profundidad de helada." -# Source-Hash: 0b82a35c71549580 +# English: "Footing width" +# Source-Hash: a4bdf4be3447baec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click or drag the selection to move it as one" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" -msgstr "Haz clic o arrastra la selección para moverla en bloque" +msgid "editor.panels.buildingFoundationSection.footingWidth" +msgstr "Ancho de la zapata" -# Source-Hash: 73a761f393f69cb9 +# English: "Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys." +# Source-Hash: bfb55823e6b7d61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click the handle dot to show move + rotate handles" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" -msgstr "Haz clic en el punto de control para ver los controles de mover y girar" +msgid "editor.panels.buildingFoundationSection.footingWidthHint" +msgstr "Tabla R403.1(1), terreno de 1500 psf: 12, 15, 23 in para 1, 2, 3 plantas." -# Source-Hash: c0045cef191fee69 +# English: "Footing {width} × {thickness}" +# Source-Hash: dd973bd2c5207dd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detach the joint while dragging an arrow" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" -msgstr "Separar la unión al arrastrar una flecha" +msgid "editor.panels.buildingFoundationSection.footingWidthThickness" +msgstr "Zapata {width} × {thickness}" -# Source-Hash: a019c8948c104a9e +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag left or right to rotate selected object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" -msgstr "Arrastra a izquierda o derecha para girar el objeto seleccionado" +msgid "editor.panels.buildingFoundationSection.foundation" +msgstr "Cimentación" -# Source-Hash: 7db0364deb12f06e +# English: "Grade beam depth" +# Source-Hash: c4460990c10fbb11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag selected movable object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragSelectedMovableObject" -msgstr "Arrastrar el objeto móvil seleccionado" +msgid "editor.panels.buildingFoundationSection.gradeBeamDepth" +msgstr "Canto de viga de cimentación" -# Source-Hash: 0f258dd389afdc73 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection (session only)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" -msgstr "Agrupar la selección (solo esta sesión)" +msgid "editor.panels.buildingFoundationSection.gradeBeamOnPiles" +msgstr "Viga de cimentación sobre pilotes" -# Source-Hash: 85330d62893a45c3 +# English: "Grade beam width" +# Source-Hash: 2208ea6b7967afc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to rotate freely" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.holdToRotateFreely" -msgstr "Mantén pulsado para girar libremente" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidth" +msgstr "Ancho de viga de cimentación" -# Source-Hash: 8a665ecaeffa9308 +# English: "Grade beam {width} × {depth}" +# Source-Hash: e70be1cfffd2567a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotate45" -msgstr "Girar ±45°" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidthDepth" +msgstr "Viga de cimentación {width} × {depth}" -# Source-Hash: 6099ee99f8c38700 +# English: "Half the edge’s depth under the slab, 6 to 12 in" +# Source-Hash: 08e07ba612a2515a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotateTheSelection45" -msgstr "Girar la selección ±45°" +msgid "editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder" +msgstr "La mitad de la profundidad del borde bajo el forjado, de 6 a 12 in" -# Source-Hash: 0f6b418b18ff96f3 +# English: "Hardwood" +# Source-Hash: af3644ad05550f7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotating freely (no angle step)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotatingFreely" -msgstr "Giro libre (sin pasos de ángulo)" +msgid "editor.panels.buildingFoundationSection.hardwood" +msgstr "Madera maciza" -# Source-Hash: 7ed174d6b03563b1 +# English: "{height} crawl" +# Source-Hash: d0ab8d2735f2e1a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch the rotation axis (Y → X → Z)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" -msgstr "Cambiar el eje de giro (Y → X → Z)" +msgid "editor.panels.buildingFoundationSection.heightCrawl" +msgstr "{height} cámara" -# Source-Hash: d6f2c5cea683de62 +# English: "{height} FF" +# Source-Hash: 70a0363496dbb627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup session selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.ungroupSessionSelection" -msgstr "Desagrupar la selección de la sesión" +msgid "editor.panels.buildingFoundationSection.heightFinishFloor" +msgstr "{height} suelo acab." -# Source-Hash: 384842ae1e1c6825 +# English: "{height} top" +# Source-Hash: 28fbe28f080c2e49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuous" -msgstr "Continuo" +msgid "editor.panels.buildingFoundationSection.heightTop" +msgstr "{height} cara sup." -# Source-Hash: ebf0eb7af4636a16 +# English: "I-joists" +# Source-Hash: 61dc716260daf09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousCanopy" -msgstr "Marquesina continua" +msgid "editor.panels.buildingFoundationSection.iJoists" +msgstr "Viguetas en I" -# Source-Hash: 1d8e87cd807e8d40 +# English: "Its layers make {depth} with the sill; the framing follows them once a layer is set." +# Source-Hash: dcaf89ca7f4ba36a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous run" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousRun" -msgstr "Línea continua" +msgid "editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill" +msgstr "Sus capas suman {depth} con el durmiente; el entramado se ajusta a ellas en cuanto se define una capa." -# Source-Hash: 8990683667567c6b +# English: "Its walls are poured under the house’s outside walls." +# Source-Hash: 47f3b8abf6c71c0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.curvedFence" -msgstr "Valla curva" +msgid "editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe" +msgstr "Sus muros se hormigonan bajo los muros exteriores de la casa." -# Source-Hash: edf1b9a2ad3ab910 +# English: "Its walls are the basement’s own, on their footing." +# Source-Hash: 2ad8c11c90166f43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Freehand fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.freehandFence" -msgstr "Valla a mano alzada" +msgid "editor.panels.buildingFoundationSection.itsWallsAreTheBasementS" +msgstr "Sus muros son los del propio sótano, sobre su zapata." -# Source-Hash: 2cdb7c1c7cd7c4fd +# English: "Joists" +# Source-Hash: c7aa596536ceeecb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place multiple" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeMultiple" -msgstr "Colocar varios" +msgid "editor.panels.buildingFoundationSection.joists" +msgstr "Viguetas" -# Source-Hash: 90bf945df6f9c979 +# English: "Kind" +# Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place once" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeOnce" -msgstr "Colocar una vez" +msgid "editor.panels.buildingFoundationSection.kind" +msgstr "Tipo" -# Source-Hash: 8ec6152e139b0e3f +# English: "{label} (auto)" +# Source-Hash: bc4b8b385cd50d18 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.polygonRoom" -msgstr "Estancia poligonal" +msgid "editor.panels.buildingFoundationSection.labelAuto" +msgstr "{label} (auto)" -# Source-Hash: 83a45603ac7f41e8 +# English: "Laminate" +# Source-Hash: 826e189e08abac28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.rectangleRoom" -msgstr "Estancia rectangular" +msgid "editor.panels.buildingFoundationSection.laminate" +msgstr "Laminado" -# Source-Hash: 7fd85d0408141157 +# English: "Least depth under grade" +# Source-Hash: 6ace3330ae71b24d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single cabinet" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCabinet" -msgstr "Mueble individual" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGrade" +msgstr "Profundidad mínima bajo rasante" -# Source-Hash: dd1bdbc26165baa5 +# English: "The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further." +# Source-Hash: 5e390c119e4d6da5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCanopy" -msgstr "Marquesina individual" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGradeHint" +msgstr "La viga llega al menos a esta profundidad bajo rasante (IRC R403.1.4: 12 in, o la profundidad de helada); una viga de más canto llega más abajo." -# Source-Hash: 5fb1a69316be983e +# English: "Least step run" +# Source-Hash: 2e918ebc24201841 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleFence" -msgstr "Valla simple" +msgid "editor.panels.buildingFoundationSection.leastStepRun" +msgstr "Longitud mínima del escalón" -# Source-Hash: aac9a732d2159ccc +# English: "IRC R403.1.5: the top of a footing is level; it steps where the ground falls." +# Source-Hash: 95c2ee3d275f29cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.walls" -msgstr "Muros" +msgid "editor.panels.buildingFoundationSection.leastStepRunHint" +msgstr "IRC R403.1.5: la cara superior de una zapata es horizontal; se escalona donde el terreno baja." -# Source-Hash: 4e4f1a24e4a9d61e +# English: "Lumber" +# Source-Hash: 73dcfae611575500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.cutOpening" -msgstr "Abrir hueco" +msgid "editor.panels.buildingFoundationSection.lumber" +msgstr "Madera aserrada" -# Source-Hash: 60e8d235f3084a9e +# English: "Name the material" +# Source-Hash: 3053c459862812ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divide room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.divideRoom" -msgstr "Dividir estancia" +msgid "editor.panels.buildingFoundationSection.nameTheMaterial" +msgstr "Pon nombre al material" -# Source-Hash: aa496ec33d21ab03 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.editKind" -msgstr "Editar {kind}" +msgid "editor.panels.buildingFoundationSection.off" +msgstr "Desactivado" -# Source-Hash: f3ea3efa7102844f +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.erase" -msgstr "Borrar" +msgid "editor.panels.buildingFoundationSection.osb" +msgstr "OSB" -# Source-Hash: 3f907e76526472bf +# English: "OSB subfloor" +# Source-Hash: 097c64d90d9fb848 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.levelTerrain" -msgstr "Nivelar el terreno" +msgid "editor.panels.buildingFoundationSection.osbSubfloor" +msgstr "Tablero base de OSB" -# Source-Hash: 4e911c300d74eb47 +# English: "Panel" +# Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lower terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.lowerTerrain" -msgstr "Bajar el terreno" +msgid "editor.panels.buildingFoundationSection.panel" +msgstr "Panel" -# Source-Hash: 194a147d4618a994 +# English: "Pile diameter" +# Source-Hash: 1372cffe6283ee90 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.mezzanine" -msgstr "Altillo" +msgid "editor.panels.buildingFoundationSection.pileDiameter" +msgstr "Diámetro del pilote" -# Source-Hash: c48c4129c9ed249a +# English: "Piles are sized by the engineer." +# Source-Hash: 58bdc4ad2c360017 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveKind" -msgstr "Mover {kind}" +msgid "editor.panels.buildingFoundationSection.pileDiameterHint" +msgstr "Los pilotes los dimensiona el ingeniero." -# Source-Hash: 89f6b7ca8904c656 +# English: "Pile {diameter} @ {spacing}, {length} deep" +# Source-Hash: 2e583e4622318f58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveRoom" -msgstr "Mover estancia" +msgid "editor.panels.buildingFoundationSection.pileDiameterSpacingDeep" +msgstr "Pilote {diameter} @ {spacing}, {length} de prof." -# Source-Hash: 9c707069572fb3db +# English: "Pile length" +# Source-Hash: 5e99bac8b7ba3af9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move selection" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveSelection" -msgstr "Mover selección" +msgid "editor.panels.buildingFoundationSection.pileLength" +msgstr "Longitud del pilote" -# Source-Hash: cf2e870fce0f2f51 +# English: "Pile spacing" +# Source-Hash: 7dfd658f6006b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paint" -msgstr "Pintar" +msgid "editor.panels.buildingFoundationSection.pileSpacing" +msgstr "Separación de pilotes" -# Source-Hash: ca197314cb70aa56 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a rectangle" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintARectangle" -msgstr "Pintar un rectángulo" +msgid "editor.panels.buildingFoundationSection.plaster" +msgstr "Enlucido" -# Source-Hash: ea4ce729e17254f8 +# English: "Plaster ceiling" +# Source-Hash: a1f03abd876ee26f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a shape" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintAShape" -msgstr "Pintar una forma" +msgid "editor.panels.buildingFoundationSection.plasterCeiling" +msgstr "Techo enlucido" -# Source-Hash: 01359502b07284d4 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick material" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.pickMaterial" -msgstr "Tomar material" +msgid "editor.panels.buildingFoundationSection.plywood" +msgstr "Contrachapado" -# Source-Hash: 4066e8a3f60b1711 +# English: "Plywood subfloor" +# Source-Hash: ded7bab2a8fe5793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.raiseTerrain" -msgstr "Elevar el terreno" +msgid "editor.panels.buildingFoundationSection.plywoodSubfloor" +msgstr "Tablero base de contrachapado" -# Source-Hash: f85bdac0a1d16603 +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.resize" -msgstr "Cambiar tamaño" +msgid "editor.panels.buildingFoundationSection.preset" +msgstr "Preajuste" -# Source-Hash: 306d409f8a1cf480 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.rotate" -msgstr "Girar" +msgid "editor.panels.buildingFoundationSection.raisedFloor" +msgstr "Suelo elevado" -# Source-Hash: a4395168488bb476 +# English: "Foundation section: basement, its wall's top {top} over grade, its floor {floor} under it" +# Source-Hash: f17c359b6460adc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.select" -msgstr "Seleccionar" +msgid "editor.panels.buildingFoundationSection.sectionBasement" +msgstr "Sección de la cimentación: sótano, con la cara superior del muro {top} sobre rasante y el suelo {floor} bajo rasante" -# Source-Hash: ec1f1d68af469867 +# English: "Foundation section: raised floor on a stem wall, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: d292ee971f5b54a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart measure" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smartMeasure" -msgstr "Medida inteligente" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloor" +msgstr "Sección de la cimentación: suelo elevado sobre murete de cimentación, suelo acabado {floor} sobre rasante, cámara sanitaria de {crawl} bajo las viguetas" -# Source-Hash: 5ba5f22bd1a69e09 +# English: "Foundation section: raised floor on a stem wall on piles, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: 786e74b8b53f3dab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smooth terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smoothTerrain" -msgstr "Suavizar el terreno" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles" +msgstr "Sección de la cimentación: suelo elevado sobre murete de cimentación apoyado en pilotes, suelo acabado {floor} sobre rasante, cámara sanitaria de {crawl} bajo las viguetas" -# Source-Hash: cbb4687261739397 +# English: "Foundation section: slab on grade, finish floor {floor} over grade" +# Source-Hash: bfb5167b838b50c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.splitKind" -msgstr "Dividir {kind}" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGrade" +msgstr "Sección de la cimentación: forjado sobre el terreno, suelo acabado {floor} sobre rasante" -# Source-Hash: 96281876ce53ba08 +# English: "Foundation section: slab on grade on piles, finish floor {floor} over grade" +# Source-Hash: 5a7458213c388c8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.terrace" -msgstr "Terraza" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles" +msgstr "Sección de la cimentación: forjado sobre el terreno apoyado en pilotes, suelo acabado {floor} sobre rasante" -# Source-Hash: 8ec6152e139b0e3f +# English: "Sill" +# Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.polygonRoom" -msgstr "Estancia poligonal" +msgid "editor.panels.buildingFoundationSection.sill" +msgstr "Durmiente" -# Source-Hash: 83a45603ac7f41e8 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.rectangleRoom" -msgstr "Estancia rectangular" +msgid "editor.panels.buildingFoundationSection.size" +msgstr "Tamaño" -# Source-Hash: aac9a732d2159ccc +# English: "{size} joists" +# Source-Hash: 2406f7fa46d251de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.walls" -msgstr "Muros" +msgid "editor.panels.buildingFoundationSection.sizeJoists" +msgstr "Viguetas {size}" +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# Source-Hash: c2626e1fbf89b3c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" -#: packages/editor/src/components/editor/registry-action-contributions.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.mechanismAction" -msgstr "{action, select, close {Cerrar} open {Abrir} stop {Detener} other {Reproducir}}" +msgid "editor.panels.buildingFoundationSection.slab" +msgstr "Forjado" +# English: "IRC R506.1 asks 3½ in at least." +# Source-Hash: 97296dd13f940848 # Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.curve" -msgstr "Curva" +msgid "editor.panels.buildingFoundationSection.slabHint" +msgstr "El IRC R506.1 exige al menos 3½ in." +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# Source-Hash: 5af05fb6661e4d6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut out" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.cutOut" -msgstr "Recortar" +msgid "editor.panels.buildingFoundationSection.slabOnGrade" +msgstr "Forjado sobre el terreno" +# English: "Slab {thickness}" +# Source-Hash: 714bbb73fd4e68b9 # Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.delete" -msgstr "Eliminar" +msgid "editor.panels.buildingFoundationSection.slabThickness" +msgstr "Forjado {thickness}" +# English: "Slab top over grade" +# Source-Hash: 4e6765b0d6a9bbe5 # Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.duplicate" -msgstr "Duplicar" +msgid "editor.panels.buildingFoundationSection.slabTopOverGrade" +msgstr "Forjado sobre rasante" +# English: "The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in)." +# Source-Hash: 7a6001a64711e175 # Review: required -# Source-Hash: 94b212bd79c05141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit mesh" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.editMesh" -msgstr "Editar malla" +msgid "editor.panels.buildingFoundationSection.slabTopOverGradeHint" +msgstr "El terreno alrededor del forjado se nivela a esta altura en todo el perímetro: se rellena donde baja y se desmonta donde sube (IRC R317.1: 8 in)." +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c # Review: required -# Source-Hash: ce90cf37224f73cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in catalog" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.findInCatalog" -msgstr "Buscar en el catálogo" +msgid "editor.panels.buildingFoundationSection.spacing" +msgstr "Separación" +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 # Review: required -# Source-Hash: d5e550c345e7161c -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.groupSelection" -msgstr "Agrupar selección" +msgid "editor.panels.buildingFoundationSection.spacingOnCentre" +msgstr "{spacing} a ejes" +# English: "Stem top over grade" +# Source-Hash: 097812a3bcb6d256 # Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.move" -msgstr "Mover" +msgid "editor.panels.buildingFoundationSection.stemTopOverGrade" +msgstr "Murete sobre rasante" +# English: "IRC R317.1 keeps wood framing 8 in off the ground." +# Source-Hash: eabab1d380a0b860 # Review: required -# Source-Hash: 473a9d4463761107 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate left" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateLeft" -msgstr "Girar a la izquierda" +msgid "editor.panels.buildingFoundationSection.stemTopOverGradeHint" +msgstr "El IRC R317.1 exige separar el entramado de madera 8 in del terreno." +# English: "Stem wall" +# Source-Hash: ce9da5096b1feb02 # Review: required -# Source-Hash: b1bf4e65c8d8c8ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate right" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateRight" -msgstr "Girar a la derecha" +msgid "editor.panels.buildingFoundationSection.stemWall" +msgstr "Murete de cimentación" +# English: "Stem wall and footing" +# Source-Hash: b0a49e954f1e5a99 # Review: required -# Source-Hash: 6f4dbbc4a3f58fca -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.ungroupSelection" -msgstr "Desagrupar selección" +msgid "editor.panels.buildingFoundationSection.stemWallAndFooting" +msgstr "Murete de cimentación y zapata" -# Source-Hash: 4275c05172abd7fb +# English: "Table R404.1.2: 6 in for one storey, 8 in for more." +# Source-Hash: 4ea0e2b0609f6137 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.ceiling" -msgstr "Techo" +msgid "editor.panels.buildingFoundationSection.stemWallHint" +msgstr "Tabla R404.1.2: 6 in para una planta, 8 in para más." -# Source-Hash: 3530ac1f4046cd22 +# English: "Stem wall {thickness}" +# Source-Hash: 965e29b358d63d58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.column" -msgstr "Pilar" +msgid "editor.panels.buildingFoundationSection.stemWallThickness" +msgstr "Murete {thickness}" -# Source-Hash: 787e10f5db16904e +# English: "Step down the slope" +# Source-Hash: 2a131b783b8e35ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.door" -msgstr "Puerta" +msgid "editor.panels.buildingFoundationSection.stepDownTheSlope" +msgstr "Escalonar en la pendiente" -# Source-Hash: ab5cbab7be160a1c +# English: "Step height" +# Source-Hash: 2d15d12859e8f169 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.elevator" -msgstr "Ascensor" +msgid "editor.panels.buildingFoundationSection.stepHeight" +msgstr "Altura del escalón" -# Source-Hash: 02042fde61b5aa11 +# English: "Subfloor sheathing" +# Source-Hash: 438e69e35a342a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.fence" -msgstr "Valla" +msgid "editor.panels.buildingFoundationSection.subfloorSheathing" +msgstr "Tablero base" -# Source-Hash: 4c3ff8cdc6feda97 +# English: "Tallest stem before stepping" +# Source-Hash: 1170f2affa3a3f4b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.guide" -msgstr "Imagen guía" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeStepping" +msgstr "Murete máx. antes de escalonar" -# Source-Hash: 2743a0e9d2743807 +# English: "A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it." +# Source-Hash: 9485def39ac25b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.item" -msgstr "Elemento" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint" +msgstr "Un lado cuyo murete sobresaldría más del terreno se escalona pendiente abajo: su murete baja hasta la altura fijada sobre rasante, con un muro enano encima." -# Source-Hash: f5f730969df63d1d +# English: "Taper" +# Source-Hash: 6063ae2877faa35d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roof" -msgstr "Cubierta" +msgid "editor.panels.buildingFoundationSection.taper" +msgstr "Chaflán" -# Source-Hash: a130b4eec084c79a +# English: "Taper {taper} at 45°" +# Source-Hash: 4c008bc3ff2c404c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roofSegment" -msgstr "Segmento de cubierta" +msgid "editor.panels.buildingFoundationSection.taperAt45" +msgstr "Chaflán {taper} a 45°" -# Source-Hash: 612dcd5bf06a79c4 +# English: "The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider." +# Source-Hash: c2d8477f6b02f2a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.room" -msgstr "Estancia" +msgid "editor.panels.buildingFoundationSection.taperHint" +msgstr "La cara interior del borde sube a plomo desde su base y luego se inclina hacia dentro a 45° hasta el forjado. En Auto, el chaflán ocupa la mitad de la profundidad del borde bajo el forjado, de 6 a 12 in, para que un borde más profundo crezca a plomo y no se ensanche." -# Source-Hash: b22b2f20cc7cd416 +# English: "The house stands on a basement: its walls are the foundation walls, so only their footing is drawn." +# Source-Hash: 8d646e2d19be2e57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D Scan" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.scan" -msgstr "Escaneo 3D" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnABasement" +msgstr "La casa se apoya en un sótano: sus muros son los muros de cimentación, así que solo se dibuja su zapata." -# Source-Hash: 170b2049ef0f9131 +# English: "The house stands on its basement, cut into the ground: the ground round it is left as it lies." +# Source-Hash: 82f250b688c1079e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selection" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.selection" -msgstr "Selección" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement" +msgstr "La casa se apoya en su sótano, excavado en el terreno: el terreno de alrededor se deja tal como está." -# Source-Hash: 8775bc8dc9593964 +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.slab" -msgstr "Forjado" +msgid "editor.panels.buildingFoundationSection.thickness" +msgstr "Espesor" -# Source-Hash: 53d7084bc41c1ad6 +# English: "{title} material" +# Source-Hash: 4cf16973c8dd006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stair" -msgstr "Escalera" +msgid "editor.panels.buildingFoundationSection.titleMaterial" +msgstr "Material de {title}" -# Source-Hash: 52de428c16bb81e6 +# English: "Turned-down edge" +# Source-Hash: 4707e4ae34281fd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stairSegment" -msgstr "Segmento de escalera" +msgid "editor.panels.buildingFoundationSection.turnedDownEdge" +msgstr "Borde engrosado" -# Source-Hash: 2210e5ee50b74dc9 +# English: "Vinyl plank" +# Source-Hash: 385c128fc1026239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.wall" -msgstr "Muro" +msgid "editor.panels.buildingFoundationSection.vinylPlank" +msgstr "Lama vinílica" -# Source-Hash: aa3c35ed33e181e5 +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.window" -msgstr "Ventana" +msgid "editor.panels.buildingFoundationSection.woodPlank" +msgstr "Tabla de madera" -# Source-Hash: a107b4d76d6972e3 +# English: "Wood plank ceiling" +# Source-Hash: 931afc430cda5de8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.zone" -msgstr "Zona" +msgid "editor.panels.buildingFoundationSection.woodPlankCeiling" +msgstr "Techo de tablas de madera" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.cancel" msgstr "Cancelar" +# English: "Collection name…" # Source-Hash: 6dbabf32ff8c6f37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection name…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collectionName" msgstr "Nombre de la colección…" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collections" msgstr "Colecciones" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.delete" msgstr "Eliminar" +# English: "Delete \"{name}\"?" # Source-Hash: c53d71b05b8163b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete \"{name}\"?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.deleteName" msgstr "¿Eliminar «{name}»?" +# English: "New" # Source-Hash: e9f7cceb39860c56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.new" msgstr "Nuevo" +# English: "No collections yet. Create one to group elements together." # Source-Hash: 649bc1b1799f2311 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No collections yet. Create one to group elements together." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" msgstr "Aún no hay colecciones. Crea una para agrupar elementos." +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.rename" msgstr "Cambiar nombre" -# Source-Hash: 2000cf124b4e1139 +# English: "Along house" +# Source-Hash: 0a605a51555bb514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.alongHouse" +msgstr "Paralelo a la casa" + +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boards" +msgstr "Tablas" + +# English: "{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." +# Source-Hash: 2bc05aaf1fc289b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boardsSummary" +msgstr "Tablas {label} de {thickness} × {width} in, separadas {gap} in, {direction, select, parallel {paralelas a la casa, sobre viguetas perpendiculares a ella} diagonal {a 45° respecto a la casa} other {perpendiculares a la casa, sobre viguetas paralelas a ella, apoyadas en vigas perpendiculares a la casa}}; viguetas a {spacing} in entre ejes." + +# English: "Deck boards" +# Source-Hash: 24c6255bfa1abd8f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.deckBoards" +msgstr "Tablas de la tarima" + +# English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.diagonal" +msgstr "Diagonal" + +# English: "Gap" +# Source-Hash: df708ece3529bcba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.gap" +msgstr "Junta" + +# English: "Out from house" +# Source-Hash: 3660fcdd12159277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.outFromHouse" +msgstr "Perpendicular a la casa" + +# English: "Run" +# Source-Hash: 9ac7e4955593cc34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.run" +msgstr "Sentido" + # English: "A thicker floor grows upward: the rooms below keep their height." +# Source-Hash: 2000cf124b4e1139 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" msgstr "Un suelo más grueso crece hacia arriba: las estancias inferiores mantienen su altura." -# Source-Hash: 0d57958b99b6b352 +# English: "Its layers make {thickness}; the thickness follows them on the first change." +# Source-Hash: 767f8969959d1c48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.buildUpFollows" +msgstr "Sus capas suman {thickness}; el espesor se ajusta a ellas con el primer cambio." + # English: "Edge finish" +# Source-Hash: 0d57958b99b6b352 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.edgeFinish" msgstr "Acabado del borde" -# Source-Hash: 8e458d8b21f26f09 +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floor" +msgstr "Suelo" + +# English: "Floor build-up" +# Source-Hash: 097b19040c031ae4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floorBuildUp" +msgstr "Composición del suelo" + # English: "Floor plate" +# Source-Hash: 8e458d8b21f26f09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlate" msgstr "Forjado" +# English: "Floor plate {thicknessLabel} thick, resting on the walls below" # Source-Hash: 2bb71e5633801afb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate {thicknessLabel} thick, resting on the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn" msgstr "Forjado de {thicknessLabel} de espesor, apoyado en los muros inferiores" -# Source-Hash: 73614b8faa059cd6 +# English: "Floor thickness {thickness}" +# Source-Hash: 68a6aff9bf44f9c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy -msgid "editor.panels.floorFoundationPanel.floorThickness" -msgstr "Espesor del suelo" +msgid "editor.panels.floorFoundationPanel.floorThicknessValue" +msgstr "Espesor del suelo {thickness}" +# English: "Floor top +{height}" # Source-Hash: a40a1fbacd5d2551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top +{height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopAbove" msgstr "Cara superior del suelo +{height}" +# English: "Floor top {height}" # Source-Hash: 5c3849e15933737f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopValue" msgstr "Cara superior del suelo {height}" +# English: "Foundation finish" # Source-Hash: 19802dd369749ea7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationFinish" msgstr "Acabado de la cimentación" +# English: "Foundation height" # Source-Hash: 3a4599be1ad687be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationHeight" msgstr "Altura de cimentación" +# English: "Floor plate" # Source-Hash: 8e458d8b21f26f09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kicker" msgstr "Forjado" +# English: "Floor plate · {level}" # Source-Hash: 66d52bfbb94f7bb3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerLevel" msgstr "Forjado · {level}" +# English: "Floor plate · {level} · rests on {below}" # Source-Hash: 0189e5a1987b4ec0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level} · rests on {below}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerRestsOn" msgstr "Forjado · {level} · apoya en {below}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" # Source-Hash: 648ef10535b0af73 # Review: required -# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.lowerTogether" msgstr "{count, plural, =2 {Bajar ambos} one {Bajar los {number}} many {Bajar los {number}} other {Bajar los {number}}}" +# English: "Not to scale" # Source-Hash: c2fcc43bb7f1c5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not to scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.notToScale" msgstr "Sin escala" +# English: "On the ground" # Source-Hash: e9440fa9f770b7a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.onTheGround" msgstr "Sobre el terreno" +# English: "Raised on a foundation" # Source-Hash: 7c3692a361075ecf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised on a foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raisedOnAFoundation" msgstr "Elevado sobre una cimentación" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" # Source-Hash: 769c3ca1ca0cd4f0 # Review: required -# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raiseTogether" msgstr "{count, plural, =2 {Subir ambos} one {Subir los {number}} many {Subir los {number}} other {Subir los {number}}}" +# English: "Floor plate · {thickness}" # Source-Hash: 719ae422da204ca7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {thickness}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorPlate" msgstr "Forjado · {thickness}" +# English: "Floor top" # Source-Hash: 069d64adffa05db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorTop" msgstr "Cara superior del suelo" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundation" msgstr "Cimentación" +# English: "Foundation · {foundation}" # Source-Hash: 7759a59b8685c885 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation · {foundation}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundationKind" msgstr "Cimentación · {foundation}" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.ground" msgstr "Terreno" -# Source-Hash: 51e274b2c90e2cf3 +# English: "{layer} · {size}" +# Source-Hash: 1a0f891c672247b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.section.layer" +msgstr "{layer} · {size}" + # English: "Slab · {thickness}" +# Source-Hash: 51e274b2c90e2cf3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.slab" msgstr "Forjado · {thickness}" +# English: "Walls below" # Source-Hash: 790a0b5c6dbf0e9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.wallsBelow" msgstr "Muros inferiores" -# Source-Hash: 90e96ae5f3d3f69c +# English: "Floor plate {thickness} thick: {layers}; resting on the walls below" +# Source-Hash: 7c1a423934a360f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayers" +msgstr "Forjado de {thickness} de espesor: {layers}; apoyado en los muros inferiores" + +# English: "{layer} {size}" +# Source-Hash: 0c5b48217a0cf663 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayerSize" +msgstr "{layer} {size}" + # English: "Slab {thickness} thick on the ground, floor top {top} above the ground" +# Source-Hash: 90e96ae5f3d3f69c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionOnGround" msgstr "Forjado de {thickness} de espesor sobre el terreno, cara superior a {top} sobre el terreno" +# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" # Source-Hash: 71ed2295d9932ed0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionRaised" msgstr "Forjado de {thickness} de espesor sobre una cimentación {foundation}, cara superior a {top} sobre el terreno" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slab" msgstr "Forjado" +# English: "Slab thickness" # Source-Hash: ca51d643691c29ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slabThickness" msgstr "Espesor del forjado" +# English: "The floor's outside edge, all around the footprint." # Source-Hash: 678c39468593275b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The floor's outside edge, all around the footprint." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll" msgstr "El borde exterior del suelo, en todo el perímetro de la huella." +# English: "The foundation stands on the ground; the slab sits on it." # Source-Hash: 6638ed5caa43bdb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The foundation stands on the ground; the slab sits on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround" msgstr "La cimentación se apoya en el terreno; el forjado se apoya en ella." +# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." # Source-Hash: 5321416b442f23b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation" msgstr "El forjado se apoya en la cimentación (o el terreno): uno más grueso sube la cara superior del suelo y todo lo que hay encima." +# English: "Under the floor" # Source-Hash: 044ccae8bec574e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Under the floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.underTheFloor" msgstr "Bajo el suelo" +# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." # Source-Hash: 6aca44df3e582a81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt" msgstr "Trampilla en la entreplanta: solo corta el suelo de la entreplanta." +# English: "Also cut the ceiling below" # Source-Hash: 2ce79639d42da658 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the ceiling below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow" msgstr "Cortar también el techo inferior" +# English: "Also cut the floor above" # Source-Hash: e360814f64792300 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the floor above" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheFloorAbove" msgstr "Cortar también el suelo superior" +# English: "{area} · Drag its corners to change the shape." # Source-Hash: c25cddf8b831c2b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Drag its corners to change the shape." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaDragCorners" msgstr "{area} · Arrastra sus esquinas para cambiar la forma." +# English: "{area} · Shape follows its owner." # Source-Hash: 32b168cf010a5daa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Shape follows its owner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaOwned" msgstr "{area} · La forma sigue a su propietario." -# Source-Hash: b26de372b4b0eaac +# English: "{area} · Drag its sides or corners to resize it; it moves with the stair." +# Source-Hash: 71b5c7184cbd2e5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-opening-panel.tsx +#, fuzzy +msgid "editor.panels.floorOpeningPanel.areaStairResize" +msgstr "{area} · Arrastra sus lados o esquinas para cambiar su tamaño; se mueve con la escalera." + # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.delete" msgstr "Eliminar" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.done" msgstr "Hecho" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kicker" msgstr "Abertura" +# English: "Opening · {level}" # Source-Hash: 079ad92dac7947cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerLevel" msgstr "Hueco · {level}" +# English: "Opening · in {room}" # Source-Hash: baf2c2c209637e5f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoom" msgstr "Hueco · en {room}" +# English: "Opening · in {room} · {level}" # Source-Hash: 1712c164335ecd86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room} · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoomLevel" msgstr "Hueco · en {room} · {level}" +# English: "On {level}, as one void with this opening." # Source-Hash: 88e673123b9dbfe1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On {level}, as one void with this opening." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.oneVoidWith" msgstr "En {level}, como un único vacío con este hueco." +# English: "Owned by Elevator" # Source-Hash: c38959fc8fd3e59d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByElevator" msgstr "Pertenece al ascensor" +# English: "Owned by Staircase" # Source-Hash: 01494805cf60d78d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByStaircase" msgstr "Pertenece a la escalera" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.room" msgstr "Estancia" +# English: "There is no floor above this storey." # Source-Hash: 1405b914a324b153 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no floor above this storey." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis" msgstr "No hay suelo encima de esta planta." +# English: "There is no storey below." # Source-Hash: 776170dcae88e969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no storey below." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoStoreyBelow" msgstr "No hay planta debajo." +# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" # Source-Hash: 0fdd1877a0f2d777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.title" msgstr "{source, select, stair {Hueco de escalera} elevator {Hueco de ascensor} ceiling {Hueco de techo} other {Hueco de suelo}}" +# English: "A raised floor on a stem wall of its own." +# Source-Hash: 1839fff3aba02d40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aRaisedFloorOnAStem" +msgstr "Un suelo elevado sobre un murete de cimentación propio." + +# English: "A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round." +# Source-Hash: 52eead98a170fd6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aSlabOfItsOwn" +msgstr "Un forjado propio (un porche, un patio, una losa de garaje), con el terreno 8 in por debajo de su cara superior en todo el perímetro." + +# English: "Click its corners; click the first again, or press Enter, to close it." +# Source-Hash: 96853854f04b1514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.clickItsCornersClickTheFirst" +msgstr "Haz clic en sus esquinas; vuelve a hacer clic en la primera, o pulsa Intro, para cerrarlo." + +# English: "Close the outside walls into a loop first, or draw one" +# Source-Hash: 4655068154039021 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA" +msgstr "Cierra primero el perímetro de los muros exteriores, o dibuja un contorno" + +# English: "Delete this outline" +# Source-Hash: 8b8a23b925c30027 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.deleteThisOutline" +msgstr "Eliminar este contorno" + +# English: "Draw" +# Source-Hash: b5c56dd8c68de309 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.draw" +msgstr "Dibujar" + +# English: "Edit it myself" +# Source-Hash: e9c386def6e2dadf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.editItMyself" +msgstr "Editarlo a mano" + +# English: "Follow the walls" +# Source-Hash: 86c8ecac2691c892 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.followTheWalls" +msgstr "Seguir los muros" + +# English: "{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it." +# Source-Hash: ee78cc2aa3f77ca1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.footingsDrawn" +msgstr "{footings, plural, one {# zapata dibujada: haz clic en ella en el plano para dimensionarla o moverla.} many {# zapatas dibujadas: haz clic en una en el plano para dimensionarla o moverla.} other {# zapatas dibujadas: haz clic en una en el plano para dimensionarla o moverla.}}" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.foundation" +msgstr "Cimentación" + +# English: "House foundation" +# Source-Hash: 5ec26bb3cea817e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.houseFoundation" +msgstr "Cimentación de la casa" + +# English: "How far its top sits under the house’s floor: a porch slab steps down from the door" +# Source-Hash: 0140833a30e08d70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.howFarItsTopSitsUnder" +msgstr "Cuánto queda su cara superior por debajo del suelo de la casa: un forjado de porche baja un escalón desde la puerta" + +# English: "Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be." +# Source-Hash: 3b101db8d8839574 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd" +msgstr "Propio: arrastra sus esquinas y lados en el plano o en 3D. Mover los muros no lo modifica." + +# English: "Outline" +# Source-Hash: 3e6d52c82419517d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outline" +msgstr "Contorno" + +# English: "Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts." +# Source-Hash: 78d4f6a686211e44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors" +msgstr "Contornos de forjados y suelos elevados (porches, patios, una losa de garaje) y zapatas bajo muros de carga interiores y postes." + +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.raisedFloor" +msgstr "Suelo elevado" + +# English: "Re-trace from walls" +# Source-Hash: e6eaea778b041b10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.reTraceFromWalls" +msgstr "Volver a trazar desde los muros" + +# English: "Set the outline back to the outside faces of the walls, keeping it your own" +# Source-Hash: 5181a67d6940cccb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.setTheOutlineBackToThe" +msgstr "Devolver el contorno a las caras exteriores de los muros, manteniéndolo como propio" + +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.slab" +msgstr "Forjado" + +# English: "The house’s" +# Source-Hash: 523f999e5b2d497e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own. +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHouses" +msgstr "De la casa" + +# English: "The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline." +# Source-Hash: 3e4d5b90c1ab8eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHousesOwnFoundation" +msgstr "La propia cimentación de la casa, hormigonada según los ajustes de la casa que aparecen debajo, en lugar del contorno de los muros." + +# English: "This outline" +# Source-Hash: 51be023b5f1983f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.thisOutline" +msgstr "Este contorno" + +# English: "Top under the floor" +# Source-Hash: 85eca682e0102861 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.topUnderTheFloor" +msgstr "Cara superior bajo el suelo" + +# English: "Traced from the outside walls: it moves when they do." +# Source-Hash: 1f7c9880395d456a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt" +msgstr "Trazado desde los muros exteriores: se mueve con ellos." + +# English: "One of them already wraps another corner. Unwrap it first." # Source-Hash: fa35c7653e1f649e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them already wraps another corner. Unwrap it first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.alreadyJoined" msgstr "Una de ellas ya rodea otra esquina. Sepárala primero." +# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" # Source-Hash: dd87eb1983876459 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.between" msgstr "{kind, select, item {Hay un elemento entre ellas.} door {Hay una puerta entre ellas.} window {Hay una ventana entre ellas.} shelf {Hay un estante entre ellas.} other {Hay algo entre ellas.}}" +# English: "These windows cannot be merged." # Source-Hash: 58aea0b9789ae280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "These windows cannot be merged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cannotMerge" msgstr "Estas ventanas no se pueden unir." +# English: "Their walls meet at too sharp or too flat an angle for a corner window." # Source-Hash: 15170cfd9d666c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls meet at too sharp or too flat an angle for a corner window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cornerAngle" msgstr "Sus muros se encuentran en un ángulo demasiado agudo o demasiado plano para una ventana de esquina." +# English: "The window selected first is taller than the other wall. Select the other one first." # Source-Hash: f2818a1e20ebd6ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window selected first is taller than the other wall. Select the other one first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.heightExceedsWall" msgstr "La ventana seleccionada primero es más alta que el otro muro. Selecciona primero la otra." +# English: "They are too far apart. Windows merge with at most {reach} between them." # Source-Hash: 7c97fb4af929d4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "They are too far apart. Windows merge with at most {reach} between them." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAdjacent" msgstr "Están demasiado separadas. Las ventanas se unen con un máximo de {reach} entre ellas." +# English: "One of them is too far from the corner. A window joins one within {reach} of it." # Source-Hash: 57a281bdc4229b6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them is too far from the corner. A window joins one within {reach} of it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAtCorner" msgstr "Una de ellas está demasiado lejos de la esquina. Una ventana se une a otra a menos de {reach}." +# English: "A roof or dormer window cannot join another window." # Source-Hash: e7d41ce7879ec075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A roof or dormer window cannot join another window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notOnWall" msgstr "Una ventana de cubierta o de buhardilla no puede unirse a otra ventana." +# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" # Source-Hash: eaad567d5e4257dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.overlap" msgstr "{kind, select, item {Deslizarlas hasta la esquina se solaparía con un elemento.} door {Deslizarlas hasta la esquina se solaparía con una puerta.} window {Deslizarlas hasta la esquina se solaparía con una ventana.} shelf {Deslizarlas hasta la esquina se solaparía con un estante.} other {Deslizarlas hasta la esquina se solaparía con otro elemento.}}" +# English: "Their walls do not meet at a corner." # Source-Hash: ecb092821ed5b5e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls do not meet at a corner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.wallsNotMeeting" msgstr "Sus muros no se encuentran en una esquina." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.mixed" msgstr "Mixto" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-panel-sheet.tsx #, fuzzy msgid "editor.panels.mobilePanelSheet.close" msgstr "Cerrar" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.delete" msgstr "Eliminar" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.duplicate" msgstr "Duplicar" +# English: "Edit properties" # Source-Hash: 7eb955d866c2668b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit properties" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editProperties" msgstr "Editar propiedades" +# English: "Edit {resolvedLabel}" # Source-Hash: 89f25aa13e8192bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {resolvedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editResolvedlabel" msgstr "Editar {resolvedLabel}" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.move" msgstr "Mover" +# English: "Currently {currentLabel}" # Source-Hash: 72be05e1796526c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {currentLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.currentlyCurrentlabel" msgstr "Actualmente {currentLabel}" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.customHeight" msgstr "Altura personalizada" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.followsLevel" msgstr "Sigue la planta" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.height" msgstr "Altura" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.top" msgstr "Superior" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx #, fuzzy msgid "editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects" msgstr "{label} (solo en esta sesión). Un clic simple vuelve a seleccionar todos los miembros. No se guarda con el proyecto." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.delete" msgstr "Eliminar" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.duplicate" msgstr "Duplicar" +# English: "Group" # Source-Hash: b0e7e3b47dfb5a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.group" msgstr "Agrupar" +# English: "Group (Ctrl/Cmd+G)" # Source-Hash: ca0a95fe0b08b8b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group (Ctrl/Cmd+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.groupCtrlCmdG" msgstr "Agrupar (Ctrl/Cmd+G)" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects" msgstr "{label} (solo en esta sesión). Un clic simple vuelve a seleccionar todos los miembros. No se guarda con el proyecto." +# English: "{length} selected" # Source-Hash: fc98ef29c2842341 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length} selected" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.lengthSelected" msgstr "{length} seleccionados" +# English: "Merge windows" # Source-Hash: 323aaf1ebd04ada7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindows" msgstr "Unir ventanas" +# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" # Source-Hash: 7855d03a6140af77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow" msgstr "Unir en una ventana de esquina: ambas toman la altura, el alféizar y el tipo de la primera seleccionada" +# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" # Source-Hash: b79da96502d2e1c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth" msgstr "Unir en una sola ventana que abarca ambas: conserva el tipo y el estilo de la primera seleccionada" +# English: "Select building" # Source-Hash: f409934681a046e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectBuilding" msgstr "Seleccionar edificio" +# English: "Select the whole building to move all its levels" # Source-Hash: c62a73873bd0164d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select the whole building to move all its levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove" msgstr "Selecciona todo el edificio para mover todas sus plantas" +# English: "Ungroup" # Source-Hash: 7bd5f29203a28efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroup" msgstr "Desagrupar" +# English: "Ungroup (Ctrl/Cmd+Shift+G)" # Source-Hash: aec9c3983a440d4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup (Ctrl/Cmd+Shift+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG" msgstr "Desagrupar (Ctrl/Cmd+Mayús+G)" +# English: "Unwrap the corner" # Source-Hash: 420385f27c627cee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCorner" msgstr "Separar la esquina" +# English: "Unwrap the corner: both windows stay, each framed at the corner" # Source-Hash: 7906c9f4baebc70b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner: both windows stay, each framed at the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay" msgstr "Separar la esquina: ambas ventanas se mantienen, cada una con su marco en la esquina" -# Source-Hash: 170b2049ef0f9131 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/panel-manager.tsx +#, fuzzy +msgid "editor.panels.panelManager.foundation" +msgstr "Cimentación" + # English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-manager.tsx #, fuzzy msgid "editor.panels.panelManager.selection" msgstr "Selección" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.back" msgstr "Volver" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.close" msgstr "Cerrar" +# English: "Close {title}" # Source-Hash: 2dff25ca30c80559 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.closeTitle" msgstr "Cerrar {title}" +# English: "Collapse panel" # Source-Hash: 4f15c3e0fe6178b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collapsePanel" msgstr "Contraer panel" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collections" msgstr "Colecciones" +# English: "Expand panel" # Source-Hash: 64916cc9ea80f43f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.expandPanel" msgstr "Expandir panel" +# English: "Manage collections…" # Source-Hash: 8a9a25621f200922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manage collections…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.manageCollections" msgstr "Gestionar colecciones…" +# English: "Open {title}" # Source-Hash: 2ff02ed91f54235c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.openTitle" msgstr "Abrir {title}" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.reset" msgstr "Restablecer" +# English: "“{title}” hit an error and was unloaded for this session." # Source-Hash: 04a212d6317f6787 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "“{title}” hit an error and was unloaded for this session." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.titleHitAnErrorAndWas" msgstr "«{title}» ha tenido un error y se ha descargado durante esta sesión." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.parametricFieldControl.mixed" msgstr "Mixto" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.actions" msgstr "Acciones" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.delete" msgstr "Eliminar" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.deleteWithContents" msgstr "Eliminar con el contenido" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.move" msgstr "Mover" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.cancel" msgstr "Cancelar" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.capture" msgstr "Captura" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.center" msgstr "Centro" +# English: "Clear Scale" # Source-Hash: a5b965dfb0bf2cd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clearScale" msgstr "Borrar escala" +# English: "Click both ends of a known distance on the plan, then type its real length." # Source-Hash: 4edc6e45b0c19447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click both ends of a known distance on the plan, then type its real length." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clickBothEndsOfAKnown" msgstr "Haz clic en los dos extremos de una distancia conocida en el plano y escribe su longitud real." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.delete" msgstr "Eliminar" +# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." # Source-Hash: 59f7d81e8d36a157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.drawALineOverAKnown" msgstr "Dibuja una línea sobre una cota conocida del plano y escribe su longitud real para escalar la imagen con exactitud." +# English: "Edit Scale" # Source-Hash: 42f6d0e4f065f114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.editScale" msgstr "Editar escala" +# English: "Guide Image" # Source-Hash: e156c84cbc90e0a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.guideImage" msgstr "Imagen guía" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hide" msgstr "Ocultar" +# English: "Hide Scale" # Source-Hash: dd282ce13b9085ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hideScale" msgstr "Ocultar escala" +# English: "Image" # Source-Hash: fc6eaa65315f601d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.image" msgstr "Imagen" +# English: "Lock" # Source-Hash: 241399e11d93bd7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.lock" msgstr "Bloquear" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.move" msgstr "Mover" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.opacity" msgstr "Opacidad" +# English: "Overlay image unavailable. Replace the image to restore it." # Source-Hash: 6ca213a6b299f19f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overlay image unavailable. Replace the image to restore it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage" msgstr "Imagen superpuesta no disponible. Sustituye la imagen para restaurarla." +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.position" msgstr "Posición" +# English: "Quick Actions" # Source-Hash: 8b003432b36230c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Quick Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.quickActions" msgstr "Acciones rápidas" +# English: "Reference Scale" # Source-Hash: dcd4728e9ca90181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.referenceScale" msgstr "Escala de referencia" +# English: "Replace" # Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replace" msgstr "Reemplazar" +# English: "Replacing..." # Source-Hash: 08b45f9ba66eab3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replacing..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replacing" msgstr "Reemplazando..." +# English: "Reset Image Scale" # Source-Hash: 2714b5a81cedb625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Image Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetImageScale" msgstr "Restablecer escala de imagen" +# English: "Reset Rotation" # Source-Hash: a05d3e429ae259ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetRotation" msgstr "Restablecer rotación" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rot" msgstr "rot" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rotation" msgstr "Rotación" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scale" msgstr "escala" +# English: "Scaled · {scale}" # Source-Hash: e7d0e7d41a775fd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaled" msgstr "Escalado · {scale}" +# English: "Scaled (hidden) · {scale}" # Source-Hash: cd92eebfb30e708f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled (hidden) · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaledHidden" msgstr "Escalado (oculto) · {scale}" +# English: "Scale & Opacity" # Source-Hash: bfd20999de7f216a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale & Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaleOpacity" msgstr "Escala y opacidad" +# English: "Set Scale" # Source-Hash: 8d8777505acdf78e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.setScale" msgstr "Definir escala" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.show" msgstr "Mostrar" +# English: "Show Scale" # Source-Hash: 78be2337f0e81973 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.showScale" msgstr "Mostrar escala" +# English: "Uncalibrated" # Source-Hash: ad85a193442c8f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncalibrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.uncalibrated" msgstr "Sin calibrar" +# English: "Unlock" # Source-Hash: 6c500cc253ab3fd4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unlock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.unlock" msgstr "Desbloquear" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.xyz" msgstr "XYZ" +# English: "+ Add mezzanine" # Source-Hash: e01549105012f8d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.addMezzanine" msgstr "+ Añadir entreplanta" +# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." # Source-Hash: 167b0fe7fdbcd51d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness" msgstr "Forjado dibujado: define aquí su espesor y acabados. No tiene altura de huella ni cimentación." +# English: "At floor level" # Source-Hash: 6cb0969b65fb60bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At floor level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.atFloorLevel" msgstr "A nivel del suelo" -# Source-Hash: e8563d5ae1ee7390 +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beam" +msgstr "Viga" + +# English: "Beam size" +# Source-Hash: 4b03c226d5f88a42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beamSize" +msgstr "Tamaño de la viga" + # English: "Built on" +# Source-Hash: e8563d5ae1ee7390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtOn" msgstr "Construido sobre" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtWith" msgstr "Construido con" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceiling" msgstr "Techo" +# English: "At {height}" # Source-Hash: 9bf177f59f971033 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceilingAt" msgstr "A {height}" +# English: "Close with walls" # Source-Hash: 48768dc0fc7b86a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close with walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.closeWithWalls" msgstr "Cerrar con muros" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" # Source-Hash: 75c84b4f21aefde4 # Review: required -# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloor" msgstr "{ambiguous, select, yes {El suelo son varios forjados dibujados, sobre todo {name}} other {El suelo es un forjado dibujado: {name}}}{shared, plural, =0 {} one { (compartido con {sharedCount} estancia)} many { (compartido con {sharedCount} estancias)} other { (compartido con {sharedCount} estancias)}}" +# English: "This floor is a drawn slab: select it to edit or delete it." # Source-Hash: 97e0fab8ffda6d6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor is a drawn slab: select it to edit or delete it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloorSwitchHint" msgstr "Este suelo es un forjado dibujado: selecciónalo para editarlo o eliminarlo." +# English: "Drawn slab" # Source-Hash: ad5426620188242c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlab" msgstr "Forjado dibujado" +# English: "Drawn slab at {height}" # Source-Hash: 0da0d104eaefbbad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab at {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlabAt" msgstr "Forjado dibujado a {height}" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.editShape" msgstr "Editar forma" +# English: "Flat, at wall height" # Source-Hash: fadf264be55063e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat, at wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.flatAtWallHeight" msgstr "Plano, a la altura del muro" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floor" msgstr "Planta" +# English: "{distance} down" # Source-Hash: 996e8191c069e779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorDown" msgstr "{distance} más bajo" +# English: "Floor thickness" # Source-Hash: 73614b8faa059cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorThickness" msgstr "Espesor del suelo" +# English: "{distance} up" # Source-Hash: 79cdde853c3e8f61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorUp" msgstr "{distance} más alto" -# Source-Hash: 77bc12b7aba0eb60 +# English: "{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: b6b724f9f4db6d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutoHint" +msgstr "{posts, plural, one {{postCount} poste} many {{postCount} postes} other {{postCount} postes}} y {beams, plural, one {{beamCount} viga} many {{beamCount} vigas} other {{beamCount} vigas}}, con un poste en cada esquina exterior, vinculados a {room, select, deck {la tarima} patio {el patio} other {el porche}}." + +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutomatic" +msgstr "Automático" + +# English: "Manual" +# Source-Hash: 387082cd5f118bed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManual" +msgstr "Manual" + +# English: "Kept as you edited them. Automatic rebuilds them from these settings." +# Source-Hash: 287400b1b4b46abd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManualHint" +msgstr "Se mantienen tal como los editaste. Automático los regenera a partir de estos ajustes." + +# English: "Off" +# Source-Hash: 50accd740fa2e736 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOff" +msgstr "Desactivado" + +# English: "None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: 4dfa7082b7e30280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOffHint" +msgstr "Ninguno. Automático coloca un poste en cada esquina exterior y una viga sobre ellos para sostener una cubierta sobre {room, select, deck {la tarima} patio {el patio} other {el porche}}." + # English: "Hand-drawn ceiling" +# Source-Hash: 77bc12b7aba0eb60 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.handDrawnCeiling" msgstr "Techo dibujado a mano" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.height" msgstr "Altura" +# English: "Include floor" # Source-Hash: 4d35023efe090b82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeFloor" msgstr "Incluir suelo" +# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" # Source-Hash: 6398ac77644eb5f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includePart" msgstr "{part, select, floor {Incluir suelo} walls {Incluir muros} other {Incluir techo}}" +# English: "Include walls" # Source-Hash: 3629e64a6e796ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeWalls" msgstr "Incluir muros" -# Source-Hash: f6d07fc7f239278b +# English: "Keep outside dimensions" +# Source-Hash: f6d07fc7f239278b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" +msgstr "Mantener dimensiones exteriores" + +# English: "Lowered {lift}" +# Source-Hash: 544410e3cb0e0b07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.lowered" +msgstr "Bajado {lift}" + +# English: "+ New floor" +# Source-Hash: c2daf78b740e4397 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.newFloor" +msgstr "+ Nuevo suelo" + +# English: "No floor" +# Source-Hash: ba9edc7910a60cea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.noFloor" +msgstr "Sin suelo" + +# English: "Nothing to change." +# Source-Hash: ea2675041df74f40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.nothingToChange" +msgstr "Nada que cambiar." + +# English: "Not included" +# Source-Hash: ebe0d375d7938c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.notIncluded" +msgstr "No incluido" + +# English: "On the foundation" +# Source-Hash: f084b3795e3edfb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheFoundation" +msgstr "Sobre la cimentación" + +# English: "On the ground" +# Source-Hash: e9440fa9f770b7a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheGround" +msgstr "Sobre el terreno" + +# English: "On the walls below" +# Source-Hash: a5deb36c485d91bd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheWallsBelow" +msgstr "Sobre los muros inferiores" + +# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: 2256aec0d5bdc3ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.openSides" +msgstr "{openSides, plural, one {{openSideCount} lado abierto} many {{openSideCount} lados abiertos} other {{openSideCount} lados abiertos}}" + +# English: "Partial · {coverage, number, percent} covered" +# Source-Hash: f704520c5e5b92b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialCovered" +msgstr "Parcial · {coverage, number, percent} cubierto" + +# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: b648bee0231a41cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialWalls" +msgstr "Parcial · {walls, plural, one {{wallCount} muro} many {{wallCount} muros} other {{wallCount} muros}}, {openSides, plural, one {{openSideCount} lado abierto} many {{openSideCount} lados abiertos} other {{openSideCount} lados abiertos}}" + +# English: "Posts and beam" +# Source-Hash: 4b2a51c39b382184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postsAndBeam" +msgstr "Postes y viga" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postSize" +msgstr "Tamaño del poste" + +# English: "Post spacing (max)" +# Source-Hash: cc25439729a97f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep outside dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" -msgstr "Mantener dimensiones exteriores" +msgid "editor.panels.roomConstructionRows.postSpacingMax" +msgstr "Separación de postes (máx.)" -# Source-Hash: 544410e3cb0e0b07 +# English: "Raised {lift}" +# Source-Hash: e503250e8ecb0030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lowered {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.lowered" -msgstr "Bajado {lift}" +msgid "editor.panels.roomConstructionRows.raised" +msgstr "Elevado {lift}" -# Source-Hash: c2daf78b740e4397 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ New floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.newFloor" -msgstr "+ Nuevo suelo" +msgid "editor.panels.roomConstructionRows.raisedFloor" +msgstr "Suelo elevado" -# Source-Hash: ba9edc7910a60cea +# English: "Replace" +# Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.noFloor" -msgstr "Sin suelo" +msgid "editor.panels.roomConstructionRows.replace" +msgstr "Reemplazar" -# Source-Hash: ea2675041df74f40 +# English: "Room type" +# Source-Hash: be1c56bb3ddc63c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nothing to change." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.nothingToChange" -msgstr "Nada que cambiar." +msgid "editor.panels.roomConstructionRows.roomType" +msgstr "Tipo de estancia" -# Source-Hash: ebe0d375d7938c93 +# English: "Deck" +# Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not included" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.notIncluded" -msgstr "No incluido" +msgid "editor.panels.roomConstructionRows.roomTypeDeck" +msgstr "Tarima" -# Source-Hash: e9440fa9f770b7a4 +# English: "Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling." +# Source-Hash: f0028986efecf7d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheGround" -msgstr "Sobre el terreno" +msgid "editor.panels.roomConstructionRows.roomTypeDeckHint" +msgstr "Exterior: tablas de tarima sobre un entramado de madera y postes, 1 in por debajo de la casa; sin techo." -# Source-Hash: a5deb36c485d91bd +# English: "Patio" +# Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheWallsBelow" -msgstr "Sobre los muros inferiores" +msgid "editor.panels.roomConstructionRows.roomTypePatio" +msgstr "Patio" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 2256aec0d5bdc3ea +# English: "Outdoors and open: a concrete slab 1½ in down from the house, no ceiling." +# Source-Hash: 5e4ea6a084f67808 # Review: required -# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.openSides" -msgstr "{openSides, plural, one {{openSideCount} lado abierto} many {{openSideCount} lados abiertos} other {{openSideCount} lados abiertos}}" +msgid "editor.panels.roomConstructionRows.roomTypePatioHint" +msgstr "Exterior y descubierto: una losa de hormigón 1½ in por debajo de la casa, sin techo." -# Source-Hash: f704520c5e5b92b2 +# English: "Porch" +# Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partial · {coverage, number, percent} covered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialCovered" -msgstr "Parcial · {coverage, number, percent} cubierto" +msgid "editor.panels.roomConstructionRows.roomTypePorch" +msgstr "Porche" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: b648bee0231a41cd +# English: "Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s." +# Source-Hash: af2c941e8ef874bf # Review: required -# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialWalls" -msgstr "Parcial · {walls, plural, one {{wallCount} muro} many {{wallCount} muros} other {{wallCount} muros}}, {openSides, plural, one {{openSideCount} lado abierto} many {{openSideCount} lados abiertos} other {{openSideCount} lados abiertos}}" +msgid "editor.panels.roomConstructionRows.roomTypePorchHint" +msgstr "Exterior bajo techo: una losa de hormigón 1½ in por debajo de la casa, con el techo a la altura del de la casa." -# Source-Hash: e503250e8ecb0030 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.raised" -msgstr "Elevado {lift}" +msgid "editor.panels.roomConstructionRows.roomTypeRoom" +msgstr "Estancia" -# Source-Hash: 0cb87327b8076151 +# English: "An ordinary room inside the house." +# Source-Hash: fb0a6cc444058631 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.replace" -msgstr "Reemplazar" +msgid "editor.panels.roomConstructionRows.roomTypeRoomHint" +msgstr "Una estancia normal dentro de la casa." +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.select" msgstr "Seleccionar" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.shape" msgstr "Forma" +# English: "Turning walls off keeps the ones shared with {rooms}." # Source-Hash: aafd84823715d698 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turning walls off keeps the ones shared with {rooms}." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sharedWallsNote" msgstr "Desactivar los muros conserva los compartidos con {rooms}." +# English: "Sits on: {name} · {preset}" # Source-Hash: c98df900d43f77bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sits on: {name} · {preset}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sitsOnPreset" msgstr "Apoya en: {name} · {preset}" -# Source-Hash: a17a79e835fa2064 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.slab" +msgstr "Forjado" + # English: "the next room" +# Source-Hash: a17a79e835fa2064 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.theNextRoom" msgstr "la estancia contigua" +# English: "Use existing" # Source-Hash: ebae699e0cb18ea6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use existing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.useExisting" msgstr "Usar el existente" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" # Source-Hash: 9976021d9162cd5e # Review: required -# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallCount" msgstr "{walls, plural, one {{wallCount} muro} many {{wallCount} muros} other {{wallCount} muros}}" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.walls" msgstr "Muros" +# English: "Walls thicken inward, so the building's outer size stays the same." # Source-Hash: 6c69eb46a0c40d52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls thicken inward, so the building's outer size stays the same." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding" msgstr "Los muros engrosan hacia dentro, así que el tamaño exterior del edificio no cambia." +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.cutOpening" msgstr "Abrir hueco" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.done" msgstr "Hecho" +# English: "Edit floor" # Source-Hash: 2c89abb1778a3f9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.editFloor" msgstr "Editar suelo" +# English: "Elevator opening" # Source-Hash: ca18a6c5398fc8ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.elevatorOpening" msgstr "Hueco de ascensor" +# English: "No openings yet. Cut one for a stair void or a hatch." # Source-Hash: eafea43e795f3708 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No openings yet. Cut one for a stair void or a hatch." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.noOpeningsYetCutOneFor" msgstr "Aún no hay huecos. Corta uno para el hueco de una escalera o una trampilla." +# English: "Opening {number}" # Source-Hash: 60fac94ffa614238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openingNumber" msgstr "Hueco {number}" +# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" # Source-Hash: 7aafbf90d659dcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openings" msgstr "{count, plural, =0 {Sin aberturas} one {{number} abertura} many {{number} aberturas} other {{number} aberturas}}" +# English: "Stair opening" # Source-Hash: c1a1794d31990f5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.stairOpening" msgstr "Hueco de escalera" +# English: "Add stairs" # Source-Hash: f0a46329fef44d94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addStairs" msgstr "Añadir escalera" +# English: "Add to a unit" # Source-Hash: 8a2ab34a61b9d181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to a unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addToAUnit" msgstr "Añadir a una unidad" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.area" msgstr "Área" +# English: "Browse" # Source-Hash: 88f1a727e5528c10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.browse" msgstr "Explorar" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.builtWith" msgstr "Construido con" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.ceiling" msgstr "Techo" +# English: "Delete room" # Source-Hash: a5e4fe4960deced9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.deleteRoom" msgstr "Eliminar estancia" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.editShape" msgstr "Editar forma" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.floor" msgstr "Planta" +# English: "{height} headroom" # Source-Hash: d17c3b63449c052b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{height} headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.headroom" msgstr "Altura libre de {height}" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.height" msgstr "Altura" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.hideCode" msgstr "Ocultar código" +# English: "Room · {level}" # Source-Hash: e93969f5a7cbb140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerLevel" msgstr "Estancia · {level}" +# English: "Mezzanine · in {room}" # Source-Hash: f4ad56b4e0c014b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerMezzanineIn" msgstr "Entreplanta · en {room}" +# English: "Room · not enclosed" # Source-Hash: de37205633316011 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · not enclosed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerNotEnclosed" msgstr "Estancia · no cerrada" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.loading" msgstr "Cargando…" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanine" msgstr "Altillo" +# English: "Mezzanine name" # Source-Hash: 97de3df03c4a0be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineName" msgstr "Nombre de la entreplanta" +# English: "Mezzanine names" # Source-Hash: 42cfb1bcb80d09e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineNames" msgstr "Nombres de entreplanta" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.newUnit" msgstr "Nueva unidad" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.none" msgstr "Ninguno" +# English: "Not enough headroom" # Source-Hash: e92d6108b57df9e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not enough headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.notEnoughHeadroom" msgstr "Altura libre insuficiente" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" # Source-Hash: 81a6770092b05d48 # Review: required -# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openingCounts" msgstr "{doors, plural, one {{doorCount} puerta} many {{doorCount} puertas} other {{doorCount} puertas}} · {windows, plural, one {{windowCount} ventana} many {{windowCount} ventanas} other {{windowCount} ventanas}}" +# English: "{area} · Close its walls to build its floor and ceiling." # Source-Hash: a8cd9507ce472a48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Close its walls to build its floor and ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openRoomArea" msgstr "{area} · Cierra sus muros para construir su suelo y su techo." +# English: "Over the mezzanine" # Source-Hash: f8aa48ec39a15616 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Over the mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.overTheMezzanine" msgstr "Sobre la entreplanta" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.perimeter" msgstr "Perímetro" +# English: "Pick" # Source-Hash: ed2aaf0a8d5a9b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pick" msgstr "Elegir" +# English: "Picking it adds a number" # Source-Hash: 7baaebbd411cc837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picking it adds a number" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pickingItAddsANumber" msgstr "Elegirlo añade un número" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.remove" msgstr "Quitar" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnit" msgstr "Quitar de la unidad" +# English: "Remove from {unit}" # Source-Hash: a11d13b92b3cefdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from {unit}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnitNamed" msgstr "Quitar de {unit}" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.rename" msgstr "Cambiar nombre" +# English: "{name} · Rename" # Source-Hash: 3562f9bea0a4a8f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{name} · Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.renameNamed" msgstr "{name} · Cambiar nombre" +# English: "Revert" # Source-Hash: d0d4e919bb58a977 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Revert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.revert" msgstr "Revertir" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.room" msgstr "Estancia" +# English: "Room colour — used in the plan and labels" # Source-Hash: 15ae260fae44f42f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room colour — used in the plan and labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomColourUsedInThePlan" msgstr "Color de la estancia: se usa en el plano y las etiquetas" +# English: "Room name" # Source-Hash: bf75e739dd7e5877 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomName" msgstr "Nombre de la estancia" +# English: "Room names" # Source-Hash: 2ecf8c059646873b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomNames" msgstr "Nombres de estancias" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.scriptedFinish" msgstr "Acabado por script" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.shape" msgstr "Forma" +# English: "Show mezzanine names" # Source-Hash: e51144ca1759a7bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showMezzanineNames" msgstr "Mostrar nombres de entreplanta" +# English: "Show room names" # Source-Hash: 875b7303c47df33f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showRoomNames" msgstr "Mostrar nombres de estancias" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.stairs" msgstr "Escalera" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.unit" msgstr "Unidad" +# English: "Use" # Source-Hash: 91436d6b374e8263 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.use" msgstr "Usar" +# English: "{used} on this level" # Source-Hash: 61880aff6cd698c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{used} on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.usedOnThisLevel" msgstr "{used} en esta planta" +# English: "{use} “{name}”" # Source-Hash: 611604740ed4549c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{use} “{name}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.useTyped" msgstr "{use} «{name}»" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.viewCode" msgstr "Ver código" +# English: "The script could not be read." # Source-Hash: ba15840e60ce672b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The script could not be read." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "editor.panels.scriptUnreadable" msgstr "No se ha podido leer el script." +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.polygon" msgstr "Polígono" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.rectangle" msgstr "Rectángulo" +# English: "Delete region" # Source-Hash: 5dc88f90e6aa4129 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete region" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.deleteRegion" msgstr "Eliminar región" +# English: "Paint regions" # Source-Hash: d81d8cbadf5d4f33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint regions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.paintRegions" msgstr "Regiones de pintura" -# Review: required -# Source-Hash: 89ee318aad485f83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Adjust design" +# Source-Hash: 89ee318aad485f83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.adjustDesign" msgstr "Ajustar diseño" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.back" msgstr "Volver" -# Review: required -# Source-Hash: ba9b68175315e177 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit parts" +# Source-Hash: ba9b68175315e177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.editParts" msgstr "Editar piezas" -# Review: required -# Source-Hash: 07c62820f014ed6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid attachment" +# Source-Hash: 07c62820f014ed6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidAttachment" msgstr "Fijación no válida" -# Review: required -# Source-Hash: f862aea8d7957c55 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid edit" +# Source-Hash: f862aea8d7957c55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidEdit" msgstr "Edición no válida" -# Review: required -# Source-Hash: 190cf30b8edd8dd1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid placement" +# Source-Hash: 190cf30b8edd8dd1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPlacement" msgstr "Colocación no válida" -# Review: required -# Source-Hash: 45bd2ec690cc48dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid position" +# Source-Hash: 45bd2ec690cc48dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPosition" msgstr "Posición no válida" -# Review: required -# Source-Hash: faccab895717c598 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid reset" +# Source-Hash: faccab895717c598 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidReset" msgstr "Restablecimiento no válido" -# Review: required -# Source-Hash: 28fe6e9deddde6e8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "pos" +# Source-Hash: 28fe6e9deddde6e8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.pos" msgstr "pos" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.position" msgstr "Posición" -# Review: required -# Source-Hash: 3fce677837d5a876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reset this part" +# Source-Hash: 3fce677837d5a876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.resetThisPart" msgstr "Restablecer esta pieza" +# English: "Reload editor" # Source-Hash: 5eab376947d29a50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload editor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.reloadEditor" msgstr "Recargar el editor" -# Review: required -# Source-Hash: 1063f1e315258b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close riser diagram" +# Source-Hash: 1063f1e315258b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.close" msgstr "Cerrar diagrama de bajantes" -# Review: required -# Source-Hash: c2d59ee62035974c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV plumbing isometric" +# Source-Hash: c2d59ee62035974c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvPlumbingIsometric" msgstr "Isometría de saneamiento y ventilación" -# Review: required -# Source-Hash: 3501a7c8a93d3b19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV riser diagram" +# Source-Hash: 3501a7c8a93d3b19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvRiserDiagram" msgstr "Diagrama de bajantes de saneamiento" -# Review: required -# Source-Hash: 225e3e3dc3bb01ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram." +# Source-Hash: 225e3e3dc3bb01ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.noDrainWasteOrVentPipes" msgstr "Aún no hay tuberías de desagüe, saneamiento ni ventilación. Dibuja la fontanería para ver el diagrama de bajantes." -# Review: required -# Source-Hash: bc8e172ba55160ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Riser Diagram" +# Source-Hash: bc8e172ba55160ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.riserDiagram" msgstr "Diagrama de bajantes" -# Review: required -# Source-Hash: 6a1132961f17f320 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" P-trap" +# Source-Hash: 6a1132961f17f320 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.trap" msgstr "Sifón de {diameter}\"" -# Review: required -# Source-Hash: 04bc0909a133a7e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vent" +# Source-Hash: 04bc0909a133a7e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.vent" msgstr "Ventilación" -# Review: required -# Source-Hash: 02f3eef9f0fb6d9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" VTR" +# Source-Hash: 02f3eef9f0fb6d9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Vent through roof: where a plumbing vent stack exits the roof. #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.ventTermination" msgstr "Salida a cubierta de {diameter}\"" -# Review: required -# Source-Hash: 29d8590334492601 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waste" +# Source-Hash: 29d8590334492601 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.waste" msgstr "Desagüe" +# English: "{current} · {distance} above {shared}" # Source-Hash: 0f8d128255bf4afa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} above {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.above" msgstr "{current} · {distance} por encima de {shared}" +# English: "{current} · {distance} below {shared}" # Source-Hash: 8416f433385a2936 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} below {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.below" msgstr "{current} · {distance} por debajo de {shared}" +# English: "{current} · level with {shared}" # Source-Hash: 6dc18e9fada391f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · level with {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.levelWith" msgstr "{current} · a nivel con {shared}" -# Source-Hash: 612dcd5bf06a79c4 +# English: "Cannot read \"{input}\"" +# Source-Hash: 782bc47cb30d5c66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.cannotReadInput" +msgstr "No se puede leer «{input}»" + +# English: "Dimension value" +# Source-Hash: 70ca08939d758332 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.dimensionValue" +msgstr "Valor de la cota" + +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.floor" +msgstr "Suelo" + +# English: "Type a size to move the wall" +# Source-Hash: de281c37906e7d14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.typeASizeToMoveThe" +msgstr "Escribe una medida para mover el muro" + # English: "Room" +# Source-Hash: 612dcd5bf06a79c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.room" msgstr "Estancia" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.zone" msgstr "Zona" +# English: "Attic" # Source-Hash: 7da15e7c88dab485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.attic" msgstr "Desván" +# English: "Balcony" # Source-Hash: 52adf6ccf54fd772 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balcony" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.balcony" msgstr "Balcón" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Basement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.basement" msgstr "Sótano" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bathroom" msgstr "Baño" +# English: "Bedroom" # Source-Hash: f25239fe3891ea8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bedroom" msgstr "Dormitorio" +# English: "Boiler room" # Source-Hash: 77d15570e2eba719 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boiler room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.boilerRoom" msgstr "Sala de calderas" +# English: "Carport" # Source-Hash: 5fbe77592f8f0ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carport" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.carport" msgstr "Cochera" +# English: "Cellar" # Source-Hash: f31dc305ce3a803a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cellar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.cellar" msgstr "Bodega" +# English: "Closet" # Source-Hash: e9af4141e3f8bdd1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.closet" msgstr "Armario" +# English: "Corridor" # Source-Hash: 388530b7b9c6647d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corridor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.corridor" msgstr "Pasillo" +# English: "Courtyard" # Source-Hash: 4d4a23b87e911d2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Courtyard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.courtyard" msgstr "Patio" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.deck" msgstr "Tarima" +# English: "Den" # Source-Hash: 18d7f85ff03c43c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Den" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.den" msgstr "Estudio" +# English: "Dining room" # Source-Hash: 4424343cad1f08b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dining room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.diningRoom" msgstr "Comedor" +# English: "Dressing room" # Source-Hash: 5efd0185e89ee84e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dressing room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.dressingRoom" msgstr "Vestidor" +# English: "En-suite" # Source-Hash: f45768e27780dc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "En-suite" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.enSuite" msgstr "Baño en suite" +# English: "Entrance" # Source-Hash: 51c664d19e5ea703 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Entrance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.entrance" msgstr "Entrada" +# English: "Family room" # Source-Hash: 4b32a81a5abf006a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Family room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.familyRoom" msgstr "Sala familiar" +# English: "Gallery" # Source-Hash: e87e6bd9a7d6d667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gallery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gallery" msgstr "Galería" +# English: "Games room" # Source-Hash: cf7cc0ba06d5eea1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Games room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gamesRoom" msgstr "Sala de juegos" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garage" msgstr "Garaje" +# English: "Garden" # Source-Hash: 5073b3c2989a626d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garden" msgstr "Jardín" +# English: "Garden shed" # Source-Hash: 52fe13434cdb7b27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gardenShed" msgstr "Caseta de jardín" +# English: "Guest room" # Source-Hash: a2712156dcaec1ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guest room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.guestRoom" msgstr "Habitación de invitados" +# English: "Gym" # Source-Hash: a1128cf188432bb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gym" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gym" msgstr "Gimnasio" +# English: "Hallway" # Source-Hash: 18664ee5a1abd06d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hallway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.hallway" msgstr "Recibidor" +# English: "Home cinema" # Source-Hash: 7dd957fc73d4d73a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Home cinema" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.homeCinema" msgstr "Cine en casa" +# English: "Kids' room" # Source-Hash: e1579515f44233ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kids' room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kidsRoom" msgstr "Habitación infantil" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchen" msgstr "Cocina" +# English: "Kitchenette" # Source-Hash: 06eb4c431b9b09b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchenette" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchenette" msgstr "Cocina americana" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.landing" msgstr "Descansillo" +# English: "Laundry" # Source-Hash: 558bcafa2ec70ded # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Laundry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.laundry" msgstr "Lavadero" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.library" msgstr "Biblioteca" +# English: "Living room" # Source-Hash: 2204538bab0e77ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.livingRoom" msgstr "Salón" +# English: "Loft" # Source-Hash: bb14c08f0c3db4ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.loft" msgstr "Altillo" +# English: "Lounge" # Source-Hash: a6a0e8bf816cbcb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lounge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.lounge" msgstr "Sala de estar" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mezzanine" msgstr "Altillo" +# English: "Mudroom" # Source-Hash: 6b2c82e5fbccb28b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mudroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mudroom" msgstr "Recibidor de servicio" +# English: "Nursery" # Source-Hash: e3c3b9b917744680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nursery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.nursery" msgstr "Cuarto del bebé" +# English: "Office" # Source-Hash: c1f1fcd9cb418340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Office" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.office" msgstr "Despacho" +# English: "Pantry" # Source-Hash: 5d98d41ec10bd05e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pantry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.pantry" msgstr "Despensa" +# English: "Patio" # Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Patio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.patio" msgstr "Patio" +# English: "Playroom" # Source-Hash: c3cfcccc167b1c19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Playroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.playroom" msgstr "Cuarto de juegos" +# English: "Pool area" # Source-Hash: 2e6324beaed18dd5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pool area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.poolArea" msgstr "Zona de piscina" +# English: "Porch" # Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Porch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.porch" msgstr "Porche" +# English: "Powder room" # Source-Hash: 04cb81f4a4ae4ff3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Powder room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.powderRoom" msgstr "Aseo de cortesía" +# English: "Primary bedroom" # Source-Hash: 599c44d857832cd0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Primary bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.primaryBedroom" msgstr "Dormitorio principal" +# English: "Reading nook" # Source-Hash: 9a6ac3f1cdd8de2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reading nook" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.readingNook" msgstr "Rincón de lectura" +# English: "Bathrooms" # Source-Hash: 01f8cc22989822c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathrooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.bathrooms" msgstr "Baños" +# English: "Circulation" # Source-Hash: 9ec82561de8cb788 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Circulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.circulation" msgstr "Circulación" +# English: "Living" # Source-Hash: f74a48b6b49644d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.living" msgstr "Estar" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.mezzanine" msgstr "Altillo" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.outdoor" msgstr "Exterior" +# English: "Sleeping" # Source-Hash: 5df703d5b5e6ef7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sleeping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.sleeping" msgstr "Dormitorios" +# English: "Utility & storage" # Source-Hash: c9d15ec19f814524 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility & storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.utility" msgstr "Servicio y almacenaje" +# English: "Work & hobbies" # Source-Hash: fbe13f7ab45e4cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Work & hobbies" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.work" msgstr "Trabajo y aficiones" +# English: "Shower room" # Source-Hash: 10ccb6591f6ad207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shower room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.showerRoom" msgstr "Cuarto de ducha" +# English: "Stairwell" # Source-Hash: 5254168cc5712cb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairwell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.stairwell" msgstr "Caja de escalera" +# English: "Storage" # Source-Hash: d1596647cd84ade3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.storage" msgstr "Almacenaje" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Studio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.studio" msgstr "Estudio" +# English: "Study" # Source-Hash: ebe68e83e73021ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Study" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.study" msgstr "Estudio" +# English: "Sunroom" # Source-Hash: d69a0244649771ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.sunroom" msgstr "Solario" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.terrace" msgstr "Terraza" +# English: "Toilet" # Source-Hash: a0cb3e6f5cf4fbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Toilet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.toilet" msgstr "Aseo" +# English: "Utility room" # Source-Hash: 6c01526f89f3106b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.utilityRoom" msgstr "Cuarto de servicio" +# English: "Veranda" # Source-Hash: fc42d89621ae7bac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veranda" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.veranda" msgstr "Veranda" +# English: "Walk-in closet" # Source-Hash: b45d7730e896fd99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk-in closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.walkInCloset" msgstr "Vestidor" +# English: "Workshop" # Source-Hash: 399f27253ef956cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Workshop" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.workshop" msgstr "Taller" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.settings" msgstr "Ajustes" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.site" msgstr "Parcela" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.all" msgstr "Todo" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.community" msgstr "Comunidad" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.all" msgstr "Todo" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.community" msgstr "Comunidad" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.library" msgstr "Biblioteca" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.mine" msgstr "Míos" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch" msgstr "No hay resultados para “{search}”" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.search" msgstr "Buscar..." +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.library" msgstr "Biblioteca" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.mine" msgstr "Míos" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.noResultsForSearch" msgstr "No hay resultados para “{search}”" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.search" msgstr "Buscar..." -# Source-Hash: 3c67cde0578ac829 +# English: "Build Tools" +# Source-Hash: fb66c49eee49a3bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools" +msgstr "Herramientas de construcción" + +# English: "Camera" +# Source-Hash: 14622745dbf93f4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera" +msgstr "Cámara" + +# English: "Direct Manipulation" +# Source-Hash: 677783232bea72a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation" +msgstr "Manipulación directa" + +# English: "Drawing Tools" +# Source-Hash: 8e67947d43b76596 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools" +msgstr "Herramientas de dibujo" + +# English: "Editor Navigation" +# Source-Hash: 8cdda30f3be2bac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation" +msgstr "Navegación del editor" + +# English: "Item Placement" +# Source-Hash: 9a996e3570d615ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement" +msgstr "Colocación de elementos" + +# English: "Keyboard shortcuts" +# Source-Hash: fc18926f9bd9eebd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts" +msgstr "Atajos de teclado" + +# English: "Modes & History" +# Source-Hash: cbb4c3e007d70aa0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory" +msgstr "Modos e historial" + +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection" +msgstr "Selección" + +# English: "Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely." +# Source-Hash: 8a708ce860d787e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints" +msgstr "Los atajos dependen del contexto. Las restricciones guiadas están activadas por defecto; mantén pulsada Mayús durante un gesto activo para construir libremente." + # English: "Add level" +# Source-Hash: 3c67cde0578ac829 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addLevel" msgstr "Añadir planta" +# English: "Add point" # Source-Hash: 9a56d90d461b4bca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addPoint" msgstr "Añadir punto" +# English: "Area:" # Source-Hash: fedccec504a9ae33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.area" msgstr "Área:" +# English: "Base elevation" # Source-Hash: 87b6880f6de7a45a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.baseElevation" msgstr "Cota base" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.building" msgstr "Edificio" +# English: "Add new level" # Source-Hash: 7c90f458259d5cd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add new level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel" msgstr "Añadir nueva planta" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.building" msgstr "Edificio" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.cameraSnapshot" msgstr "Instantánea de cámara" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.capture" msgstr "Captura" +# English: "Clear selection" # Source-Hash: 71f2343818c55e0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSelection" msgstr "Borrar selección" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSnapshot" msgstr "Borrar instantánea" +# English: "Close walls around a space to make a room." # Source-Hash: def517c77fda49ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close walls around a space to make a room." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo" msgstr "Cierra muros alrededor de un espacio para crear una estancia." +# English: "Collection colour" # Source-Hash: 2b9af8da4b34cbfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection colour" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour" msgstr "Color de la colección" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collections" msgstr "Colecciones" +# English: "Delete collection" # Source-Hash: 6f4751c26f12aef9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete collection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection" msgstr "Eliminar colección" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.delete" msgstr "Eliminar" +# English: "Delete level" # Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.deleteLevel" msgstr "Eliminar planta" +# English: "Draw a zone" # Source-Hash: 6552e45eecc0a227 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.drawAZone" msgstr "Dibujar una zona" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicate" msgstr "Duplicar" +# English: "Duplicate level" # Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevel" msgstr "Duplicar planta" +# English: "Duplicate level with options" # Source-Hash: c038947f317cabba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level with options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevelWithOptions" msgstr "Duplicar planta con opciones" +# English: "Duplicate with options..." # Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateWithOptions" msgstr "Duplicar con opciones..." +# English: "Elements" # Source-Hash: 7c4196b9e791c195 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.elements" msgstr "Elementos" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.guide" msgstr "Guía" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.hide" msgstr "Ocultar" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.level" msgstr "Planta" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.newUnit" msgstr "Nueva unidad" +# English: "No buildings yet" # Source-Hash: 0192748d50f0e5f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No buildings yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noBuildingsYet" msgstr "Aún no hay edificios" +# English: "No elements on this level" # Source-Hash: 1be310702a12e433 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No elements on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noElementsOnThisLevel" msgstr "No hay elementos en esta planta" +# English: "No levels yet" # Source-Hash: 0956af88ced36992 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No levels yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noLevelsYet" msgstr "Aún no hay plantas" +# English: "{count, plural, one {# object selected} other {# objects selected}}" # Source-Hash: 45c41bfb2ae19627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# object selected} other {# objects selected}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.objectsSelected" msgstr "{count, plural, one {# objeto seleccionado} many {# objetos seleccionados} other {# objetos seleccionados}}" +# English: "Other zones" # Source-Hash: a5434e26e5cb1b26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.otherZones" msgstr "Otras zonas" +# English: "Perimeter:" # Source-Hash: 2aa38d84079d4dba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.perimeter" msgstr "Perímetro:" +# English: "Property Line" # Source-Hash: 4004b9a8e4f8c9d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Property Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.propertyLine" msgstr "Lindero" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.rooms" msgstr "Estancias" +# English: "Select a level to view content" # Source-Hash: ca39c0581c7318e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view content" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.selectALevelToViewContent" msgstr "Selecciona una planta para ver su contenido" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.show" msgstr "Mostrar" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.site" msgstr "Parcela" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.takeSnapshot" msgstr "Tomar instantánea" +# English: "The ground level cannot be deleted" # Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted" msgstr "La planta baja no se puede eliminar" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot" msgstr "Instantánea de cámara" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot" msgstr "Borrar instantánea" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.hide" msgstr "Ocultar" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.show" msgstr "Mostrar" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot" msgstr "Tomar instantánea" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot" msgstr "Actualizar instantánea" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot" msgstr "Ver instantánea" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.units" msgstr "Unidades" +# English: "Delete unit" # Source-Hash: 149fec3fd1f08042 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit" msgstr "Eliminar unidad" +# English: "Focused" # Source-Hash: 930399d3fb247a7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.focused" msgstr "Enfocada" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit" msgstr "Quitar de la unidad" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.room" msgstr "Estancia" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.zone" msgstr "Zona" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.updateSnapshot" msgstr "Actualizar instantánea" +# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" # Source-Hash: 28a12e6806c091b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadingProgress" msgstr "{assetType, select, guide {Subiendo guía... {progress}%} other {Subiendo escaneo 3D... {progress}%}}" +# English: "Upload scan/floorplan" # Source-Hash: de389a2a46d53384 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload scan/floorplan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadScanFloorplan" msgstr "Subir escaneo 3D o plano" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.viewSnapshot" msgstr "Ver instantánea" +# English: "In {unitName}" # Source-Hash: ba9f809cb032ee57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "In {unitName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname" msgstr "En {unitName}" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.actions" msgstr "Acciones" +# English: "Add one" # Source-Hash: fcc43f22937d1536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add one" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.addOne" msgstr "Añadir una" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.cameraSnapshot" msgstr "Instantánea de cámara" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.clearSnapshot" msgstr "Borrar instantánea" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.delete" msgstr "Eliminar" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.deleteWithContents" msgstr "Eliminar con el contenido" +# English: "No zones on this level." # Source-Hash: 22ca3935e8754072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones on this level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.noZonesOnThisLevel" msgstr "No hay zonas en esta planta." +# English: "Save to catalog" # Source-Hash: 941ea30f406dcfa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save to catalog" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.saveToCatalog" msgstr "Guardar en el catálogo" +# English: "Select a level to view and create zones" # Source-Hash: 98662dfb371cac13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view and create zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.selectALevelToViewAnd" msgstr "Selecciona una planta para ver y crear zonas" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.takeSnapshot" msgstr "Tomar instantánea" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.updateSnapshot" msgstr "Actualizar instantánea" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.viewSnapshot" msgstr "Ver instantánea" +# English: "\"{label}\" plugin crashed" # Source-Hash: 58b05af5b5724efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "\"{label}\" plugin crashed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.labelPluginCrashed" msgstr "El plugin “{label}” se ha bloqueado" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.loading" msgstr "Cargando…" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.plugins" msgstr "Complementos" +# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." # Source-Hash: c6312309378aaaea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd" msgstr "Este panel ha tenido un error y se ha descargado durante esta sesión. El resto del editor no se ve afectado: recarga para volver a intentarlo." -# Review: required -# Source-Hash: 2ab3b6983e384ecc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ceiling attachment" +# Source-Hash: 2ab3b6983e384ecc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.ceiling" msgstr "Fijación al techo" -# Review: required -# Source-Hash: 54a00bbb699e3778 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof attachment" +# Source-Hash: 54a00bbb699e3778 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.roof" msgstr "Fijación a la cubierta" -# Review: required -# Source-Hash: e45fee9c27f6ae69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall attachment" +# Source-Hash: e45fee9c27f6ae69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.wall" msgstr "Fijación a la pared" +# English: "The editor scene failed to render" # Source-Hash: 834de1a7027c4da1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The editor scene failed to render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.theEditorSceneFailedToRender" msgstr "No se pudo mostrar la escena del editor" +# English: "A curtain wall takes no corner window" # Source-Hash: 151f7c7e4110016f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curtain wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i" msgstr "Un muro cortina no admite ventana de esquina" +# English: "A curved wall takes no corner window" # Source-Hash: 97f5e044d46501fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curved wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5" msgstr "Un muro curvo no admite ventana de esquina" +# English: "Add a corner · click the first to close" # Source-Hash: 776d8ebece7fe1bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a corner · click the first to close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.addACornerClickTheFirst.o4r67t" msgstr "Añadir esquina · haz clic en la primera para cerrar" -# Source-Hash: 6a0745dd047b149a +# English: "Add a wall · each one starts where the last ended" +# Source-Hash: d0ddef756fb49fbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.addAWallEachOneStarts.4zkf12" +msgstr "Añadir muro · cada uno empieza donde acabó el anterior" + # English: "Another opening is in the way at the corner" +# Source-Hash: 6a0745dd047b149a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0" msgstr "Otra abertura estorba en la esquina" +# English: "Auto hangers" # Source-Hash: 441124187e7da2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangers.10554ka" msgstr "Colgadores automáticos" +# English: "Auto hangers - click or press H to toggle" # Source-Hash: 47a7f9a6a81156c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers - click or press H to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersClickOrPressH.rwa3ka" msgstr "Colgadores automáticos: haz clic o pulsa H para alternar" +# English: "Auto hangers: Off" # Source-Hash: d7944f31cba90e26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOff.7yx01x" msgstr "Colgadores automáticos: desactivados" +# English: "Auto hangers: On" # Source-Hash: 295882d015d08742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOn.10b1umh" msgstr "Colgadores automáticos: activados" +# English: "Bypass snapping" # Source-Hash: 8c039e5182b01c4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bypass snapping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.bypassSnapping.w5lmdv" msgstr "Omitir el ajuste" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancel.ew9em3" msgstr "Cancelar" +# English: "Cancel move" # Source-Hash: 5b33b576fd30b74e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelMove.lvp31c" msgstr "Cancelar movimiento" +# English: "Cancel run / exit" # Source-Hash: 136fa29b9ae419bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel run / exit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelRunExit.zqjf3h" msgstr "Cancelar tramo / salir" +# English: "Cancel start point" # Source-Hash: 75b406824be91ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel start point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelStartPoint.1xbq2lh" msgstr "Cancelar punto inicial" -# Source-Hash: ffa5aaa1746303c4 +# English: "Close the outline" +# Source-Hash: e9b0a295122e7ad4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.closeTheOutline.f6cvzi" +msgstr "Cerrar el contorno" + # English: "Corner window" +# Source-Hash: ffa5aaa1746303c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cornerWindow.1avkzpo" msgstr "Ventana de esquina" +# English: "Cuts" # Source-Hash: b0afdd855a02e4a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cuts.1y0axps" msgstr "Cortes" +# English: "Cuts: {value}" # Source-Hash: dc9fe7601cc525f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValue.ouzhvb" msgstr "Cortes: {value}" +# English: "Cuts: {value}, even" # Source-Hash: a2e13bc4dc82624c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}, even" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValueEven.1ngsz3" msgstr "Cortes: {value}, uniformes" +# English: "Cycle mono / gable / butterfly" # Source-Hash: 04152b4cec726f34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle mono / gable / butterfly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleMonoGableButterfly.1poga3m" msgstr "Cambiar un agua / dos aguas / mariposa" +# English: "Cycle snapping mode" # Source-Hash: 4fb6c83edb6652b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle snapping mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleSnappingMode.1ct0gn6" msgstr "Cambiar el modo de ajuste" +# English: "Door style — click or press L to cycle" # Source-Hash: 5d550a3ac76279de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorStyleClickOrPressL.33pe65" msgstr "Estilo de puerta: haz clic o pulsa L para cambiar" +# English: "Door type — click or press O to cycle" # Source-Hash: 3272a7df5d7c6a9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorTypeClickOrPressO.1hhh8gt" msgstr "Tipo de puerta: haz clic o pulsa O para cambiar" +# English: "Drop downspout from outlet" # Source-Hash: 366222b4435eefda # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drop downspout from outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.dropDownspoutFromOutlet.1k4h1m4" msgstr "Bajar la bajante desde la salida" +# English: "Duct diameter down / up" # Source-Hash: 03f306371f345007 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct diameter down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.ductDiameterDownUp.1wdz5vq" msgstr "Diámetro del conducto menos / más" +# English: "End run / cancel" # Source-Hash: 7b4137e27f8327d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End run / cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.endRunCancel.59dh2w" msgstr "Terminar tramo / cancelar" -# Source-Hash: 938aacb119b29dc1 +# English: "End the run" +# Source-Hash: b714e91c31108a71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.endTheRun.1c1raig" +msgstr "Terminar el tramo" + # English: "Exit" +# Source-Hash: 938aacb119b29dc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exit.engmg5" msgstr "Salir" +# English: "Exit drawing" # Source-Hash: 7cb4e0688cabcb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit drawing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exitDrawing.9noxyb" msgstr "Salir del dibujo" +# English: "Finish and continue" # Source-Hash: 47025a103c456125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish and continue" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishAndContinue.1gpfsks" msgstr "Terminar y continuar" +# English: "Finish ceiling" # Source-Hash: 000918429b74128f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishCeiling.1c1lfel" msgstr "Terminar el techo" +# English: "Finish grid axis" # Source-Hash: 51a80a0dba5a6f30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishGridAxis.1wvrfhr" msgstr "Terminar el eje" +# English: "Finish measurement" # Source-Hash: 87c7b98fc2e010e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMeasurement.1e4gv16" msgstr "Terminar la medida" +# English: "Finish multi-point witnesses" # Source-Hash: 5e1a0af7dc506826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish multi-point witnesses" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMultiPointWitnesses.8wzskz" msgstr "Terminar los testigos de varios puntos" +# English: "Finish slab" # Source-Hash: 30fc0862501ba2e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishSlab.xmnm8e" msgstr "Terminar el forjado" -# Source-Hash: 66ed7c7598d3aa29 +# English: "Finish the run" +# Source-Hash: 54ae97157eacbc56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.finishTheRun.ayliww" +msgstr "Terminar el tramo" + # English: "Flip side" +# Source-Hash: 66ed7c7598d3aa29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.flipSide.172xku9" msgstr "Invertir el lado" +# English: "Follow: trace a lineset" # Source-Hash: f5717f6a0905ffab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow: trace a lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.followTraceALineset.mgquwe" msgstr "Seguir: trazar junto a una línea frigorífica" +# English: "Force place" # Source-Hash: ed7f3d8417a3e074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.forcePlace.1hq1nr" msgstr "Forzar colocación" -# Source-Hash: cc0c14065951444d +# English: "Free angle" +# Source-Hash: 64b4ac6d3da25183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freeAngle.1ptblho" +msgstr "Ángulo libre" + +# English: "Free place" +# Source-Hash: 446b8aac1e5e8422 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freePlace.1s1uusw" +msgstr "Colocación libre" + # English: "Free placement" +# Source-Hash: cc0c14065951444d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.freePlacement.1nztdac" msgstr "Colocación libre" +# English: "Go vertical ↕, click to place" # Source-Hash: f098a27096a9c1ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Go vertical ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.goVerticalClickToPlace.dg8g6m" msgstr "Pasar a vertical ↕, haz clic para colocar" +# English: "Highlight outlet" # Source-Hash: fd47f1820d8799c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Highlight outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.highlightOutlet.65hoq8" msgstr "Resaltar la salida" +# English: "Join as a corner window" # Source-Hash: eb90058d0f856f67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Join as a corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.joinAsACornerWindow.jvdp4j" msgstr "Unir como ventana de esquina" +# English: "Alt + drag" # Source-Hash: 225b3161f2e59757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alt + drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.altDrag.ai3wn9" msgstr "Alt + arrastrar" +# English: "Backspace" # Source-Hash: dd66f883b173fc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Backspace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.backspace.qy6qx8" msgstr "Retroceso" +# English: "Click" # Source-Hash: a18387bc0be54c32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.click.m8sun" msgstr "Clic" +# English: "Click again" # Source-Hash: db9e60c1d30b8499 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click again" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.clickAgain.190ws9j" msgstr "Volver a hacer clic" +# English: "Command" # Source-Hash: f400330b9c9c12e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Command" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.command.1j2mkia" msgstr "Comando" +# English: "Control" # Source-Hash: 976c63285a510b09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Control" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.control.10fbl8e" msgstr "Control" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.delete.oay2cq" msgstr "Eliminar" +# English: "Double click" # Source-Hash: 88888c9d2719fa92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.doubleClick.vlma6o" msgstr "Doble clic" +# English: "Drag" # Source-Hash: dfb85b2addb7b9fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.drag.8pwtpf" msgstr "Arrastrar" +# English: "Enter" # Source-Hash: ed9140d42148c99b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.enter.1ctpui5" msgstr "Intro" +# English: "Hover" # Source-Hash: 9195fa8b9c8ebdfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hover.6xw79p" msgstr "Pasar el cursor" +# English: "Hover a duct end" # Source-Hash: 2d78441b741bc562 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a duct end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverADuctEnd.19axr3b" msgstr "Pasa el cursor por un extremo del conducto" +# English: "Hover a pipe end" # Source-Hash: 6083405086f61c76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a pipe end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverAPipeEnd.1jbum5f" msgstr "Pasa el cursor por un extremo de la tubería" +# English: "Hover gutter" # Source-Hash: a5a337eb76fdb066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverGutter.1dwcsno" msgstr "Pasa el cursor por el canalón" +# English: "Left click" # Source-Hash: a58533594411e088 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.leftClick.s4gwfc" msgstr "Clic izquierdo" +# English: "Middle click" # Source-Hash: bc4ab608ec3d3b4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Middle click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.middleClick.1jbaan4" msgstr "Clic central" +# English: "Right click" # Source-Hash: 6791879c651108c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rightClick.1ovld0h" msgstr "Clic derecho" -# Source-Hash: c836b21b42198868 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.key.room.1f3jkpm" +msgstr "Estancia" + # English: "R / Shift+R" +# Source-Hash: c836b21b42198868 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rShiftR.13bnobh" msgstr "R / Mayús+R" +# English: "Scroll" # Source-Hash: 9812aa06d8bb7544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scroll.ai6wr0" msgstr "Desplazar" +# English: "Scroll wheel" # Source-Hash: f2174536230657c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll wheel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scrollWheel.18j0ix7" msgstr "Rueda del ratón" +# English: "Space" # Source-Hash: 82dd231086d87f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Space" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.space.1hrh82t" msgstr "Espacio" +# English: "Tab" # Source-Hash: 9e68aa2f472cad56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.tab.1xsal0s" msgstr "Tab" +# English: "Mount: floor / ceiling / wall" # Source-Hash: 4b221b73f9496a49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount: floor / ceiling / wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.mountFloorCeilingWall.32dxpv" msgstr "Montaje: suelo / techo / muro" +# English: "No room for the matching window on the other wall" # Source-Hash: 225edfb1e45aed95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No room for the matching window on the other wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noRoomForTheMatchingWindow.1755948" msgstr "No hay espacio para la ventana correspondiente en el otro muro" -# Source-Hash: a7045d6b678bcd13 +# English: "Not a rectangle: draw this roof instead" +# Source-Hash: e367d28a655f1cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.notARectangleDrawThisRoof.100pa5g" +msgstr "No es un rectángulo: dibuja esta cubierta en su lugar" + # English: "No wall meets here" +# Source-Hash: a7045d6b678bcd13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noWallMeetsHere.19n99yc" msgstr "Aquí no se une ningún muro" +# English: "Number of cuts — scroll or click to change" # Source-Hash: a639341dc21fed2c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Number of cuts — scroll or click to change" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.numberOfCutsScrollOrClick.1kqe84b" msgstr "Número de cortes: desplaza o haz clic para cambiar" +# English: "Pick witness point" # Source-Hash: af01bde3ae4b7e59 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick witness point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pickWitnessPoint.k3hgh1" msgstr "Elegir punto testigo" +# English: "Pipe size down / up" # Source-Hash: ecfd58576b28a9f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe size down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pipeSizeDownUp.wp04og" msgstr "Diámetro de tubería menos / más" -# Source-Hash: 7a1eb7b64f06bad0 +# English: "Place a corner of the foundation" +# Source-Hash: e0162c885a961919 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j" +msgstr "Colocar una esquina de la cimentación" + # English: "Place and continue" +# Source-Hash: 7a1eb7b64f06bad0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeAndContinue.qfwkrq" msgstr "Colocar y continuar" +# English: "Place block" # Source-Hash: c6cda39e2e4b5d3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBlock.1aylwrx" msgstr "Colocar bloque" +# English: "Place box vent on roof" # Source-Hash: 45a743c52a1d72de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place box vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBoxVentOnRoof.kf88e5" msgstr "Colocar aireador en la cubierta" +# English: "Place cabinet" # Source-Hash: 75142dd1d557efe2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCabinet.b0ouyk" msgstr "Colocar mueble" +# English: "Place canopy or set the next run point" # Source-Hash: da603a713d4c9909 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place canopy or set the next run point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCanopyOrSetTheNext.12l7zf8" msgstr "Colocar la marquesina o el siguiente punto" +# English: "Place chimney on roof" # Source-Hash: 8b3a30c6b7a5612d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place chimney on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeChimneyOnRoof.19dbqu" msgstr "Colocar chimenea en la cubierta" +# English: "Place column" # Source-Hash: 710d41e4e2f259fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeColumn.1w2vq86" msgstr "Colocar pilar" +# English: "Place cupola on roof" # Source-Hash: ee1da4e12a71d9a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cupola on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCupolaOnRoof.ofx3bn" msgstr "Colocar linterna en la cubierta" +# English: "Place dimension line when needed" # Source-Hash: 19c1fe8c46d213a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dimension line when needed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj" msgstr "Colocar la línea de cota cuando haga falta" +# English: "Place door on wall" # Source-Hash: 58f3e7efb6ea120b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place door on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDoorOnWall.1jwadf" msgstr "Colocar puerta en el muro" +# English: "Place dormer on roof" # Source-Hash: de04e9d03d937c97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dormer on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDormerOnRoof.1w518a6" msgstr "Colocar buhardilla en la cubierta" +# English: "Place elevator" # Source-Hash: c581645f2ee447c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeElevator.1f65zva" msgstr "Colocar ascensor" +# English: "Place eyebrow vent on roof" # Source-Hash: 096b8d6482717358 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place eyebrow vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn" msgstr "Colocar respiradero de ceja en la cubierta" +# English: "Place fitting" # Source-Hash: e8d42c1c8370773f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeFitting.1pmx3vn" msgstr "Colocar accesorio" +# English: "Place gutter on roof eave" # Source-Hash: 2340a4b70cb0d9ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gutter on roof eave" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeGutterOnRoofEave.llwzf9" msgstr "Colocar canalón en el alero" +# English: "Place item" # Source-Hash: 706a8eafb6212833 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItem.teynf" msgstr "Colocar elemento" +# English: "Place it (locked to 45°)" # Source-Hash: 1815c482e249932e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place it (locked to 45°)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItLockedTo45.16e4csk" msgstr "Colocar (bloqueado a 45°)" +# English: "Place measurement point" # Source-Hash: 4a14340657532838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place measurement point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeMeasurementPoint.bsatli" msgstr "Colocar punto de medida" +# English: "Placement" # Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placement.13lcvts" msgstr "Colocación" +# English: "Placement: Auto" # Source-Hash: 2bb2ce6276c69b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementAuto.1caa52d" msgstr "Colocación: automática" +# English: "Placement: Ground" # Source-Hash: ed78ac5354a1dcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementGround.1ruz145" msgstr "Colocación: suelo" +# English: "Placement: Roof" # Source-Hash: a066a2c9d5e65a1a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementRoof.renkw8" msgstr "Colocación: cubierta" +# English: "Placement surface - click or press P to cycle" # Source-Hash: 8f74dbe5064c4c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement surface - click or press P to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementSurfaceClickOrPressP.w56h19" msgstr "Superficie de colocación: haz clic o pulsa P para cambiar" +# English: "Placement type" # Source-Hash: fe119955c925feaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementType.1mdpl60" msgstr "Tipo de colocación" +# English: "Placement type — click or press I to toggle" # Source-Hash: 33043a35500168f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type — click or press I to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementTypeClickOrPressI.10cgxh2" msgstr "Tipo de colocación: haz clic o pulsa I para alternar" +# English: "Place on a fence" # Source-Hash: 9945f412e6a9a446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place on a fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeOnAFence.1dptstr" msgstr "Colocar en una valla" +# English: "Place register" # Source-Hash: 6708118775a7da7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRegister.1uebycd" msgstr "Colocar rejilla" +# English: "Place ridge vent on roof" # Source-Hash: ebd2731f5c79306e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place ridge vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRidgeVentOnRoof.2tpd8r" msgstr "Colocar ventilación de cumbrera en la cubierta" +# English: "Place shelf" # Source-Hash: 9b3c66a867c23326 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeShelf.y4l3iw" msgstr "Colocar estantería" +# English: "Place skylight on roof" # Source-Hash: f11cf5c1c9fe862d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place skylight on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSkylightOnRoof.e215pe" msgstr "Colocar claraboya en la cubierta" +# English: "Place solar panel array on roof" # Source-Hash: e989a58d6709cc16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place solar panel array on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn" msgstr "Colocar paneles solares en la cubierta" +# English: "Place spawn point" # Source-Hash: b67f7036d5180cf5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSpawnPoint.205o25" msgstr "Colocar punto de aparición" +# English: "Place stairs" # Source-Hash: 6c7f32a17e5636c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeStairs.zwxxu2" msgstr "Colocar escalera" +# English: "Place trap" # Source-Hash: 641c0d3debd798a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTrap.1gxemn3" msgstr "Colocar sifón" +# English: "Place turbine vent on roof" # Source-Hash: 6f0a6f6c4b7588a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place turbine vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTurbineVentOnRoof.1iqeluf" msgstr "Colocar extractor eólico en la cubierta" +# English: "Place unit" # Source-Hash: e65cf5edb4f51ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeUnit.8zkx4q" msgstr "Colocar equipo" +# English: "Place window on wall" # Source-Hash: 86ce1c730154f68b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place window on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWindowOnWall.1k3qnfb" msgstr "Colocar ventana en el muro" +# English: "Place without the corner snap" # Source-Hash: 2b612180a7617901 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place without the corner snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWithoutTheCornerSnap.1nifqew" msgstr "Colocar sin el ajuste a la esquina" -# Source-Hash: 0dc6d121791696f7 +# English: "Point inside a room, porch or deck" +# Source-Hash: dd2ea442152dca5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.pointInsideARoomPorchOr.58w64l" +msgstr "Sitúa el cursor dentro de una estancia, un porche o una tarima" + # English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastPoint.2dmdcx" msgstr "Quitar el último punto" +# English: "Remove last witness" # Source-Hash: 8b74b1d962be10fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last witness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastWitness.1b6vvzw" msgstr "Quitar el último testigo" -# Source-Hash: 306d409f8a1cf480 +# English: "Ridge direction" +# Source-Hash: 1185bd2407dd9172 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirection.5m1369" +msgstr "Dirección de la cumbrera" + +# English: "Ridge direction - click or press R to turn it 90°" +# Source-Hash: 4352028e50450055 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk" +msgstr "Dirección de la cumbrera: haz clic o pulsa R para girarla 90°" + +# English: "Ridge: Long side" +# Source-Hash: 96c889f047db9de5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeLongSide.ew0euv" +msgstr "Cumbrera: lado largo" + +# English: "Ridge: Short side" +# Source-Hash: 58143198d3f2a265 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeShortSide.gye8pb" +msgstr "Cumbrera: lado corto" + # English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate.1thqa0a" msgstr "Girar" +# English: "Rotate ±45°" # Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45.9zq68u" msgstr "Girar ±45°" +# English: "Rotate ±45° (floor / ceiling)" # Source-Hash: be2240289aa74df5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45° (floor / ceiling)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45FloorCeiling.m9t57d" msgstr "Girar ±45° (suelo / techo)" +# English: "Rotate ghost ±15°" # Source-Hash: d5fb78b07c0f8138 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ghost ±15°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateGhost15.rsa52c" msgstr "Girar la vista previa ±15°" +# English: "Rotate or flip the run side" # Source-Hash: 912d340cd16e27fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate or flip the run side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateOrFlipTheRunSide.t5ior7" msgstr "Girar o invertir el lado de la línea" -# Source-Hash: ad1dc0b5536cceef -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate roof direction 90°" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.rotateRoofDirection90.tvbji2" -msgstr "Girar la orientación de la cubierta 90°" - +# English: "Rotate spawn point" # Source-Hash: fa24651e9e5f595a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateSpawnPoint.1opcuu7" msgstr "Girar el punto de aparición" +# English: "Round / rectangular / oval" # Source-Hash: 2d693d87fe9b59e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round / rectangular / oval" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.roundRectangularOval.e4nh6x" msgstr "Redondo / rectangular / ovalado" -# Source-Hash: f7d2c9cdcc637c97 +# English: "Set beam start / end; the next beam starts there" +# Source-Hash: ee4ca2612f1ea085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setBeamStartEndTheNext.di3bzj" +msgstr "Fijar inicio / fin de la viga; la siguiente viga empieza ahí" + # English: "Set fence start / end" +# Source-Hash: f7d2c9cdcc637c97 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setFenceStartEnd.1phbwsq" msgstr "Fijar inicio / fin de la valla" -# Source-Hash: c93ae898b4d59f6f +# English: "Set footing start / end, or place a pad" +# Source-Hash: b9693a8a1f7cd6c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setFootingStartEndOrPlace.nnv1av" +msgstr "Fijar inicio / fin de la zapata, o colocar una zapata aislada" + # English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setOneCornerThenTheOpposite.8tsl49" msgstr "Fija una esquina y luego la opuesta" +# English: "Set roof footprint" # Source-Hash: 4f55171f10c6ceeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set roof footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setRoofFootprint.p50n0o" msgstr "Fijar la planta de la cubierta" -# Source-Hash: 0243859b1d64c21e -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set wall start / end" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.setWallStartEnd.1r8ubcx" -msgstr "Fijar inicio / fin del muro" - +# English: "Slope / level" # Source-Hash: c2806cce9612fa50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope / level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.slopeLevel.ftqp67" msgstr "Pendiente / horizontal" +# English: "Snap onto the run" # Source-Hash: 06c0ec40606db679 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap onto the run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.snapOntoTheRun.86yhmp" msgstr "Ajustar al tramo" +# English: "Split at the marks" # Source-Hash: e214cccbac9c4fd2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split at the marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.splitAtTheMarks.x5tpdd" msgstr "Dividir en las marcas" +# English: "Start grid axis" # Source-Hash: 94455c35bfeac6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startGridAxis.k92yza" msgstr "Iniciar el eje" +# English: "Start lineset" # Source-Hash: 2edb4c29bbe7c513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLineset.23q8dh" msgstr "Iniciar la línea frigorífica" +# English: "Start liquid line" # Source-Hash: 9e50f1b30e325208 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start liquid line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLiquidLine.1rlxsgd" msgstr "Iniciar la línea de líquido" +# English: "Start run" # Source-Hash: 877ddcb778c211cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startRun.r6fc5u" msgstr "Iniciar el tramo" +# English: "Start segment" # Source-Hash: 8a62bee64174bbc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startSegment.rolym6" msgstr "Iniciar el segmento" +# English: "Step back or cancel" # Source-Hash: 6cc5dc51a8e66779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step back or cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stepBackOrCancel.zbfdkn" msgstr "Retroceder o cancelar" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.style.nf3vcm" msgstr "Estilo" +# English: "Style: Barn" # Source-Hash: 81f595b1c47c0080 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleBarn.k9n1x" msgstr "Estilo: granero" +# English: "Style: Casement" # Source-Hash: 3a82c718fe215273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCasement.ziklec" msgstr "Estilo: abatible" +# English: "Style: Craftsman" # Source-Hash: 072e3331d1d105c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCraftsman.52e6a9" msgstr "Estilo: artesano" +# English: "Style: Double-hung" # Source-Hash: b64d07557dec1714 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleDoubleHung.1lg8pkg" msgstr "Estilo: guillotina doble" +# English: "Style: French" # Source-Hash: 55ef8761a537637f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleFrench.1ue3j64" msgstr "Estilo: francesa" +# English: "Style: Glass" # Source-Hash: bc8a751d87536dc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGlass.qudnb4" msgstr "Estilo: vidrio" +# English: "Style: Grid" # Source-Hash: 40c1eb0bc83e207d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGrid.1vuq4po" msgstr "Estilo: cuadrícula" +# English: "Style: Half-louvered" # Source-Hash: f516886bab2e7f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHalfLouvered.13b1few" msgstr "Estilo: media lama" +# English: "Style: Horizontal bands" # Source-Hash: 64e4389a695371a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHorizontalBands.12ethxk" msgstr "Estilo: bandas horizontales" +# English: "Style: Modern" # Source-Hash: ff6d226a425b00c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleModern.1fh8hhn" msgstr "Estilo: moderno" +# English: "Style: Panel" # Source-Hash: c6d17ba078ff47e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePanel.922jze" msgstr "Estilo: panel" +# English: "Style: Paneled glass" # Source-Hash: 5e6baad0d2ce3a62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePaneledGlass.17a17en" msgstr "Estilo: vidrio con paneles" +# English: "Style: Shaker" # Source-Hash: 6314388e6832c252 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleShaker.49z17k" msgstr "Estilo: shaker" +# English: "Style: Single" # Source-Hash: a0f8e524f3ca121f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSingle.6f3cm0" msgstr "Estilo: simple" +# English: "Style: Six-panel" # Source-Hash: 368279df3a472c86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSixPanel.u49laj" msgstr "Estilo: seis paneles" +# English: "Style: Sliding" # Source-Hash: 8f3c2571d8168fee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSliding.q81s0i" msgstr "Estilo: corredera" +# English: "Style: Tall grid" # Source-Hash: aa9d9eb137ca38d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTallGrid.1eotg3x" msgstr "Estilo: cuadrícula alta" +# English: "Style: Transom" # Source-Hash: bc164648722b63e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTransom.kb9bh6" msgstr "Estilo: montante" +# English: "Style: Triple-hung" # Source-Hash: 9a218f0c9ceb4697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTripleHung.pxnqi3" msgstr "Estilo: guillotina triple" +# English: "Style: Wide grid" # Source-Hash: 9f6df1344bd3d694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleWideGrid.12n27et" msgstr "Estilo: cuadrícula ancha" +# English: "Switch rotation axis (Y → X → Z)" # Source-Hash: 711d4050c0cb95d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch rotation axis (Y → X → Z)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.switchRotationAxisYXZ.15lohm6" msgstr "Cambiar el eje de giro (Y → X → Z)" +# English: "The walls meet at too sharp or too flat an angle" # Source-Hash: d650c71e042c2598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The walls meet at too sharp or too flat an angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWallsMeetAtTooSharp.kh3wi3" msgstr "Los muros se unen en un ángulo demasiado agudo o demasiado plano" +# English: "The window waiting there is taller than this wall" # Source-Hash: ab206ac56e65f190 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window waiting there is taller than this wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4" msgstr "La ventana prevista ahí es más alta que este muro" +# English: "Three or more walls meet here" # Source-Hash: 9c1f47138ed5c7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Three or more walls meet here" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.threeOrMoreWallsMeetHere.rgifif" msgstr "Aquí se unen tres o más muros" +# English: "Trace ceiling outline" # Source-Hash: c3ca56fa28e15f07 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace ceiling outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceCeilingOutline.1b639wv" msgstr "Trazar el contorno del techo" +# English: "Trace slab outline" # Source-Hash: bdf71499d30a29de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace slab outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceSlabOutline.z0j2lc" msgstr "Trazar el contorno del forjado" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.type.1m2zofh" msgstr "Tipo" +# English: "Type: Awning" # Source-Hash: b54eaaff3349dcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeAwning.1cj59al" msgstr "Tipo: proyectante" +# English: "Type: Barn" # Source-Hash: f399572a27417c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBarn.sbnygq" msgstr "Tipo: granero" +# English: "Type: Bay" # Source-Hash: a1df9782200b5f1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBay.1200hc1" msgstr "Tipo: mirador" +# English: "Type: Bow" # Source-Hash: ff069a390e8e3f66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBow.14s2t9p" msgstr "Tipo: mirador curvo" +# English: "Type: Cabinet" # Source-Hash: bdac5f3f2ea70fff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCabinet.1r68je5" msgstr "Tipo: armario" +# English: "Type: Casement" # Source-Hash: e0754fc03997cbf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCasement.176uo9j" msgstr "Tipo: abatible" +# English: "Type: Double" # Source-Hash: 47d873446d6e173b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDouble.af7w4i" msgstr "Tipo: doble" +# English: "Type: Double Hung" # Source-Hash: 5942e484513aaab5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDoubleHung.t482fu" msgstr "Tipo: guillotina doble" +# English: "Type: Fixed" # Source-Hash: f9497fbfceefda27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFixed.v1cjez" msgstr "Tipo: fija" +# English: "Type: Folding" # Source-Hash: 0dcf07acc9cbf000 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFolding.sxxyng" msgstr "Tipo: plegable" +# English: "Type: French" # Source-Hash: bff7331f87f279aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFrench.1y57z1b" msgstr "Tipo: francesa" +# English: "Type: Hinged" # Source-Hash: 9d2c3b82079eb769 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeHinged.1xrcfju" msgstr "Tipo: batiente" +# English: "Type: Island" # Source-Hash: b8001b90ff522838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Island" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeIsland.ufr3ce" msgstr "Tipo: isla" +# English: "Type: Louvered" # Source-Hash: 516833c5160d412e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeLouvered.oxl1cz" msgstr "Tipo: de lamas" +# English: "Type: Pocket" # Source-Hash: 291272bf848ad6ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typePocket.hc65x9" msgstr "Tipo: corredera empotrada" +# English: "Type: Single Hung" # Source-Hash: 46d0e53d6ae07c72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSingleHung.p53pyl" msgstr "Tipo: guillotina simple" +# English: "Type: Sliding" # Source-Hash: b11115b50df8faac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSliding.nwmr3f" msgstr "Tipo: corredera" +# English: "Vertical stack ↕, click to place" # Source-Hash: bff0c1694ee0a37e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical stack ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.verticalStackClickToPlace.ijcn4q" msgstr "Montante vertical ↕, haz clic para colocar" +# English: "Waste / vent" # Source-Hash: bac471ee67af6412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste / vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.wasteVent.1hvi3vj" msgstr "Desagüe / ventilación" +# English: "Window style — click or press L to cycle" # Source-Hash: 1c744b7e24fc786e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowStyleClickOrPressL.ah7qm9" msgstr "Estilo de ventana: haz clic o pulsa L para cambiar" +# English: "Window type — click or press O to cycle" # Source-Hash: dd6f0ab314fea26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowTypeClickOrPressO.wv4x35" msgstr "Tipo de ventana: haz clic o pulsa O para cambiar" -# Review: required -# Source-Hash: a48c4b2e4d71b08d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length: {field} m" +# Source-Hash: a48c4b2e4d71b08d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.lengthField" msgstr "Longitud: {field} m" -# Review: required -# Source-Hash: e53695d6669245c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tab: direction" +# Source-Hash: e53695d6669245c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.tabDirection" msgstr "Tab: dirección" -# Review: required -# Source-Hash: abb1d8c8bfb41acf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "type a length" +# Source-Hash: abb1d8c8bfb41acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.typeALength" msgstr "escribe una longitud" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.plugins" msgstr "Complementos" +# English: "2D" # Source-Hash: c54f9bac358893d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "2D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view2d" msgstr "2D" +# English: "3D" # Source-Hash: f78f2fa035f25f85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view3d" msgstr "3D" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.auto" msgstr "Auto" +# English: "Browse plugins" # Source-Hash: 7890c110650ad1e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.browsePlugins" msgstr "Explorar complementos" +# English: "Close {label}" # Source-Hash: 3b11e7af681a9dcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeLabel" msgstr "Cerrar {label}" +# English: "Close split (\\)" # Source-Hash: 3f00e92517e3f4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close split (\\)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeSplit" msgstr "Cerrar vista dividida (\\)" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.ground" msgstr "Terreno" +# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" # Source-Hash: 0d472502a4d36e86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundSolid" msgstr "{standing, select, partly {Parcialmente bajo rasante · terreno sólido} below {Bajo rasante · terreno sólido} other {Terreno sólido}}" +# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" # Source-Hash: 1b6798c307ac1b96 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundVeiled" msgstr "{standing, select, partly {Parcialmente bajo rasante · terreno velado} below {Bajo rasante · terreno velado} other {Terreno velado}}" +# English: "Keep the ground solid" # Source-Hash: ebdc976a98e82626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep the ground solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.keepTheGroundSolid" msgstr "Mantener el terreno sólido" +# English: "More views" # Source-Hash: 5f827686a006d88c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.moreViews" msgstr "Más vistas" +# English: "Pane {pane} views" # Source-Hash: f175bbcf457ab73c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane {pane} views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.paneViews" msgstr "Vistas del panel {pane}" +# English: "Pin {label}" # Source-Hash: 33c20cc33dce9a9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinLabel" msgstr "Fijar {label}" +# English: "Pin to the view bar" # Source-Hash: 3f7231b1eb2f93f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin to the view bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.pinToTheViewBar" +msgstr "Fijar a la barra de vistas" + +# English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.solid" +msgstr "Sólido" + +# English: "Split (\\)" +# Source-Hash: 98f22f3f89f17686 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split" +msgstr "Vista dividida (\\)" + +# English: "Split" +# Source-Hash: e4cd64b1680ac0d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split2" +msgstr "Dividir" + +# English: "Swap panes" +# Source-Hash: 15b0adc705923a27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.swapPanes" +msgstr "Intercambiar paneles" + +# English: "Unpin from the view bar" +# Source-Hash: 9781bcdb7a8549e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinFromTheViewBar" +msgstr "Quitar de la barra de vistas" + +# English: "Unpin {label}" +# Source-Hash: f667358dec4abdea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinLabel" +msgstr "Dejar de fijar {label}" + +# English: "Veil the ground over a level below grade" +# Source-Hash: e96581f0bbf9fa91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.veilTheGroundOverALevel" +msgstr "Velar el terreno sobre una planta bajo rasante" + +# English: "Views" +# Source-Hash: fb82f09f8ed3059e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy -msgid "editor.views.viewBar.pinToTheViewBar" -msgstr "Fijar a la barra de vistas" +msgid "editor.views.viewBar.views" +msgstr "Vistas" + +# English: "You can retry the scene or return home without reloading the whole app shell." +# Source-Hash: fdece5c2158dfe88 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.youCanRetryTheSceneOr" +msgstr "Puedes reintentar la escena o volver al inicio sin recargar toda la aplicación." + +# English: "A framing beam drawn end to end: a porch header, a deck beam, a girder." +# Source-Hash: bafa390e07160f3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.description" +msgstr "Una viga de entramado dibujada de extremo a extremo: un dintel de porche, una viga de tarima, una jácena." + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.label" +msgstr "Viga" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.actions" +msgstr "Acciones" + +# English: "Add posts" +# Source-Hash: 59f845b250e0df6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.addPosts" +msgstr "Añadir postes" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beam" +msgstr "Viga" + +# English: "Beam {size}" +# Source-Hash: 26e977da636cc882 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beamSize" +msgstr "Viga {size}" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.delete" +msgstr "Eliminar" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.depth" +msgstr "Canto" + +# English: "Dropped" +# Source-Hash: 73694f3f45cf2ee1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.dropped" +msgstr "Descolgada" + +# English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.flush" +msgstr "Enrasada" + +# English: "Follow the ceiling plane" +# Source-Hash: 0ee3edea19fd153e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.followTheCeilingPlane" +msgstr "Seguir el plano del techo" + +# English: "{length} long; its underside {underside} over the floor, the ceiling plane {plane}." +# Source-Hash: 193b75ac869c251d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.lengthLongItsUnderside" +msgstr "{length} de largo; su cara inferior a {underside} sobre el suelo y el plano del techo a {plane}." -# Source-Hash: ef3e765e692faf4c +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.solid" -msgstr "Sólido" +msgid "node.beam.panel.material" +msgstr "Material" -# Source-Hash: 98f22f3f89f17686 +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split (\\)" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split" -msgstr "Vista dividida (\\)" +msgid "node.beam.panel.move" +msgstr "Mover" -# Source-Hash: e4cd64b1680ac0d2 +# English: "1 ply" +# Source-Hash: fa876c632c284d81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split2" -msgstr "Dividir" +msgid "node.beam.panel.n1Ply" +msgstr "1 pieza" -# Source-Hash: 15b0adc705923a27 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swap panes" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.swapPanes" -msgstr "Intercambiar paneles" +msgid "node.beam.panel.placement" +msgstr "Colocación" -# Source-Hash: 9781bcdb7a8549e2 +# English: "{plies} plies" +# Source-Hash: 6ac7987d650b9742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin from the view bar" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinFromTheViewBar" -msgstr "Quitar de la barra de vistas" +msgid "node.beam.panel.pliesPlies" +msgstr "{plies} piezas" -# Source-Hash: f667358dec4abdea +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin {label}" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinLabel" -msgstr "Dejar de fijar {label}" +msgid "node.beam.panel.size" +msgstr "Tamaño" -# Source-Hash: e96581f0bbf9fa91 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veil the ground over a level below grade" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.veilTheGroundOverALevel" -msgstr "Velar el terreno sobre una planta bajo rasante" +msgid "node.beam.panel.top" +msgstr "Cara superior" -# Source-Hash: fb82f09f8ed3059e +# English: "Top elevation" +# Source-Hash: 5b4c960036b3d05b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Views" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.views" -msgstr "Vistas" +msgid "node.beam.panel.topElevation" +msgstr "Cota superior" -# Source-Hash: fdece5c2158dfe88 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "You can retry the scene or return home without reloading the whole app shell." -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.youCanRetryTheSceneOr" -msgstr "Puedes reintentar la escena o volver al inicio sin recargar toda la aplicación." +msgid "node.beam.panel.width" +msgstr "Ancho" +# English: "A topology-backed solid edited directly in the canvas." # Source-Hash: a273dacdf51b26f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A topology-backed solid edited directly in the canvas." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.description" msgstr "Sólido con topología que se edita directamente en el lienzo." +# English: "Block" # Source-Hash: 279f456ebb2f5da0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.label" msgstr "Bloque" +# English: "Small louvered exhaust vent that sits on a roof slope." # Source-Hash: b2a371b4041b5e38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Small louvered exhaust vent that sits on a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.description" msgstr "Pequeño respiradero de extracción con lamas sobre un faldón." +# English: "Box Vent" # Source-Hash: f736ca24e84825c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.label" msgstr "Aireador de cubierta" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: c0e9837e67038e92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Flange" +# Source-Hash: c0e9837e67038e92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseFlange" msgstr "Brida de base" -# Review: required -# Source-Hash: fa9118f6f4f020d7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Height" +# Source-Hash: fa9118f6f4f020d7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseHeight" msgstr "Altura de la base" -# Review: required -# Source-Hash: 0869f5a92dabb4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Inset" +# Source-Hash: 0869f5a92dabb4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseInset" msgstr "Retranqueo de la base" -# Review: required -# Source-Hash: e9a89a5e4ac8cd61 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.box" msgstr "Caja" -# Review: required -# Source-Hash: f736ca24e84825c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Vent" +# Source-Hash: f736ca24e84825c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.boxVent" msgstr "Aireador de cubierta" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cap" msgstr "Remate" -# Review: required -# Source-Hash: 0ce3b53766b550e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap Height" +# Source-Hash: 0ce3b53766b550e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.capHeight" msgstr "Altura del sombrerete" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cornerBevel" msgstr "Chaflán de esquina" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dome" msgstr "Cúpula" -# Review: required -# Source-Hash: 1c5c81aa21193d07 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome Curvature" +# Source-Hash: 1c5c81aa21193d07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.domeCurvature" msgstr "Curvatura de la cúpula" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: 8cac057f7ae6804d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Height" +# Source-Hash: 8cac057f7ae6804d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.gapHeight" msgstr "Altura de la holgura" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 8a9c2ea0783a0e53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hood Overhang" +# Source-Hash: 8a9c2ea0783a0e53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.hoodOverhang" msgstr "Vuelo de la caperuza" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: 71d82d47e11d2564 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Taper" +# Source-Hash: 71d82d47e11d2564 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.topTaper" msgstr "Estrechamiento superior" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.width" msgstr "Ancho" +# English: "A building container holding one or more levels." # Source-Hash: f49d8836ae037dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A building container holding one or more levels." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.description" msgstr "Contenedor de edificio con una o varias plantas." +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.label" msgstr "Edificio" -# Review: required -# Source-Hash: 5a8274cbe3034558 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Baskets" +# Source-Hash: 5a8274cbe3034558 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.baskets" msgstr "Cestas" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottom" msgstr "Inferior" -# Review: required -# Source-Hash: 486501f027037732 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Freezer" +# Source-Hash: 486501f027037732 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottomFreezer" msgstr "Congelador inferior" -# Review: required -# Source-Hash: 375acc0016b3aa14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bowls" +# Source-Hash: 375acc0016b3aa14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bowls" msgstr "Senos" -# Review: required -# Source-Hash: 8449b59bf054c27d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Burners on" +# Source-Hash: 8449b59bf054c27d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.burnersOn" msgstr "Fuegos encendidos" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.chimney" msgstr "Chimenea" -# Review: required -# Source-Hash: a6b76187db231ae7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Decrease {label}" +# Source-Hash: a6b76187db231ae7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.decrease" msgstr "Reducir {label}" -# Review: required -# Source-Hash: 787e10f5db16904e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door" +# Source-Hash: 787e10f5db16904e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.door" msgstr "Puerta" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.double" msgstr "Doble" -# Review: required -# Source-Hash: 63fe49d73892aac8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer" +# Source-Hash: 63fe49d73892aac8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawer" msgstr "Cajón" -# Review: required -# Source-Hash: f98242a5dacd33f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawers" +# Source-Hash: f98242a5dacd33f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawers" msgstr "Cajones" -# Review: required -# Source-Hash: 3af5347c94ae6443 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip hinge" +# Source-Hash: 3af5347c94ae6443 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipHinge" msgstr "Invertir bisagra" -# Review: required -# Source-Hash: c930f681e61c029c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip the door hinge to the opposite side" +# Source-Hash: c930f681e61c029c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipTheDoorHingeToThe" msgstr "Pasar la bisagra de la puerta al lado opuesto" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.fridge" msgstr "Frigorífico" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.gas" msgstr "Gas" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.glass" msgstr "Vidrio" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.height" msgstr "Altura" -# Review: required -# Source-Hash: 6f434b84c8b59b8c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hob" +# Source-Hash: 6f434b84c8b59b8c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.hob" msgstr "Placa" -# Review: required -# Source-Hash: d52570d41b4df776 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase {label}" +# Source-Hash: d52570d41b4df776 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.increase" msgstr "Aumentar {label}" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.induction" msgstr "Inducción" -# Review: required -# Source-Hash: 54800340a0eeab23 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Layout" +# Source-Hash: 54800340a0eeab23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.layout" msgstr "Distribución" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.left" msgstr "Izquierda" -# Review: required -# Source-Hash: 12ade82be3830a0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Micro" +# Source-Hash: 12ade82be3830a0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.micro" msgstr "Microondas" -# Review: required -# Source-Hash: a06a50e63f0c240e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move down" +# Source-Hash: a06a50e63f0c240e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveDown" msgstr "Bajar" -# Review: required -# Source-Hash: d49ea60a2312df2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move up" +# Source-Hash: d49ea60a2312df2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveUp" msgstr "Subir" -# Review: required -# Source-Hash: 596023c8b9a8f2e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven" +# Source-Hash: 596023c8b9a8f2e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.oven" msgstr "Horno" -# Review: required -# Source-Hash: 83b20797fb0a01a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pullout" +# Source-Hash: 83b20797fb0a01a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.pullout" msgstr "Extraíble" -# Review: required -# Source-Hash: 43c251f465b647ce -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rack" +# Source-Hash: 43c251f465b647ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.rack" msgstr "Estante" -# Review: required -# Source-Hash: 3c2b3026d37cbcc2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove compartment" +# Source-Hash: 3c2b3026d37cbcc2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.removeCompartment" msgstr "Quitar compartimento" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.right" msgstr "Derecha" -# Review: required -# Source-Hash: d7145f84f8e0e3fe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelf" +# Source-Hash: d7145f84f8e0e3fe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelf" msgstr "Estantería" -# Review: required -# Source-Hash: 5fbb88f1f079b6db -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves" +# Source-Hash: 5fbb88f1f079b6db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelves" msgstr "Baldas" -# Review: required -# Source-Hash: 47173085127029f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves inside" +# Source-Hash: 47173085127029f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelvesInside" msgstr "Baldas interiores" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.single" msgstr "Simple" -# Review: required -# Source-Hash: 522a7535fc93fb4f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink" +# Source-Hash: 522a7535fc93fb4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.sink" msgstr "Fregadero" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.style" msgstr "Estilo" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.surface" msgstr "Superficie" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.top" msgstr "Superior" -# Review: required -# Source-Hash: eaadfd2d19b27d87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Freezer" +# Source-Hash: eaadfd2d19b27d87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topFreezer" msgstr "Congelador superior" -# Review: required -# Source-Hash: 04f3a3462ef5d247 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top grate" +# Source-Hash: 04f3a3462ef5d247 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topGrate" msgstr "Parrilla superior" -# Review: required -# Source-Hash: 4a616e1d2ca55bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tray" +# Source-Hash: 4a616e1d2ca55bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.tray" msgstr "Bandeja" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.type" msgstr "Tipo" -# Review: required -# Source-Hash: 6c4de540d19316ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Washer" +# Source-Hash: 6c4de540d19316ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.washer" msgstr "Lavavajillas" -# Review: required -# Source-Hash: 5211a0caf3518125 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wire" +# Source-Hash: 5211a0caf3518125 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.wire" msgstr "Alambre" +# English: "A configurable parametric base cabinet." # Source-Hash: 693a0d8896f5cc43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable parametric base cabinet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.description" msgstr "Mueble bajo paramétrico configurable." -# Review: required -# Source-Hash: b0568ea943addd5b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom freezer" +# Source-Hash: b0568ea943addd5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.bottomFreezer" msgstr "Congelador inferior" -# Review: required -# Source-Hash: da78819903ae0863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cooktop type" +# Source-Hash: da78819903ae0863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.cooktopType" msgstr "Tipo de placa" -# Review: required -# Source-Hash: c608188f58ed1cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door direction" +# Source-Hash: c608188f58ed1cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.doorDirection" msgstr "Sentido de apertura" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.double" msgstr "Doble" -# Review: required -# Source-Hash: f7fb8f6393cc4cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge type" +# Source-Hash: f7fb8f6393cc4cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.fridgeType" msgstr "Tipo de frigorífico" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.gas" msgstr "Gas" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.glass" msgstr "Vidrio" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.induction" msgstr "Inducción" -# Review: required -# Source-Hash: 76df477d42cec629 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Item options" +# Source-Hash: 76df477d42cec629 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.itemOptions" msgstr "Opciones del elemento" -# Review: required -# Source-Hash: bca1b184af1c0c4b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left hinge" +# Source-Hash: bca1b184af1c0c4b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.leftHinge" msgstr "Bisagra izquierda" -# Review: required -# Source-Hash: e32da7429c85f1a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right hinge" +# Source-Hash: e32da7429c85f1a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.rightHinge" msgstr "Bisagra derecha" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.single" msgstr "Simple" -# Review: required -# Source-Hash: 4bc3042055df1e85 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink layout" +# Source-Hash: 4bc3042055df1e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.sinkLayout" msgstr "Distribución del fregadero" -# Review: required -# Source-Hash: 29cffe75281dbea6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top freezer" +# Source-Hash: 29cffe75281dbea6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.topFreezer" msgstr "Congelador superior" -# Review: required -# Source-Hash: 09c2cb2ea7b1be3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose {label}" +# Source-Hash: 09c2cb2ea7b1be3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.chooseLabel" msgstr "Elegir {label}" -# Review: required -# Source-Hash: 1dd2cac6488ddc30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No kitchen items match. Try “fridge”, “sink”, or “cabinet”." +# Source-Hash: 1dd2cac6488ddc30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge" msgstr "Ningún elemento de cocina coincide. Prueba «frigorífico», «fregadero» o «mueble»." -# Review: required -# Source-Hash: 6752675ca6280ddd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search fridge, sink, cabinets…" +# Source-Hash: 6752675ca6280ddd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchFridgeSinkCabinets" msgstr "Buscar frigorífico, fregadero, muebles…" -# Review: required -# Source-Hash: fe3c6d57a07c323d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search kitchen items" +# Source-Hash: fe3c6d57a07c323d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchKitchenItems" msgstr "Buscar elementos de cocina" -# Review: required -# Source-Hash: d44a46146db2e44c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliances & fixtures" +# Source-Hash: d44a46146db2e44c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.appliances" msgstr "Electrodomésticos y equipamiento" -# Review: required -# Source-Hash: d4d3ad7c099b6513 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinets" +# Source-Hash: d4d3ad7c099b6513 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.cabinets" msgstr "Muebles" -# Review: required -# Source-Hash: 465db52f98c2d94f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside" +# Source-Hash: 465db52f98c2d94f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBeside" msgstr "Añadir al lado" -# Review: required -# Source-Hash: 9733e2ad85deb6d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside selected item" +# Source-Hash: 9733e2ad85deb6d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBesideSelectedItem" msgstr "Añadir junto al elemento seleccionado" -# Review: required -# Source-Hash: 4b2f676fb4c9ec63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add {label}" +# Source-Hash: 4b2f676fb4c9ec63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addLabel" msgstr "Añadir {label}" -# Review: required -# Source-Hash: 5122b8e9b4ec6a00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "An upper cabinet attached to this item will be removed to make room." +# Source-Hash: 5122b8e9b4ec6a00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis" msgstr "Se quitará un mueble alto unido a este elemento para dejar espacio." -# Review: required -# Source-Hash: 5737c4567e0f6863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cabinet" msgstr "Mueble" -# Review: required -# Source-Hash: c0774c185060fc13 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cancel" msgstr "Cancelar" -# Review: required -# Source-Hash: f42912b116015f58 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "This {width} item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again." +# Source-Hash: f42912b116015f58 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cannotFit" msgstr "Este elemento de {width} no cabe en el tramo actual ni en sus límites de pared o esquina. Mueve un mueble del extremo o libera espacio y vuelve a intentarlo." -# Review: required -# Source-Hash: b7eeb26a356d03c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Inserts on the left of the selected item.} other {Inserts on the right of the selected item.}}" +# Source-Hash: b7eeb26a356d03c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.insertsBeside" msgstr "{side, select, left {Se inserta a la izquierda del elemento seleccionado.} other {Se inserta a la derecha del elemento seleccionado.}}" -# Review: required -# Source-Hash: f65332177316d11d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The kitchen changed. Select an item again to refresh the preview." +# Source-Hash: f65332177316d11d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenChanged" msgstr "La cocina ha cambiado. Vuelve a seleccionar un elemento para actualizar la vista previa." -# Review: required -# Source-Hash: 5b6990b17111fd6b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run preview, highlighted item is the proposed change" +# Source-Hash: 5b6990b17111fd6b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs" msgstr "Vista previa del tramo de cocina; el elemento resaltado es el cambio propuesto" -# Review: required -# Source-Hash: cb59562ba7eb1d25 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left side" +# Source-Hash: cb59562ba7eb1d25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.leftSide" msgstr "Lado izquierdo" -# Review: required -# Source-Hash: 766354d53f17d78c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: moves {distance}" +# Source-Hash: 766354d53f17d78c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.moved" msgstr "{name}: se mueve {distance}" -# Review: required -# Source-Hash: df1083d9ffc2c59d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {width}" +# Source-Hash: df1083d9ffc2c59d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.previewItem" msgstr "{name}: {width}" -# Review: required -# Source-Hash: ada7fbece9ca9e3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace selected item" +# Source-Hash: ada7fbece9ca9e3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceSelectedItem" msgstr "Reemplazar el elemento seleccionado" -# Review: required -# Source-Hash: ab8b52d2704537d2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replaces only the selected item." +# Source-Hash: ab8b52d2704537d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem" msgstr "Solo reemplaza el elemento seleccionado." -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWith" msgstr "Reemplazar por…" -# Review: required -# Source-Hash: 948fe657641f298c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with {label}" +# Source-Hash: 948fe657641f298c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWithLabel" msgstr "Reemplazar por {label}" -# Review: required -# Source-Hash: 154f9fa86b7670ba -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {before} → {after}" +# Source-Hash: 154f9fa86b7670ba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.resized" msgstr "{name}: {before} → {after}" -# Review: required -# Source-Hash: 4ecc493400fb6aa2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right side" +# Source-Hash: 4ecc493400fb6aa2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.rightSide" msgstr "Lado derecho" -# Review: required -# Source-Hash: f2c30e7e21cf27e6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to update this kitchen." +# Source-Hash: f2c30e7e21cf27e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen" msgstr "No se puede actualizar esta cocina." +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.label" msgstr "Mueble modular" -# Review: required -# Source-Hash: 740e0a4e4afa78ea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished height extends {overflow} mm above the ceiling." +# Source-Hash: 740e0a4e4afa78ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.aboveCeiling" msgstr "La altura acabada sobrepasa el techo en {overflow} mm." -# Review: required -# Source-Hash: e3b4d2ed4f6f5efb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add chimney" +# Source-Hash: e3b4d2ed4f6f5efb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addChimney" msgstr "Añadir campana" -# Review: required -# Source-Hash: 90bdc92c0a91d060 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add compartment" +# Source-Hash: 90bdc92c0a91d060 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addCompartment" msgstr "Añadir compartimento" -# Review: required -# Source-Hash: ece05d9b6f4b616d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add wall cabinet" +# Source-Hash: ece05d9b6f4b616d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addWallCabinet" msgstr "Añadir mueble alto" -# Review: required -# Source-Hash: f571cb2d06ae01ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Advanced" +# Source-Hash: f571cb2d06ae01ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.advanced" msgstr "Avanzado" -# Review: required -# Source-Hash: 7f4dcb90defe1ac2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance" +# Source-Hash: 7f4dcb90defe1ac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.appliance" msgstr "Electrodoméstico" -# Review: required -# Source-Hash: bd5e3f533412ff6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance front" +# Source-Hash: bd5e3f533412ff6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.applianceFront" msgstr "Frente de electrodoméstico" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.auto" msgstr "Auto" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.bar" msgstr "Barra" -# Review: required -# Source-Hash: 133893b177bd05d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Cabinet" +# Source-Hash: 133893b177bd05d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.baseCabinet" msgstr "Mueble bajo" -# Review: required -# Source-Hash: d63f16df2c754126 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet construction, compartments, and opening preview." +# Source-Hash: d63f16df2c754126 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview" msgstr "Construcción del mueble, compartimentos y vista previa de apertura." -# Review: required -# Source-Hash: d62dc35dac835228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet panel" +# Source-Hash: d62dc35dac835228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetPanel" msgstr "Panel del mueble" -# Review: required -# Source-Hash: 81caf4809d58b5f5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet Type" +# Source-Hash: 81caf4809d58b5f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetType" msgstr "Tipo de mueble" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.center" msgstr "Centro" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.close" msgstr "Cerrar" -# Review: required -# Source-Hash: cf29134333e47184 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close cabinet" +# Source-Hash: cf29134333e47184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.closeCabinet" msgstr "Cerrar mueble" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.commonWallCabinetHeightsUseThe" msgstr "Alturas habituales de muebles altos. Usa el control deslizante de abajo para una altura personalizada." -# Review: required -# Source-Hash: fe9e63d5c5087386 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compartments" +# Source-Hash: fe9e63d5c5087386 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.compartments" msgstr "Compartimentos" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cutout" msgstr "Recorte" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: 82c9ea6f403fc934 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit kitchen run" +# Source-Hash: 82c9ea6f403fc934 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.editKitchenRun" msgstr "Editar tramo de cocina" -# Review: required -# Source-Hash: 7e9bbeb035fc73e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fill to ceiling" +# Source-Hash: 7e9bbeb035fc73e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.fillToCeiling" msgstr "Hasta el techo" -# Review: required -# Source-Hash: b3907da0ad234f10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.finish" msgstr "Terminar" -# Review: required -# Source-Hash: c1796152935221ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front construction" +# Source-Hash: c1796152935221ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.frontConstruction" msgstr "Construcción del frente" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.handles" msgstr "Tiradores" - -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.heightPreset" msgstr "Altura predefinida" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.hole" msgstr "Orificio" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.inset" msgstr "Empotrado" -# Review: required -# Source-Hash: 1176d2568d093bf1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen" +# Source-Hash: 1176d2568d093bf1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchen" msgstr "Cocina" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchenRun" msgstr "Tramo de cocina" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.knob" msgstr "Pomo" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.mounting" msgstr "Montaje" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.none" msgstr "Ninguno" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.open" msgstr "Abrir" -# Review: required -# Source-Hash: b8ecc16c07d66f42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open Animation" +# Source-Hash: b8ecc16c07d66f42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openAnimation" msgstr "Animación de apertura" -# Review: required -# Source-Hash: 2d4adbbd70c3ae6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open cabinet" +# Source-Hash: 2d4adbbd70c3ae6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openCabinet" msgstr "Abrir mueble" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.overlay" msgstr "Superpuesta" -# Review: required -# Source-Hash: a35b7357943a5cae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel-ready" +# Source-Hash: a35b7357943a5cae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.panelReady" msgstr "Panelable" -# Review: required -# Source-Hash: 3d0d4eee59177824 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Planning checks" +# Source-Hash: 3d0d4eee59177824 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.planningChecks" msgstr "Comprobaciones de diseño" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.play" msgstr "Reproducir" -# Review: required -# Source-Hash: c530f8af9dfd846f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play animation" +# Source-Hash: c530f8af9dfd846f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.playAnimation" msgstr "Reproducir animación" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.raisedArch" msgstr "Arco en relieve" -# Review: required -# Source-Hash: a8639a97ab07cd59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove wall cabinet" +# Source-Hash: a8639a97ab07cd59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.removeWallCabinet" msgstr "Quitar mueble alto" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.replaceWith" msgstr "Reemplazar por…" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.revealGap" msgstr "Holgura entre frentes" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.shaker" msgstr "Shaker" -# Review: required -# Source-Hash: c378ed346bf83375 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared countertop, plinth, and finishes" +# Source-Hash: c378ed346bf83375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.sharedCountertopPlinthAndFinishes" msgstr "Encimera, zócalo y acabados compartidos" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.slab" msgstr "Forjado" -# Review: required -# Source-Hash: 8606fe2c60764a95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler." +# Source-Hash: 8606fe2c60764a95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.solidMatchingFrontBetweenTheCabinet" msgstr "Frente macizo a juego entre el mueble y la pared. La encimera y el zócalo continúan sobre este relleno." -# Review: required -# Source-Hash: 5a4d5353cc496a63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard width" +# Source-Hash: 5a4d5353cc496a63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.standardWidth" msgstr "Ancho estándar" -# Review: required -# Source-Hash: 4a784e3e95a08715 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop" +# Source-Hash: 4a784e3e95a08715 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stop" msgstr "Detener" -# Review: required -# Source-Hash: 72c9fcd7a0eb5380 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop animation" +# Source-Hash: 72c9fcd7a0eb5380 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stopAnimation" msgstr "Detener animación" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: bfd618690e0c2f24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Cabinet" +# Source-Hash: bfd618690e0c2f24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.tallCabinet" msgstr "Columna" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.top" msgstr "Superior" -# Review: required -# Source-Hash: d09654ebd12a538a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Cabinet" +# Source-Hash: d09654ebd12a538a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCabinet" msgstr "Mueble superior" -# Review: required -# Source-Hash: bc19e7805b3e3703 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top / Ceiling" +# Source-Hash: bc19e7805b3e3703 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCeiling" msgstr "Superior / Techo" -# Review: required -# Source-Hash: 29ea8a68eed99925 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top depth" +# Source-Hash: 29ea8a68eed99925 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topDepth" msgstr "Fondo superior" -# Review: required -# Source-Hash: 2d40f591e7dcbf5a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top height" +# Source-Hash: 2d40f591e7dcbf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topHeight" msgstr "Altura superior" -# Review: required -# Source-Hash: 3b878d6015c03945 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trim / Soffit" +# Source-Hash: 3b878d6015c03945 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.trimSoffit" msgstr "Remate / Plafón" -# Review: required -# Source-Hash: a6e5be20001e8d26 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Uses the cabinet front style, reveal, handle, and front material settings below." +# Source-Hash: a6e5be20001e8d26 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.usesTheCabinetFrontStyleReveal" msgstr "Usa el estilo de frente, la holgura, el tirador y el material de frente definidos abajo." -# Review: required -# Source-Hash: cd55643590c64a42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall filler" +# Source-Hash: cd55643590c64a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.wallFiller" msgstr "Relleno de pared" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: 4ef03cfd6782316b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base cabinet" +# Source-Hash: 4ef03cfd6782316b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.baseCabinet" msgstr "Mueble bajo" -# Review: required -# Source-Hash: d353fff221a78228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dishwasher" +# Source-Hash: d353fff221a78228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.dishwasher" msgstr "Lavavajillas" -# Review: required -# Source-Hash: ecb596567b8b7179 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer cabinet" +# Source-Hash: ecb596567b8b7179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.drawerCabinet" msgstr "Mueble de cajones" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.fridge" msgstr "Frigorífico" -# Review: required -# Source-Hash: 72d587d58b645a81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas cooktop" +# Source-Hash: 72d587d58b645a81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.gasCooktop" msgstr "Placa de gas" -# Review: required -# Source-Hash: 6b02c36e6aac2cd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction cooktop" +# Source-Hash: 6b02c36e6aac2cd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.inductionCooktop" msgstr "Placa de inducción" -# Review: required -# Source-Hash: d5fcb790aef707bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven Tower" +# Source-Hash: d5fcb790aef707bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.ovenTower" msgstr "Columna de horno" -# Review: required -# Source-Hash: edd4d1ab7525dcaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink cabinet" +# Source-Hash: edd4d1ab7525dcaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.sinkCabinet" msgstr "Mueble de fregadero" -# Review: required -# Source-Hash: 9b7427a8954f618b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Pantry" +# Source-Hash: 9b7427a8954f618b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.tallPantry" msgstr "Despensero alto" -# Review: required -# Source-Hash: 3bd9629eb17cc572 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall cabinet" +# Source-Hash: 3bd9629eb17cc572 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.wallCabinet" msgstr "Mueble alto" -# Review: required -# Source-Hash: 7c40d26770eede82 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add left" +# Source-Hash: 7c40d26770eede82 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addLeft" msgstr "Añadir a la izquierda" -# Review: required -# Source-Hash: 25082cdf77e219c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add right" +# Source-Hash: 25082cdf77e219c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addRight" msgstr "Añadir a la derecha" -# Review: required -# Source-Hash: 4b1204c1b706f618 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Align cabinets & handles" +# Source-Hash: 4b1204c1b706f618 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.alignCabinetsHandles" msgstr "Alinear muebles y tiradores" -# Review: required -# Source-Hash: 8c73c7a9406db5ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Applies depth, carcass, plinth, and countertop thickness to this run." +# Source-Hash: 8c73c7a9406db5ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop" msgstr "Aplica a este tramo el fondo, el casco, el zócalo y el grosor de la encimera." -# Review: required -# Source-Hash: ef576e6c8f9ebe5f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} module copy} other {Create {number} module copies}}" +# Source-Hash: ef576e6c8f9ebe5f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreate" msgstr "{count, plural, one {Crear {number} copia del módulo} many {Crear {number} copias del módulo} other {Crear {number} copias del módulo}}" -# Review: required -# Source-Hash: 0eec3888096306a1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} {name} copy} other {Create {number} {name} copies}}" +# Source-Hash: 0eec3888096306a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreateNamed" msgstr "{count, plural, one {Crear {number} copia de {name}} many {Crear {number} copias de {name}} other {Crear {number} copias de {name}}}" -# Review: required -# Source-Hash: 8cdc0b808b551d3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a copy count from 1 to 20 and spacing from 0 to 2 m" +# Source-Hash: 8cdc0b808b551d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayInvalidOptions" msgstr "Elige un número de copias de 1 a 20 y una separación de 0 a 2 m" -# Review: required -# Source-Hash: cb89fe4161e87d68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "There is not enough room in this run for the requested array" +# Source-Hash: cb89fe4161e87d68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoRoom" msgstr "No hay espacio suficiente en este tramo para la serie solicitada" -# Review: required -# Source-Hash: 075d027345689724 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a standard or appliance module as the source" +# Source-Hash: 075d027345689724 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoSource" msgstr "Elige un módulo estándar o de electrodoméstico como origen" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.auto" msgstr "Auto" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.back" msgstr "Volver" -# Review: required -# Source-Hash: b2c3a03bf67a97a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Balances standard base cabinets while keeping appliance and corner-filler widths fixed." +# Source-Hash: b2c3a03bf67a97a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping" msgstr "Equilibra los muebles bajos estándar manteniendo fijos los anchos de electrodomésticos y rellenos de esquina." -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.bar" msgstr "Barra" -# Review: required -# Source-Hash: ccec5da17ca39eee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar counter" +# Source-Hash: ccec5da17ca39eee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barCounter" msgstr "Barra de bar" -# Review: required -# Source-Hash: 97c1605fff221752 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar depth" +# Source-Hash: 97c1605fff221752 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barDepth" msgstr "Fondo de la barra" -# Review: required -# Source-Hash: 9514a6ddebdca34e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar height" +# Source-Hash: 9514a6ddebdca34e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barHeight" msgstr "Altura de la barra" -# Review: required -# Source-Hash: 7af3b69b4b5aeb19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.carcassHeight" msgstr "Altura del casco" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.center" msgstr "Centro" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.commonWallCabinetHeightsUseThe" msgstr "Alturas habituales de muebles altos. Usa el control deslizante de abajo para una altura personalizada." -# Review: required -# Source-Hash: 88ccc87b9b45a464 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies" +# Source-Hash: 88ccc87b9b45a464 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copies" msgstr "Copias" -# Review: required -# Source-Hash: d215c489b8385d9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space." +# Source-Hash: d215c489b8385d9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure" msgstr "Las copias incluyen la estructura del mueble de origen y cualquier mueble alto unido. Los módulos existentes no se mueven; la serie solicitada debe caber en el espacio disponible del tramo." -# Review: required -# Source-Hash: 221d1b64649b08f8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner fillers cannot be used as array sources" +# Source-Hash: 221d1b64649b08f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cornerFillersCannotBeUsedAs" msgstr "Los rellenos de esquina no pueden ser origen de una serie" -# Review: required -# Source-Hash: d8d1bc5a0f9fa0cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop depth" +# Source-Hash: d8d1bc5a0f9fa0cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopDepth" msgstr "Fondo de la encimera" -# Review: required -# Source-Hash: 10bba4a11996c84b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop height" +# Source-Hash: 10bba4a11996c84b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopHeight" msgstr "Altura de la encimera" -# Review: required -# Source-Hash: 8abdfbc1798d9eaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Create array" +# Source-Hash: 8abdfbc1798d9eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.createArray" msgstr "Crear serie" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cutout" msgstr "Recorte" -# Review: required -# Source-Hash: 555240ffcca44233 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete {name}" +# Source-Hash: 555240ffcca44233 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.deleteModule" msgstr "Eliminar {name}" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 76cd03300faa9b2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Direction" +# Source-Hash: 76cd03300faa9b2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.direction" msgstr "Dirección" -# Review: required -# Source-Hash: 5ca5884d3ff7edf4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate along run" +# Source-Hash: 5ca5884d3ff7edf4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.duplicateAlongRun" msgstr "Duplicar a lo largo del tramo" -# Review: required -# Source-Hash: a834f687290afc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize all resizeable standard base cabinets in this run" +# Source-Hash: a834f687290afc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAll" msgstr "Igualar todos los muebles bajos estándar redimensionables de este tramo" -# Review: required -# Source-Hash: 24da8a2de2c6e345 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The resizeable cabinet widths are already equal" +# Source-Hash: 24da8a2de2c6e345 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAlreadyEqual" msgstr "Los anchos de los muebles redimensionables ya son iguales" -# Review: required -# Source-Hash: c38a4faef2097b73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "At least two standard base cabinets are required" +# Source-Hash: c38a4faef2097b73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNeedsTwo" msgstr "Se necesitan al menos dos muebles bajos estándar" -# Review: required -# Source-Hash: 61f15ae24419430c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The available run width cannot satisfy the cabinet width limits" +# Source-Hash: 61f15ae24419430c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNoFit" msgstr "El ancho disponible del tramo no cumple los límites de ancho de los muebles" -# Review: required -# Source-Hash: 7617bb3b455dcdda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize widths" +# Source-Hash: 7617bb3b455dcdda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeWidths" msgstr "Igualar anchos" -# Review: required -# Source-Hash: ad8c948d883a0c9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Left: {width} mm} other {Right: {width} mm}}" +# Source-Hash: ad8c948d883a0c9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fillerPlanSide" msgstr "{side, select, left {Izquierda: {width} mm} other {Derecha: {width} mm}}" -# Review: required -# Source-Hash: 47ccd8c8b04475cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish & align" +# Source-Hash: 47ccd8c8b04475cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishAlign" msgstr "Acabado y alineación" -# Review: required -# Source-Hash: 855892e18c74153a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished back" +# Source-Hash: 855892e18c74153a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedBack" msgstr "Trasera acabada" -# Review: required -# Source-Hash: 7ab47aea30c73663 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished exposed ends" +# Source-Hash: 7ab47aea30c73663 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedExposedEnds" msgstr "Laterales vistos acabados" -# Review: required -# Source-Hash: c9356f25caf33eec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fits 50–150 mm gaps at run ends beside perpendicular walls." +# Source-Hash: c9356f25caf33eec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fits50150MmGapsAt" msgstr "Cubre huecos de 50–150 mm en los extremos del tramo junto a paredes perpendiculares." -# Review: required -# Source-Hash: b6f541bc28e38d2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fit wall fillers" +# Source-Hash: b6f541bc28e38d2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fitWallFillers" msgstr "Ajustar rellenos de pared" -# Review: required -# Source-Hash: 2af738f010ce28f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fronts" +# Source-Hash: 2af738f010ce28f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fronts" msgstr "Frentes" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.handles" msgstr "Tiradores" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.heightPreset" msgstr "Altura predefinida" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.hole" msgstr "Orificio" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.inset" msgstr "Empotrado" -# Review: required -# Source-Hash: 2997c230c529590d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Island & Bar" +# Source-Hash: 2997c230c529590d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.islandBar" msgstr "Isla y barra" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.kitchenRun" msgstr "Tramo de cocina" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.knob" msgstr "Pomo" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.left" msgstr "Izquierda" -# Review: required -# Source-Hash: 3c88a1840043e15a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments." +# Source-Hash: 3c88a1840043e15a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd" msgstr "Iguala alturas de muebles bajos, traseras, holguras y tiradores. Alinea los muebles altos; conserva los electrodomésticos altos y los compartimentos personalizados." -# Review: required -# Source-Hash: b112e31fe44cc94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Metric · 600 mm" +# Source-Hash: b112e31fe44cc94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.metricProfile" msgstr "Métrico · 600 mm" - -# Review: required -# Source-Hash: 4739f6315c5e8680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Module {number}" +# Source-Hash: 4739f6315c5e8680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.moduleNumber" msgstr "Módulo {number}" -# Review: required -# Source-Hash: b8801184e07e4815 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modules" +# Source-Hash: b8801184e07e4815 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.modules" msgstr "Módulos" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.mounting" msgstr "Montaje" -# Review: required -# Source-Hash: a3914bd96488175c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No eligible module" +# Source-Hash: a3914bd96488175c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.noEligibleModule" msgstr "Ningún módulo válido" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.none" msgstr "Ninguno" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.overlay" msgstr "Superpuesta" -# Review: required -# Source-Hash: 49fb05dfafd6001d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth height" +# Source-Hash: 49fb05dfafd6001d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.plinthHeight" msgstr "Altura del zócalo" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.position" msgstr "Posición" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.raisedArch" msgstr "Arco en relieve" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.revealGap" msgstr "Holgura entre frentes" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.right" msgstr "Derecha" -# Review: required -# Source-Hash: e022da49ae8b4aef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Seating overhang" +# Source-Hash: e022da49ae8b4aef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.seatingOverhang" msgstr "Voladizo para asientos" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.shaker" msgstr "Shaker" -# Review: required -# Source-Hash: cba29b18ce666b0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared Plinth & Countertop" +# Source-Hash: cba29b18ce666b0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sharedPlinthCountertop" msgstr "Zócalo y encimera compartidos" -# Review: required -# Source-Hash: f7d577c4064c16ff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show countertop" +# Source-Hash: f7d577c4064c16ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showCountertop" msgstr "Mostrar encimera" -# Review: required -# Source-Hash: 5d98e04dfd2e0a83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show plinth" +# Source-Hash: 5d98e04dfd2e0a83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showPlinth" msgstr "Mostrar zócalo" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.slab" msgstr "Forjado" -# Review: required -# Source-Hash: 5bdc0a959203c52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Source module" +# Source-Hash: 5bdc0a959203c52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sourceModule" msgstr "Módulo de origen" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.spacing" msgstr "Separación" -# Review: required -# Source-Hash: 40e3105cbe68d163 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard dimensions" +# Source-Hash: 40e3105cbe68d163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.standardDimensions" msgstr "Dimensiones estándar" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.style" msgstr "Estilo" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" # Source-Hash: e9d7ad9851d476d9 # Review: required -# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartment" msgstr "{type, select, shelf {Balda} drawer {Cajón} door {Puerta} oven {Horno} microwave {Microondas} dishwasher {Lavavajillas} sink {Fregadero} cooktop_gas {Placa de gas} cooktop_induction {Placa de inducción} pull_out_pantry {Despensero extraíble} fridge_single {Frigorífico} fridge_double {Frigorífico doble} fridge_top_freezer {Frigorífico con congelador superior} fridge_bottom_freezer {Frigorífico con congelador inferior} hood_pyramid {Campana piramidal} hood_curved_glass {Campana de vidrio curvo} other {{raw}}}" -# Review: required -# Source-Hash: 98582c257f149d44 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} compartment} other {{number} compartments}}" +# Source-Hash: 98582c257f149d44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartments" msgstr "{count, plural, one {{number} compartimento} many {{number} compartimentos} other {{number} compartimentos}}" -# Review: required -# Source-Hash: 65b1cbba3b92c5b2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Empty" +# Source-Hash: 65b1cbba3b92c5b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryEmpty" msgstr "Vacío" -# Review: required -# Source-Hash: 5db8b868684cf9f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall cabinet" +# Source-Hash: 5db8b868684cf9f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryTall" msgstr "Columna" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.top" msgstr "Superior" -# Review: required -# Source-Hash: 88c85283afe0c0bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to finish this kitchen run." +# Source-Hash: 88c85283afe0c0bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.unableToFinish" msgstr "No se puede terminar este tramo de cocina." -# Review: required -# Source-Hash: 8f668fb3e1a1696a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use {name} as array source" +# Source-Hash: 8f668fb3e1a1696a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useAsArraySource" msgstr "Usar {name} como origen de la serie" -# Review: required -# Source-Hash: 9308a109d017dcd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use this module as the array source" +# Source-Hash: 9308a109d017dcd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useThisModuleAsTheArray" msgstr "Usar este módulo como origen de la serie" -# Review: required -# Source-Hash: 1ce253a4d11bfa68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "US · 24 in" +# Source-Hash: 1ce253a4d11bfa68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.usProfile" msgstr "EE. UU. · 24 in" -# Review: required -# Source-Hash: a2d852b2c5629796 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waterfall ends" +# Source-Hash: a2d852b2c5629796 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.waterfallEnds" msgstr "Laterales en cascada" +# English: "An editable module inside a modular cabinet run." # Source-Hash: ffc8d80e438f40b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An editable module inside a modular cabinet run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.description" msgstr "Módulo editable dentro de una línea de muebles modulares." +# English: "Cabinet Module" # Source-Hash: d5c19ec529e26a98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet Module" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.label" msgstr "Módulo de mueble" -# Source-Hash: 4d6d5e9439f40e9b +# English: "Bottom chord" +# Source-Hash: 25be6d614ec09df9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.bottomChord" +msgstr "Cordón inferior" + +# English: "Ceiling joists" +# Source-Hash: a0c71abc0f146a2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.ceilingJoists" +msgstr "Viguetas de techo" + +# English: "Decking" +# Source-Hash: 7d286c87375d417e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.decking" +msgstr "Entablado" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.finish" +msgstr "Acabado" + +# English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.framing" +msgstr "Entramado" + +# English: "Joists" +# Source-Hash: c7aa596536ceeecb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.joists" +msgstr "Viguetas" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.layers" +msgstr "Capas" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.none" +msgstr "Ninguno" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.preset" +msgstr "Preajuste" + +# English: "The height is the finished underside; the layers rise from it." +# Source-Hash: 2bc7b44c217cb8fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside" +msgstr "La altura es la cara inferior acabada; las capas suben desde ella." + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.thickness" +msgstr "Espesor" + +# English: "Truss bottom chords" +# Source-Hash: 2cf52770d658d006 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.trussBottomChords" +msgstr "Cordones inferiores de cercha" + # English: "A polygon-bounded ceiling surface that hosts ceiling-mounted items." +# Source-Hash: 4d6d5e9439f40e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.description" msgstr "Superficie de techo delimitada por un polígono que aloja elementos de techo." +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.label" msgstr "Techo" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.addHole" msgstr "Añadir hueco" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.area" msgstr "Área" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.auto" msgstr "Auto" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.ceiling" msgstr "Techo" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.customHeight" msgstr "Altura personalizada" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.cutOpening" msgstr "Abrir hueco" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.done" msgstr "Hecho" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.followsLevel" msgstr "Sigue la planta" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.height" msgstr "Altura" +# English: "High (3.0m)" # Source-Hash: 703f56131ac45e60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (3.0m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high30m" msgstr "Alto (3,0 m)" +# English: "High (9'0\")" # Source-Hash: 4ed40bdab397fe22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (9'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high90" msgstr "Alto (9'0\")" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.holes" msgstr "Huecos" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.info" msgstr "Info" +# English: "Limited by the level to {height} — raise the level height for a taller ceiling." # Source-Hash: 6925a008b1924062 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Limited by the level to {height} — raise the level height for a taller ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.limitedByLevel" msgstr "Limitado por la planta a {height}: sube la altura de la planta para un techo más alto." +# English: "Low (2.4m)" # Source-Hash: c7410ff087be3a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (2.4m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low24m" msgstr "Bajo (2,4 m)" +# English: "Low (8'0\")" # Source-Hash: a59425761b7c9084 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (8'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low80" msgstr "Bajo (8'0\")" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.move" msgstr "Mover" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.noHoles" msgstr "Sin huecos" +# English: "Standard (2.5m)" # Source-Hash: 24f5fbd158798197 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2.5m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard25m" msgstr "Estándar (2,5 m)" +# English: "Standard (8'6\")" # Source-Hash: 73cf00cf1900c22b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (8'6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard86" msgstr "Estándar (8'6\")" +# English: "Taller than this level ({available} available). Raise the level height first." # Source-Hash: 065626498678bc5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Taller than this level ({available} available). Raise the level height first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.tallerThanLevel" msgstr "Más alto que esta planta ({available} disponibles). Sube primero la altura de la planta." +# English: "Vertical masonry stack on a roof segment." # Source-Hash: ea12ce3c2b3588e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical masonry stack on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.description" msgstr "Chimenea vertical de obra sobre un faldón." +# English: "Chimney" # Source-Hash: 95169058fec6db14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.label" msgstr "Chimenea" -# Review: required -# Source-Hash: dc393661f0038c2d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Above Ridge" +# Source-Hash: dc393661f0038c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.aboveRidge" msgstr "Sobre la cumbrera" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.back" msgstr "Volver" -# Review: required -# Source-Hash: 0d42509b34b05dec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bands" +# Source-Hash: 0d42509b34b05dec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.bands" msgstr "Bandas" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cap" msgstr "Remate" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimney" msgstr "Chimenea" -# Review: required -# Source-Hash: b619f366d2a1dfda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney Type" +# Source-Hash: b619f366d2a1dfda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimneyType" msgstr "Tipo de chimenea" -# Review: required -# Source-Hash: 6786ac75cbe9af57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.corbeled" msgstr "Con ménsulas" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cornerBevel" msgstr "Chaflán de esquina" -# Review: required -# Source-Hash: 8a4beb9a57edc26e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Count" +# Source-Hash: 8a4beb9a57edc26e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.count" msgstr "Cantidad" -# Review: required -# Source-Hash: f8ba7d73d3006d57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cricket" msgstr "Caballete desviador" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cutoutOffset" msgstr "Desfase del recorte" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.diameter" msgstr "Diámetro" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.double" msgstr "Doble" -# Review: required -# Source-Hash: 150340cd05f4a6dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extent" +# Source-Hash: 150340cd05f4a6dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.extent" msgstr "Extensión" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flat" msgstr "Plana" -# Review: required -# Source-Hash: f817c09ef0bb6d69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flues" +# Source-Hash: f817c09ef0bb6d69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flues" msgstr "Conductos" -# Review: required -# Source-Hash: a3c10560fa3c1612 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Footprint" +# Source-Hash: a3c10560fa3c1612 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.footprint" msgstr "Huella" -# Review: required -# Source-Hash: e360f89d3c0ec456 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front" +# Source-Hash: e360f89d3c0ec456 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.front" msgstr "Frente" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 5b490bc19dddf201 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hollow Depth" +# Source-Hash: 5b490bc19dddf201 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.hollowDepth" msgstr "Profundidad del hueco" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.length" msgstr "Longitud" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.none" msgstr "Ninguno" -# Review: required -# Source-Hash: 0f0e13abf9b2bb67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Offset" +# Source-Hash: 0f0e13abf9b2bb67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.offset" msgstr "Desfase" -# Review: required -# Source-Hash: 88540def2ce16ac5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.overhang" msgstr "Vuelo" -# Review: required -# Source-Hash: 6ce059aee1386f72 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panels" +# Source-Hash: 6ce059aee1386f72 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.panels" msgstr "Paneles" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rectangular" msgstr "Rectangular" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.round" msgstr "Redondo" -# Review: required -# Source-Hash: 8c6e3985459dacf9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.shoulder" msgstr "Hombro" -# Review: required -# Source-Hash: 84b9195c73dd7a67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Side Margin" +# Source-Hash: 84b9195c73dd7a67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sideMargin" msgstr "Margen lateral" -# Review: required -# Source-Hash: a05a390ae205fdd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple" +# Source-Hash: a05a390ae205fdd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.simple" msgstr "Sencillo" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.single" msgstr "Simple" -# Review: required -# Source-Hash: f6998707123fe72d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sloped" +# Source-Hash: f6998707123fe72d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sloped" msgstr "Inclinado" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.spacing" msgstr "Separación" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.square" msgstr "Cuadrado" -# Review: required -# Source-Hash: 2c6b1cf17a973d67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.stepped" msgstr "Escalonado" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.tapered" msgstr "Ahusado" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.thickness" msgstr "Espesor" -# Review: required -# Source-Hash: 14b162761ce18fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Offset" +# Source-Hash: 14b162761ce18fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.topOffset" msgstr "Desfase superior" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.wallThickness" msgstr "Grosor de pared" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: abff67400a76e5eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brick" +# Source-Hash: abff67400a76e5eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.brick" msgstr "Ladrillo" -# Review: required -# Source-Hash: f6cbdaa17e989b91 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modern" +# Source-Hash: f6cbdaa17e989b91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.modern" msgstr "Moderno" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.round" msgstr "Redondo" +# English: "A parametric column with configurable cross-section, base, and capital." # Source-Hash: 6a06e628257f3393 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A parametric column with configurable cross-section, base, and capital." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.description" msgstr "Pilar paramétrico con sección, basa y capitel configurables." +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.label" msgstr "Pilar" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: 14b21685d375581a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "A-Frame" +# Source-Hash: 14b21685d375581a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.aFrame" msgstr "Marco en A" -# Review: required -# Source-Hash: 4007d88108e25c2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply preset..." +# Source-Hash: 4007d88108e25c2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyPreset" msgstr "Aplicar ajuste predefinido…" -# Review: required -# Source-Hash: dbfe7c561383c964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply proportion..." +# Source-Hash: dbfe7c561383c964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyProportion" msgstr "Aplicar proporción…" -# Review: required -# Source-Hash: 1f3fd938f0b26c9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Depth" +# Source-Hash: 1f3fd938f0b26c9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomDepth" msgstr "Fondo inferior" -# Review: required -# Source-Hash: 85576e19cd3da914 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Height" +# Source-Hash: 85576e19cd3da914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomHeight" msgstr "Altura inferior" -# Review: required -# Source-Hash: 4df0bbb04744a810 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Spread" +# Source-Hash: 4df0bbb04744a810 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomSpread" msgstr "Apertura inferior" -# Review: required -# Source-Hash: 67479fecb980a441 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Step Spread" +# Source-Hash: 67479fecb980a441 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomStepSpread" msgstr "Separación de escalones inferiores" -# Review: required -# Source-Hash: 6ea977c42202ca24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Tiers" +# Source-Hash: 6ea977c42202ca24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomTiers" msgstr "Escalones inferiores" -# Review: required -# Source-Hash: 709ca788827f8a94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Width" +# Source-Hash: 709ca788827f8a94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomWidth" msgstr "Ancho inferior" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.boxFrame" msgstr "Marco cerrado" -# Review: required -# Source-Hash: 381b1f481f747677 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Depth" +# Source-Hash: 381b1f481f747677 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceDepth" msgstr "Fondo del arriostramiento" -# Review: required -# Source-Hash: 8509b09b4ecdd6b6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Width" +# Source-Hash: 8509b09b4ecdd6b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceWidth" msgstr "Ancho del arriostramiento" -# Review: required -# Source-Hash: 08558dd786f45281 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulge" +# Source-Hash: 08558dd786f45281 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulge" msgstr "Abombamiento" -# Review: required -# Source-Hash: 663b9a9e6a469b02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulged" +# Source-Hash: 663b9a9e6a469b02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulged" msgstr "Abombada" -# Review: required -# Source-Hash: 3530ac1f4046cd22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Column" +# Source-Hash: 3530ac1f4046cd22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.column" msgstr "Pilar" -# Review: required -# Source-Hash: ca0a856f04460bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Connector Plates" +# Source-Hash: ca0a856f04460bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.connectorPlates" msgstr "Placas de unión" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: b963a08f0d540014 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edge Softness" +# Source-Hash: b963a08f0d540014 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.edgeSoftness" msgstr "Suavizado de aristas" -# Review: required -# Source-Hash: 82b3db356639ef92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ends" +# Source-Hash: 82b3db356639ef92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ends" msgstr "Extremos" -# Review: required -# Source-Hash: 646c3cd99df3e285 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Width" +# Source-Hash: 646c3cd99df3e285 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.endWidth" msgstr "Ancho del extremo" -# Review: required -# Source-Hash: 02e229bca2a2b5f1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fork Spread" +# Source-Hash: 02e229bca2a2b5f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.forkSpread" msgstr "Apertura de la horquilla" -# Review: required -# Source-Hash: fdebfac05413abcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Heavy" +# Source-Hash: fdebfac05413abcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heavy" msgstr "Robusta" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 957fae84ed52a2e3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to." +# Source-Hash: 957fae84ed52a2e3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heightAndFootprintAreControlledBy" msgstr "La altura y la huella las controla la extensión de un agua. Gira o cambia el estilo de soporte aquí; cambia el tamaño desde la extensión principal." -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.hourglass" msgstr "Reloj de arena" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.kBrace" msgstr "Arriostramiento en K" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 6dc190abd3f22787 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Width" +# Source-Hash: 6dc190abd3f22787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.neckWidth" msgstr "Ancho del cuello" -# Review: required -# Source-Hash: 7dba7ed55eeba18f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Bottom" +# Source-Hash: 7dba7ed55eeba18f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noBottom" msgstr "Sin base" -# Review: required -# Source-Hash: 682d71815bb20e94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Top" +# Source-Hash: 682d71815bb20e94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noTop" msgstr "Sin capitel" -# Review: required -# Source-Hash: 4da8688437f64db8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth Thickness" +# Source-Hash: 4da8688437f64db8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.plinthThickness" msgstr "Grosor del plinto" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.portalFrame" msgstr "Pórtico" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.preset" msgstr "Preajuste" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.rectangular" msgstr "Rectangular" -# Review: required -# Source-Hash: 23b9d290b165cd49 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Pairs" +# Source-Hash: 23b9d290b165cd49 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringPairs" msgstr "Pares de anillos" -# Review: required -# Source-Hash: 8db82d731a19651e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Spread" +# Source-Hash: 8db82d731a19651e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringSpread" msgstr "Separación de anillos" -# Review: required -# Source-Hash: bdb524a15d9e1509 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Thickness" +# Source-Hash: bdb524a15d9e1509 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringThickness" msgstr "Grosor de anillos" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.round" msgstr "Redondo" -# Review: required -# Source-Hash: baa84a2d17095dcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round Band Width" +# Source-Hash: baa84a2d17095dcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundBandWidth" msgstr "Ancho de la banda redonda" -# Review: required -# Source-Hash: 5f1f67fb0ca59a6d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Bottom" +# Source-Hash: 5f1f67fb0ca59a6d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedBottom" msgstr "Base redondeada" -# Review: required -# Source-Hash: 446816980ed0da27 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Top" +# Source-Hash: 446816980ed0da27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedTop" msgstr "Capitel redondeado" -# Review: required -# Source-Hash: 9e554c26be80f0d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segment Twist" +# Source-Hash: 9e554c26be80f0d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.segmentTwist" msgstr "Torsión de segmentos" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaft" msgstr "Hueco" -# Review: required -# Source-Hash: 9bc3a704a9a96b3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Corner Radius" +# Source-Hash: 9bc3a704a9a96b3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftCornerRadius" msgstr "Radio de esquina del fuste" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftWidth" msgstr "Ancho del fuste" -# Review: required -# Source-Hash: 85ea1d3c8b749680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shape" +# Source-Hash: 85ea1d3c8b749680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shape" msgstr "Forma" -# Review: required -# Source-Hash: 49a2ce13016d4073 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Short / Stout" +# Source-Hash: 49a2ce13016d4073 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shortStout" msgstr "Corta / Gruesa" -# Review: required -# Source-Hash: 4637a9a61638cc66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Block Bottom" +# Source-Hash: 4637a9a61638cc66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleBlockBottom" msgstr "Base de bloque simple" -# Review: required -# Source-Hash: 9278f9b4a48208b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Top" +# Source-Hash: 9278f9b4a48208b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleTop" msgstr "Capitel simple" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.singleStrut" msgstr "Puntal único" -# Review: required -# Source-Hash: d87ce271009ce063 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slender" +# Source-Hash: d87ce271009ce063 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.slender" msgstr "Esbelta" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.square" msgstr "Cuadrado" -# Review: required -# Source-Hash: 2dc84759d8df709d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Plinth Bottom" +# Source-Hash: 2dc84759d8df709d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.squarePlinthBottom" msgstr "Base de plinto cuadrado" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.standard" msgstr "Estándar" -# Review: required -# Source-Hash: 01a6393135e0fb10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Bottom" +# Source-Hash: 01a6393135e0fb10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedBottom" msgstr "Base escalonada" -# Review: required -# Source-Hash: 3d7e443797d4146e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Top" +# Source-Hash: 3d7e443797d4146e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedTop" msgstr "Capitel escalonado" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.straight" msgstr "Recto" -# Review: required -# Source-Hash: 6063ae2877faa35d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Taper" +# Source-Hash: 6063ae2877faa35d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.taper" msgstr "Ahusamiento" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tapered" msgstr "Ahusado" -# Review: required -# Source-Hash: 3e7ec47626ee3f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Depth" +# Source-Hash: 3e7ec47626ee3f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topDepth" msgstr "Fondo superior" -# Review: required -# Source-Hash: 0ca9047731784f56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Height" +# Source-Hash: 0ca9047731784f56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topHeight" msgstr "Altura superior" -# Review: required -# Source-Hash: 3d8a2be83b5b536a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Spread" +# Source-Hash: 3d8a2be83b5b536a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topSpread" msgstr "Apertura superior" -# Review: required -# Source-Hash: f4ae0dc639dbc220 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Step Spread" +# Source-Hash: f4ae0dc639dbc220 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topStepSpread" msgstr "Separación de escalones superiores" -# Review: required -# Source-Hash: 8acc4e843934b086 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Tiers" +# Source-Hash: 8acc4e843934b086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topTiers" msgstr "Escalones superiores" -# Review: required -# Source-Hash: a50eb6400611a57c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Width" +# Source-Hash: a50eb6400611a57c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topWidth" msgstr "Ancho superior" -# Review: required -# Source-Hash: f425799b82e085ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Transform" +# Source-Hash: f425799b82e085ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.transform" msgstr "Transformación" -# Review: required -# Source-Hash: 952fd1ef187c6646 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle" +# Source-Hash: 952fd1ef187c6646 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.trestle" msgstr "Caballete" -# Review: required -# Source-Hash: 7430dad59aaf98f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod" +# Source-Hash: 7430dad59aaf98f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tripod" msgstr "Trípode" -# Review: required -# Source-Hash: 0db559f22647ec73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Twist Segments" +# Source-Hash: 0db559f22647ec73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.twistSegments" msgstr "Segmentos de torsión" -# Review: required -# Source-Hash: 8038a34ffebf4932 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical" +# Source-Hash: 8038a34ffebf4932 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vertical" msgstr "Vertical" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vSupport" msgstr "Soporte en V" -# Review: required -# Source-Hash: 44dda7136b93c8ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waist" +# Source-Hash: 44dda7136b93c8ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.waist" msgstr "Losa" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.xBrace" msgstr "Cruz de San Andrés" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.yaw" msgstr "Guiñada" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ySupport" msgstr "Soporte en Y" +# English: "A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up." +# Source-Hash: 4d3a89de7073d38d # Review: required -# Source-Hash: bf2baf3d7b55f0df -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.aSizeDeckPostStandsNo" +msgstr "Un poste de tarima {size} no debe superar {limit} de altura (tabla R507.4 del IRC, según la especie y el área que soporta): compruébalo o usa el tamaño superior." + +# English: "Custom" +# Source-Hash: 9e9715d52410c86f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.custom" +msgstr "Personalizado" + +# English: "Fit to the beam over it ({height})" +# Source-Hash: 31f74f6b8e7f077e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheBeamOverIt" +msgstr "Ajustar a la viga superior ({height})" + +# English: "Fit to the ceiling plane ({height})" +# Source-Hash: f09c0bf0ea50fc47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheCeilingPlane" +msgstr "Ajustar al plano del techo ({height})" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.height" +msgstr "Altura" + +# English: "Keep its shape and style it as a column" +# Source-Hash: b1778491849d76d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.keepItsShapeAndStyleIt" +msgstr "Conservar su forma y darle estilo de pilar" + +# English: "Make it a column" +# Source-Hash: 151f47e85cf7cc8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.makeItAColumn" +msgstr "Convertir en pilar" + +# English: "Post" +# Source-Hash: 5cc981e79d737694 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.post" +msgstr "Poste" + +# English: "Treated" +# Source-Hash: 64ed304f3773f93d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.treated" +msgstr "Tratado" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.untreated" +msgstr "Sin tratar" + # English: "A-Frame Support" +# Source-Hash: bf2baf3d7b55f0df +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.aFrameSupport" msgstr "Soporte en A" -# Review: required -# Source-Hash: 51cf77bb359cee43 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight Round" +# Source-Hash: 51cf77bb359cee43 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.basicPillar" msgstr "Redonda recta" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.boxFrameSupport" msgstr "Marco cerrado" -# Review: required -# Source-Hash: 4f22058e862d1f81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Soft Bulged" +# Source-Hash: 4f22058e862d1f81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.bulgedPillar" msgstr "Ligeramente abombada" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.hourglassPillar" msgstr "Reloj de arena" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.kBraceSupport" msgstr "Arriostramiento en K" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.portalFrameSupport" msgstr "Pórtico" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.singleStrutSupport" msgstr "Puntal único" -# Review: required -# Source-Hash: 0c13af329b9b57cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Block" +# Source-Hash: 0c13af329b9b57cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.squarePillar" msgstr "Bloque cuadrado" -# Review: required -# Source-Hash: 4c294bad84924665 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered Round" +# Source-Hash: 4c294bad84924665 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.taperedPillar" msgstr "Redonda ahusada" -# Review: required -# Source-Hash: 360e405bfd74a379 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle Frame" +# Source-Hash: 360e405bfd74a379 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.trestleSupport" msgstr "Marco de caballete" -# Review: required -# Source-Hash: 4589103259b4fc4d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod Support" +# Source-Hash: 4589103259b4fc4d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.tripodSupport" msgstr "Soporte trípode" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.vFrameSupport" msgstr "Soporte en V" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.xBraceSupport" msgstr "Cruz de San Andrés" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.yFrameSupport" msgstr "Soporte en Y" +# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." # Source-Hash: 895766ad1b766c2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.description" msgstr "Cota de plano asociativa: lineal, curva, circular, angular o de coordenadas." +# English: "Construction Dimension" # Source-Hash: 6875b60ee8a7cc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction Dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.label" msgstr "Cota de construcción" +# English: "Bottom Left" # Source-Hash: 61a6c6b9bc29c4f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomLeft" msgstr "Inferior izquierda" +# English: "Bottom Right" # Source-Hash: c5368accab136fa6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomRight" msgstr "Inferior derecha" +# English: "Top Left" # Source-Hash: c8f86af3f03af6c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topLeft" msgstr "Superior izquierda" +# English: "Top Right" # Source-Hash: 0ead7f1ffdbbd892 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topRight" msgstr "Superior derecha" +# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." # Source-Hash: a276c9693f01dec7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.description" msgstr "Linterna de cubierta con lamas, remate en cúpula o pirámide y pináculo opcional." +# English: "Cupola" # Source-Hash: e679dd19f96eba0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.label" msgstr "Linterna" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: e679dd19f96eba0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cupola" +# Source-Hash: e679dd19f96eba0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.cupola" msgstr "Linterna" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dome" msgstr "Cúpula" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: 1cfe33235b30b1d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.finial" msgstr "Remate" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: b5e0cb318d484d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Finial" +# Source-Hash: b5e0cb318d484d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.noFinial" msgstr "Sin remate" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 47587b825e9a696b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pyramid" +# Source-Hash: 47587b825e9a696b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.pyramid" msgstr "Pirámide" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.width" msgstr "Ancho" +# English: "Auto elevator cutout" # Source-Hash: 5d4ccdf13668fab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto elevator cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoElevator" msgstr "Recorte automático de ascensor" +# English: "Auto stair cutout" # Source-Hash: 78aa03576e02dda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto stair cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoStair" msgstr "Recorte automático de escalera" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.opening" msgstr "Abertura" +# English: "Room cutout" # Source-Hash: ce37dbd7a309940b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.room" msgstr "Recorte de estancia" -# Source-Hash: 6123b20edb9569de +# English: "Swap hinge side" +# Source-Hash: b1757d76048706b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapHingeSide" +msgstr "Cambiar el lado de las bisagras" + +# English: "Swap swing side" +# Source-Hash: d8bbb9fdf69726ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapSwingSide" +msgstr "Cambiar el sentido de apertura" + # English: "A door cut into a wall. Animated open/close state." +# Source-Hash: 6123b20edb9569de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.description" msgstr "Puerta abierta en un muro, con apertura y cierre animados." +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.label" msgstr "Puerta" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.actions" msgstr "Acciones" +# English: "+ Add Segment" # Source-Hash: 8337b3213849faca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.addSegment" msgstr "+ Añadir segmento" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.all" msgstr "Todo" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.arch" msgstr "Arco" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.archHeight" msgstr "Altura del arco" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.auto" msgstr "Auto" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.autoFollowsTheWall" msgstr "Auto sigue el muro" +# English: "Bar Height" # Source-Hash: 387773c8aa2820a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bar Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.barHeight" msgstr "Altura de la barra" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.columns" msgstr "Columnas" +# English: "Content Padding" # Source-Hash: f533584833a4a7bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Content Padding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.contentPadding" msgstr "Relleno del contenido" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.cornerRadius" msgstr "Radio de esquina" +# English: "Custom: the segments match no style." # Source-Hash: aa0e8e02212091e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the segments match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.customTheSegmentsMatchNoStyle" msgstr "Personalizado: los segmentos no coinciden con ningún estilo." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.delete" msgstr "Eliminar" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.depth" msgstr "Profundidad" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.dimensions" msgstr "Dimensiones" +# English: "Direction" # Source-Hash: 76cd03300faa9b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.direction" msgstr "Dirección" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.divider" msgstr "Divisor" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.door" msgstr "Puerta" +# English: "Door Closer" # Source-Hash: 4a0aa7f647e6461b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door Closer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.doorCloser" msgstr "Cierrapuertas" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.duplicate" msgstr "Duplicar" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.empty" msgstr "Vacío" +# English: "Enable Handle" # Source-Hash: 6982301da2dbe186 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableHandle" msgstr "Activar manilla" +# English: "Enable Threshold" # Source-Hash: 2e96ebf9c6e7fe49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableThreshold" msgstr "Activar umbral" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.flipSide" msgstr "Invertir lado" +# English: "Fold" # Source-Hash: 7e8fdd28f9d54d1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.fold" msgstr "Pliegue" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frame" msgstr "Marco" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frameDepthAuto" msgstr "Profundidad (auto)" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.garage" msgstr "Garaje" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.glass" msgstr "Vidrio" +# English: "Handle" # Source-Hash: 41483b645efc0c65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handle" msgstr "Manilla" +# English: "Handle Side" # Source-Hash: b4a496a904640583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handleSide" msgstr "Lado de la manilla" +# English: "Hardware" # Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hardware" msgstr "Herrajes" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.height" msgstr "Altura" +# English: "Hinges Side" # Source-Hash: 7c8f079bbefe86c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinges Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hingesSide" msgstr "Lado de las bisagras" +# English: "Horizontal" # Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.horizontal" msgstr "Horizontal" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.individual" msgstr "Individual" +# English: "Inset" # Source-Hash: 3db1dea825785a30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inset" msgstr "Empotrado" +# English: "Inward" # Source-Hash: 1f4ad5ad3e1be222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inward" msgstr "Hacia dentro" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.left" msgstr "Izquierda" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.move" msgstr "Mover" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.open" msgstr "Abrir" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.opening" msgstr "Abertura" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.openingShape" msgstr "Forma del hueco" +# English: "Outward" # Source-Hash: ccd8f06b8f8d27c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.outward" msgstr "Hacia fuera" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panel" msgstr "Panel" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panels" msgstr "Paneles" +# English: "Panic Bar" # Source-Hash: ad56a8ab82ff1a77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panic Bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panicBar" msgstr "Barra antipánico" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.pocket" msgstr "Corredera empotrada" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.position" msgstr "Posición" +# English: "Rail" # Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rail" msgstr "Travesaño" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rect" msgstr "Rect." +# English: "- Remove" # Source-Hash: c05172113af5a2b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "- Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.remove" msgstr "- Quitar" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.revealRadius" msgstr "Radio del derrame" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.right" msgstr "Derecha" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rounded" msgstr "Redondeado" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.segments" msgstr "Segmentos" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.sizeIsLimitedToTheWall" msgstr "El tamaño está limitado al muro, incluida la holgura del marco del hueco." +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.slide" msgstr "Deslizar" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.style" msgstr "Estilo" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.swing" msgstr "Abatir" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.thickness" msgstr "Espesor" +# English: "Threshold" # Source-Hash: f165d6c05be2b651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.threshold" msgstr "Umbral" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.topShape" msgstr "Forma superior" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.type" msgstr "Tipo" +# English: "Vertical" # Source-Hash: 8038a34ffebf4932 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.vertical" msgstr "Vertical" +# English: "wall" # Source-Hash: 63d6b766151965f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.wall" msgstr "muro" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.width" msgstr "Ancho" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.barn" msgstr "Granero" +# English: "Craftsman" # Source-Hash: f747fbe71717772e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.craftsman" msgstr "Craftsman" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.french" msgstr "Francesa" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.glass" msgstr "Vidrio" +# English: "Half-louvered" # Source-Hash: 6ab8b98b38eb2e65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.halfLouvered" msgstr "Media persiana" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.modern" msgstr "Moderno" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.panel" msgstr "Panel" +# English: "Paneled glass" # Source-Hash: ac42c328610a0edd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.paneledGlass" msgstr "Cuarterones con vidrio" +# English: "Shaker" # Source-Hash: 5b68e435b7210876 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.shaker" msgstr "Shaker" +# English: "Six-panel" # Source-Hash: 9ad6ac42b4152b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.sixPanel" msgstr "Seis cuarterones" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.barn" msgstr "Granero" +# English: "Double" # Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.double" msgstr "Doble" +# English: "Folding" # Source-Hash: 29ff60e662b789fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.folding" msgstr "Plegable" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.french" msgstr "Francesa" +# English: "Roll-up" # Source-Hash: c86309fefe89699b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roll-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageRollup" msgstr "Enrollable" +# English: "Sectional" # Source-Hash: fb6a868430547544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sectional" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageSectional" msgstr "Seccional" +# English: "Tilt-up" # Source-Hash: 0b5eb64b569db837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageTiltup" msgstr "Basculante" +# English: "Hinged" # Source-Hash: e744be60baad6abc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.hinged" msgstr "Abatible" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.pocket" msgstr "Corredera empotrada" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.sliding" msgstr "Corredera" +# English: "House-shaped protrusion on a roof segment." # Source-Hash: 387e57242c8d110a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "House-shaped protrusion on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.description" msgstr "Saliente con forma de casa en un faldón." +# English: "Dormer" # Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.label" msgstr "Buhardilla" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: 6b6f8068bbc4c902 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dormer" msgstr "Buhardilla" -# Review: required -# Source-Hash: 6949d667043bee0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dutch" +# Source-Hash: 6949d667043bee0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dutch" msgstr "Holandesa" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.flat" msgstr "Plana" -# Review: required -# Source-Hash: deaffa095d126dfe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gable" +# Source-Hash: deaffa095d126dfe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gable" msgstr "Dos aguas" -# Review: required -# Source-Hash: 7f53d9ffca39984a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gambrel" msgstr "Holandés de buhardilla" -# Review: required -# Source-Hash: 9eda0ea8d447066d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hip" +# Source-Hash: 9eda0ea8d447066d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.hip" msgstr "Cuatro aguas" -# Review: required -# Source-Hash: 7f7bb0e875af3260 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.mansard" msgstr "Mansarda" -# Review: required -# Source-Hash: 53af9efc7eca6594 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Direction" +# Source-Hash: 53af9efc7eca6594 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchDirection" msgstr "Dirección de la pendiente" -# Review: required -# Source-Hash: 6c39ef7125494c4a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Rise" +# Source-Hash: 6c39ef7125494c4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchRise" msgstr "Altura de la pendiente" -# Review: required -# Source-Hash: 96336d4b0820b977 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Back" +# Source-Hash: 96336d4b0820b977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseBack" msgstr "Sube hacia atrás" -# Review: required -# Source-Hash: ee3d45f8b756d029 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Front" +# Source-Hash: ee3d45f8b756d029 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseFront" msgstr "Sube hacia delante" -# Review: required -# Source-Hash: 5e0622f3b09c9326 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Height" +# Source-Hash: 5e0622f3b09c9326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofHeight" msgstr "Altura de cubierta" -# Review: required -# Source-Hash: 19ab9527ae7889f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Type" +# Source-Hash: 19ab9527ae7889f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofType" msgstr "Tipo de cubierta" -# Review: required -# Source-Hash: dba6a7b56cf7083f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Section" +# Source-Hash: dba6a7b56cf7083f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.section" msgstr "Sección" -# Review: required -# Source-Hash: c8d0df27faa3feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shed" +# Source-Hash: c8d0df27faa3feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.shed" msgstr "Un agua" -# Review: required -# Source-Hash: e0f973edce3e4ca8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Height" +# Source-Hash: e0f973edce3e4ca8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.wallHeight" msgstr "Altura del muro" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: 0b1d1e9a36456a50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows" +# Source-Hash: 0b1d1e9a36456a50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.windows" msgstr "Ventanas" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.actions" msgstr "Acciones" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.move" msgstr "Mover" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.position" msgstr "Posición" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: 7b19d21b9ad868c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Window" +# Source-Hash: 7b19d21b9ad868c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.addWindow" msgstr "Añadir ventana" -# Review: required -# Source-Hash: 70d7cc5a6422feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit {name}" +# Source-Hash: 70d7cc5a6422feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editNamed" msgstr "Editar {name}" -# Review: required -# Source-Hash: b6648f023e6c83dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit window" +# Source-Hash: b6648f023e6c83dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editWindow" msgstr "Editar ventana" -# Review: required -# Source-Hash: 4649baf9753aeb74 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase the dormer width to add another window." +# Source-Hash: 4649baf9753aeb74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd" msgstr "Aumenta el ancho de la buhardilla para añadir otra ventana." -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.move" msgstr "Mover" -# Review: required -# Source-Hash: c24226954309d83e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move {name}" +# Source-Hash: c24226954309d83e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveNamed" msgstr "Mover {name}" -# Review: required -# Source-Hash: f07faaf0d5e43eef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move window" +# Source-Hash: f07faaf0d5e43eef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveWindow" msgstr "Mover ventana" -# Review: required -# Source-Hash: d2816490ea93d363 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No windows" +# Source-Hash: d2816490ea93d363 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.noWindows" msgstr "Sin ventanas" -# Review: required -# Source-Hash: 5cbb24f8b1ce7fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Window {number}" +# Source-Hash: 5cbb24f8b1ce7fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowNumber" msgstr "Ventana {number}" -# Review: required -# Source-Hash: 9ca7d951240907cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows ({length})" +# Source-Hash: 9ca7d951240907cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowsLength" msgstr "Ventanas ({length})" -# Review: required -# Source-Hash: f975c4759607806a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{face, select, back {Back} left {Left} right {Right} other {Front}} · {width} × {height} m" +# Source-Hash: f975c4759607806a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowSummary" msgstr "{face, select, back {Trasera} left {Izquierda} right {Derecha} other {Frontal}} · {width} × {height} m" +# English: "Vertical drop pipe from a gutter outlet to the ground." # Source-Hash: b46392d2ea865760 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical drop pipe from a gutter outlet to the ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.description" msgstr "Tubo vertical desde la salida del canalón hasta el suelo." -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/downspout/inspector-editors.tsx #, fuzzy msgid "node.downspout.inspectorEditors.position" msgstr "Posición" +# English: "Downspout" # Source-Hash: 1a1ec08a311f1b35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.label" msgstr "Bajante" +# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." # Source-Hash: 29eadb8c84ae9632 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.description" msgstr "Codo, T, reducción o transición cuadrado-redondo que une conductos." -# Review: required -# Source-Hash: 7323a0fc9efb077f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap W/H" +# Source-Hash: 7323a0fc9efb077f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWH" msgstr "Intercambiar An/Al" -# Review: required -# Source-Hash: 064b36c4fc5deb87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap width and height" +# Source-Hash: 064b36c4fc5deb87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWidthAndHeight" msgstr "Intercambiar ancho y alto" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.label" msgstr "Accesorio de conducto" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis" msgstr "Eje" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis2" msgstr "⌥ eje" -# Review: required -# Source-Hash: 880b34f6fc0ae854 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hover a duct face that fits the door" +# Source-Hash: 880b34f6fc0ae854 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.hoverADuctFaceThatFits" msgstr "Pasa sobre una cara de conducto donde quepa la trampilla" -# Review: required -# Source-Hash: 706f5b43a1d4a84a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Place access door" +# Source-Hash: 706f5b43a1d4a84a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.placeAccessDoor" msgstr "Colocar trampilla de acceso" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.rTRotate" msgstr "R/T girar" +# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." # Source-Hash: 09eb45a9b35d1a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.description" msgstr "Conducto de climatización: polilínea de secciones redondas, rectangulares u ovaladas planas." +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.label" msgstr "Conducto" +# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." # Source-Hash: 33181d0f42421013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.description" msgstr "Terminal de conducto: rejilla de impulsión, difusor de techo o rejilla de retorno. Los conductos terminan en su collarín." +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.label" msgstr "Rejilla" -# Review: required -# Source-Hash: 83c665eafcf43a68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{mount, select, floor {Mount Floor} ceiling {Mount Ceiling} wall {Mount Wall} other {Mount {label}}}" +# Source-Hash: 83c665eafcf43a68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mount" msgstr "{mount, select, floor {Montaje en suelo} ceiling {Montaje en techo} wall {Montaje en pared} other {Montaje {label}}}" -# Review: required -# Source-Hash: f067dd52ea051385 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "M surface" +# Source-Hash: f067dd52ea051385 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Key hint: pressing M switches the mounting surface. #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mSurface" msgstr "M superficie" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.rTRotate" msgstr "R/T girar" -# Review: required -# Source-Hash: 98446855908ee71d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Snapped to duct" +# Source-Hash: 98446855908ee71d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.snappedToDuct" msgstr "Ajustado al conducto" +# English: "A multi-level elevator shaft with configurable openings per level." # Source-Hash: d0afa386e65cd3a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A multi-level elevator shaft with configurable openings per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.description" msgstr "Hueco de ascensor de varias plantas con aberturas configurables por planta." +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.label" msgstr "Ascensor" -# Review: required -# Source-Hash: 7e6d5f138e315bdb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Access" +# Source-Hash: 7e6d5f138e315bdb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.access" msgstr "Acceso" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: fdf7108a011bd94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab" +# Source-Hash: fdf7108a011bd94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cab" msgstr "Cabina" -# Review: required -# Source-Hash: 2d0ab44f3820f22c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab Height" +# Source-Hash: 2d0ab44f3820f22c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cabHeight" msgstr "Altura de cabina" -# Review: required -# Source-Hash: 65f410261609060b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center opening" +# Source-Hash: 65f410261609060b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.centerOpening" msgstr "Apertura central" -# Review: required -# Source-Hash: c5433f4d3ea8a8a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Default Floor" +# Source-Hash: c5433f4d3ea8a8a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.defaultFloor" msgstr "Planta predeterminada" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 748877ae309c313c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Destination" +# Source-Hash: 748877ae309c313c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.destination" msgstr "Destino" -# Review: required -# Source-Hash: 3bff69bd74544339 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Disabled" +# Source-Hash: 3bff69bd74544339 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.disabled" msgstr "Desactivado" -# Review: required -# Source-Hash: d8baf402fa9c52f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Height" +# Source-Hash: d8baf402fa9c52f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorHeight" msgstr "Altura de puerta" -# Review: required -# Source-Hash: 20f60b98de67849b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Doors" +# Source-Hash: 20f60b98de67849b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doors" msgstr "Puertas" -# Review: required -# Source-Hash: eb0d4023a513b16c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Time" +# Source-Hash: eb0d4023a513b16c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorTime" msgstr "Tiempo de puerta" -# Review: required -# Source-Hash: b3e001acdb1f869d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Type" +# Source-Hash: b3e001acdb1f869d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorType" msgstr "Tipo de puerta" -# Review: required -# Source-Hash: c6203a69fdf44065 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Width" +# Source-Hash: c6203a69fdf44065 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorWidth" msgstr "Ancho de puerta" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: eb3abe5340b26138 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dwell" +# Source-Hash: eb3abe5340b26138 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.dwell" msgstr "Tiempo de parada" -# Review: required -# Source-Hash: ab5cbab7be160a1c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Elevator" +# Source-Hash: ab5cbab7be160a1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.elevator" msgstr "Ascensor" -# Review: required -# Source-Hash: 702140636ff3977a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "From" +# Source-Hash: 702140636ff3977a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.from" msgstr "Desde" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glass" msgstr "Vidrio" -# Review: required -# Source-Hash: 88810658a2b97bc6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glassFrame" msgstr "Marco de vidrio" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.motion" msgstr "Movimiento" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 825ac8855353a4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Style" +# Source-Hash: 825ac8855353a4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.openingStyle" msgstr "Tipo de apertura" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: f9a95d8407fafda4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.segmentedPanel" msgstr "Panel segmentado" -# Review: required -# Source-Hash: aa7b49b13f96057f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Service" +# Source-Hash: aa7b49b13f96057f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.service" msgstr "Servicio" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaft" msgstr "Hueco" -# Review: required -# Source-Hash: 6605ade39317cd42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Depth" +# Source-Hash: 6605ade39317cd42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftDepth" msgstr "Fondo del hueco" -# Review: required -# Source-Hash: b28db5a3a48fe52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Style" +# Source-Hash: b28db5a3a48fe52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftStyle" msgstr "Estilo del hueco" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftWidth" msgstr "Ancho del hueco" -# Review: required -# Source-Hash: aee3b2bd78c87d0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single left" +# Source-Hash: aee3b2bd78c87d0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleLeft" msgstr "Simple izquierda" -# Review: required -# Source-Hash: cc43ffbef4ac7ae0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleRight" msgstr "Simple derecha" -# Review: required -# Source-Hash: ef3e765e692faf4c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solid" msgstr "Sólido" -# Review: required -# Source-Hash: 8008b6f17089beaf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid panel" +# Source-Hash: 8008b6f17089beaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solidPanel" msgstr "Panel opaco" -# Review: required -# Source-Hash: 3b006be0b1f0493f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Speed" +# Source-Hash: 3b006be0b1f0493f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.speed" msgstr "Velocidad" -# Review: required -# Source-Hash: 6d37a1679fa3708e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop {stopOrder}" +# Source-Hash: 6d37a1679fa3708e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.stopStoporder" msgstr "Parada {stopOrder}" -# Review: required -# Source-Hash: b3214a20e45a7aed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "To" +# Source-Hash: b3214a20e45a7aed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.to" msgstr "Hasta" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.wallThickness" msgstr "Grosor de pared" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.yaw" msgstr "Guiñada" +# English: "Low curved lens-shaped roof vent with a louvered front." # Source-Hash: 7b22c91cf520e161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low curved lens-shaped roof vent with a louvered front." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.description" msgstr "Respiradero de cubierta bajo, curvo y lenticular con frente de lamas." +# English: "Eyebrow Vent" # Source-Hash: 654b29319185ac66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.label" msgstr "Respiradero de ceja" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: abc97377800a8333 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back height" +# Source-Hash: abc97377800a8333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.backHeight" msgstr "Altura trasera" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: 654b29319185ac66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Eyebrow Vent" +# Source-Hash: 654b29319185ac66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.eyebrowVent" msgstr "Respiradero de ceja" -# Review: required -# Source-Hash: a2d107a5deaa01f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Half-round" +# Source-Hash: a2d107a5deaa01f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.halfRound" msgstr "Media caña" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 318192f27a774fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Louvers" +# Source-Hash: 318192f27a774fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.louvers" msgstr "Lamas" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: e16f85e4bebfefb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.scoop" msgstr "Cuchara" -# Review: required -# Source-Hash: ec53d40877e11b62 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slant-box" +# Source-Hash: ec53d40877e11b62 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.slantBox" msgstr "Caja inclinada" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.width" msgstr "Ancho" +# English: "A straight or curved fence segment with configurable posts and infill." # Source-Hash: 20032fe9430f610d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved fence segment with configurable posts and infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.description" msgstr "Tramo de valla recto o curvo con postes y relleno configurables." -# Review: required -# Source-Hash: 6f84b85097bc1ffc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add point after" +# Source-Hash: 6f84b85097bc1ffc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.addPointAfter" msgstr "Añadir punto después" -# Review: required -# Source-Hash: 1a15ab3101e2d166 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a slab or shaped surface on this level." +# Source-Hash: 1a15ab3101e2d166 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASlabOrShapedSurface" msgstr "Elige una losa o una superficie modelada en esta planta." -# Review: required -# Source-Hash: 78a5625401384000 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a surface" +# Source-Hash: 78a5625401384000 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASurface" msgstr "Elegir una superficie" -# Review: required -# Source-Hash: 2ee121dca87bb64a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner" +# Source-Hash: 2ee121dca87bb64a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.corner" msgstr "Esquina" -# Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curve" +# Source-Hash: f08fbb462e9afc0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curve" msgstr "Curva" -# Review: required -# Source-Hash: a028e08962c388e0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curved" +# Source-Hash: a028e08962c388e0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curved" msgstr "Curvo" -# Review: required -# Source-Hash: 81b75058b7843a0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{style, select, picket {Distribution controls pickets in each bay between posts.} horizontal {Distribution controls posts along the full fence.} other {Distribution controls infill pieces between the two end posts.}} Spacing is measured between centers. The available width limits the count so pieces do not overlap." +# Source-Hash: 81b75058b7843a0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.distributionInfo" msgstr "{style, select, picket {La distribución controla los listones de cada tramo entre postes.} horizontal {La distribución controla los postes a lo largo de toda la valla.} other {La distribución controla las piezas de relleno entre los dos postes extremos.}} La separación se mide entre centros. El ancho disponible limita la cantidad para que las piezas no se solapen." -# Review: required -# Source-Hash: fbc8f54b0e3fdeb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit path" +# Source-Hash: fbc8f54b0e3fdeb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.editPath" msgstr "Editar trazado" -# Review: required -# Source-Hash: 55686db44529c013 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extend path" +# Source-Hash: 55686db44529c013 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.extendPath" msgstr "Prolongar trazado" -# Review: required -# Source-Hash: 0bf0f27e631aa98d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow highest surface" +# Source-Hash: 0bf0f27e631aa98d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followHighestSurface" msgstr "Seguir la superficie más alta" -# Review: required -# Source-Hash: e0b381fcb05a65c4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow selected surface" +# Source-Hash: e0b381fcb05a65c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followSelectedSurface" msgstr "Seguir la superficie seleccionada" -# Review: required -# Source-Hash: c418d97f803af8dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hold starting height" +# Source-Hash: c418d97f803af8dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.holdStartingHeight" msgstr "Mantener la altura inicial" -# Review: required -# Source-Hash: 295ca9336dc1935b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Join" +# Source-Hash: 295ca9336dc1935b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.join" msgstr "Unir" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.length" msgstr "Longitud" -# Review: required -# Source-Hash: 1fedc8a2f2ea6887 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} point} other {{number} points}} · {length} m" +# Source-Hash: 1fedc8a2f2ea6887 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.pathSummary" msgstr "{count, plural, one {{number} punto} many {{number} puntos} other {{number} puntos}} · {length} m" -# Review: required -# Source-Hash: c96d499e11181429 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket rail projection" +# Source-Hash: c96d499e11181429 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.picketRailProjection" msgstr "Saliente de los listones sobre el travesaño" -# Review: required -# Source-Hash: a1880fca8372d06f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Point" +# Source-Hash: a1880fca8372d06f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.point" msgstr "Punto" -# Review: required -# Source-Hash: 39bc6254b5307f53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove point" +# Source-Hash: 39bc6254b5307f53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.removePoint" msgstr "Quitar punto" -# Review: required -# Source-Hash: ea56af3a0d7a65e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Set each span to straight or curved, then drag its points and curve handles in either view." +# Source-Hash: ea56af3a0d7a65e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.setEachSpanToStraightOr" msgstr "Haz que cada tramo sea recto o curvo y luego arrastra sus puntos y tiradores de curva en cualquiera de las vistas." -# Review: required -# Source-Hash: 354c8bc5c8da5a6e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Smooth" +# Source-Hash: 354c8bc5c8da5a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.smooth" msgstr "Suavizar" -# Review: required -# Source-Hash: 3c700cc1299a150f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Span to point {number}" +# Source-Hash: 3c700cc1299a150f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.spanToPointNumber" msgstr "Tramo hasta el punto {number}" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.straight" msgstr "Recto" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surface" msgstr "Superficie" -# Review: required -# Source-Hash: ab2de0428c093018 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface behavior" +# Source-Hash: ab2de0428c093018 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surfaceBehavior" msgstr "Comportamiento en la superficie" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.label" msgstr "Valla" -# Review: required -# Source-Hash: 3e07a9889c1bd7f9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Detach endpoint" +# Source-Hash: 3e07a9889c1bd7f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.detachEndpoint" msgstr "Separar extremo" -# Review: required -# Source-Hash: 7a3ce9fec6370e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drag endpoint" +# Source-Hash: 7a3ce9fec6370e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.dragEndpoint" msgstr "Arrastrar extremo" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.close" msgstr "Cerrar" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.open" msgstr "Abrir" -# Review: required -# Source-Hash: fc4cf2bc034fc547 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Board width" +# Source-Hash: fc4cf2bc034fc547 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.boardWidth" msgstr "Ancho de tabla" -# Review: required -# Source-Hash: f3b11e5b2dd3b25f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bracing" +# Source-Hash: f3b11e5b2dd3b25f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.bracing" msgstr "Arriostramiento" -# Review: required -# Source-Hash: d09ec3d9565734c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Distance from start" +# Source-Hash: d09ec3d9565734c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.distanceFromStart" msgstr "Distancia desde el inicio" -# Review: required -# Source-Hash: dcd803eaf3796298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fence unavailable." +# Source-Hash: dcd803eaf3796298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.fenceUnavailable" msgstr "Valla no disponible." -# Review: required -# Source-Hash: 3519d91b7bda18a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame width" +# Source-Hash: 3519d91b7bda18a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.frameWidth" msgstr "Ancho del marco" -# Review: required -# Source-Hash: cb18554b2420e700 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate height" +# Source-Hash: cb18554b2420e700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateHeight" msgstr "Altura de la puerta" -# Review: required -# Source-Hash: 54098b5ee574737a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate style" +# Source-Hash: 54098b5ee574737a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateStyle" msgstr "Estilo de puerta de valla" -# Review: required -# Source-Hash: 4fc3e60be28f60d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.groundClearance" msgstr "Separación al suelo" -# Review: required -# Source-Hash: 916f2e07bf082795 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinges and latch" +# Source-Hash: 916f2e07bf082795 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingesAndLatch" msgstr "Bisagras y pestillo" -# Review: required -# Source-Hash: 5bd2e597fbb2a2c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinge side" +# Source-Hash: 5bd2e597fbb2a2c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingeSide" msgstr "Lado de las bisagras" -# Review: required -# Source-Hash: 8fec7e969045a8e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Horizontal boards" +# Source-Hash: 8fec7e969045a8e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.horizontalBoards" msgstr "Tablas horizontales" -# Review: required -# Source-Hash: f4addad87cc4223e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Infill spacing" +# Source-Hash: f4addad87cc4223e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.infillSpacing" msgstr "Separación del relleno" -# Review: required -# Source-Hash: cdec08b292742b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Jamb posts" +# Source-Hash: cdec08b292742b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.jambPosts" msgstr "Postes de jamba" -# Review: required -# Source-Hash: ef88410453ee7414 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Keep the opening inside the fence and clear of other gates or passages." +# Source-Hash: ef88410453ee7414 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.keepTheOpeningInsideTheFence" msgstr "Mantén la abertura dentro de la valla y lejos de otras puertas o pasos." -# Review: required -# Source-Hash: 67cd0171b226fedb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaf thickness" +# Source-Hash: 67cd0171b226fedb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leafThickness" msgstr "Grosor de la hoja" -# Review: required -# Source-Hash: c390ba248e629b9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaves" +# Source-Hash: c390ba248e629b9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leaves" msgstr "Hojas" -# Review: required -# Source-Hash: d7bb934589475512 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left leaf proportion" +# Source-Hash: d7bb934589475512 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leftLeafProportion" msgstr "Proporción de la hoja izquierda" -# Review: required -# Source-Hash: aa9fe0ebace8957d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence height" +# Source-Hash: aa9fe0ebace8957d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceHeight" msgstr "Igualar altura de la valla" -# Review: required -# Source-Hash: c15c1af69ec5c1eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence style" +# Source-Hash: c15c1af69ec5c1eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceStyle" msgstr "Igualar el estilo de la valla" -# Review: required -# Source-Hash: 51b7ab6a0a78d90a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingAngle" msgstr "Ángulo de apertura" -# Review: required -# Source-Hash: d2e20300c07aa722 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening width" +# Source-Hash: d2e20300c07aa722 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingWidth" msgstr "Ancho de la abertura" -# Review: required -# Source-Hash: 6b108fb1d0eb8f9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open rails" +# Source-Hash: 6b108fb1d0eb8f9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openRails" msgstr "Barandas abiertas" -# Review: required -# Source-Hash: d500c4b7827d3390 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cross" +# Source-Hash: d500c4b7827d3390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.cross" msgstr "Cruz" -# Review: required -# Source-Hash: 7420c0d2caee1c22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.diagonal" msgstr "Diagonal" -# Review: required -# Source-Hash: 7308869944020839 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double gate" +# Source-Hash: 7308869944020839 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.doubleGate" msgstr "Puerta doble" -# Review: required -# Source-Hash: 1f4ad5ad3e1be222 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inward" +# Source-Hash: 1f4ad5ad3e1be222 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.inward" msgstr "Hacia dentro" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.left" msgstr "Izquierda" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.none" msgstr "Ninguno" -# Review: required -# Source-Hash: ccd8f06b8f8d27c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Outward" +# Source-Hash: ccd8f06b8f8d27c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.outward" msgstr "Hacia fuera" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.right" msgstr "Derecha" -# Review: required -# Source-Hash: d327097d9ac1099f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single gate" +# Source-Hash: d327097d9ac1099f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.singleGate" msgstr "Puerta simple" -# Review: required -# Source-Hash: 9295ddc2805b21e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket" +# Source-Hash: 9295ddc2805b21e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.picket" msgstr "Estacas" -# Review: required -# Source-Hash: 8dd5999b235f60ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Post height" +# Source-Hash: 8dd5999b235f60ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.postHeight" msgstr "Altura de los postes" -# Review: required -# Source-Hash: 90bcd7a8123ec343 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove gate" +# Source-Hash: 90bcd7a8123ec343 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeGate" msgstr "Quitar puerta" -# Review: required -# Source-Hash: 7e3505710613543f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove opening" +# Source-Hash: 7e3505710613543f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeOpening" msgstr "Quitar abertura" -# Review: required -# Source-Hash: af6f8885fe0fa4e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid privacy" +# Source-Hash: af6f8885fe0fa4e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.solidPrivacy" msgstr "Privacidad total" -# Review: required -# Source-Hash: 1a7048255c3bdfcb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swing direction" +# Source-Hash: 1a7048255c3bdfcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.swingDirection" msgstr "Sentido de apertura" -# Review: required -# Source-Hash: eba3df14a2de0812 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical slats" +# Source-Hash: eba3df14a2de0812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.verticalSlats" msgstr "Lamas verticales" +# English: "A gate hosted by a fence." # Source-Hash: df61821ed00852d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A gate hosted by a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.description" msgstr "Puerta alojada en una valla." +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.label" msgstr "Puerta de valla" +# English: "An intentional opening in a fence." # Source-Hash: 8f9c927d79796c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An intentional opening in a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.description" msgstr "Abertura intencionada en una valla." +# English: "Open Passage" # Source-Hash: ab67bb47e647d594 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.label" msgstr "Paso abierto" +# English: "Floor opening" # Source-Hash: dc3adf0c2d50dba6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.floorOpening.label" msgstr "Hueco de forjado" -# Source-Hash: 2f4fac2b1d39fdb1 +# English: "A concrete strip or pad footing poured under the lowest floor." +# Source-Hash: 0d2952c324c93b87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.description" +msgstr "Una zapata de hormigón, corrida o aislada, vertida bajo la planta más baja." + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.label" +msgstr "Zapata" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.actions" +msgstr "Acciones" + +# English: "A grade beam on piles, as the building is." +# Source-Hash: 5cbf600af0f430c1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aGradeBeamOnPilesAs" +msgstr "Una viga de cimentación sobre pilotes, como el edificio." + +# English: "A pier carries the floor." +# Source-Hash: 4de35a53f73e6d74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPierCarriesTheFloor" +msgstr "Un pilarete soporta el suelo." + +# English: "A pile cap on one pile, as the building is." +# Source-Hash: b07f0dafd314ce61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPileCapOnOnePile" +msgstr "Un encepado sobre un pilote, como el edificio." + +# English: "A stem wall carries the floor." +# Source-Hash: 9b596c6f41bdd6dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aStemWallCarriesTheFloor" +msgstr "Un murete de cimentación soporta el suelo." + +# English: "A thickened slab under the floor slab." +# Source-Hash: edd463abf1ba203f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aThickenedSlabUnderTheFloor" +msgstr "Un forjado engrosado bajo el forjado del suelo." + +# English: "At least" +# Source-Hash: 7d978d555a1fba4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.atLeast" +msgstr "Al menos" + +# English: ", bottom" +# Source-Hash: a6b79a24d92094bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.bottom" +msgstr ", cara inferior" + +# English: "Clear the depth" +# Source-Hash: f09c3e23c581e6e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.clearTheDepth" +msgstr "Borrar la profundidad" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.delete" +msgstr "Eliminar" + +# English: "Depth under grade" +# Source-Hash: 13c7635ae6ebd98b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGrade" +msgstr "Profundidad bajo rasante" + +# English: "Depth under grade (building's)" +# Source-Hash: fb5c116d6c004e8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGradeBuildingS" +msgstr "Prof. bajo rasante (edificio)" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.footing" +msgstr "Zapata" + +# English: "IRC R403.1 asks a footing at least 12 in wide and 6 in thick." +# Source-Hash: 742775b7550e4d10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.ircR4031AsksAFooting" +msgstr "El IRC R403.1 exige una zapata de al menos 12 in de ancho y 6 in de espesor." + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.length" +msgstr "Longitud" + +# English: "Pad" +# Source-Hash: 862a4fa9c40e14b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.pad" +msgstr "Aislada" + +# English: "Strip" +# Source-Hash: 168d7c95aa1872fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.strip" +msgstr "Corrida" + +# English: "the footing’s thickness under the floor reaches this far." +# Source-Hash: 2a4c3ad2bb841f1d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theFootingSThicknessUnderThe" +msgstr "el espesor de la zapata bajo el suelo llega hasta ahí." + +# English: "the grade beam reaches this far." +# Source-Hash: b907c56408d326b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theGradeBeamReachesThisFar" +msgstr "la viga de cimentación llega hasta ahí." + +# English: "the slab and the footing’s thickness reach this far." +# Source-Hash: 0ec23acc847b60c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theSlabAndTheFootingS" +msgstr "el forjado y el espesor de la zapata llegan hasta ahí." + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.thickness" +msgstr "Espesor" + +# English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.top" +msgstr "Cara superior" + +# English: "Use the building's depth" +# Source-Hash: 78d9e3ca09235067 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.useTheBuildingSDepth" +msgstr "Usar la profundidad del edificio" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.width" +msgstr "Ancho" + +# English: "The outline the foundation is poured to: drawn, or traced from the walls and stretched." +# Source-Hash: 1e069ed68ad21990 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.description" +msgstr "El contorno según el cual se hormigona la cimentación: dibujado, o trazado desde los muros y estirado." + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.label" +msgstr "Cimentación" + # English: "A measurement / reference annotation (linear, area, or arc)." +# Source-Hash: 2f4fac2b1d39fdb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.description" msgstr "Anotación de medida o referencia (lineal, área o arco)." +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.label" msgstr "Guía" +# English: "Rain-water channel running along the eave of a roof segment." # Source-Hash: 5402d634f40b2f97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rain-water channel running along the eave of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.description" msgstr "Canal de agua de lluvia a lo largo del alero de un faldón." -# Review: required -# Source-Hash: fc7d4ecf973db135 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Downspout" +# Source-Hash: fc7d4ecf973db135 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.addDownspout" msgstr "Añadir bajante" -# Review: required -# Source-Hash: 9b9929bac2c39bca -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspout {number}" +# Source-Hash: 9b9929bac2c39bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspoutNumber" msgstr "Bajante {number}" -# Review: required -# Source-Hash: fc34d45725838141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspouts" +# Source-Hash: fc34d45725838141 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspouts" msgstr "Bajantes" -# Review: required -# Source-Hash: ac329c74056f0616 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove downspout" +# Source-Hash: ac329c74056f0616 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.removeDownspout" msgstr "Quitar bajante" +# English: "Gutter" # Source-Hash: 14ba981da3627ae2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.label" msgstr "Canalón" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.hole.manual" msgstr "Manual" +# English: "Hole {number} (Editing)" # Source-Hash: fffde6fa82e65ffc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number} (Editing)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeEditing" msgstr "Hueco {number} (editando)" +# English: "Hole {number}" # Source-Hash: 0fe446c72451e4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeNumber" msgstr "Hueco {number}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: fd26f8df9d087c6b +# English: "{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# Source-Hash: 8eeeb79ff4221939 # Review: required -# English: "{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeSummary" -msgstr "{area} m² · {count, plural, one {{points} pto} many {{points} ptos} other {{points} ptos}} · {source}" +msgstr "{area} {unit} · {count, plural, one {{points} pto} many {{points} ptos} other {{points} ptos}} · {source}" +# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." # Source-Hash: 50e9252d30f3fb4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.description" msgstr "Caldera, climatizadora o condensadora: los conductos se conectan a sus collarines de impulsión y retorno." +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.label" msgstr "Equipo de climatización" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/hvac-equipment/tool.tsx #, fuzzy msgid "node.hvacEquipment.tool.rTRotate" msgstr "R/T girar" +# English: "Geometry preserved from an imported model when no native Pascal shape exists." # Source-Hash: f24a1089d9256460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry preserved from an imported model when no native Pascal shape exists." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.description" msgstr "Geometría conservada de un modelo importado cuando no existe una forma nativa de Pascal." +# English: "Imported Mesh" # Source-Hash: 9f9d11f287f722d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Imported Mesh" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.label" msgstr "Malla importada" +# English: "Couldn't rebuild with these values." # Source-Hash: 9cce89f94c673821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Couldn't rebuild with these values." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.couldnTRebuildWithTheseValues" msgstr "No se ha podido reconstruir con estos valores." +# English: "Parameters" # Source-Hash: 373b929318436fe3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.parameters" msgstr "Parámetros" +# English: "Rebuilding…" # Source-Hash: 558cb886dbe0babe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuilding" msgstr "Reconstruyendo…" +# English: "Rebuilding in Blender…" # Source-Hash: a094c99dc8ad29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding in Blender…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuildingInBlender" msgstr "Reconstruyendo en Blender…" +# English: "Reset {param}" # Source-Hash: 1778f93ee143cd95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset {param}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParam" msgstr "Restablecer {param}" +# English: "Reset parameters" # Source-Hash: 5a28cf5f2402dd7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParameters" msgstr "Restablecer parámetros" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetToDefault" msgstr "Restablecer valores predeterminados" +# English: "A catalog-backed item (furniture, fixtures, decorations)." # Source-Hash: 63f79b3c467177b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A catalog-backed item (furniture, fixtures, decorations)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.description" msgstr "Elemento del catálogo (muebles, equipamiento, decoración)." +# English: "Item" # Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.label" msgstr "Elemento" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.actions" msgstr "Acciones" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.delete" msgstr "Eliminar" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.dimensions" msgstr "Dimensiones" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.duplicate" msgstr "Duplicar" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.info" msgstr "Info" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.move" msgstr "Mover" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.position" msgstr "Posición" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rot" msgstr "rot" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rotation" msgstr "Rotación" +# English: "Scale" # Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale" msgstr "Escala" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale2" msgstr "escala" +# English: "Uniform Scale" # Source-Hash: b0d264950558d50e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uniform Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.uniformScale" msgstr "Escala uniforme" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.xyz" msgstr "XYZ" +# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." # Source-Hash: a9e2d7b215696e2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.description" msgstr "Marquesina adosada a un agua o exenta (un agua, dos aguas o mariposa) con estructura y desagüe gestionados." +# English: "Canopy" # Source-Hash: 8503bedfbafde9a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.label" msgstr "Marquesina" +# English: "A single floor of a building, holding walls / slabs / ceilings / items." # Source-Hash: 45673462f914d3df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single floor of a building, holding walls / slabs / ceilings / items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.description" msgstr "Una planta de un edificio con muros, forjados, techos y elementos." +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.label" msgstr "Planta" +# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." # Source-Hash: 156bdee8fee5bec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.description" msgstr "Línea frigorífica: par de cobre de aspiración y líquido que une la condensadora con la batería interior." +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.label" msgstr "Línea frigorífica" +# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." # Source-Hash: 2bd4a85bed158269 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.description" msgstr "Línea de líquido frigorífico independiente: un tubo fino de cobre desnudo; el modo Seguir traza junto a una línea existente." +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.label" msgstr "Línea de líquido" -# Review: required -# Source-Hash: 601aaf23ce5a9279 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Click to trace this lineset run" +# Source-Hash: 601aaf23ce5a9279 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.clickToTraceThisLinesetRun" msgstr "Haz clic para seguir este tramo de línea frigorífica" -# Review: required -# Source-Hash: 77df017af9260468 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow: hover a lineset" +# Source-Hash: 77df017af9260468 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.followHoverALineset" msgstr "Seguir: pasa sobre una línea frigorífica" +# English: "A persistent distance, angle, area, perimeter, or volume annotation." # Source-Hash: ff4e3bb72267859e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A persistent distance, angle, area, perimeter, or volume annotation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.description" msgstr "Anotación persistente de distancia, ángulo, área, perímetro o volumen." +# English: "Measurement" # Source-Hash: ec3567fd058bab7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.label" msgstr "Medida" +# English: "{kind} {number}" # Source-Hash: d71b38e990539364 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind} {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.numberedName" msgstr "{kind} {number}" +# English: "Currently {value}" # Source-Hash: 09865d8781ef8699 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -24566,1951 +28873,2321 @@ msgstr "{kind} {number}" msgid "node.panel.currently" msgstr "Actualmente {value}" +# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." # Source-Hash: 7764b50334e9f71d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.description" msgstr "Unión de desagüe: codo, Y a 45° o T sanitaria." +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.label" msgstr "Accesorio de tubería" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis" msgstr "Eje" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis2" msgstr "⌥ eje" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.rTRotate" msgstr "R/T girar" +# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." # Source-Hash: a0bb4762aa66fa3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.description" msgstr "Tubería de desagüe y ventilación: dibújala horizontal o alterna una pendiente de ¼″ por pie con S." +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.label" msgstr "Tubería de desagüe" +# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." # Source-Hash: 6d0cdd14d0675e05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.description" msgstr "Sifón en P: cierre hidráulico en el desagüe. El brazo del sifón llega a la ventilación." +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.label" msgstr "Sifón" -# Review: required -# Source-Hash: 73078bc0f5d686fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" Trap" +# Source-Hash: 73078bc0f5d686fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.diameterTrap" msgstr "Sifón de {diameter}\"" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.rTRotate" msgstr "R/T girar" +# English: "Experimental editable recipe" # Source-Hash: 0efe82436b915f6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Experimental editable recipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.description" msgstr "Receta editable experimental" +# English: "Procedural item" # Source-Hash: c8bbcc888cce7b8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Procedural item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.label" msgstr "Elemento procedural" +# English: "Ventilation strip running along the ridge of a roof segment." # Source-Hash: 356aa0ed69cb8ec5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ventilation strip running along the ridge of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.description" msgstr "Banda de ventilación a lo largo de la cumbrera de un faldón." +# English: "Ridge Vent" # Source-Hash: 54b74080f33c8c4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.label" msgstr "Ventilación de cumbrera" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: 18bf817325c1c537 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Caps" +# Source-Hash: 18bf817325c1c537 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.endCaps" msgstr "Tapas de extremo" -# Review: required -# Source-Hash: 466f817a0102d4a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flanged" +# Source-Hash: 466f817a0102d4a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.flanged" msgstr "Con brida" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.length" msgstr "Longitud" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.open" msgstr "Abrir" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 54b74080f33c8c4e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ridge Vent" +# Source-Hash: 54b74080f33c8c4e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.ridgeVent" msgstr "Ventilación de cumbrera" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: e9046e9823b400a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shingled" +# Source-Hash: e9046e9823b400a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.shingled" msgstr "Con tejas" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.standard" msgstr "Estándar" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.width" msgstr "Ancho" +# English: "Arch bays" # Source-Hash: c6d3976b4eb80586 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bays" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.archBays" msgstr "Vanos en arco" +# English: "Barrel arch" # Source-Hash: 1cfdd5affedf153c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.barrelArch" msgstr "Bóveda de cañón" +# English: "Flat panes follow a circular arch. Rise is measured from the eaves." # Source-Hash: b3e8929750d18f7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat panes follow a circular arch. Rise is measured from the eaves." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.flatPanesFollowACircularArch" msgstr "Los paños planos siguen un arco circular. La flecha se mide desde el alero." +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.rise" msgstr "Altura" +# English: "A pitched / hip / gable roof composed of one or more segments." # Source-Hash: 6afeec8de7ed618d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A pitched / hip / gable roof composed of one or more segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.description" msgstr "Cubierta inclinada, a cuatro aguas o a dos aguas compuesta por uno o varios faldones." +# English: "Arch bay {number}" # Source-Hash: b5be04471fd3fb7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bay {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.archBay" msgstr "Vano en arco {number}" +# English: "Back slope" # Source-Hash: 93381b9024a75a99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.back" msgstr "Faldón trasero" +# English: "Left end" # Source-Hash: 08b00107b635e11d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endLeft" msgstr "Extremo izquierdo" +# English: "Right end" # Source-Hash: 821734ea5d17a693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endRight" msgstr "Extremo derecho" +# English: "Front slope" # Source-Hash: 40c9ef62055067e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.front" msgstr "Faldón delantero" +# English: "Shed slope" # Source-Hash: 1a88da71d5e5f0d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.slope" msgstr "Faldón a un agua" +# English: "Alignment" # Source-Hash: 3bcc542295b4c61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.alignment" msgstr "Alineación" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.center" msgstr "Centro" +# English: "Choose a layout to edit the grid." # Source-Hash: 512e47607a37af46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a layout to edit the grid." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.chooseALayoutToEditThe" msgstr "Elige una distribución para editar la retícula." +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.empty" msgstr "Vacío" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.end" msgstr "Final" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.fixedSpacing" msgstr "Separación fija" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.glass" msgstr "Vidrio" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.layout" msgstr "Distribución" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maximumSpacing" msgstr "Separación máxima" +# English: "Max spacing" # Source-Hash: d10743fce478d9f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Max spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maxSpacing" msgstr "Separación máx." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.mixed" msgstr "Mixto" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panelCount" msgstr "Número de paneles" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panels" msgstr "Paneles" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.solid" msgstr "Sólido" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.spacing" msgstr "Separación" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.start" msgstr "Inicio" +# English: "{title} alignment" # Source-Hash: 095237e9e2df4d9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{title} alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.titleAlignment" msgstr "Alineación de {title}" +# English: "Changes apply to all {length} roof segments." # Source-Hash: c98a4e6b28f05e0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Changes apply to all {length} roof segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.changesApplyToAllLengthRoof" msgstr "Los cambios se aplican a los {length} segmentos de cubierta." +# English: "Choose one roof system to edit settings for all selected segments." # Source-Hash: c5d2ff7fda4787cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose one roof system to edit settings for all selected segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.chooseOneRoofSystemToEdit" msgstr "Elige un sistema de cubierta para editar los ajustes de todos los segmentos seleccionados." +# English: "Close the roof ends" # Source-Hash: 80e07a119b63187c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close the roof ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.closeTheRoofEnds" msgstr "Cerrar los extremos de la cubierta" +# English: "Default panel infill" # Source-Hash: e28dd27d54aaa7ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.defaultPanelInfill" msgstr "Relleno predeterminado del panel" +# English: "Divides each roof face into columns." # Source-Hash: 134d33e2465937e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides each roof face into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesEachRoofFaceIntoColumns" msgstr "Divide cada faldón en columnas." +# English: "Divides the length of each arch bay into columns." # Source-Hash: 896a6e4da3357c39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides the length of each arch bay into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesTheLengthOfEachArch" msgstr "Divide la longitud de cada vano en arco en columnas." +# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." # Source-Hash: be9c810d28d1feb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.endInfillFillsTheSpaceAbove" msgstr "El relleno de testero llena el espacio sobre los muros. Edita los muros y techos de las estancias por separado." +# English: "Frame color" # Source-Hash: 0b8b27dbc57cca44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameColor" msgstr "Color del marco" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameDepth" msgstr "Profundidad del marco" +# English: "Frame width" # Source-Hash: 3519d91b7bda18a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameWidth" msgstr "Ancho del marco" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.framing" msgstr "Entramado" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glass" msgstr "Vidrio" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glassTint" msgstr "Tinte del vidrio" +# English: "Glazed" # Source-Hash: dd330a94b3f7a65a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazed" msgstr "Acristalado" +# English: "Glazing is available for gable, straight shed, and barrel roofs." # Source-Hash: e6be52df9197243a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing is available for gable, straight shed, and barrel roofs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazingIsAvailableForGableStraight" msgstr "El acristalamiento está disponible para cubiertas a dos aguas, a un agua rectas y abovedadas." +# English: "Grid across the roof" # Source-Hash: a4e7ef2e31d7220b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid across the roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAcrossTheRoof" msgstr "Retícula transversal a la cubierta" +# English: "Grid along the length" # Source-Hash: 2f28a3b33fccf56b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheLength" msgstr "Retícula a lo largo" +# English: "Grid along the slope" # Source-Hash: 1f701893dbf9d72b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheSlope" msgstr "Retícula según la pendiente" +# English: "Grid within each arch bay" # Source-Hash: 20c8dfbadda20e3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid within each arch bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridWithinEachArchBay" msgstr "Retícula en cada vano en arco" +# English: "Infill" # Source-Hash: 7c339a5fae55681d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.infill" msgstr "Relleno" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.opacity" msgstr "Opacidad" +# English: "Pane thickness" # Source-Hash: 4d412746658f2faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.paneThickness" msgstr "Espesor del paño" +# English: "Roof system" # Source-Hash: 570136e723cb7d99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roofSystem" msgstr "Sistema de cubierta" +# English: "Roughness" # Source-Hash: a6a7a36d982b4911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roughness" msgstr "Rugosidad" +# English: "Spacing is measured on the slope, not its horizontal projection." # Source-Hash: 2fa6fbeda5d45949 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing is measured on the slope, not its horizontal projection." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.spacingIsMeasuredOnTheSlope" msgstr "La separación se mide sobre la pendiente, no en su proyección horizontal." +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.standard" msgstr "Estándar" +# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." # Source-Hash: efbe1c4facf561f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.subdividesEachFlatArchBayArch" msgstr "Subdivide cada vano plano en arco. Vanos en arco controla las divisiones a lo largo de la curva." +# English: "Choose a panel to override its default infill." # Source-Hash: 1497eac9e4f63016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a panel to override its default infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.chooseAPanelToOverrideIts" msgstr "Elige un panel para sustituir su relleno predeterminado." +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.column2" msgstr "Pilar" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.custom" msgstr "Personalizado" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.default" msgstr "Predeterminado" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" # Source-Hash: 9ef92ad8ae49ed72 # Review: required -# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.grid" msgstr "{columns, plural, one {{columnCount} columna} many {{columnCount} columnas} other {{columnCount} columnas}} · {rows, plural, one {{rowCount} fila} many {{rowCount} filas} other {{rowCount} filas}}" +# English: "Individual panels" # Source-Hash: 11624abbcfdd5506 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.individualPanels" msgstr "Paneles individuales" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.panelInfill" msgstr "Relleno del panel" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.resetToDefault" msgstr "Restablecer valores predeterminados" +# English: "Roof face" # Source-Hash: 6fba44f2bfd8af8e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.roofFace" msgstr "Faldón" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.row2" msgstr "Fila" +# English: "Selected panel infill" # Source-Hash: 6c2cc2d9cd778998 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.selectedPanelInfill" msgstr "Relleno del panel seleccionado" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.label" msgstr "Cubierta" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.actions" msgstr "Acciones" +# English: "Add Chimney" # Source-Hash: d60471ce3cd45348 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addChimney" msgstr "Añadir chimenea" +# English: "Add Cupola" # Source-Hash: 1ca6a1cf9cc6e3ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addCupola" msgstr "Añadir cúpula" +# English: "Add Dormer" # Source-Hash: 94dea147eeaac52b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addDormer" msgstr "Añadir buhardilla" +# English: "Add Eyebrow Vent" # Source-Hash: 5679acdd64484726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addEyebrowVent" msgstr "Añadir ventilación de ceja" +# English: "Add Gutter" # Source-Hash: 25a141dc0d7c4b44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addGutter" msgstr "Añadir canalón" +# English: "Add Skylight" # Source-Hash: 7230bdc8a2252626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSkylight" msgstr "Añadir claraboya" +# English: "Add Solar Panel" # Source-Hash: 0f220e2602b7ff24 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSolarPanel" msgstr "Añadir panel solar" +# English: "Add Vent" # Source-Hash: 4d83a2e5a4d9e6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addVent" msgstr "Añadir ventilación" +# English: "Box" # Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.box" msgstr "Caja" +# English: "box vent" # Source-Hash: 3bf078fe2560daca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "box vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.boxVent" msgstr "ventilación de caja" +# English: "chimney" # Source-Hash: 4986740251121471 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.chimney" msgstr "chimenea" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.custom" msgstr "Personalizado" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.delete" msgstr "Eliminar" +# English: "dormer" # Source-Hash: 5169968a4b0c1840 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.dormer" msgstr "buhardilla" +# English: "Draw Segment" # Source-Hash: 72e51c6e5e9c6c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.drawSegment" msgstr "Dibujar segmento" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.duplicate" msgstr "Duplicar" -# Source-Hash: 7c4196b9e791c195 +# English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaveOverhang" +msgstr "Vuelo del alero" + +# English: "Eaves" +# Source-Hash: 34756c5ea718ccb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaves" +msgstr "Aleros" + # English: "Elements" +# Source-Hash: 7c4196b9e791c195 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.elements" msgstr "Elementos" +# English: "Follows walls" # Source-Hash: 6b8ba49aa355fe87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.followsWalls" msgstr "Sigue los muros" -# Source-Hash: 23b434461c990711 +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.gableOverhang" +msgstr "Vuelo del hastial" + # English: "gutter" +# Source-Hash: 23b434461c990711 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.gutter" msgstr "canalón" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.level" msgstr "Planta" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.move" msgstr "Mover" +# English: "Move to {level}" # Source-Hash: 39661f4827b0f435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToLevel" msgstr "Mover a {level}" +# English: "Move to the roof level" # Source-Hash: c534093ff560a9f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to the roof level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToTheRoofLevel" msgstr "Mover a la planta de cubierta" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" # Source-Hash: 0939fad1cb1831f0 # Review: required -# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.onStoreyWalls" msgstr "En {storey} con sus muros, así que oculta el plano de esa planta en las vistas aislada y explosionada.{roofs, plural, =0 {} =1 {} one {} many { Las {roofCount} cubiertas de la planta se mueven juntas.} other { Las {roofCount} cubiertas de la planta se mueven juntas.}}" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.position" msgstr "Posición" +# English: "Ridge" # Source-Hash: e6ddc8a624465793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridge" msgstr "Cumbrera" +# English: "ridge vent" # Source-Hash: 83112cbc205e9972 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridgeVent" msgstr "ventilación de cumbrera" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.roof" msgstr "Cubierta" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.rotation" msgstr "Rotación" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segments" msgstr "Segmentos" +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" # Source-Hash: 0c084c517dbc153b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segmentType" msgstr "{roofType, select, hip {A cuatro aguas} gable {A dos aguas} shed {A un agua} gambrel {Holandesa} dutch {Mixta} mansard {Mansarda} flat {Plana} conical {Cónica} barrel {Abovedada} other {Cubierta}}" +# English: "skylight" # Source-Hash: 0c4c82caccb4775c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.skylight" msgstr "claraboya" +# English: "solar panel" # Source-Hash: b9f931508dd6ec70 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "solar panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.solarPanel" msgstr "panel solar" +# English: "Turbine" # Source-Hash: d2428b22f1757a4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbine" msgstr "Turbina" +# English: "turbine vent" # Source-Hash: 86d02a3d3a3a7fa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "turbine vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbineVent" msgstr "aireador de turbina" -# Source-Hash: 99afd827dbe5d647 +# English: "Apply to all planes of this roof" +# Source-Hash: 6df330bcfcaac604 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.applyToAllPlanesOfThis" +msgstr "Aplicar a todos los faldones de esta cubierta" + +# English: "Architectural shingle" +# Source-Hash: 7254a3eb47471b8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.architecturalShingle" +msgstr "Teja asfáltica arquitectónica" + +# English: "Asphalt 3-tab" +# Source-Hash: afd23a6d653689c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.asphalt3Tab" +msgstr "Teja asfáltica de 3 lengüetas" + +# English: "Clay tile" +# Source-Hash: 2650e7c436297ef2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.clayTile" +msgstr "Teja cerámica" + +# English: "Concrete tile" +# Source-Hash: 92af091c56892f25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.concreteTile" +msgstr "Teja de hormigón" + +# English: "Covering" +# Source-Hash: 75168c15633cbad3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.covering" +msgstr "Cubrición" + +# English: "Deck" +# Source-Hash: f3aa561772702108 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.deck" +msgstr "Tarima" + +# English: "Edit layers" +# Source-Hash: 14187d972ce9e1de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.editLayers" +msgstr "Editar capas" + +# English: "Finish name" +# Source-Hash: 5aff5f630e4b18c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.finishName" +msgstr "Nombre del acabado" + +# English: "Ice and water shield" +# Source-Hash: b9ed3f21665672eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.iceAndWaterShield" +msgstr "Barrera contra hielo y agua" + +# English: "Insulation name" +# Source-Hash: 6d79431c0092abac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.insulationName" +msgstr "Nombre del aislamiento" + +# English: "Interior finish" +# Source-Hash: e4e9305964238fda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.interiorFinish" +msgstr "Acabado interior" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.layers" +msgstr "Capas" + +# English: "#15 felt" +# Source-Hash: 8f343a87f121347e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n15Felt" +msgstr "Fieltro #15" + +# English: "#30 felt" +# Source-Hash: faa54521399dc5b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n30Felt" +msgstr "Fieltro #30" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.none" +msgstr "Ninguna" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.preset" +msgstr "Preajuste" + +# English: "Rafters" +# Source-Hash: a9cb232546779779 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.rafters" +msgstr "Cabios" + +# English: "Roofing" +# Source-Hash: 5c0d95afa820e706 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofing" +msgstr "Cobertura" + +# English: "Roofing name" +# Source-Hash: 75a1bff32810894e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofingName" +msgstr "Nombre de la cobertura" + +# English: "Sheathing" +# Source-Hash: 36a588e44085108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathing" +msgstr "Tablero" + +# English: "Sheathing name" +# Source-Hash: abdad84e524e56e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathingName" +msgstr "Nombre del tablero" + +# English: "Slate" +# Source-Hash: a7b5921026935493 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.slate" +msgstr "Pizarra" + +# English: "Standing-seam metal" +# Source-Hash: c156b8bd7920b129 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.standingSeamMetal" +msgstr "Chapa de junta alzada" + +# English: "Synthetic" +# Source-Hash: f5defb7d8a17154e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.synthetic" +msgstr "Sintética" + +# English: "Top chord" +# Source-Hash: 472f395a8b6319a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.topChord" +msgstr "Cordón superior" + +# English: "Trusses" +# Source-Hash: 4dd558e7eeec7acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.trusses" +msgstr "Cerchas" + +# English: "Underlayment" +# Source-Hash: 3909955f8a920be1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlayment" +msgstr "Lámina impermeable" + +# English: "Underlayment name" +# Source-Hash: 0167cfd2ec25cf5d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlaymentName" +msgstr "Nombre de la lámina" + +# English: "Wood shake" +# Source-Hash: 7095fa6e1c927c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.woodShake" +msgstr "Tejuela de madera" + # English: "A single pitched plane of a parent roof." +# Source-Hash: 99afd827dbe5d647 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.description" msgstr "Un faldón inclinado de una cubierta padre." +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.label" msgstr "Faldón" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.actions" msgstr "Acciones" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.angle" msgstr "Ángulo" +# English: "Arc" # Source-Hash: eb8f344a3c137280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.arc" msgstr "Arco" +# English: "Auto gutters" # Source-Hash: 1eb45f83eeaabfad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto gutters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoGutters" msgstr "Canalones automáticos" +# English: "Auto ridge vent" # Source-Hash: 0986c4933d2b5808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoRidgeVent" msgstr "Ventilación de cumbrera automática" +# English: "Barrel" # Source-Hash: 344fe9d407a233a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.barrel" msgstr "Bóveda" +# English: "Clipped version" # Source-Hash: 6ca418a2ecab5984 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clipped version" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.clippedVersion" msgstr "Versión recortada" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conical" msgstr "Cónica" +# English: "Conical Shape" # Source-Hash: a37cfbc4db93cfd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conicalShape" msgstr "Forma cónica" +# English: "Deck Thick." # Source-Hash: a10f29fcde60eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.deckThick" msgstr "Esp. tarima" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.delete" msgstr "Eliminar" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.depth" msgstr "Profundidad" +# English: "Diameter" # Source-Hash: 016d8e70618916d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.diameter" msgstr "Diámetro" +# English: "Done editing" # Source-Hash: e132bf88b9e4d75e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.doneEditing" msgstr "Terminar edición" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.drainage" msgstr "Desagüe" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.duplicate" msgstr "Duplicar" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.dutch" msgstr "Holandesa" -# Source-Hash: 5c99a67253bfc02a +# English: "Eave cut" +# Source-Hash: 4c539865731d5d66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.eaveCut" +msgstr "Corte del alero" + # English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.eaveOverhang" msgstr "Vuelo del alero" +# English: "Edit footprint" # Source-Hash: dc90862aafc9d287 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.editFootprint" msgstr "Editar huella" +# English: "End overhang" # Source-Hash: 9532d5e73585ea62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.endOverhang" msgstr "Vuelo del testero" +# English: "Fascia" # Source-Hash: 74f4143cd02de4a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fascia" msgstr "Tabica" +# English: "Fascia height" # Source-Hash: 45baed9dc8b8047a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fasciaHeight" msgstr "Altura de la tabica" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.flat" msgstr "Plana" +# English: "Footprint" # Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.footprint" msgstr "Huella" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gable" msgstr "Dos aguas" +# English: "Gable overhang" # Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gableOverhang" msgstr "Vuelo del hastial" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gambrel" msgstr "Holandés de buhardilla" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.hip" msgstr "Cuatro aguas" +# English: "Kink Depth" # Source-Hash: 7ae4179d0abd80f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkDepth" msgstr "Profundidad del quiebro" +# English: "Kink Height" # Source-Hash: e19b2bca70030855 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkHeight" msgstr "Altura del quiebro" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.length" msgstr "Longitud" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.mansard" msgstr "Mansarda" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.move" msgstr "Mover" +# English: "Pitch" # Source-Hash: 75e3449fc1776d09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.pitch" msgstr "Pendiente" -# Source-Hash: 9da0cbfad144d737 +# English: "Plumb" +# Source-Hash: 6ae4075a23bb4ec4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumb" +msgstr "A plomo" + +# English: "Plumb + level" +# Source-Hash: 16d8ab5937d74bdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumbLevel" +msgstr "A plomo + horizontal" + # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.position" msgstr "Posición" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.reset" msgstr "Restablecer" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofSegment" msgstr "Faldón" +# English: "Roof Type" # Source-Hash: 19ab9527ae7889f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofType" msgstr "Tipo de cubierta" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.rotation" msgstr "Rotación" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shape" msgstr "Forma" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shed" msgstr "Un agua" +# English: "Shingle Thick." # Source-Hash: 4d944a2393bbddb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shingleThick" msgstr "Esp. teja" +# English: "Span" # Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.span" msgstr "Luz" -# Source-Hash: e90aceedf4a6b183 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.square" +msgstr "A escuadra" + # English: "Start Angle" +# Source-Hash: e90aceedf4a6b183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.startAngle" msgstr "Ángulo inicial" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.structure" msgstr "Estructura" +# English: "Top Rake Length" # Source-Hash: a2db62c74fe2bcee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeLength" msgstr "Longitud del alero superior" +# English: "Top Rake Thick." # Source-Hash: 913d9552030ac86a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeThick" msgstr "Esp. alero superior" +# English: "Trim" # Source-Hash: 3beb5eb35f0c5c17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.trim" msgstr "Molduras" +# English: "Waist Height" # Source-Hash: e6c5009c0d31fca1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistHeight" msgstr "Altura de cintura" +# English: "Waist Length" # Source-Hash: 4a8caffd24b6ee02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistLength" msgstr "Longitud de cintura" +# English: "Waist Width" # Source-Hash: 71045af5c81a11e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistWidth" msgstr "Ancho de cintura" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wall" msgstr "Muro" +# English: "Wall Height" # Source-Hash: e0f973edce3e4ca8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallHeight" msgstr "Altura del muro" +# English: "Wall Thick." # Source-Hash: d0db6dee5fe2edbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallThick" msgstr "Esp. muro" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.width" msgstr "Ancho" +# English: "A captured session with optional mesh, motion, media, and sensor data." # Source-Hash: 6600a850000b98ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A captured session with optional mesh, motion, media, and sensor data." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.description" msgstr "Sesión capturada con malla, movimiento, medios y datos de sensores opcionales." +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.label" msgstr "Captura" +# English: "Segment {number}" # Source-Hash: 95b5307eefb187c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -26518,7007 +31195,7752 @@ msgstr "Captura" msgid "node.segmentNumber" msgstr "Segmento {number}" +# English: "Separator" # Source-Hash: acb8cb92b3674b50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.separator.label" msgstr "Separador" -# Source-Hash: e7512c891d8da7d8 +# English: "Batt" +# Source-Hash: e34eb5b5bfbfce7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.batt" +msgstr "Manta" + +# English: "Blown" +# Source-Hash: 430cef5fa9bd2787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.blown" +msgstr "Insuflado" + +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywall" +msgstr "Placa de yeso" + +# English: "Drywall thickness" +# Source-Hash: 4e8f119349fa2dd4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywallThickness" +msgstr "Espesor de la placa de yeso" + +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulation" +msgstr "Aislamiento" + +# English: "Insulation thickness" +# Source-Hash: 4251fc1e9051a7db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulationThickness" +msgstr "Espesor del aislamiento" + +# English: "{label} thickness" +# Source-Hash: f567133879529366 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.labelThickness" +msgstr "Espesor de {label}" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.none" +msgstr "Ninguno" + +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.osb" +msgstr "OSB" + +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plaster" +msgstr "Enlucido" + +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plywood" +msgstr "Contrachapado" + +# English: "Rigid board" +# Source-Hash: 3d153fa2037159f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.rigidBoard" +msgstr "Panel rígido" + +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacing" +msgstr "Separación" + +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. A framing spacing on centre (centre of one member to the next), e.g. 16" o.c. +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacingOC" +msgstr "{spacing} a ejes" + +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.woodPlank" +msgstr "Tabla de madera" + # English: "Turn light off" +# Source-Hash: e7512c891d8da7d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOff" msgstr "Apagar la luz" +# English: "Turn light on" # Source-Hash: d47ec95e14909fe5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turn light on" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOn" msgstr "Encender la luz" +# English: "Casing" +# Source-Hash: 18bf48fbbe121ef0 # Review: required -# Source-Hash: ee1c8bc3e27fb550 -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.casing" +msgstr "Tapajuntas" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.width" +msgstr "Ancho" + # English: "Automatic support" +# Source-Hash: ee1c8bc3e27fb550 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.automaticSupport" msgstr "Soporte automático" -# Review: required -# Source-Hash: 1353e40320c72a99 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} · segment {segment}" +# Source-Hash: 1353e40320c72a99 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.hangerOnSegment" msgstr "Colgador {number} · segmento {segment}" -# Review: required -# Source-Hash: 737ced90d0454197 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Individual hangers" +# Source-Hash: 737ced90d0454197 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.individualHangers" msgstr "Colgadores individuales" -# Review: required -# Source-Hash: e6b17b09ed91c8c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Missing support" +# Source-Hash: e6b17b09ed91c8c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.missingSupport" msgstr "Falta soporte" -# Review: required -# Source-Hash: c895c3c603382ec9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No support within reach. Move the hanger or choose another host." +# Source-Hash: c895c3c603382ec9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.noSupportWithinReachMoveThe" msgstr "No hay soporte al alcance. Mueve el colgador o elige otro anclaje." -# Review: required -# Source-Hash: 6ce526cb7444be36 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} position in metres" +# Source-Hash: 6ce526cb7444be36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionLabel" msgstr "Posición del colgador {number} en metros" -# Review: required -# Source-Hash: cbd36c4b82a55539 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position (m)" +# Source-Hash: cbd36c4b82a55539 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionM" msgstr "Posición (m)" -# Review: required -# Source-Hash: b629c4f8388fd36f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Positions are measured along each path segment. Changing spacing regenerates the numbered slots." +# Source-Hash: b629c4f8388fd36f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath" msgstr "Las posiciones se miden a lo largo de cada segmento del trazado. Cambiar la separación regenera las posiciones numeradas." -# Review: required -# Source-Hash: c21cfbdb64e53388 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal support" +# Source-Hash: c21cfbdb64e53388 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.revealSupport" msgstr "Mostrar soporte" -# Review: required -# Source-Hash: 9d5e4440db18e477 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skip" +# Source-Hash: 9d5e4440db18e477 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.skip" msgstr "Omitir" -# Review: required -# Source-Hash: 1b184f78bf989931 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} support" +# Source-Hash: 1b184f78bf989931 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.supportLabel" msgstr "Soporte del colgador {number}" +# English: "About these checks" # Source-Hash: b2d703d283baf64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "About these checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.aboutTheseChecks" msgstr "Acerca de estas comprobaciones" +# English: "All findings" # Source-Hash: 461cc26706956993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allFindings" msgstr "Todos los hallazgos" +# English: "All systems" # Source-Hash: 7beeb4c4997f5edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All systems" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allSystems" msgstr "Todos los sistemas" +# English: "Connections" # Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.connections" msgstr "Conexiones" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.drainage" msgstr "Desagüe" +# English: "Error" # Source-Hash: fc4ac19649f0ee8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Error" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.error" msgstr "Error" +# English: "Errors" # Source-Hash: b92720ff88a1bcfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Errors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.errors" msgstr "Errores" +# English: "Filter system checks" # Source-Hash: d0e23bdbe1b380ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Filter system checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.filterSystemChecks" msgstr "Filtrar comprobaciones" +# English: "Hangers" # Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.hangers" msgstr "Colgadores" +# English: "Intersections" # Source-Hash: 8cd416ca57d969f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Intersections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.intersections" msgstr "Intersecciones" +# English: "Locate {name} ({id})" # Source-Hash: 18d652275a9a525c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Locate {name} ({id})" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.locate" msgstr "Localizar {name} ({id})" +# English: "Missing item" # Source-Hash: 88546aa3a5987966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Missing item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.missingItem" msgstr "Elemento ausente" +# English: "No findings" # Source-Hash: 2452d2922a7ecf4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindings" msgstr "Sin hallazgos" +# English: "No findings in this category" # Source-Hash: 995442fbfc786768 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings in this category" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindingsInThisCategory" msgstr "Sin hallazgos en esta categoría" +# English: "Open ends" # Source-Hash: b5c58159b3eef89c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEnds" msgstr "Extremos abiertos" +# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." # Source-Hash: 3003dacc858d36ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay" msgstr "Los extremos abiertos y las ramas separadas pueden ser intencionados. Las intersecciones se comprueban con cajas envolventes; revisa huecos y holguras." +# English: "Selected connected system" # Source-Hash: c40c64562b6a1a91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected connected system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedConnectedSystem" msgstr "Sistema conectado seleccionado" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" # Source-Hash: 68355a98439229e2 # Review: required -# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedRuns" msgstr "Seleccionado: {count, plural, one {{number} tramo} many {{number} tramos} other {{number} tramos}} · {length} m" +# English: "Separate branches" # Source-Hash: e7a352627f8fcd45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate branches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.separateBranches" msgstr "Ramas separadas" +# English: "Show all findings" # Source-Hash: 910a5ee73f2ef8a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show all findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.showAllFindings" msgstr "Mostrar todos los hallazgos" +# English: "System checks" # Source-Hash: dda8887833d07390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemChecks" msgstr "Comprobaciones del sistema" +# English: "System finding" # Source-Hash: 4dad0ed68e63d124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System finding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFinding" msgstr "Hallazgo del sistema" +# English: "System findings" # Source-Hash: e37307a519466b38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFindings" msgstr "Hallazgos del sistema" +# English: "Warning" # Source-Hash: 5128de8b5a618339 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warning" msgstr "Advertencia" +# English: "Warnings" # Source-Hash: 7e1647f9ce51509e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warnings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warnings" msgstr "Advertencias" +# English: "A configurable shelving unit. Items host on each row." # Source-Hash: 26aff27b7284fc84 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable shelving unit. Items host on each row." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.description" msgstr "Estantería configurable. Los elementos se apoyan en cada fila." +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.label" msgstr "Estantería" +# English: "The top-level container holding buildings, zones, and the property boundary." # Source-Hash: c52492c73312a922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The top-level container holding buildings, zones, and the property boundary." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.description" msgstr "Contenedor de nivel superior con edificios, zonas y el límite de la parcela." +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.label" msgstr "Parcela" +# English: "Address" # Source-Hash: 3b5a53b6d058a02a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.address" msgstr "Dirección" +# English: "APN" # Source-Hash: ea03c49930137a0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "APN" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.apn" msgstr "N.º de parcela" +# English: "Auto — most north-facing edge" # Source-Hash: 8519c9ddf5e6b371 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto — most north-facing edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.autoMostNorthFacingEdge" msgstr "Auto — borde más orientado al norte" +# English: "Contour interval (site plan and 3D)" # Source-Hash: bc493d7655b3e3ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Contour interval (site plan and 3D)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.contourIntervalSitePlanAnd3d" msgstr "Equidistancia de curvas (plano de emplazamiento y 3D)" +# English: "County" # Source-Hash: 99208d226da1315f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "County" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.county" msgstr "Condado" +# English: "e.g. RS-60" # Source-Hash: 0830cf0f95ad429d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. RS-60" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGRs60" msgstr "p. ej. RS-60" +# English: "e.g. Tampa LDC §27-156" # Source-Hash: b3e7e118266b5074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. Tampa LDC §27-156" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGTampaLdc27156" msgstr "p. ej. Tampa LDC §27-156" +# English: "every 12 in" # Source-Hash: 187e11889d08a2dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 12 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every12In" msgstr "cada 12 in" +# English: "every 24 in" # Source-Hash: 17ee041f655085f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 24 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every24In" msgstr "cada 24 in" +# English: "every 6 in" # Source-Hash: 40d7f9ddf5efcb7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 6 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every6In" msgstr "cada 6 in" +# English: "Finding…" # Source-Hash: 442a3ce78ae6b462 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.finding" msgstr "Buscando…" +# English: "Find parcel" # Source-Hash: 734c1eac014adc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.findParcel" msgstr "Buscar parcela" +# English: "Front (street) edge" # Source-Hash: ccb70faaf91dbcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front (street) edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.frontStreetEdge" msgstr "Borde frontal (calle)" +# English: "Looking up the parcel and the streets around it…" # Source-Hash: 4232218226616327 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Looking up the parcel and the streets around it…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lookingUpTheParcel" msgstr "Buscando la parcela y las calles de alrededor…" +# English: "Lot area" # Source-Hash: c39eb5933468a637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lot area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lotArea" msgstr "Superficie de la parcela" +# English: "1200 W Cass St, Tampa, FL" # Source-Hash: c8a874b339ff9696 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "1200 W Cass St, Tampa, FL" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.n1200WCassStTampaFl" msgstr "1200 W Cass St, Tampa, FL" +# English: "none" # Source-Hash: 30a0924a3360be68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "none" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.none" msgstr "ninguno" +# English: "North rotation" # Source-Hash: dd4e835cb90c5736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "North rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.northRotation" msgstr "Rotación del norte" +# English: "No surveyed lines — contours are drawn from the sculpted ground." # Source-Hash: a4bfb2891c820e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No surveyed lines — contours are drawn from the sculpted ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.noSurveyedLinesContoursAreDrawn" msgstr "Sin líneas topográficas: las curvas se dibujan a partir del terreno modelado." +# English: "Orientation" # Source-Hash: 1f0d024c0adbf0e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orientation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.orientation" msgstr "Orientación" +# English: "Parcel" # Source-Hash: 9a3961409e34bf66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcel" msgstr "Parcela" +# English: "Parcel lookup failed." # Source-Hash: 3aecc563cb9a2c68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel lookup failed." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcelLookupFailed" msgstr "La búsqueda de la parcela ha fallado." +# English: "Project look" # Source-Hash: 36696c4a8bede421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.projectLook" msgstr "Aspecto del proyecto" +# English: "Save current look" # Source-Hash: 5b50fc78ccdaf7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save current look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveCurrentLook" msgstr "Guardar el aspecto actual" +# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." # Source-Hash: f06b316f421963d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveTheCurrentLightingAndDisplay" msgstr "Guardar la iluminación y la visualización actuales como predeterminadas del proyecto. Los cambios personales de visualización se quedan en este navegador." +# English: "Setbacks" # Source-Hash: e87df48b61eda8ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Setbacks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.setbacks" msgstr "Retranqueos" +# English: "Show ground" # Source-Hash: 9ede31abea7ad545 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showGround" msgstr "Mostrar terreno" +# English: "Show the contour lines in 3D" # Source-Hash: 1e16a47e5e10c83e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show the contour lines in 3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showTheContourLinesIn3d" msgstr "Mostrar las curvas de nivel en 3D" +# English: "side" # Source-Hash: 464241679edcbdb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.side" msgstr "lateral" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.site" msgstr "Parcela" +# English: "Source" # Source-Hash: 6165ee130f0082e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.source" msgstr "Fuente" +# English: "Source / citation" # Source-Hash: 5f3110d075c6a1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source / citation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.sourceCitation" msgstr "Fuente / cita" +# English: "Street address" # Source-Hash: 041cfc610fef830b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Street address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.streetAddress" msgstr "Dirección postal" +# English: "survey" # Source-Hash: bdca51c5e6437075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "survey" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.survey" msgstr "levantamiento" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" # Source-Hash: 2177f21713b01a24 # Review: required -# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.surveyedContours" msgstr "{count, plural, one {{lineCount} línea topográfica} many {{lineCount} líneas topográficas} other {{lineCount} líneas topográficas}} cada {interval} ft ({datum}) — {source}" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.terrain" msgstr "Terreno" +# English: "Type an address first." # Source-Hash: 4c8601a0b7aeed3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type an address first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.typeAnAddressFirst" msgstr "Escribe primero una dirección." +# English: "Use project look" # Source-Hash: dfe4cd35b3e0d5f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.useProjectLook" msgstr "Usar el aspecto del proyecto" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.zone" msgstr "Zona" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.front" msgstr "Frente" +# English: "Left (override)" # Source-Hash: ab1354f1aa520a54 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.leftOverride" msgstr "Izquierda (sustitución)" +# English: "Rear" # Source-Hash: 1a7e8e8f8e9232d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rear" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rear" msgstr "Fondo" +# English: "Right (override)" # Source-Hash: 8370146b38c30102 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rightOverride" msgstr "Derecha (sustitución)" +# English: "Side" # Source-Hash: feefe9c7cb273e79 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.side" msgstr "Lateral" +# English: "Framed glass opening on a roof segment." # Source-Hash: 67d903d7fe0c69c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framed glass opening on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.description" msgstr "Abertura acristalada con marco en un faldón." +# English: "Skylight" # Source-Hash: 358826fbdeaf72b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.label" msgstr "Claraboya" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: f1113261ad2797de -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along X" +# Source-Hash: f1113261ad2797de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongX" msgstr "Según X" -# Review: required -# Source-Hash: 5b84b1c644493370 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along Z" +# Source-Hash: 5b84b1c644493370 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongZ" msgstr "Según Z" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.bottom" msgstr "Inferior" -# Review: required -# Source-Hash: b8ff053666ee326b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.curb" msgstr "Bordillo" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.cutoutOffset" msgstr "Desfase del recorte" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.depth" msgstr "Profundidad" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: ce3a6c03765dbbd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.frame" msgstr "Marco" -# Review: required -# Source-Hash: 911ea36eb835f3a6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass Thickness" +# Source-Hash: 911ea36eb835f3a6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.glassThickness" msgstr "Grosor del vidrio" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: d56303913961984f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Lantern Height" +# Source-Hash: d56303913961984f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.lanternHeight" msgstr "Altura del lucernario" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.left" msgstr "Izquierda" -# Review: required -# Source-Hash: f3e9604e3b85510f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor" +# Source-Hash: f3e9604e3b85510f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motor" msgstr "Motor" -# Review: required -# Source-Hash: a025444e0f500725 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor Housing" +# Source-Hash: a025444e0f500725 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motorHousing" msgstr "Carcasa del motor" -# Review: required -# Source-Hash: b2cbc4e2e5545a6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No" +# Source-Hash: b2cbc4e2e5545a6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.no" msgstr "No" -# Review: required -# Source-Hash: a8e0b9fe5cfb4e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Motor" +# Source-Hash: a8e0b9fe5cfb4e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.noMotor" msgstr "Sin motor" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.open" msgstr "Abrir" -# Review: required -# Source-Hash: 9bfc55f879926fe6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Angle" +# Source-Hash: 9bfc55f879926fe6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.openingAngle" msgstr "Ángulo de apertura" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.right" msgstr "Derecha" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: 358826fbdeaf72b9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skylight" +# Source-Hash: 358826fbdeaf72b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.skylight" msgstr "Claraboya" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.thickness" msgstr "Espesor" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.top" msgstr "Superior" -# Review: required -# Source-Hash: 75991d106eb93dae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Scale" +# Source-Hash: 75991d106eb93dae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.topScale" msgstr "Escala superior" -# Review: required -# Source-Hash: 3272a61417949895 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Track Width" +# Source-Hash: 3272a61417949895 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.trackWidth" msgstr "Ancho del riel" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.type" msgstr "Tipo" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: 184fda5c980b9d9c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yes" +# Source-Hash: 184fda5c980b9d9c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.yes" msgstr "Sí" -# Review: required -# Source-Hash: 445aacf307de133d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat roof skylight" +# Source-Hash: 445aacf307de133d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.flat" msgstr "Claraboya para cubierta plana" -# Review: required -# Source-Hash: 446176b7c5d8b284 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof lantern" +# Source-Hash: 446176b7c5d8b284 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.lantern" msgstr "Linterna de cubierta" -# Review: required -# Source-Hash: a6559387f108518f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening skylight" +# Source-Hash: a6559387f108518f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.opening" msgstr "Claraboya practicable" -# Review: required -# Source-Hash: 379436bb5a74867a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sliding skylight" +# Source-Hash: 379436bb5a74867a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.sliding" msgstr "Claraboya corredera" -# Review: required -# Source-Hash: d26ac61f593fe1bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Walk-on rooflight" +# Source-Hash: d26ac61f593fe1bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.walkOn" msgstr "Claraboya transitable" +# English: "A polygon-bounded floor surface that hosts items on top." # Source-Hash: ad8f8036d34f6cea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygon-bounded floor surface that hosts items on top." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.description" msgstr "Superficie de suelo delimitada por un polígono que aloja elementos encima." +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.label" msgstr "Forjado" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.addHole" msgstr "Añadir hueco" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.area" msgstr "Área" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.auto" msgstr "Auto" +# English: "Base" # Source-Hash: fadada809e27477c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.base" msgstr "Base" +# English: "Cut hatch" # Source-Hash: 12195951bba19804 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut hatch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutHatch" msgstr "Cortar trampilla" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutOpening" msgstr "Abrir hueco" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.depth" msgstr "Profundidad" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.done" msgstr "Hecho" +# English: "Elevation" # Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.elevation" msgstr "Cota" +# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." # Source-Hash: 201b720b76d79762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.extendsThePerimeterDownToTerrain" msgstr "Extiende el perímetro hasta el terreno. La superficie plana, la base y el espesor no cambian." +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.fixed" msgstr "Fijo" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.floor" msgstr "Planta" +# English: "Follows terrain" # Source-Hash: 89b2cfd72a0a7e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.followsTerrain" msgstr "Sigue el terreno" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.foundation" msgstr "Cimentación" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.holes" msgstr "Huecos" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.info" msgstr "Info" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.move" msgstr "Mover" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.noHoles" msgstr "Sin huecos" +# English: "Rim" # Source-Hash: c6a29e75bf215488 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.rim" msgstr "Borde" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.slab" msgstr "Forjado" +# English: "Standard (2\")" # Source-Hash: fb23c960abf5de23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard2" msgstr "Estándar (2\")" +# English: "Standard (5cm)" # Source-Hash: 5f865b052c9b40eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (5cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard5cm" msgstr "Estándar (5 cm)" +# English: "Sunken (15cm)" # Source-Hash: cff0e2e72cfab157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken15cm" msgstr "Hundido (15 cm)" +# English: "Sunken (6\")" # Source-Hash: 8abc2aec4be98fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken6" msgstr "Hundido (6\")" +# English: "Surface" # Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.surface" msgstr "Superficie" +# English: "Thick (15cm)" # Source-Hash: 516636d85be919f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick15cm" msgstr "Grueso (15 cm)" +# English: "Thick (6\")" # Source-Hash: c84078f3560da233 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick6" msgstr "Grueso (6\")" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thickness" msgstr "Espesor" +# English: "Thin (1\")" # Source-Hash: 61c191f6df40285b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (1\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin1" msgstr "Fino (1\")" +# English: "Thin (2cm)" # Source-Hash: d345b1f707506fe7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (2cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin2cm" msgstr "Fino (2 cm)" +# English: "Grid of photovoltaic panels mounted on a roof segment." # Source-Hash: 065ffaaf64ab479d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid of photovoltaic panels mounted on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.description" msgstr "Conjunto de paneles fotovoltaicos montados en un faldón." +# English: "Solar Panel" # Source-Hash: 4d18446ff8eaa680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.label" msgstr "Panel solar" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: 2184867946897298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Array" +# Source-Hash: 2184867946897298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.array" msgstr "Matriz" -# Review: required -# Source-Hash: 6eaa0e2a78d44d59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto-fit to roof" +# Source-Hash: 6eaa0e2a78d44d59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.autoFitToRoof" msgstr "Ajustar a la cubierta" -# Review: required -# Source-Hash: 30edd1c180aac9b4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.columns" msgstr "Columnas" -# Review: required -# Source-Hash: d1c6dd6335337cc1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Custom — dimensions don't match any preset" +# Source-Hash: d1c6dd6335337cc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.customDimensionsDonTMatchAny" msgstr "Personalizado: las dimensiones no coinciden con ningún ajuste predefinido" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: fe9356b1d0e0396b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip orientation" +# Source-Hash: fe9356b1d0e0396b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flipOrientation" msgstr "Invertir orientación" -# Review: required -# Source-Hash: eaaa20f3b603140f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flush" msgstr "Enrasado" -# Review: required -# Source-Hash: fd413477f1a9e5ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameDepth" msgstr "Profundidad del marco" -# Review: required -# Source-Hash: 6aa2318fa815e5f2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameThickness" msgstr "Espesor del marco" -# Review: required -# Source-Hash: c5657f721eae3dff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap X" +# Source-Hash: c5657f721eae3dff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapX" msgstr "Separación X" -# Review: required -# Source-Hash: e6e3b393aa7c0d98 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Y" +# Source-Hash: e6e3b393aa7c0d98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapY" msgstr "Separación Y" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.mounting" msgstr "Montaje" -# Review: required -# Source-Hash: 482469f5a0873f73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel" +# Source-Hash: 482469f5a0873f73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.panel" msgstr "Panel" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.preset" msgstr "Preajuste" -# Review: required -# Source-Hash: c913b494c3901a17 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rows" +# Source-Hash: c913b494c3901a17 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.rows" msgstr "Filas" -# Review: required -# Source-Hash: a4dba2b2675dbba0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Setbacks too large to fit a panel." +# Source-Hash: a4dba2b2675dbba0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.setbacksTooLarge" msgstr "Retranqueos demasiado grandes para colocar un panel." -# Review: required -# Source-Hash: 4d18446ff8eaa680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solar Panel" +# Source-Hash: 4d18446ff8eaa680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.solarPanel" msgstr "Panel solar" -# Review: required -# Source-Hash: 0af6194a8b8d8be6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.standoff" msgstr "Separador" -# Review: required -# Source-Hash: 58cb60c14826f1d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tiltAngle" msgstr "Ángulo de inclinación" -# Review: required -# Source-Hash: 787005b26aec58a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilted" +# Source-Hash: 787005b26aec58a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tilted" msgstr "Inclinado" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.width" msgstr "Ancho" -# Review: required -# Source-Hash: cd53f16290b67f7f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compact" +# Source-Hash: cd53f16290b67f7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.compact" msgstr "Compacto" -# Review: required -# Source-Hash: 317ebe2aa996af70 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frameless" +# Source-Hash: 317ebe2aa996af70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.frameless" msgstr "Sin marco" -# Review: required -# Source-Hash: 46660ad432e19312 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential" +# Source-Hash: 46660ad432e19312 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residential" msgstr "Residencial" -# Review: required -# Source-Hash: b33386b8fe3725fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential Large" +# Source-Hash: b33386b8fe3725fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residentialLarge" msgstr "Residencial grande" +# English: "Player or camera origin within a level. One per level." # Source-Hash: 638f17c80f424dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Player or camera origin within a level. One per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.description" msgstr "Origen del jugador o la cámara en una planta. Uno por planta." +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.label" msgstr "Punto de aparición" +# English: "Body" # Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.body" msgstr "Cuerpo" +# English: "Center stringer" # Source-Hash: 29b9fdb127a54435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center stringer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.centerStringer" msgstr "Zanca central" +# English: "Closed risers" # Source-Hash: b2b2383577835a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closed risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.closedRisers" msgstr "Contrahuellas cerradas" -# Source-Hash: 6f6b188bff3c0223 +# English: "From structure" +# Source-Hash: 49a25acbaf33ee3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/construction-controls.tsx +#, fuzzy +msgid "node.stair.constructionControls.fromStructure" +msgstr "Según la estructura" + # English: "Inherit" +# Source-Hash: 6f6b188bff3c0223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.inherit" msgstr "Heredar" +# English: "Nosing" # Source-Hash: ce25dc4a23f3ae10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nosing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.nosing" msgstr "Mamperlán" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.open" msgstr "Abrir" +# English: "Original" # Source-Hash: 7717e0464ad1e7df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.original" msgstr "Original" +# English: "Riser thickness" # Source-Hash: 8a6f6c6b0cca7460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.riserThickness" msgstr "Espesor de la contrahuella" +# English: "Side stringers" # Source-Hash: 2fe058966023b542 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side stringers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.sideStringers" msgstr "Zancas laterales" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.solid" msgstr "Sólido" +# English: "Stringer depth" # Source-Hash: 4c1489839e306865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerDepth" msgstr "Canto de la zanca" +# English: "Stringer width" # Source-Hash: 17e1a09c422d3660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerWidth" msgstr "Ancho de la zanca" +# English: "Tread finish" # Source-Hash: 146f8e61ff310dc5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadFinish" msgstr "Acabado de huella" +# English: "Tread thickness" # Source-Hash: 9089f91f3fbbf4cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadThickness" msgstr "Espesor de la huella" +# English: "Waist" # Source-Hash: 44dda7136b93c8ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waist" msgstr "Losa" +# English: "Waist thickness" # Source-Hash: dfd6d2ff7a94f22e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waistThickness" msgstr "Espesor de la losa" +# English: "A stair composed of one or more flights with configurable treads, risers, railings." # Source-Hash: 8535b75637105412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A stair composed of one or more flights with configurable treads, risers, railings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.description" msgstr "Escalera de uno o varios tramos con huellas, contrahuellas y barandillas configurables." +# English: "The flight chain does not reach the declared destination." # Source-Hash: c24b098693e1006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The flight chain does not reach the declared destination." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.arrivalMismatch" msgstr "La cadena de tramos no llega al destino declarado." +# English: "Two risers plus going is outside the 0.60–0.65 m design range." # Source-Hash: 124ad1cd6bb00013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Two risers plus going is outside the 0.60–0.65 m design range." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.comfortTarget" msgstr "Dos contrahuellas más una huella quedan fuera del rango de diseño de 0,60–0,65 m." +# English: "Going is below the design target." # Source-Hash: 7452fcd8370bef34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Going is below the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.goingTarget" msgstr "La huella es inferior al objetivo de diseño." +# English: "Overhead surfaces leave less than the target headroom." # Source-Hash: 449683eb727091d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhead surfaces leave less than the target headroom." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomTarget" msgstr "Las superficies superiores dejan menos altura libre que el objetivo." +# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." # Source-Hash: 9e9357408f6a0d08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomUnresolved" msgstr "No se ha podido evaluar la altura libre detallada: revisa el número de contrahuellas y el presupuesto de consultas de superficie." +# English: "Flight dimensions and integer riser count must be positive." # Source-Hash: 40fd019d72ff3835 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight dimensions and integer riser count must be positive." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidFlight" msgstr "Las dimensiones del tramo y el número entero de contrahuellas deben ser positivos." +# English: "The destination must be above the stair base." # Source-Hash: 3d4b6f859bd8de50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The destination must be above the stair base." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidRise" msgstr "El destino debe estar por encima de la base de la escalera." +# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." # Source-Hash: 5f196b22e2c8296a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.landingOverlap" msgstr "La profundidad del descansillo supera un círculo completo en la línea de huella; el descansillo visible se limita a un círculo." +# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." # Source-Hash: 0122ec92b951a114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.narrowWinderEnd" msgstr "El extremo estrecho de una huella compensada es inferior a 0,10 m; revisa el uso previsto de la escalera." +# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." # Source-Hash: a664dc7f3b014af8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.nonuniformRisers" msgstr "Las contrahuellas del tramo difieren. Usa la reparación de contrahuellas uniformes solo si es un diseño propuesto." +# English: "A landing has a stored rise; inspect its transition before repair." # Source-Hash: cd140b519d37b01e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A landing has a stored rise; inspect its transition before repair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.raisedLanding" msgstr "Un descansillo tiene una altura guardada; revisa su transición antes de reparar." +# English: "Riser height exceeds the design target." # Source-Hash: a94cfa7b2a21cf14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser height exceeds the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.riserTarget" msgstr "La contrahuella supera el objetivo de diseño." +# English: "Stair" # Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.label" msgstr "Escalera" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.straight" msgstr "Recto" +# English: "{layout} turn" # Source-Hash: 1cda16cb49e6fb93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{layout} turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.turn" msgstr "Giro {layout}" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.actions" msgstr "Acciones" +# English: "Add flight" # Source-Hash: 15eaf25f306541f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addFlight" msgstr "Añadir tramo" +# English: "Add landing" # Source-Hash: 1fa37c346bffb8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addLanding" msgstr "Añadir descansillo" +# English: "Apply {label} layout" # Source-Hash: 9ccbd6c1785f1e73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply {label} layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.applyLabelLayout" msgstr "Aplicar la distribución {label}" +# English: "Auto Cutout" # Source-Hash: 7eb42ea0fc442b8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto Cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.autoCutout" msgstr "Recorte automático" +# English: "Balusters" # Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.balusters" msgstr "Balaustres" +# English: "Boards" # Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.boards" msgstr "Tablas" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.both" msgstr "Ambos" +# English: "Cable" # Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.cable" msgstr "Cable" +# English: "Center Column" # Source-Hash: 11d6eec5d485488d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.centerColumn" msgstr "Columna central" +# English: "Clockwise" # Source-Hash: 5fbbf4ad24befa7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.clockwise" msgstr "Horario" +# English: "Connected to {surface}. Move the stair to connect to another edge." # Source-Hash: 31028ca51b49018b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to {surface}. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedTo" msgstr "Conectada a {surface}. Mueve la escalera para conectarla a otro borde." +# English: "Connected to a surface. Move the stair to connect to another edge." # Source-Hash: 33133921e270509f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to a surface. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedToSurface" msgstr "Conectada a una superficie. Mueve la escalera para conectarla a otro borde." +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.construction" msgstr "Construcción" +# English: "Counterclockwise" # Source-Hash: 5e3e3ab7d91c2c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Counterclockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.counterclockwise" msgstr "Antihorario" +# English: "Curved" # Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.curved" msgstr "Curvo" +# English: "Custom rise" # Source-Hash: 0bd4232a56d25366 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.customRise" msgstr "Contrahuella personalizada" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.deck" msgstr "Tarima" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.delete" msgstr "Eliminar" +# English: "Design targets" # Source-Hash: 1c34e5cc37add163 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Design targets" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.designTargets" msgstr "Objetivos de diseño" +# English: "Destination" # Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.destination" msgstr "Destino" +# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." # Source-Hash: 1fa48e82752d1505 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.dragNearADeckPatioConcrete" msgstr "Arrastra cerca de una tarima, patio, losa o borde de descansillo para ajustar la escalera." +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.duplicate" msgstr "Duplicar" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalAngle" msgstr "Ángulo igual" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalGoing" msgstr "Huella igual" +# English: "Fit risers and going" # Source-Hash: 6ff728d5058c7e31 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit risers and going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitRisersAndGoing" msgstr "Ajustar contrahuellas y huellas" +# English: "Fit To Floor" # Source-Hash: d60a4c7995bd2184 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit To Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitToFloor" msgstr "Ajustar al suelo" +# English: "Fit uniform risers" # Source-Hash: 0638614580661eb0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit uniform risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitUniformRisers" msgstr "Ajustar contrahuellas uniformes" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" # Source-Hash: 4d365dbc1fb92b90 # Review: required -# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.flightMeasurements" msgstr "Tramo {number}: {count, plural, one {{riserCount} contrahuella} many {{riserCount} contrahuellas} other {{riserCount} contrahuellas}} · CH {riser} · H {going} · {slope}" +# English: "Floors, ceilings and stairs only." # Source-Hash: 6e946bd1acd94637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floors, ceilings and stairs only." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.floorsCeilingsAndStairsOnly" msgstr "Solo suelos, techos y escaleras." +# English: "Follows deck" # Source-Hash: fa10ea42944107a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsDeck" msgstr "Sigue la tarima" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsLevel" msgstr "Sigue la planta" +# English: "From Level" # Source-Hash: b7fc627a965bc016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fromLevel" msgstr "Desde la planta" +# English: "Geometry" # Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.geometry" msgstr "Geometría" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.glass" msgstr "Vidrio" +# English: "Headroom:" # Source-Hash: 55adba80c39500f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Headroom:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.headroom" msgstr "Altura libre:" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.height" msgstr "Altura" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerGap" msgstr "Separación interior" +# English: "Inner Radius" # Source-Hash: 8b54356df7ae40bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerRadius" msgstr "Radio interior" +# English: "Integrated" # Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.integrated" msgstr "Integrado" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.landing" msgstr "Descansillo" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.layout" msgstr "Distribución" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.left" msgstr "Izquierda" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.leftTurn" msgstr "Giro a la izquierda" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.measurements" msgstr "Medidas" +# English: "Metal" # Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.metal" msgstr "Metal" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.move" msgstr "Mover" +# English: "Moving this stair keeps its current rise, steps, and run." # Source-Hash: a4f1cc34aae4db27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Moving this stair keeps its current rise, steps, and run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.movingThisStairKeepsItsCurrent" msgstr "Mover esta escalera mantiene su altura, peldaños y desarrollo actuales." +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.none" msgstr "Ninguno" +# English: "No overhead surface" # Source-Hash: 47c858b3022b3ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No overhead surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.noOverheadSurface" msgstr "Sin superficie superior" +# English: "Not evaluated" # Source-Hash: 17f1933d93cf655c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not evaluated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.notEvaluated" msgstr "No evaluado" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.opening" msgstr "Abertura" +# English: "Opening Offset" # Source-Hash: 599220556fc4bbfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.openingOffset" msgstr "Desfase del hueco" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.position" msgstr "Posición" +# English: "Post & rail" # Source-Hash: f522323372a7a59e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post & rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.postRail" msgstr "Poste y travesaño" +# English: "Railing" # Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.railing" msgstr "Barandilla" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.right" msgstr "Derecha" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rightTurn" msgstr "Giro a la derecha" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rise" msgstr "Altura" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rotation" msgstr "Rotación" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segments" msgstr "Segmentos" +# English: "{type, select, landing {Landing} other {Stair}}" # Source-Hash: 0b9a0bef102b75bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{type, select, landing {Landing} other {Stair}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segmentType" msgstr "{type, select, landing {Descansillo} other {Tramo}}" +# English: "Sizing" # Source-Hash: 87b926bbf34a78ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sizing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sizing" msgstr "Dimensionado" +# English: "Snap to nearby surfaces" # Source-Hash: 8a0f4562198c266f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap to nearby surfaces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.snapToNearbySurfaces" msgstr "Ajustar a superficies cercanas" +# English: "Spiral" # Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.spiral" msgstr "Helicoidal" +# English: "Staircase" # Source-Hash: f8d4f66145e1eba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.staircase" msgstr "Escalera" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.steps" msgstr "Peldaños" +# English: "Step Supports" # Source-Hash: 8846dc96332c5181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step Supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.stepSupports" msgstr "Soportes de peldaño" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.straight" msgstr "Recto" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.style" msgstr "Estilo" +# English: "Surface connection" # Source-Hash: cde3878287069df2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.surfaceConnection" msgstr "Conexión a la superficie" +# English: "Sweep" # Source-Hash: 4e0f8b363503efff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sweep" msgstr "Barrido" +# English: "Target going" # Source-Hash: 4c294d88d712855a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetGoing" msgstr "Huella objetivo" +# English: "Target headroom" # Source-Hash: 74cfba22cf2e6eb8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetHeadroom" msgstr "Altura libre objetivo" +# English: "Target max riser" # Source-Hash: 9d05958a265a6726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target max riser" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMaxRiser" msgstr "Contrahuella máxima objetivo" +# English: "Target minimum going" # Source-Hash: d10322b16178ce37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target minimum going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMinimumGoing" msgstr "Huella mínima objetivo" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.thickness" msgstr "Espesor" +# English: "To" # Source-Hash: b3214a20e45a7aed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.to" msgstr "Hasta" +# English: "Top Landing" # Source-Hash: 846fc58043454194 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.topLanding" msgstr "Descansillo superior" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.type" msgstr "Tipo" +# English: "Unavailable" # Source-Hash: b4c2f503b19443c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.unavailable" msgstr "No disponible" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.walkingLineOffset" msgstr "Desfase de la línea de huella" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.width" msgstr "Ancho" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.winder" msgstr "Compensada" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.both" msgstr "Ambos" +# English: "Bottom sloped extension" # Source-Hash: fe633d20f198d7e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom sloped extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.bottomExtension" msgstr "Prolongación inclinada inferior" +# English: "Continuous" # Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.continuous" msgstr "Continuo" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.floor" msgstr "Planta" +# English: "Handrail diameter" # Source-Hash: 644d2d9e9c3dd31f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailDiameter" msgstr "Diámetro del pasamanos" +# English: "Handrail height" # Source-Hash: 5f3f2be1e572a066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailHeight" msgstr "Altura del pasamanos" +# English: "Handrail inset" # Source-Hash: cb3239847f068a1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailInset" msgstr "Retranqueo del pasamanos" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.left" msgstr "Izquierda" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.none" msgstr "Ninguno" +# English: "No return" # Source-Hash: e6ac8d7c9639c351 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.noReturn" msgstr "Sin retorno" +# English: "Original layout" # Source-Hash: 5688b2003002d03a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.originalLayout" msgstr "Distribución original" +# English: "Post-facing" # Source-Hash: d0d904fbd052d7b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post-facing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.postFacing" msgstr "Hacia el poste" +# English: "Return length" # Source-Hash: fc4d8c6f156b0430 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.returnLength" msgstr "Longitud del retorno" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.right" msgstr "Derecha" +# English: "Separate handrail" # Source-Hash: e1f8354a95b5060a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate handrail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.separateHandrail" msgstr "Pasamanos independiente" +# English: "Set the length to reach the wall or post." # Source-Hash: 005f0197ec3bc95a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set the length to reach the wall or post." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.setTheLengthToReachThe" msgstr "Ajusta la longitud para llegar al muro o al poste." +# English: "Top horizontal extension" # Source-Hash: bef713f3ec96f758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top horizontal extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy -msgid "node.stair.railingControls.topExtension" -msgstr "Prolongación horizontal superior" +msgid "node.stair.railingControls.topExtension" +msgstr "Prolongación horizontal superior" + +# English: "Wall-facing" +# Source-Hash: bdad79929de6b142 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/railing-controls.tsx +#, fuzzy +msgid "node.stair.railingControls.wallFacing" +msgstr "Hacia el muro" + +# English: "A solid poured flight on a footing; risers closed, no stringers." +# Source-Hash: 1709ce71eba750ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.aSolidPouredFlightOnA" +msgstr "Un tramo macizo hormigonado sobre una zapata; contrahuellas cerradas, sin zancas." + +# English: "Auto ({autoCount})" +# Source-Hash: 56be143b76f65392 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoAutocount" +msgstr "Auto ({autoCount})" + +# English: "Auto (closed)" +# Source-Hash: d7820aaac08e4fcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoClosed" +msgstr "Auto (cerradas)" + +# English: "Auto (open)" +# Source-Hash: c7cbd493df719e71 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoOpen" +msgstr "Auto (abiertas)" + +# English: "Auto (pressure-treated)" +# Source-Hash: be21258d52930d5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoPressureTreated" +msgstr "Auto (tratada a presión)" + +# English: "Auto ({treads})" +# Source-Hash: 1828540d95a5b5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoTreads" +msgstr "Auto ({treads})" + +# English: "Auto (untreated)" +# Source-Hash: 43c1949fa53685b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoUntreated" +msgstr "Auto (sin tratar)" + +# English: "Closed (1x8 boards)" +# Source-Hash: d0a3ca3ce29ae833 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.closed1x8Boards" +msgstr "Cerradas (tablas 1x8)" + +# English: "Concrete" +# Source-Hash: 824471a3691b51db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.concrete" +msgstr "Hormigón" + +# English: "{count, plural, one {# steel channel} other {# steel channels}}" +# Source-Hash: dcb2dd54c3301068 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannels" +msgstr "{count, plural, one {# perfil en U de acero} many {# perfiles en U de acero} other {# perfiles en U de acero}}" + +# English: "{count, plural, one {# steel channel at {spacing} o.c.} other {# steel channels at {spacing} o.c.}}" +# Source-Hash: fbf12088b828a103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannelsAtSpacing" +msgstr "{count, plural, one {# perfil en U de acero a {spacing} entre ejes} many {# perfiles en U de acero a {spacing} entre ejes} other {# perfiles en U de acero a {spacing} entre ejes}}" + +# English: "{count, plural, one {# stringer} other {# stringers}}" +# Source-Hash: 6f0f06f0531c7e96 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringers" +msgstr "{count, plural, one {# zanca} many {# zancas} other {# zancas}}" + +# English: "{count, plural, one {# stringer at {spacing} o.c.} other {# stringers at {spacing} o.c.}}" +# Source-Hash: cfdb3f3fc95ea940 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringersAtSpacing" +msgstr "{count, plural, one {# zanca a {spacing} entre ejes} many {# zancas a {spacing} entre ejes} other {# zancas a {spacing} entre ejes}}" + +# English: "Cut" +# Source-Hash: fa4b97d3aa600bcd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.cut" +msgstr "Dentada" + +# English: "Flight {number}: {layout}" +# Source-Hash: ba4f3c1346b3e7af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.flightLayout" +msgstr "Tramo {number}: {layout}" + +# English: "Housed sides" +# Source-Hash: 4cbb34fdf4c1e107 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.housedSides" +msgstr "Laterales cajeados" + +# English: "{inches} in o.c." +# Source-Hash: e3ddff241f642295 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.inchesInOC" +msgstr "{inches} in a ejes" + +# English: "{layout} (auto)" +# Source-Hash: 542ca0bee99a1533 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. The stringer count was chosen automatically, not set by hand. +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.layoutAuto" +msgstr "{layout} (auto)" + +# English: "Lumber" +# Source-Hash: 73dcfae611575500 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.lumber" +msgstr "Madera" + +# English: "Max spacing" +# Source-Hash: d10743fce478d9f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.maxSpacing" +msgstr "Separación máx." + +# English: "Nosing" +# Source-Hash: ce25dc4a23f3ae10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.nosing" +msgstr "Mamperlán" + +# English: "One {stock}" +# Source-Hash: ba534b07476bc06d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.oneStock" +msgstr "Una tabla {stock}" + +# English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.open" +msgstr "Abiertas" + +# English: "Original body" +# Source-Hash: 20f023962f848e14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.originalBody" +msgstr "Cuerpo original" + +# English: "Plywood + finish" +# Source-Hash: ac658d76a942652f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.plywoodFinish" +msgstr "Contrachapado + acabado" + +# English: "Pressure-treated" +# Source-Hash: 86fda85cdc89fd27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.pressureTreated" +msgstr "Tratada a presión" + +# English: "Risers" +# Source-Hash: d903bba423ddb755 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.risers" +msgstr "Contrahuellas" + +# English: "Steel stringers" +# Source-Hash: ff0400a550f24adf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.steelStringers" +msgstr "Zancas de acero" + +# English: "Stringer count" +# Source-Hash: 50d4cac7f3002cd2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerCount" +msgstr "Número de zancas" + +# English: "Stringers" +# Source-Hash: 71733e5b479af2a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringers" +msgstr "Zancas" + +# English: "Stringer type" +# Source-Hash: 191196b94d1c86f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerType" +msgstr "Tipo de zanca" + +# English: "Structure" +# Source-Hash: a20c89205a4448d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.structure" +msgstr "Estructura" + +# English: "Treads" +# Source-Hash: 869e3785611a0858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.treads" +msgstr "Huellas" + +# English: "Two {stock}" +# Source-Hash: e1e6555b827b4620 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.twoStock" +msgstr "Dos tablas {stock}" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.untreated" +msgstr "Sin tratar" -# Source-Hash: bdad79929de6b142 +# English: "Wood stringers" +# Source-Hash: 401bae5ffee978fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall-facing" -#: packages/nodes/src/stair/railing-controls.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx #, fuzzy -msgid "node.stair.railingControls.wallFacing" -msgstr "Hacia el muro" +msgid "node.stair.structureControls.woodStringers" +msgstr "Zancas de madera" +# English: "A single flight of a parent stair." # Source-Hash: ce0b6361eb77e93c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single flight of a parent stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.description" msgstr "Un tramo de una escalera padre." +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.label" msgstr "Tramo de escalera" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.actions" msgstr "Acciones" +# English: "Attachment" # Source-Hash: 551b04c03b07595e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.attachment" msgstr "Conexión" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.delete" msgstr "Eliminar" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.dimensions" msgstr "Dimensiones" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.duplicate" msgstr "Duplicar" +# English: "Editing switches the stair to Custom rise" # Source-Hash: ad063fb27f370c28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Editing switches the stair to Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.editingSwitchesTheStairToCustom" msgstr "Editar cambia la escalera a contrahuella personalizada" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalAngle" msgstr "Ángulo igual" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalGoing" msgstr "Huella igual" +# English: "Fill to floor" # Source-Hash: 66f1018b50c2e426 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.fillToFloor" msgstr "Rellenar hasta el suelo" +# English: "Flight" # Source-Hash: deff4254bb163b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flight" msgstr "Tramo" +# English: "Flight shape" # Source-Hash: d6da828cd0c5e8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flightShape" msgstr "Forma del tramo" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.front" msgstr "Frente" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.height" msgstr "Altura" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.innerGap" msgstr "Separación interior" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.landing" msgstr "Descansillo" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.left" msgstr "Izquierda" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.leftTurn" msgstr "Giro a la izquierda" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.length" msgstr "Longitud" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.move" msgstr "Mover" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.position" msgstr "Posición" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.right" msgstr "Derecha" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rightTurn" msgstr "Giro a la derecha" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rotation" msgstr "Rotación" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.stairSegment" msgstr "Tramo de escalera" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.steps" msgstr "Peldaños" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.straight" msgstr "Recto" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.structure" msgstr "Estructura" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.thickness" msgstr "Espesor" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.type" msgstr "Tipo" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.walkingLineOffset" msgstr "Desfase de la línea de huella" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.width" msgstr "Ancho" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.winder" msgstr "Compensada" +# English: "Persistent construction grid axis with identification bubbles." # Source-Hash: 5434a3189441c913 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Persistent construction grid axis with identification bubbles." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.description" msgstr "Eje de replanteo persistente con burbujas de identificación." +# English: "Structural Grid" # Source-Hash: 0cfe636f7fdd9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.label" msgstr "Retícula estructural" +# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." # Source-Hash: 36b714210f25c284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.description" msgstr "Extractor eólico giratorio para un faldón." +# English: "Turbine Vent" # Source-Hash: 4a855abb962f5e7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.label" msgstr "Extractor eólico" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.actions" msgstr "Acciones" -# Review: required -# Source-Hash: c65f02bcdc3231d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.cylinder" msgstr "Cilindro" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.delete" msgstr "Eliminar" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.diameter" msgstr "Diámetro" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.dimensions" msgstr "Dimensiones" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.duplicate" msgstr "Duplicar" -# Review: required -# Source-Hash: 2d4223f80dc3945c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Globe" +# Source-Hash: 2d4223f80dc3945c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.globe" msgstr "Globo" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.height" msgstr "Altura" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.motion" msgstr "Movimiento" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.move" msgstr "Mover" -# Review: required -# Source-Hash: 6faf9532dcbfe4bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Height" +# Source-Hash: 6faf9532dcbfe4bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.neckHeight" msgstr "Altura del cuello" -# Review: required -# Source-Hash: 1550d72740e66f77 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pause" +# Source-Hash: 1550d72740e66f77 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.pause" msgstr "Pausa" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.play" msgstr "Reproducir" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.position" msgstr "Posición" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.rotation" msgstr "Rotación" -# Review: required -# Source-Hash: bf553d40ce68834d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spin Speed" +# Source-Hash: bf553d40ce68834d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.spinSpeed" msgstr "Velocidad de giro" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.style" msgstr "Estilo" -# Review: required -# Source-Hash: 4a855abb962f5e7a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Turbine Vent" +# Source-Hash: 4a855abb962f5e7a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.turbineVent" msgstr "Extractor eólico" -# Review: required -# Source-Hash: 80cef0dce71e121f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vanes" +# Source-Hash: 80cef0dce71e121f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.vanes" msgstr "Álabes" +# English: "A named group of zones forming one apartment, hotel room or commercial lot." # Source-Hash: e3d2aaaf5c72bc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A named group of zones forming one apartment, hotel room or commercial lot." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.description" msgstr "Grupo con nombre de zonas que forma un piso, una habitación de hotel o un local comercial." +# English: "Apartment" # Source-Hash: 40e5a51d0d205fcf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apartment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.apartment" msgstr "Apartamento" +# English: "Commercial" # Source-Hash: ad041d9634dd55ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Commercial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.commercial" msgstr "Comercial" +# English: "Common" # Source-Hash: 862f30cb2d49f812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Common" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.common" msgstr "Común" +# English: "Hotel room" # Source-Hash: cd7e1bc42d47a105 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hotel room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.hotelRoom" msgstr "Habitación de hotel" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.label" msgstr "Unidad" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberRoom" msgstr "Estancia" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberZone" msgstr "Zona" +# English: "Members sit on levels that are not adjacent." # Source-Hash: e25d5b5630b9d8df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members sit on levels that are not adjacent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.nonAdjacentLevels" msgstr "Los miembros están en plantas no contiguas." +# English: "No zones assigned yet." # Source-Hash: 5ebeb440e0e64d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones assigned yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.noZonesAssigned" msgstr "Aún no hay zonas asignadas." +# English: "Remove {name} from unit" # Source-Hash: 07731fa851c75d8c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove {name} from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeMember" msgstr "Quitar {name} de la unidad" +# English: "Remove zone from unit" # Source-Hash: 389839734c8350b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove zone from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeZone" msgstr "Quitar la zona de la unidad" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.actions" msgstr "Acciones" +# English: "Color" # Source-Hash: fe18958cedd627d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.color" msgstr "Color" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.delete" msgstr "Eliminar" +# English: "Focus" # Source-Hash: b80dc93401f953a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focus" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.focus" msgstr "Enfocar" +# English: "Gross area" # Source-Hash: 1218205b3bb34f1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gross area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.grossArea" msgstr "Superficie bruta" +# English: "Kind" # Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kind" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.kind" msgstr "Tipo" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.levels" msgstr "Plantas" +# English: "Members" # Source-Hash: 6bbc51529519435c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.members" msgstr "Miembros" +# English: "Name" # Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.name" msgstr "Nombre" +# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." # Source-Hash: 54b25f70c7b63446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.noRoomsYetPickThisUnit" msgstr "Aún no hay estancias. Elige esta unidad desde el panel de una estancia o dibuja zonas mientras está enfocada." +# English: "Report" # Source-Hash: 85d640066187108d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Report" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.report" msgstr "Informe" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.unit" msgstr "Unidad" -# Source-Hash: d53242481ff46157 +# English: "Dimensions to the exterior faces (outside size). Click for both" +# Source-Hash: e04a35319a83ad7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheExteriorFacesOutside" +msgstr "Cotas a las caras exteriores (medida exterior). Haz clic para ambas" + +# English: "Dimensions to the interior and exterior faces. Click for interior only" +# Source-Hash: 41c6c329ecb15086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorAndExterior" +msgstr "Cotas a las caras interiores y exteriores. Haz clic para solo interiores" + +# English: "Dimensions to the interior faces (room size). Click for exterior only" +# Source-Hash: 57524fee08ddde73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorFacesRoom" +msgstr "Cotas a las caras interiores (medida de la estancia). Haz clic para solo exteriores" + +# English: "ID" +# Source-Hash: 81ef3a25ccb6eaf7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: interior dimension, measured to the interior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.id" +msgstr "Int." + +# English: "ID+OD" +# Source-Hash: e846b1fcc616fb6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: ID is the interior dimension, OD the outside dimension. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.idOd" +msgstr "Int.+Ext." + # English: "Merge walls" +# Source-Hash: d53242481ff46157 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.mergeWalls" msgstr "Unir muros" -# Source-Hash: d7ead4708d50965f +# English: "OD" +# Source-Hash: fdaa3b3faa1844fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: outside dimension, measured to the exterior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.od" +msgstr "Ext." + # English: "Split wall" +# Source-Hash: d7ead4708d50965f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.splitWall" msgstr "Dividir muro" -# Source-Hash: 96a6f084d095ea36 +# English: "These walls cannot be merged." +# Source-Hash: 2f8c3189a0b3972b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.theseWallsCannotBeMerged" +msgstr "Estos muros no se pueden unir." + # English: "Column {column}, row {row}: {type, select, glass {glass} solid {solid} other {empty}}" +# Source-Hash: 96a6f084d095ea36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.cell" msgstr "Columna {column}, fila {row}: {type, select, glass {vidrio} solid {opaco} other {vacío}}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" # Source-Hash: 904e9b269b68c670 # Review: required -# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.columnSpacing" msgstr "{count, plural, one {{number} columna} many {{number} columnas} other {{number} columnas}} · retícula de {spacing}" +# English: "No panels" # Source-Hash: 8b2b2f11fca9f077 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.noPanels" msgstr "Sin paneles" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" # Source-Hash: 9f5d9775377b666e # Review: required -# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.rowSpacing" msgstr "{count, plural, one {{number} fila} many {{number} filas} other {{number} filas}} · retícula de {spacing}" +# English: "Apply tint" # Source-Hash: 90638d5a19a4f0a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.applyTint" msgstr "Aplicar tinte" +# English: "Border / opening frame" # Source-Hash: a7d624ec0f879829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Border / opening frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.borderOpeningFrame" msgstr "Borde / marco del hueco" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottom" msgstr "Inferior" +# English: "Bottom row" # Source-Hash: 5503fb89c40cefeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottomRow" msgstr "Fila inferior" +# English: "Cancel tint" # Source-Hash: 1928d9e7a16360d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.cancelTint" msgstr "Cancelar tinte" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.center" msgstr "Centro" +# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." # Source-Hash: 9f4c033fa80119db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.chooseACellOrEnterIts" msgstr "Elige una celda o introduce su columna y fila. Usa las flechas para moverte entre celdas. Las columnas parten del inicio del muro; las filas se cuentan hacia arriba." +# English: "Clear hidden overrides" # Source-Hash: bbeadcebc59c923f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear hidden overrides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.clearHiddenOverrides" msgstr "Borrar sustituciones ocultas" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.column" msgstr "Pilar" +# English: "Column layout" # Source-Hash: 711a6426e336877a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnLayout" msgstr "Distribución de columnas" +# English: "Column {column}, row {row}" # Source-Hash: 59b2ab5fdf54c2e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column {column}, row {row}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnRow" msgstr "Columna {column}, fila {row}" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.construction" msgstr "Construcción" +# English: "Continuous mullions and transoms assembled along the wall." # Source-Hash: 59d2bec8b9ae1025 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous mullions and transoms assembled along the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong" msgstr "Montantes y travesaños continuos montados a lo largo del muro." +# English: "Curtain wall panel selection" # Source-Hash: bbc5e5f3f3b99232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall panel selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.curtainWallPanelSelection" msgstr "Selección de paneles del muro cortina" +# English: "Custom infill" # Source-Hash: 9fd79cf417eab9ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.customInfill" msgstr "Relleno personalizado" +# English: "Default panel" # Source-Hash: 097ed751fa47e5fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.defaultPanel" msgstr "Panel predeterminado" +# English: "Edit individual panels" # Source-Hash: 0a0c98ae42f519ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.editIndividualPanels" msgstr "Editar paneles individuales" +# English: "Effective thickness: {thickness}, limited by frame depth." # Source-Hash: 8690f82f64806e4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Effective thickness: {thickness}, limited by frame depth." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.effectiveThicknessLimited" msgstr "Espesor efectivo: {thickness}, limitado por la profundidad del marco." +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.empty" msgstr "Vacío" +# English: "Enable panel editing" # Source-Hash: c6e7792839d09c69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable panel editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.enablePanelEditing" msgstr "Activar edición de paneles" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.end" msgstr "Final" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fixedSpacing" msgstr "Separación fija" +# English: "Following wall defaults" # Source-Hash: 1c851bde836b3b57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Following wall defaults" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.followingWallDefaults" msgstr "Siguiendo los valores del muro" +# English: "Frame dimensions" # Source-Hash: 49be990b89741501 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.frameDimensions" msgstr "Dimensiones del marco" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.framing" msgstr "Entramado" +# English: "Fully capped" # Source-Hash: 221f418784836f11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fully capped" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fullyCapped" msgstr "Totalmente rematado" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glass" msgstr "Vidrio" +# English: "Glass appearance" # Source-Hash: a17c63662aa4404d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearance" msgstr "Apariencia del vidrio" +# English: "Glass appearance is controlled by the paint tool." # Source-Hash: f59c4938a2116a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance is controlled by the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearanceIsControlledByThe" msgstr "La apariencia del vidrio se controla con la herramienta de pintura." +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassTint" msgstr "Tinte del vidrio" +# English: "Glazing gap" # Source-Hash: 7660a6aa7e7cd365 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glazingGap" msgstr "Holgura del acristalamiento" +# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." # Source-Hash: 6e739c8a698efaab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.gridLimitedTo32PanelsPer" msgstr "Retícula limitada a 32 paneles por eje. La separación se amplía para ajustarse a este muro." +# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" # Source-Hash: 1c63255b675bbcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.hiddenCustomPanelCount" msgstr "{length, plural, one {{number} panel personalizado está fuera de esta cuadrícula. Vuelve si la cuadrícula se amplía.} many {{number} paneles personalizados están fuera de esta cuadrícula. Vuelven si la cuadrícula se amplía.} other {{number} paneles personalizados están fuera de esta cuadrícula. Vuelven si la cuadrícula se amplía.}}" +# English: "Horizontal caps" # Source-Hash: 84af317e818c10cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalCaps" msgstr "Tapajuntas horizontales" +# English: "Horizontal frame width" # Source-Hash: 7a71f6b25c5dc718 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalFrameWidth" msgstr "Ancho del marco horizontal" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.layout" msgstr "Distribución" +# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." # Source-Hash: fc37833ef19c8feb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness" msgstr "Una rugosidad baja es lisa; una alta da una superficie mate en el sombreado renderizado." +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.maximumSpacing" msgstr "Separación máxima" +# English: "Module joint width" # Source-Hash: 1452f51ec5dfc6ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Module joint width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.moduleJointWidth" msgstr "Ancho de junta entre módulos" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.none" msgstr "Ninguno" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opacity" msgstr "Opacidad" +# English: "Opaque band" # Source-Hash: 0f9ba67daa6b4625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque band" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBand" msgstr "Banda opaca" +# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." # Source-Hash: fdf533d6592eff60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow" msgstr "Las bandas opacas sustituyen una fila entera por paneles opacos. Aplica su acabado con la herramienta de pintura." +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelCount" msgstr "Número de paneles" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelInfill" msgstr "Relleno del panel" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panels" msgstr "Paneles" +# English: "Panel thickness" # Source-Hash: d24fe036670f54a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelThickness" msgstr "Espesor del panel" +# English: "Reset this panel" # Source-Hash: a780f13a732bab88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset this panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.resetThisPanel" msgstr "Restablecer este panel" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.row2" msgstr "Fila" +# English: "Row layout" # Source-Hash: 0e18ec19d0e4ea93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.rowLayout" msgstr "Distribución de filas" +# English: "Separate framed modules with joints between units." # Source-Hash: dfb58bbd8932811d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate framed modules with joints between units." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween" msgstr "Módulos enmarcados separados con juntas entre unidades." +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.solid" msgstr "Sólido" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.spacing" msgstr "Separación" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.start" msgstr "Inicio" +# English: "Stick-built" # Source-Hash: d3d738ccbc7103ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stick-built" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.stickBuilt" msgstr "Construido in situ" +# English: "Structural glazing" # Source-Hash: fbdb9260ee302d3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural glazing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.structuralGlazing" msgstr "Acristalamiento estructural" +# English: "Surface roughness" # Source-Hash: 393d5c5ec4e7014c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.surfaceRoughness" msgstr "Rugosidad de superficie" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.top" msgstr "Superior" +# English: "Top row" # Source-Hash: 51aa1c35a1761178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.topRow" msgstr "Fila superior" +# English: "Uncapped members sit behind the glazing." # Source-Hash: 1181faa315f26c83 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncapped members sit behind the glazing." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing" msgstr "Los perfiles sin tapa quedan detrás del acristalamiento." +# English: "Unitized" # Source-Hash: e68eeb4033268842 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unitized" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.unitized" msgstr "Unitizado" +# English: "Use glass settings instead" # Source-Hash: 355b469b8a9c42f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use glass settings instead" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useGlassSettingsInstead" msgstr "Usar en su lugar los ajustes del vidrio" +# English: "Use the Door or Window tool to add an opening through the wall." # Source-Hash: 9d75fad413e547e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use the Door or Window tool to add an opening through the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useTheDoorOrWindowTool" msgstr "Usa la herramienta Puerta o Ventana para añadir un hueco en el muro." +# English: "Vertical caps" # Source-Hash: c1fa8c24203a5dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalCaps" msgstr "Tapajuntas verticales" +# English: "Vertical frame width" # Source-Hash: 0560516882b93f0d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalFrameWidth" msgstr "Ancho del marco vertical" +# English: "Wall system" # Source-Hash: 4b903041c38cdd72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.wallSystem" msgstr "Sistema de muro" +# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." # Source-Hash: 6c753f6234766116 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.description" msgstr "Tramo de muro recto o curvo. Aloja puertas, ventanas, anexos a un agua y elementos de pared." +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.label" msgstr "Muro" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.actions" msgstr "Acciones" +# English: "Assembly" # Source-Hash: 67d2ec01cdce2bdc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.assembly" msgstr "Composición" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.auto" msgstr "Auto" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.both" msgstr "Ambos" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.bottom" msgstr "Inferior" +# English: "Brick veneer" # Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brick veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.brickVeneer" msgstr "Chapado de ladrillo" +# English: "Chair rail" # Source-Hash: 32d0bfee628c42a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chair rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.chairRail" msgstr "Moldura de silla" +# English: "CMU" # Source-Hash: a85f39db8f92f8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "CMU" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cmu" msgstr "Bloque de hormigón" +# English: "Colonial" # Source-Hash: f76e125a47ed0612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Colonial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.colonial" msgstr "Colonial" +# English: "Cove" # Source-Hash: a4962c0f39d629ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cove" msgstr "Media caña" +# English: "Craft" # Source-Hash: e5d425095ade6f06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.craft" msgstr "Artesanal" +# English: "Crown molding" # Source-Hash: fa8a6b7f2f4eee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crown molding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.crownMolding" msgstr "Moldura de techo" +# English: "Curtain wall" # Source-Hash: 9736e0a78294e68d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curtainWall" msgstr "Muro cortina" +# English: "Curve" # Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curve" msgstr "Curva" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.custom" msgstr "Personalizado" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.customHeight" msgstr "Altura personalizada" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.depth" msgstr "Profundidad" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.dimensions" msgstr "Dimensiones" +# English: "Drywall" # Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drywall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.drywall" msgstr "Placa de yeso" +# English: "Extends downward to meet the terrain. Height and top stay unchanged." # Source-Hash: d6ba089e1ca40cdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends downward to meet the terrain. Height and top stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.extendsDownwardToMeetTheTerrain" msgstr "Se extiende hacia abajo hasta el terreno. La altura y la parte superior no cambian." +# English: "Exterior" # Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exterior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.exterior" msgstr "Exterior" +# English: "Fiber cement" # Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fiber cement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fiberCement" msgstr "Fibrocemento" +# English: "Fill to terrain" # Source-Hash: a6be391d580de5cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fillToTerrain" msgstr "Rellenar hasta el terreno" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.flat" msgstr "Plana" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.followsLevel" msgstr "Sigue la planta" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.frameDepth" msgstr "Profundidad del marco" -# Source-Hash: c7e72beff9fe4b37 +# English: "Framer's default" +# Source-Hash: cb22c8e4b0d92234 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.framersDefault" +msgstr "Predeterminado del entramado" + # English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.framing" msgstr "Entramado" +# English: "Gypsum" # Source-Hash: eda78e8a76f068d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gypsum" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.gypsum" msgstr "Yeso" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.height" msgstr "Altura" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.hideCode" msgstr "Ocultar código" +# English: "ICF" # Source-Hash: 0c6dcbbbda6b4dbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ICF" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.icf" msgstr "Encofrado aislante" +# English: "Interior" # Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Interior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.interior" msgstr "Interior" +# English: "Lap siding" # Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lap siding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.lapSiding" msgstr "Revestimiento solapado" +# English: "Layered" # Source-Hash: 297b21e90e8c25f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layered" msgstr "Por capas" +# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." # Source-Hash: bf279d0f8f1e3b78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layerThicknessesFollowThe2021Irc" msgstr "Los espesores de capa siguen los datos de composición del IRC 2021. Ayuda de dibujo, no ingeniería: verifícalo con la autoridad competente." +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.length" msgstr "Longitud" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.loading" msgstr "Cargando…" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.modern" msgstr "Moderno" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.none" msgstr "Ninguno" +# English: "None (single layer)" # Source-Hash: c7300ca33851fba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None (single layer)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.noneSingleLayer" msgstr "Ninguno (capa única)" +# English: "Offset" # Source-Hash: 0f0e13abf9b2bb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.offset" msgstr "Desfase" +# English: "Ogee" # Source-Hash: 04b3006bd4e6e207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ogee" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.ogee" msgstr "Gola" +# English: "OSB" # Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "OSB" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.osb" msgstr "OSB" +# English: "Partition: the interior finish is applied to both faces." # Source-Hash: 1243161deca7d470 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partition: the interior finish is applied to both faces." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.partitionTheInteriorFinishIsApplied" msgstr "Tabique: el acabado interior se aplica a ambas caras." +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.picture" msgstr "Marco" +# English: "Plaster" # Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plaster" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plaster" msgstr "Enlucido" +# English: "Plywood" # Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plywood" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plywood" msgstr "Contrachapado" +# English: "Preset" # Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.preset" msgstr "Preajuste" -# Source-Hash: 62bf2677b9ec30fc +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.railing" +msgstr "Barandilla" + # English: "Reference" +# Source-Hash: 62bf2677b9ec30fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.reference" msgstr "Referencia" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.remove" msgstr "Quitar" +# English: "Round" # Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.round" msgstr "Redondo" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.scriptedFinish" msgstr "Acabado por script" +# English: "Sheathing" # Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheathing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sheathing" msgstr "Tablero" +# English: "Shoe" # Source-Hash: 6ae9aa7061d8c617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.shoe" msgstr "Cuarto bocel" +# English: "Side A" # Source-Hash: 136762ac1bc96e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side A" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideA" msgstr "Cara A" +# English: "Side B" # Source-Hash: 8b9cb16850549c35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side B" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideB" msgstr "Cara B" +# English: "Side {face}" # Source-Hash: e6a27f6f8cd489b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side {face}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideFace" msgstr "Cara {face}" +# English: "Skirting" # Source-Hash: d9d55c5883a53789 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skirting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.skirting" msgstr "Rodapié" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.standard" msgstr "Estándar" +# English: "Steel studs" # Source-Hash: da3e3dd077163ba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steel studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.steelStuds" msgstr "Montantes de acero" +# English: "Step" # Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.step" msgstr "Escalonado" +# English: "Stone veneer" # Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stone veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stoneVeneer" msgstr "Chapado de piedra" +# English: "Stucco" # Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stucco" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stucco" msgstr "Estuco" -# Source-Hash: fdbb503f0941bd56 +# English: "Studs" +# Source-Hash: 4fce02ee3f69bf99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.studs" +msgstr "Montantes" + # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thickness" msgstr "Espesor" +# English: "{thickness} · from assembly" # Source-Hash: 00cb7b58f3a10140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{thickness} · from assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thicknessFromAssembly" msgstr "{thickness} · según la composición" -# Source-Hash: 27644f0497fd9849 +# English: "This project" +# Source-Hash: 9eef4c34dfbc970b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.thisProject" +msgstr "Este proyecto" + # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.top" msgstr "Superior" +# English: "Total thickness" # Source-Hash: 3ee03b0e9ae1be4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Total thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.totalThickness" msgstr "Espesor total" +# English: "Unverified: {presetNote}" # Source-Hash: ea7e8bbd87ce4152 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unverified: {presetNote}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.unverifiedPresetnote" msgstr "Sin verificar: {presetNote}" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.viewCode" msgstr "Ver código" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wall" msgstr "Muro" +# English: "Wall type" # Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wallType" msgstr "Tipo de muro" +# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." # Source-Hash: e97a456d667465f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.whichFaceIsOutsideIsUndetermined" msgstr "No se sabe qué cara es la exterior (no se detecta ninguna estancia a ningún lado): las capas exteriores se dibujan en la cara B." +# English: "Wood studs" # Source-Hash: 31e404a4a4870faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wood studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.woodStuds" msgstr "Montantes de madera" -# Source-Hash: 5211341ed18f33f6 +# English: "A guard's openings must not pass a 4 in sphere (IRC R312.1.3)." +# Source-Hash: ece946252401f4ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.aGuardSOpeningsMustNot" +msgstr "Las aberturas de una barandilla no deben dejar pasar una esfera de 4 in (IRC R312.1.3)." + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.height" +msgstr "Altura" + +# English: "Infill" +# Source-Hash: 7c339a5fae55681d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.infill" +msgstr "Relleno" + +# English: "2x6 cap" +# Source-Hash: d814a47c4eccee3f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n2x6Cap" +msgstr "Remate 2x6" + +# English: "36 in" +# Source-Hash: 137b45940b723a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n36In" +msgstr "36 in" + +# English: "42 in" +# Source-Hash: ebf854ac4dd4daaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n42In" +msgstr "42 in" + +# English: "Posts" +# Source-Hash: ede386e1d510ebfb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.posts" +msgstr "Postes" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.railing" +msgstr "Barandilla" + +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.style" +msgstr "Estilo" + # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.center" msgstr "Centro" +# English: "Inside face" # Source-Hash: 09576259041494f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.inside" msgstr "Cara interior" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.left" msgstr "Izquierda" +# English: "Outside face" # Source-Hash: 697001376b872e74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.outside" msgstr "Cara exterior" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.right" msgstr "Derecha" +# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" # Source-Hash: eac63d14f275db3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.hide" msgstr "{trim, select, skirting {Ocultar rodapié} crown {Ocultar moldura de techo} other {Ocultar moldura de silla}}" +# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" # Source-Hash: 8db070a4247db221 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.show" msgstr "{trim, select, skirting {Mostrar rodapié} crown {Mostrar moldura de techo} other {Mostrar moldura de silla}}" +# English: "A window cut into a wall. Animated open/close for opening windows." # Source-Hash: ea6a51e66eb0c0dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A window cut into a wall. Animated open/close for opening windows." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.description" msgstr "Ventana abierta en un muro, con apertura animada en las practicables." +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.label" msgstr "Ventana" +# English: "Raise" # Source-Hash: 011e4de244050a78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.raise" msgstr "Elevar" +# English: "Slats" # Source-Hash: 19ca2434d4f7eb87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slats" msgstr "Lamas" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slide" msgstr "Deslizar" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.swing" msgstr "Abatir" +# English: "Tilt" # Source-Hash: 1615637fe68e188a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.tilt" msgstr "Inclinar" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.actions" msgstr "Acciones" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.all" msgstr "Todo" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.arch" msgstr "Arco" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.archHeight" msgstr "Altura del arco" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.auto" msgstr "Auto" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.autoFollowsTheWall" msgstr "Auto sigue el muro" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.columns" msgstr "Columnas" +# English: "Col Widths" # Source-Hash: 2f417371c399ad67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Col Widths" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.colWidths" msgstr "Anchos de columna" +# English: "Corner" # Source-Hash: 2ee121dca87bb64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.corner" msgstr "Esquina" +# English: "Corner post" # Source-Hash: 04e3425c5ea29359 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerPost" msgstr "Poste de esquina" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerRadius" msgstr "Radio de esquina" +# English: "Custom: the panes match no style." # Source-Hash: 67d10b97eff316c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the panes match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.customThePanesMatchNoStyle" msgstr "Personalizado: los paños no coinciden con ningún estilo." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.delete" msgstr "Eliminar" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.depth" msgstr "Profundidad" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.dimensions" msgstr "Dimensiones" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.divider" msgstr "Divisor" +# English: "Down" # Source-Hash: 72c65752a1d1bbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.down" msgstr "Abajo" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.duplicate" msgstr "Duplicar" +# English: "Enable Sill" # Source-Hash: 8497c4373d5d18ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.enableSill" msgstr "Activar alféizar" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.flipSide" msgstr "Invertir lado" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frame" msgstr "Marco" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frameDepthAuto" msgstr "Profundidad (auto)" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.french" msgstr "Francesa" +# English: "Fused glass" # Source-Hash: a6362b937fffe999 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fused glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.fusedGlass" msgstr "Vidrio fundido" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.grid" msgstr "Cuadrícula" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.height" msgstr "Altura" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.individual" msgstr "Individual" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.left" msgstr "Izquierda" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.move" msgstr "Mover" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.opening" msgstr "Abertura" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.openingShape" msgstr "Forma del hueco" +# English: "Other side" # Source-Hash: cf40395b71bedcca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.otherSide" msgstr "Otro lado" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.position" msgstr "Posición" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rect" msgstr "Rect." +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.revealRadius" msgstr "Radio del derrame" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.right" msgstr "Derecha" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rounded" msgstr "Redondeado" +# English: "Row Heights" # Source-Hash: fb811056b862b6d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row Heights" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rowHeights" msgstr "Alturas de fila" +# English: "Rows" # Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rows" msgstr "Filas" +# English: "Sill" # Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sill" msgstr "Alféizar" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.single" msgstr "Simple" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sizeIsLimitedToTheWall" msgstr "El tamaño está limitado al muro, incluida la holgura del marco del hueco." +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.style" msgstr "Estilo" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thickness" msgstr "Espesor" +# English: "This side" # Source-Hash: 79ff1ae7250c1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thisSide" msgstr "Este lado" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.topShape" msgstr "Forma superior" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.type" msgstr "Tipo" +# English: "Unwrap" # Source-Hash: c29767f86bd9df1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.unwrap" msgstr "Separar" +# English: "Up" # Source-Hash: 1d732503a54a532f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.up" msgstr "Arriba" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.width" msgstr "Ancho" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.window" msgstr "Ventana" +# English: "Window Type" # Source-Hash: f59cd625e6082651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.windowType" msgstr "Tipo de ventana" +# English: "Wraps the corner with {partner}" # Source-Hash: 7bf8c4e2b90b3021 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with {partner}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithNamed" msgstr "Rodea la esquina con {partner}" +# English: "Wraps the corner with its partner" # Source-Hash: 8f1f8eff5fbe90e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with its partner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithPartner" msgstr "Rodea la esquina con su pareja" +# English: "Wrap the corner" # Source-Hash: cccf75b8c8dc5ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapTheCorner" msgstr "Rodear la esquina" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.casement" msgstr "Abatible" +# English: "Double-hung" # Source-Hash: e06fc5a432b74a5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.doubleHung" msgstr "Guillotina doble" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.grid" msgstr "Cuadrícula" +# English: "Horizontal bands" # Source-Hash: fbb293fe8aee84b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.horizontalBands" msgstr "Bandas horizontales" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.picture" msgstr "Marco" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.single" msgstr "Simple" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.sliding" msgstr "Corredera" +# English: "Tall grid" # Source-Hash: 0c88aea2bd130193 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tallGrid" msgstr "Retícula alta" +# English: "Transom" # Source-Hash: 9628d0cc155e29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.transom" msgstr "Montante" +# English: "Triple-hung" # Source-Hash: 305f3aba543b1962 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tripleHung" msgstr "Guillotina triple" +# English: "Wide grid" # Source-Hash: 8af77effb76bc6e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.wideGrid" msgstr "Retícula ancha" +# English: "Awning" # Source-Hash: 50393830365aaf2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.awning" msgstr "Proyectante" +# English: "Bay" # Source-Hash: 8447ee52258e16e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bay" msgstr "Mirador" +# English: "Bow" # Source-Hash: e828c89bd0ba1ea4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bow" msgstr "Ventana en arco" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.casement" msgstr "Ventana abatible" +# English: "Double Hung" # Source-Hash: b0b737fc5cb1d32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.doubleHung" msgstr "Guillotina doble" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.fixed" msgstr "Fijo" +# English: "Hopper" # Source-Hash: 04a40f27911c3192 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hopper" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.hopper" msgstr "Abatible inferior" +# English: "Louvered" # Source-Hash: 5746be19ba2a53be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.louvered" msgstr "De lamas" +# English: "Single Hung" # Source-Hash: de6ffb1cf676f270 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.singleHung" msgstr "Guillotina simple" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.sliding" msgstr "Corredera" +# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." # Source-Hash: 4a4d97a4101793b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.description" msgstr "Zona poligonal del terreno (césped, agua, pavimento) con un material de degradado TSL." +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.label" msgstr "Zona" +# English: "Enclosed room" # Source-Hash: e9a6d2e71b97e757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enclosed room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.enclosedRoom" msgstr "Estancia cerrada" +# English: "Floor surface" # Source-Hash: 3660922f0a8f1d3c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.floorSurface" msgstr "Superficie del suelo" +# English: "Footprint only" # Source-Hash: 88c8703c4ff21ee1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint only" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.footprintOnly" msgstr "Solo huella" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.none" msgstr "Ninguno" +# English: "Not proven" # Source-Hash: a9756a3da570c3dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not proven" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.notProven" msgstr "No demostrado" +# English: "Room quantities" # Source-Hash: ff97d005a536cf58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.roomQuantities" msgstr "Mediciones de la estancia" +# English: "Top view with zone edge dimensions" # Source-Hash: f520e6a06fbeb803 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top view with zone edge dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions" msgstr "Vista superior con las cotas de los bordes de la zona" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.unit" msgstr "Unidad" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.volume" msgstr "Volumen" +# English: "Wall surface" # Source-Hash: a7941c60a3b6998a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.wallSurface" msgstr "Superficie de muro" +# English: "Zone boundary unavailable" # Source-Hash: b8b0dbdff53a83ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone boundary unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneBoundaryUnavailable" msgstr "Límite de zona no disponible" +# English: "Zone quantities" # Source-Hash: 6bfb4c6f4d7b0ff2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneQuantities" msgstr "Mediciones de la zona" +# English: "Orbit left" # Source-Hash: 8bf25afa3cb8baea # Review: human -# English: "Orbit left" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitLeft" msgstr "Orbitar a la izquierda" +# English: "Orbit right" # Source-Hash: b9cc64c1d1bb0d46 # Review: human -# English: "Orbit right" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitRight" msgstr "Orbitar a la derecha" +# English: "Top view" # Source-Hash: 1ab1eb3a14f4b0e0 # Review: human -# English: "Top view" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.topView" msgstr "Vista superior" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.off" msgstr "Desactivado" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: human -# English: "On" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.on" msgstr "Activado" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: human -# English: "Camera" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.camera" msgstr "Cámara" +# English: "Colored" # Source-Hash: d3d478fa6c6d7ddc # Review: human -# English: "Colored" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colored" msgstr "A color" +# English: "Colors" # Source-Hash: 82b74c4f5ef43336 # Review: human -# English: "Colors" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colors" msgstr "Colores" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.off" msgstr "Desactivadas" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: human -# English: "No edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.offDetail" msgstr "Sin líneas de arista" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: human -# English: "Soft" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.soft" msgstr "Suaves" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: human -# English: "Faint outline of major creases" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.softDetail" msgstr "Contorno tenue de los pliegues principales" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: human -# English: "Strong" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strong" msgstr "Marcadas" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: human -# English: "Crisp, opaque edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strongDetail" msgstr "Líneas de arista nítidas y opacas" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: human -# English: "Edges" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.title" msgstr "Aristas" +# English: "Monochrome" # Source-Hash: ecde2f7c26914be3 # Review: human -# English: "Monochrome" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.monochrome" msgstr "Monocromo" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: human -# English: "Orthographic" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.orthographic" msgstr "Ortográfica" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: human -# English: "Perspective" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.perspective" msgstr "Perspectiva" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: human -# English: "Rendered" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.rendered" msgstr "Realista" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: human -# English: "Full ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.renderedDetail" msgstr "Oclusión ambiental completa" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: human -# English: "Solid" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solid" msgstr "Sólido" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: human -# English: "Flat and fast — no ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solidDetail" msgstr "Plano y rápido, sin oclusión ambiental" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: human -# English: "Render" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.title" msgstr "Renderizado" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: human -# English: "Shadows" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.shadows" msgstr "Sombras" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: human -# English: "Display settings" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.title" msgstr "Ajustes de visualización" +# English: "Align view to north" # Source-Hash: 2bdf83801d4a40af # Review: human -# English: "Align view to north" #: packages/editor/src/components/viewer/floorplan-compass-button.tsx msgid "viewer.floorplan.alignNorth" msgstr "Orientar la vista al norte" +# English: "No floor plans are available for this scene." # Source-Hash: 286a757d8b52bb02 # Review: human -# English: "No floor plans are available for this scene." #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.empty" msgstr "No hay planos de planta disponibles para esta escena." +# English: "Fit floor plan" # Source-Hash: bf63c679ab94dbcd # Review: human -# English: "Fit floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.fit" msgstr "Ajustar plano" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: human -# English: "Floor" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floor" msgstr "Planta" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: human -# English: "Floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floorPlan" msgstr "Plano de planta" +# English: "{floor} 2D view" # Source-Hash: 28b255b7bf5f1084 # Review: human -# English: "{floor} 2D view" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.viewLabel" msgstr "Vista 2D de {floor}" +# English: "Zoom in" # Source-Hash: d17804412f8dd081 # Review: human -# English: "Zoom in" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomIn" msgstr "Acercar" +# English: "Zoom out" # Source-Hash: 23b012a283b2c14a # Review: human -# English: "Zoom out" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomOut" msgstr "Alejar" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: human -# English: "Back" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.back" msgstr "Volver" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: human -# English: "Levels" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.levels" msgstr "Plantas" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.rooms" msgstr "Estancias" +# English: "Selection" # Source-Hash: 170b2049ef0f9131 # Review: human -# English: "Selection" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.selection" msgstr "Selección" +# English: "Units" # Source-Hash: b404dded35241274 # Review: human -# English: "Units" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.units" msgstr "Unidades" +# English: "Untitled" # Source-Hash: 6547a4c0c368042a # Review: human -# English: "Untitled" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.untitled" msgstr "Sin título" +# English: "Zones" # Source-Hash: 665db49a26f81425 # Review: human -# English: "Zones" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.zones" msgstr "Zonas" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: human -# English: "Exploded" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.exploded" msgstr "Separadas" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: human -# English: "Levels: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.label" msgstr "Plantas: {mode}" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: human -# English: "Manual" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.manual" msgstr "Manual" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: human -# English: "Solo" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.solo" msgstr "Aislada" +# English: "Stacked" # Source-Hash: c203c84eee18c0f6 # Review: human -# English: "Stacked" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.stacked" msgstr "Apiladas" +# English: "Viewer layout" # Source-Hash: 9dd21ca47cffa3b8 # Review: human -# English: "Viewer layout" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.layout" msgstr "Disposición de vistas" +# English: "Split" # Source-Hash: e4cd64b1680ac0d2 # Review: human -# English: "Split" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.split" msgstr "Dividida" +# English: "Blueprint" # Source-Hash: f14b89d713d2528a # Review: human -# English: "Blueprint" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.blueprint" msgstr "Plano técnico" +# English: "Mediterranean" # Source-Hash: 142bcb2841f423c5 # Review: human -# English: "Mediterranean" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.mediterranean" msgstr "Mediterráneo" +# English: "Night" # Source-Hash: b399942ec5d7e1ba # Review: human -# English: "Night" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.night" msgstr "Noche" +# English: "Overcast" # Source-Hash: f512d1b947b8ae5a # Review: human -# English: "Overcast" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.overcast" msgstr "Nublado" +# English: "Paper" # Source-Hash: 583542b3bc837192 # Review: human -# English: "Paper" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.paper" msgstr "Papel" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: human -# English: "Studio" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.studio" msgstr "Estudio" +# English: "Sunset" # Source-Hash: efe94d3ec8d8c44e # Review: human -# English: "Sunset" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.sunset" msgstr "Atardecer" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: human -# English: "Scene theme" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.theme.title" msgstr "Ambiente" +# English: "Twilight" # Source-Hash: 4c10ea0aa0e2ea41 # Review: human -# English: "Twilight" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.twilight" msgstr "Crepúsculo" +# English: "Verdant" # Source-Hash: f9592fb0672740c0 # Review: human -# English: "Verdant" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.verdant" msgstr "Verde" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: human -# English: "Guides" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.guides" msgstr "Guías" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: human -# English: "Scans" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.scans" msgstr "Escaneos 3D" +# English: "Visibility" # Source-Hash: 2cefbb554eadbb6e # Review: human -# English: "Visibility" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.title" msgstr "Visibilidad" +# English: "Walkthrough: {state}" # Source-Hash: 31afd9dee47ba943 # Review: human -# English: "Walkthrough: {state}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walkthrough.label" msgstr "Recorrido: {state}" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: human -# English: "Cutaway" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.cutaway" msgstr "Vista seccionada" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: human -# English: "Full height" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.fullHeight" msgstr "Altura completa" +# English: "Walls: {mode}" # Source-Hash: 32f3d058cf2526d2 # Review: human -# English: "Walls: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.label" msgstr "Muros: {mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: human -# English: "Low" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.low" msgstr "Bajos" diff --git a/packages/editor/src/i18n/locales/es/messages.ts b/packages/editor/src/i18n/locales/es/messages.ts index 24fa7da3af..2a7f25575b 100644 --- a/packages/editor/src/i18n/locales/es/messages.ts +++ b/packages/editor/src/i18n/locales/es/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"Sótano\"],\"core.level.basementNumber\":[\"Sótano \",[\"number\"]],\"core.level.floorNumber\":[\"Planta \",[\"number\"]],\"core.level.groundFloor\":[\"Planta baja\"],\"editor.buildPanel.advanced\":[\"Avanzado\"],\"editor.buildPanel.rooms.hint\":[\"muros · suelo · techo\"],\"editor.buildPanel.rooms.idle\":[\"Elige una forma para empezar. Cada estancia incluye sus muros, suelo y techo.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" y vuelve a pulsar la primera para cerrar. Para estancias en L o con ángulos.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polígono\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Haz clic en cada esquina\"],\"editor.buildPanel.rooms.polygon.title\":[\"Estancia poligonal\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" en la cuadrícula. Los muros, el suelo y el techo se crean a la vez.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectángulo\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Haz clic en dos esquinas\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Estancia rectangular\"],\"editor.buildPanel.rooms.title\":[\"Estancias\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\" Cierra el contorno para crear una estancia o déjalo abierto para tabiques.\"],\"editor.buildPanel.rooms.walls.label\":[\"Muros\"],\"editor.buildPanel.rooms.walls.lead\":[\"Dibuja un muro cada vez.\"],\"editor.buildPanel.rooms.walls.title\":[\"Muros\"],\"viewer.camera.orbitLeft\":[\"Orbitar a la izquierda\"],\"viewer.camera.orbitRight\":[\"Orbitar a la derecha\"],\"viewer.camera.topView\":[\"Vista superior\"],\"viewer.common.off\":[\"Desactivado\"],\"viewer.common.on\":[\"Activado\"],\"viewer.display.camera\":[\"Cámara\"],\"viewer.display.colored\":[\"A color\"],\"viewer.display.colors\":[\"Colores\"],\"viewer.display.edges.off\":[\"Desactivadas\"],\"viewer.display.edges.offDetail\":[\"Sin líneas de arista\"],\"viewer.display.edges.soft\":[\"Suaves\"],\"viewer.display.edges.softDetail\":[\"Contorno tenue de los pliegues principales\"],\"viewer.display.edges.strong\":[\"Marcadas\"],\"viewer.display.edges.strongDetail\":[\"Líneas de arista nítidas y opacas\"],\"viewer.display.edges.title\":[\"Aristas\"],\"viewer.display.monochrome\":[\"Monocromo\"],\"viewer.display.orthographic\":[\"Ortográfica\"],\"viewer.display.perspective\":[\"Perspectiva\"],\"viewer.display.render.rendered\":[\"Realista\"],\"viewer.display.render.renderedDetail\":[\"Oclusión ambiental completa\"],\"viewer.display.render.solid\":[\"Sólido\"],\"viewer.display.render.solidDetail\":[\"Plano y rápido, sin oclusión ambiental\"],\"viewer.display.render.title\":[\"Renderizado\"],\"viewer.display.shadows\":[\"Sombras\"],\"viewer.display.title\":[\"Ajustes de visualización\"],\"viewer.floorplan.alignNorth\":[\"Orientar la vista al norte\"],\"viewer.floorplan.empty\":[\"No hay planos de planta disponibles para esta escena.\"],\"viewer.floorplan.fit\":[\"Ajustar plano\"],\"viewer.floorplan.floor\":[\"Planta\"],\"viewer.floorplan.floorPlan\":[\"Plano de planta\"],\"viewer.floorplan.viewLabel\":[\"Vista 2D de \",[\"floor\"]],\"viewer.floorplan.zoomIn\":[\"Acercar\"],\"viewer.floorplan.zoomOut\":[\"Alejar\"],\"viewer.header.back\":[\"Volver\"],\"viewer.header.levels\":[\"Plantas\"],\"viewer.header.rooms\":[\"Estancias\"],\"viewer.header.selection\":[\"Selección\"],\"viewer.header.units\":[\"Unidades\"],\"viewer.header.untitled\":[\"Sin título\"],\"viewer.header.zones\":[\"Zonas\"],\"viewer.levels.exploded\":[\"Separadas\"],\"viewer.levels.label\":[\"Plantas: \",[\"mode\"]],\"viewer.levels.manual\":[\"Manual\"],\"viewer.levels.solo\":[\"Aislada\"],\"viewer.levels.stacked\":[\"Apiladas\"],\"viewer.stage.layout\":[\"Disposición de vistas\"],\"viewer.stage.split\":[\"Dividida\"],\"viewer.theme.blueprint\":[\"Plano técnico\"],\"viewer.theme.mediterranean\":[\"Mediterráneo\"],\"viewer.theme.night\":[\"Noche\"],\"viewer.theme.overcast\":[\"Nublado\"],\"viewer.theme.paper\":[\"Papel\"],\"viewer.theme.studio\":[\"Estudio\"],\"viewer.theme.sunset\":[\"Atardecer\"],\"viewer.theme.title\":[\"Ambiente\"],\"viewer.theme.twilight\":[\"Crepúsculo\"],\"viewer.theme.verdant\":[\"Verde\"],\"viewer.visibility.guides\":[\"Guías\"],\"viewer.visibility.scans\":[\"Escaneos 3D\"],\"viewer.visibility.title\":[\"Visibilidad\"],\"viewer.walkthrough.label\":[\"Recorrido: \",[\"state\"]],\"viewer.walls.cutaway\":[\"Vista seccionada\"],\"viewer.walls.fullHeight\":[\"Altura completa\"],\"viewer.walls.label\":[\"Muros: \",[\"mode\"]],\"viewer.walls.low\":[\"Bajos\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"Sótano\"],\"core.level.basementNumber\":[\"Sótano \",[\"number\"]],\"core.level.floorNumber\":[\"Planta \",[\"number\"]],\"core.level.groundFloor\":[\"Planta baja\"],\"editor.buildPanel.advanced\":[\"Avanzado\"],\"editor.buildPanel.rooms.hint\":[\"muros · suelo · techo\"],\"editor.buildPanel.rooms.idle\":[\"Elige una forma para empezar. Cada estancia incluye sus muros, suelo y techo.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" y vuelve a pulsar la primera para cerrar. Para estancias en L o con ángulos.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polígono\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Haz clic en cada esquina\"],\"editor.buildPanel.rooms.polygon.title\":[\"Estancia poligonal\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" en la cuadrícula. Los muros, el suelo y el techo se crean a la vez.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectángulo\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Haz clic en dos esquinas\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Estancia rectangular\"],\"editor.buildPanel.rooms.title\":[\"Estancias\"],\"editor.buildPanel.rooms.walls.label\":[\"Muros\"],\"editor.buildPanel.rooms.walls.title\":[\"Muros\"],\"viewer.camera.orbitLeft\":[\"Orbitar a la izquierda\"],\"viewer.camera.orbitRight\":[\"Orbitar a la derecha\"],\"viewer.camera.topView\":[\"Vista superior\"],\"viewer.common.off\":[\"Desactivado\"],\"viewer.common.on\":[\"Activado\"],\"viewer.display.camera\":[\"Cámara\"],\"viewer.display.colored\":[\"A color\"],\"viewer.display.colors\":[\"Colores\"],\"viewer.display.edges.off\":[\"Desactivadas\"],\"viewer.display.edges.offDetail\":[\"Sin líneas de arista\"],\"viewer.display.edges.soft\":[\"Suaves\"],\"viewer.display.edges.softDetail\":[\"Contorno tenue de los pliegues principales\"],\"viewer.display.edges.strong\":[\"Marcadas\"],\"viewer.display.edges.strongDetail\":[\"Líneas de arista nítidas y opacas\"],\"viewer.display.edges.title\":[\"Aristas\"],\"viewer.display.monochrome\":[\"Monocromo\"],\"viewer.display.orthographic\":[\"Ortográfica\"],\"viewer.display.perspective\":[\"Perspectiva\"],\"viewer.display.render.rendered\":[\"Realista\"],\"viewer.display.render.renderedDetail\":[\"Oclusión ambiental completa\"],\"viewer.display.render.solid\":[\"Sólido\"],\"viewer.display.render.solidDetail\":[\"Plano y rápido, sin oclusión ambiental\"],\"viewer.display.render.title\":[\"Renderizado\"],\"viewer.display.shadows\":[\"Sombras\"],\"viewer.display.title\":[\"Ajustes de visualización\"],\"viewer.floorplan.alignNorth\":[\"Orientar la vista al norte\"],\"viewer.floorplan.empty\":[\"No hay planos de planta disponibles para esta escena.\"],\"viewer.floorplan.fit\":[\"Ajustar plano\"],\"viewer.floorplan.floor\":[\"Planta\"],\"viewer.floorplan.floorPlan\":[\"Plano de planta\"],\"viewer.floorplan.viewLabel\":[\"Vista 2D de \",[\"floor\"]],\"viewer.floorplan.zoomIn\":[\"Acercar\"],\"viewer.floorplan.zoomOut\":[\"Alejar\"],\"viewer.header.back\":[\"Volver\"],\"viewer.header.levels\":[\"Plantas\"],\"viewer.header.rooms\":[\"Estancias\"],\"viewer.header.selection\":[\"Selección\"],\"viewer.header.units\":[\"Unidades\"],\"viewer.header.untitled\":[\"Sin título\"],\"viewer.header.zones\":[\"Zonas\"],\"viewer.levels.exploded\":[\"Separadas\"],\"viewer.levels.label\":[\"Plantas: \",[\"mode\"]],\"viewer.levels.manual\":[\"Manual\"],\"viewer.levels.solo\":[\"Aislada\"],\"viewer.levels.stacked\":[\"Apiladas\"],\"viewer.stage.layout\":[\"Disposición de vistas\"],\"viewer.stage.split\":[\"Dividida\"],\"viewer.theme.blueprint\":[\"Plano técnico\"],\"viewer.theme.mediterranean\":[\"Mediterráneo\"],\"viewer.theme.night\":[\"Noche\"],\"viewer.theme.overcast\":[\"Nublado\"],\"viewer.theme.paper\":[\"Papel\"],\"viewer.theme.studio\":[\"Estudio\"],\"viewer.theme.sunset\":[\"Atardecer\"],\"viewer.theme.title\":[\"Ambiente\"],\"viewer.theme.twilight\":[\"Crepúsculo\"],\"viewer.theme.verdant\":[\"Verde\"],\"viewer.visibility.guides\":[\"Guías\"],\"viewer.visibility.scans\":[\"Escaneos 3D\"],\"viewer.visibility.title\":[\"Visibilidad\"],\"viewer.walkthrough.label\":[\"Recorrido: \",[\"state\"]],\"viewer.walls.cutaway\":[\"Vista seccionada\"],\"viewer.walls.fullHeight\":[\"Altura completa\"],\"viewer.walls.label\":[\"Muros: \",[\"mode\"]],\"viewer.walls.low\":[\"Bajos\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/i18n/locales/fr/messages.po b/packages/editor/src/i18n/locales/fr/messages.po index b261e6fc85..b5e18a7e0f 100644 --- a/packages/editor/src/i18n/locales/fr/messages.po +++ b/packages/editor/src/i18n/locales/fr/messages.po @@ -13,24551 +13,28858 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" +# English: "Pascal home" # Source-Hash: 8ffb1c5066e53a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pascal home" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/app-home-link.tsx #, fuzzy msgid "app.appShell.pascalHome" msgstr "Accueil Pascal" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addGate" msgstr "Ajouter un portail" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addOpenPassage" msgstr "Ajouter un passage" +# English: "Add to rooms" # Source-Hash: 9a4d56454dc89966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addToRooms" msgstr "Ajouter aux pièces" +# English: "← All build tools" # Source-Hash: a17210912addc85f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "← All build tools" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.allBuildTools" msgstr "← Tous les outils de construction" +# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." # Source-Hash: 6b63404967e1d5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.chooseGateOrOpenPassageThen" msgstr "Choisissez Portail ou Passage, puis cliquez sur sa position sur une clôture. Les portails et ouvertures placés se trouvent sous leur clôture dans le graphe de scène." +# English: "Click a lineset to lay the line beside it." # Source-Hash: 8fd051e1e1a6c1a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a lineset to lay the line beside it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.clickALinesetToLayThe" msgstr "Cliquez sur une liaison frigorifique pour poser la ligne à côté." +# English: "Double gate" # Source-Hash: 7308869944020839 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.doubleGate" msgstr "Portail double" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.featuresExtensions" msgstr "Éléments et extensions" +# English: "Fence features" # Source-Hash: b18788f1d889c1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence features" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.fenceFeatures" msgstr "Éléments de clôture" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.followLineset" msgstr "Suivre la liaison frigorifique" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gate" msgstr "Portail" +# English: "Gate style" # Source-Hash: 54098b5ee574737a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gateStyle" msgstr "Style de portail" +# English: "Horizontal boards" # Source-Hash: 8fec7e969045a8e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.horizontalBoards" msgstr "Lames horizontales" +# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." # Source-Hash: 09197c4fea516277 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.hoverAFenceToPreviewClick" msgstr "Survolez une clôture pour prévisualiser. Cliquez pour placer. Échap pour annuler. Laissez de l’espace entre les ouvertures." +# English: "Leaves" # Source-Hash: c390ba248e629b9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leaves" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.leaves" msgstr "Vantaux" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.liquidLine" msgstr "Ligne liquide" +# English: "Match fence style" # Source-Hash: c15c1af69ec5c1eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match fence style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.matchFenceStyle" msgstr "Assortir au style de clôture" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.mep" msgstr "CVC-P" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.off" msgstr "Désactivé" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.on" msgstr "Activé" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.opening" msgstr "Ouverture" +# English: "Opening width (m)" # Source-Hash: 9514d3c022e68808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening width (m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openingWidthM" msgstr "Largeur de l’ouverture (m)" +# English: "Open rails" # Source-Hash: 6b108fb1d0eb8f9d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rails" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openRails" msgstr "Lisses ajourées" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.outdoor" msgstr "Extérieur" +# English: "Picket" # Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.picket" msgstr "Piquets" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roofType" msgstr "Type de toit" +# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." # Source-Hash: 0f9daa442776a317 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roomsAlreadyCreateTheirFloorAnd" msgstr "Les pièces créent déjà leur sol et leur plafond. Utilisez ces outils pour les plateformes et les structures ponctuelles." +# English: "Search build tools and kitchen items" # Source-Hash: fc79fd0c7730de76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search build tools and kitchen items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchBuildToolsAndKitchenItems" msgstr "Rechercher des outils de construction et des éléments de cuisine" +# English: "Search tools, fridge, sink…" # Source-Hash: dc1901f276edca6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search tools, fridge, sink…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchToolsFridgeSink" msgstr "Rechercher des outils, frigo, évier…" +# English: "Select a curved wall to match its radius and arc." # Source-Hash: 9558db89023d4f05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a curved wall to match its radius and arc." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.selectACurvedWallToMatch" msgstr "Sélectionnez un mur courbe pour reprendre son rayon et son arc." +# English: "Single gate" # Source-Hash: d327097d9ac1099f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.singleGate" msgstr "Portail simple" +# English: "Slab, ceiling, column…" # Source-Hash: 99acd5128c51c8a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab, ceiling, column…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.slabCeilingColumn" msgstr "Dalle, plafond, poteau…" +# English: "Solid privacy" # Source-Hash: af6f8885fe0fa4e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid privacy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.solidPrivacy" msgstr "Occultant plein" +# English: "Trace a line alongside an existing lineset (F)." # Source-Hash: e2b1a3bb9630b33b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace a line alongside an existing lineset (F)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.traceALineAlongsideAnExisting" msgstr "Tracer une ligne le long d’une liaison existante (F)." +# English: "Vertical slats" # Source-Hash: eba3df14a2de0812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.verticalSlats" msgstr "Lames verticales" +# English: "All projects" # Source-Hash: 2ee62be78ba76f53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All projects" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.allProjects" msgstr "Tous les projets" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.preview" msgstr "Aperçu" +# English: "Project name" # Source-Hash: 9f2cbc9214c8f7b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.projectName" msgstr "Nom du projet" +# English: "Rename project" # Source-Hash: 150e143a84240eee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.renameProject" msgstr "Renommer le projet" +# English: "Share" # Source-Hash: 331e1584943b09b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.share" msgstr "Partager" +# English: "Add {label}" # Source-Hash: 4b2f676fb4c9ec63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addLabel" msgstr "Ajouter {label}" +# English: "Add to kitchen" # Source-Hash: 53f98cc505aacbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addToKitchen" msgstr "Ajouter à la cuisine" +# English: "Choose an item, then click to place it. Appliances include their cabinetry." # Source-Hash: 923a2e81dcc77852 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose an item, then click to place it. Appliances include their cabinetry." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.chooseAnItemThenClickTo" msgstr "Choisissez un élément, puis cliquez pour le placer. L’électroménager inclut ses meubles." +# English: "Find in this project · {length}" # Source-Hash: 44156a2c888e8a4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in this project · {length}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findInThisProjectLength" msgstr "Rechercher dans ce projet · {length}" +# English: "Find {name}" # Source-Hash: aeda65db67edac21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find {name}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findName" msgstr "Trouver {name}" +# English: "Kitchen · Add" # Source-Hash: c1d847411ba09cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · Add" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupAdd" msgstr "Cuisine · Ajouter" +# English: "Kitchen · In this project" # Source-Hash: bb3f4f8d3407c409 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · In this project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupInThisProject" msgstr "Cuisine · Dans ce projet" +# English: "Kitchen cabinet" # Source-Hash: 25ffad31086309f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.kitchenCabinet" msgstr "Meuble de cuisine" +# English: "No matching kitchen items placed yet." # Source-Hash: a5af7817a7e8947d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No matching kitchen items placed yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.noMatchingKitchenItemsPlacedYet" msgstr "Aucun élément de cuisine correspondant placé pour l’instant." +# English: "Select and show in scene" # Source-Hash: 640155f99c25773b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select and show in scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.selectAndShowInScene" msgstr "Sélectionner et afficher dans la scène" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.ceiling" msgstr "Plafond" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.column" msgstr "Poteau" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.door" msgstr "Porte" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.duct" msgstr "Gaine" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.dwvPipe" msgstr "Tuyau d’évacuation" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.elevator" msgstr "Ascenseur" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.fence" msgstr "Clôture" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.hvacUnit" msgstr "Unité CVC" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.kitchen" msgstr "Cuisine" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.lineset" msgstr "Liaison frigorifique" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.liquidLine" msgstr "Ligne liquide" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.mep" msgstr "CVC-P" -# Source-Hash: 161e5386b939e1bc +# English: "Post" +# Source-Hash: 5cc981e79d737694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/lib/build-palette.ts +#, fuzzy +msgid "app.lib.buildPalette.post" +msgstr "Poteau bois" + # English: "Register" +# Source-Hash: 161e5386b939e1bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.register" msgstr "Bouche" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.roof" msgstr "Toit" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.shelf" msgstr "Étagère" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.slab" msgstr "Dalle" +# English: "Spawn point" # Source-Hash: 95e8af5d2b3ca73d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.spawnPoint" msgstr "Point d’apparition" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.stairs" msgstr "Escalier" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrace" msgstr "Terrasse" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrain" msgstr "Terrain" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.wall" msgstr "Mur" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.window" msgstr "Fenêtre" +# English: "Add Fitting" # Source-Hash: d40c50013043284f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addFitting" msgstr "Ajouter un raccord" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addGate" msgstr "Ajouter un portail" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addOpenPassage" msgstr "Ajouter un passage" +# English: "Add Trap" # Source-Hash: 7136b5836de6e69f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addTrap" msgstr "Ajouter un siphon" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.build" msgstr "Construire" +# English: "Create from" # Source-Hash: a23831beeb35ad3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Create from" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.createFrom" msgstr "Créer à partir de" +# English: "Draw the roof footprint with two corner clicks." # Source-Hash: 8146c309bd8326f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the roof footprint with two corner clicks." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.drawTheRoofFootprintWithTwo" msgstr "Tracez l’emprise du toit en cliquant deux angles." +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.duct" msgstr "Gaine" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.dwvPipe" msgstr "Tuyau d’évacuation" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.featuresExtensions" msgstr "Éléments et extensions" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.fence" msgstr "Clôture" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.followLineset" msgstr "Suivre la liaison frigorifique" +# English: "Gates & openings" # Source-Hash: c80972ec078ac551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gates & openings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.gatesOpenings" msgstr "Portails et ouvertures" +# English: "Hover a room to preview its boundary, then click to place." # Source-Hash: 3720347aa16ee777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a room to preview its boundary, then click to place." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.hoverARoomToPreviewIts" msgstr "Survolez une pièce pour prévisualiser son contour, puis cliquez pour placer." +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.kitchen" msgstr "Cuisine" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.mep" msgstr "CVC-P" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.modularCabinet" msgstr "Meuble modulaire" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.off" msgstr "Désactivé" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.on" msgstr "Activé" +# English: "Painting" # Source-Hash: 3fafdc69e7756f19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Painting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.painting" msgstr "Peinture" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roof" msgstr "Toit" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roofType" msgstr "Type de toit" +# English: "{count, plural, one {# tool} other {# tools}}" # Source-Hash: 0c35bee851cc57a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# tool} other {# tools}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.toolCount" msgstr "{count, plural, one {# outil} many {# outils} other {# outils}}" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.conical" msgstr "Conique" +# English: "Draw" # Source-Hash: b5c56dd8c68de309 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.draw" msgstr "Dessiner" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.dutch" msgstr "Demi-croupe" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.flat" msgstr "Plat" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gable" msgstr "Deux pans" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gambrel" msgstr "Brisé" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.hip" msgstr "Croupe" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.mansard" msgstr "Mansarde" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.room" msgstr "Pièce" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.shed" msgstr "Appentis" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.wall" msgstr "Mur" +# English: "Another session saved first — refresh?" # Source-Hash: 8d17e7556ce142da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Another session saved first — refresh?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.anotherSessionSavedFirstRefresh" msgstr "Une autre session a enregistré en premier — actualiser ?" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.build" msgstr "Construire" +# English: "Dismiss" # Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.dismiss" msgstr "Fermer" +# English: "Items" # Source-Hash: daebc0755d239c99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.items" msgstr "Objets" +# English: "Paint" # Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.paint" msgstr "Peindre" +# English: "Reload" # Source-Hash: 854555777b69a4b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.reload" msgstr "Recharger" +# English: "Scene" # Source-Hash: 8dc78e215a97d9fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.scene" msgstr "Scène" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.settings" msgstr "Paramètres" +# English: "Your changes haven't been saved. Reload to pick up the latest version." # Source-Hash: ee255fa26d076dcc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Your changes haven't been saved. Reload to pick up the latest version." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.yourChangesHavenTBeenSaved" msgstr "Vos modifications n’ont pas été enregistrées. Rechargez pour récupérer la dernière version." +# English: "Anyone with the link can view this project. They cannot edit it." # Source-Hash: 703e1b4b9b715d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Anyone with the link can view this project. They cannot edit it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.anyoneWithTheLinkCanView" msgstr "Toute personne disposant du lien peut voir ce projet, sans pouvoir le modifier." +# English: "Copied" # Source-Hash: 07e04484867c17d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copied" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copied" msgstr "Copié" +# English: "Copy" # Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copy" msgstr "Copier" +# English: "Could not read the sharing state." # Source-Hash: 8ff50e1a9962a4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not read the sharing state." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotReadTheSharingState" msgstr "Impossible de lire l’état du partage." +# English: "Could not turn off the public link." # Source-Hash: 791e281338a51483 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn off the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOffThePublic" msgstr "Impossible de désactiver le lien public." +# English: "Could not turn on the public link." # Source-Hash: 2c5ddecfd652ba63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn on the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOnThePublic" msgstr "Impossible d’activer le lien public." +# English: "Embed code" # Source-Hash: 15e49d4da8765318 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedCode" msgstr "Code d’intégration" +# English: "Embed link" # Source-Hash: d4523c2d303f99b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedLink" msgstr "Lien d’intégration" +# English: "Hide controls" # Source-Hash: 252f2cbf5e1609a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.hideControls" msgstr "Masquer les commandes" +# English: "Link" # Source-Hash: ba77c0610a63bb2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.link" msgstr "Lien" +# English: "Only the 3D view, no header or toolbar." # Source-Hash: a2560430aaec31d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Only the 3D view, no header or toolbar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.onlyThe3dViewNoHeader" msgstr "Uniquement la vue 3D, sans en-tête ni barre d’outils." +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.open" msgstr "Ouvrir" +# English: "Public link" # Source-Hash: b34faaa5378056c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Public link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.publicLink" msgstr "Lien public" +# English: "Share project" # Source-Hash: 7525e8007fcab09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.shareProject" msgstr "Partager le projet" +# English: "Automatic dimensions" # Source-Hash: 3d3f438d5a49eba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.automaticDimensions" msgstr "Cotes automatiques" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.camera" msgstr "Caméra" -# Source-Hash: e6bce525c47ba3ac +# English: "Ceilings" +# Source-Hash: a03ae72f209e32db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/components/viewer-toolbar.tsx +#, fuzzy +msgid "app.viewerToolbar.ceilings" +msgstr "Plafonds" + # English: "Clean plan; dimensions appear with selection" +# Source-Hash: e6bce525c47ba3ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cleanPlanDimensionsAppearWithSelection" msgstr "Plan épuré ; les cotes apparaissent à la sélection" +# English: "Collapse sidebar" # Source-Hash: 61ce356577cfb04a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.collapseSidebar" msgstr "Réduire la barre latérale" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crisp, opaque edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.crispOpaqueEdgeLines" msgstr "Arêtes nettes et opaques" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cutaway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cutaway" msgstr "Coupe" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.default" msgstr "Par défaut" +# English: "Display" # Source-Hash: a313661ef9dd0a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.display" msgstr "Affichage" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.displaySettings" msgstr "Paramètres d’affichage" +# English: "Door/window marks" # Source-Hash: 0286962ab8e9cfcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door/window marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.doorWindowMarks" msgstr "Repères de portes et fenêtres" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edges" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.edges" msgstr "Arêtes" +# English: "Enter VR" # Source-Hash: d86126c5d16c6238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.enterVr" msgstr "Entrer en VR" +# English: "Exit VR" # Source-Hash: eb99fb0b382e58cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exitVr" msgstr "Quitter la VR" +# English: "Expand sidebar" # Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expandSidebar" msgstr "Développer la barre latérale" +# English: "Expert" # Source-Hash: 08c178ee733ff009 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expert" msgstr "Expert" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exploded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exploded" msgstr "Éclatée" +# English: "Face of stud" # Source-Hash: c6f865a366ef5755 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face of stud" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faceOfStud" msgstr "Nu de l’ossature" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Faint outline of major creases" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faintOutlineOfMajorCreases" msgstr "Contour léger des arêtes principales" +# English: "Feet & inches" # Source-Hash: 08ecd5878b08139d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Feet & inches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.feetInches" msgstr "Pieds et pouces" +# English: "Finished faces" # Source-Hash: 43b8017fa821c46c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finished faces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.finishedFaces" msgstr "Faces finies" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat and fast — no ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.flatAndFastNoAmbientOcclusion" msgstr "Plat et rapide — sans occlusion ambiante" +# English: "Floor plan annotations" # Source-Hash: 060a718cdcff61b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanAnnotations" msgstr "Annotations du plan d’étage" +# English: "Floor plan mode" # Source-Hash: 483171f79dc32ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanMode" msgstr "Mode plan d’étage" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullAmbientOcclusion" msgstr "Occlusion ambiante complète" +# English: "Full documentation and annotation controls" # Source-Hash: 91fd3544d9ce16bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full documentation and annotation controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullDocumentationAndAnnotationControls" msgstr "Documentation et annotations complètes" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullHeight" msgstr "Pleine hauteur" +# English: "Full wall thickness" # Source-Hash: c9838bc98cb35125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullWallThickness" msgstr "Épaisseur totale du mur" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.grid" msgstr "Grille" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels: {mode}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.levelsMode" msgstr "Niveaux : {mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.low" msgstr "Bas" +# English: "Magnetic snap" # Source-Hash: 32f9a404c0bdce10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Magnetic snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.magneticSnap" msgstr "Aimantation" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manual" msgstr "Manuel" +# English: "Manual dimensions" # Source-Hash: 2fb69122dd996c0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manualDimensions" msgstr "Cotes manuelles" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.measurements" msgstr "Mesures" +# English: "Meters" # Source-Hash: 0a39470be3b84536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Meters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.meters" msgstr "Mètres" +# English: "Millimeters" # Source-Hash: 83cce6c521e71d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Millimeters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.millimeters" msgstr "Millimètres" +# English: "3D measurements" # Source-Hash: b784527bb90b9c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.n3dMeasurements" msgstr "Mesures 3D" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.noEdgeLines" msgstr "Aucune arête" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.off" msgstr "Désactivé" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.on" msgstr "Activé" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orthographic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.orthographic" msgstr "Orthographique" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perspective" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.perspective" msgstr "Perspective" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.preview" msgstr "Aperçu" +# English: "Preview mode" # Source-Hash: 34c20b72a42ca618 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.previewMode" msgstr "Mode aperçu" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.render" msgstr "Rendu" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rendered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.rendered" msgstr "Rendu" +# English: "Room labels" # Source-Hash: 4959d5538efc595c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.roomLabels" msgstr "Étiquettes des pièces" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene theme" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.sceneTheme" msgstr "Thème de la scène" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shadows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.shadows" msgstr "Ombres" +# English: "Single wall axis" # Source-Hash: 59dc62417b44931d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single wall axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.singleWallAxis" msgstr "Axe unique du mur" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Soft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.soft" msgstr "Douces" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solid" msgstr "Plein" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solo" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solo" msgstr "Isolé" +# English: "Stack" # Source-Hash: 481b7608ce12139b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stack" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stack" msgstr "Empilé" +# English: "Stair annotations" # Source-Hash: 61ad94ca68b0458e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stairAnnotations" msgstr "Annotations d’escalier" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strong" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.strong" msgstr "Marquées" +# English: "Structural core face" # Source-Hash: e1be031e67d95382 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural core face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralCoreFace" msgstr "Nu du noyau structurel" +# English: "Structural grids & column centers" # Source-Hash: efc50964d7b0ec47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural grids & column centers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralGridsColumnCenters" msgstr "Trames structurelles et axes des poteaux" +# English: "Translucent" # Source-Hash: 5ce4f6b80cd30f86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Translucent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.translucent" msgstr "Translucide" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.units" msgstr "Unités" +# English: "Walkthrough" # Source-Hash: faf51e28b748e0d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walkthrough" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.walkthrough" msgstr "Visite" +# English: "Wall centerline" # Source-Hash: 62281975e51da7e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall centerline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallCenterline" msgstr "Axe du mur" +# English: "Wall dimensions" # Source-Hash: ff145d7003118ec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallDimensions" msgstr "Cotes des murs" +# English: "Walls: {label}" # Source-Hash: 13969d1d6e310aa4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls: {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallsLabel" msgstr "Murs : {label}" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: human -# English: "Basement" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basement" msgstr "Sous-sol" +# English: "Basement {number}" # Source-Hash: 14927085ee91ba05 # Review: human -# English: "Basement {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basementNumber" msgstr "Sous-sol {number}" +# English: "Floor {number}" # Source-Hash: e59074a7b9b3f56e # Review: human -# English: "Floor {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.floorNumber" msgstr "Étage {number}" +# English: "Ground floor" # Source-Hash: cde3efc9b7ddfa41 # Review: human -# English: "Ground floor" #: packages/editor/src/i18n/level-name.ts msgid "core.level.groundFloor" msgstr "Rez-de-chaussée" +# English: "Orbit Left" # Source-Hash: fdd8b3366021a1cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitLeft" msgstr "Orbiter à gauche" +# English: "Orbit Right" # Source-Hash: 4a56359f284a3b17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitRight" msgstr "Orbiter à droite" +# English: "Top View" # Source-Hash: f5c12d738240f889 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top View" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.topView" msgstr "Vue de dessus" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.delete" msgstr "Supprimer" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.select" msgstr "Sélectionner" +# English: "Resume following" # Source-Hash: d9a257653600d5a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resume following" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-pill.tsx #, fuzzy msgid "editor.actionMenu.followPascalPill.resumeFollowing" msgstr "Reprendre le suivi" +# English: "Follow Pascal: Off" # Source-Hash: 4f073f8051cca3ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.off" msgstr "Suivre Pascal : désactivé" +# English: "Follow Pascal: On" # Source-Hash: 328ea04bb8cd12f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.on" msgstr "Suivre Pascal : activé" +# English: "Appliance" # Source-Hash: 7f4dcb90defe1ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appliance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.appliance" msgstr "Électroménager" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.bathroom" msgstr "Salle de bain" +# English: "Furniture" # Source-Hash: ed68e72f5353712b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furniture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.furniture" msgstr "Mobilier" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.kitchen" msgstr "Cuisine" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.outdoor" msgstr "Extérieur" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angle" msgstr "Angle" +# English: "Angular dimension" # Source-Hash: 622f566fe1e3d8e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angular dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angularDimension" msgstr "Cote angulaire" +# English: "Arc length" # Source-Hash: 7e3b0d541355305e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.arcLength" msgstr "Longueur d’arc" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.area" msgstr "Surface" +# English: "Center mark" # Source-Hash: 88c7750ed51d47a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center mark" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.centerMark" msgstr "Repère de centre" +# English: "Chord dimension" # Source-Hash: 634912dabb092872 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chord dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.chordDimension" msgstr "Cote de corde" +# English: "Continuous dimension" # Source-Hash: 3cb090120253f68f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.continuousDimension" msgstr "Cote continue" +# English: "Coordinate dimensions" # Source-Hash: fb91b1d43b8362d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coordinate dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.coordinateDimensions" msgstr "Cotes en coordonnées" +# English: "Diameter dimension" # Source-Hash: 530cb7ea461e8298 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.diameterDimension" msgstr "Cote de diamètre" +# English: "Distance" # Source-Hash: fe7ddb094aa8ff4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Distance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.distance" msgstr "Distance" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.floorPlan" msgstr "Plan d’étage" +# English: "Linear dimension" # Source-Hash: a73539fc472e2e12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Linear dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.linearDimension" msgstr "Cote linéaire" +# English: "Measurement options" # Source-Hash: 6bba239f244c3d28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementOptions" msgstr "Options de mesure" +# English: "Measurement type" # Source-Hash: 2cf5ca9b834ce8d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementType" msgstr "Type de mesure" +# English: "Measure: {selectedLabel}" # Source-Hash: 0431574a0e6d69d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measure: {selectedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measureSelectedlabel" msgstr "Mesure : {selectedLabel}" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.perimeter" msgstr "Périmètre" +# English: "Radius dimension" # Source-Hash: 6c9acc0589bdd4d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Radius dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.radiusDimension" msgstr "Cote de rayon" +# English: "Smart" # Source-Hash: 01b0015e4e176124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.smart" msgstr "Intelligente" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.volume" msgstr "Volume" +# English: "Point and ask" # Source-Hash: 64f3eb5e1936906d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAsk" msgstr "Pointer et demander" +# English: "Point and ask (C)" # Source-Hash: d86d31e83f6f0888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask (C)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskC" msgstr "Pointer et demander (C)" +# English: "Point and ask works in the 3D view" # Source-Hash: 65622d2ef53c1f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask works in the 3D view" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskWorksInThe" msgstr "Pointer et demander fonctionne dans la vue 3D" -# Source-Hash: 4275c05172abd7fb +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.beam" +msgstr "Poutre" + # English: "Ceiling" +# Source-Hash: 4275c05172abd7fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ceiling" msgstr "Plafond" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.column" msgstr "Poteau" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.door" msgstr "Porte" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.duct" msgstr "Gaine" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ductFitting" msgstr "Raccord de gaine" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.dwvPipe" msgstr "Tuyau d’évacuation" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.elevator" msgstr "Ascenseur" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.fence" msgstr "Clôture" -# Source-Hash: 7f4d93d5f2083844 +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.footing" +msgstr "Semelle" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.foundation" +msgstr "Fondation" + # English: "Gable Roof" +# Source-Hash: 7f4d93d5f2083844 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.gableRoof" msgstr "Toit à deux pans" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.hvacUnit" msgstr "Unité CVC" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.lineset" msgstr "Liaison frigorifique" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.liquidLine" msgstr "Ligne liquide" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.pipeFitting" msgstr "Raccord de tuyau" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.register" msgstr "Bouche" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.shelf" msgstr "Étagère" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.slab" msgstr "Dalle" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.spawnPoint" msgstr "Point d’apparition" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.stairs" msgstr "Escalier" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.trap" msgstr "Siphon" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.wall" msgstr "Mur" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.window" msgstr "Fenêtre" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.zone" msgstr "Zone" -# Source-Hash: cdf3f12939ba108c +# English: "Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it." +# Source-Hash: a309ae5ee641f072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels" +msgstr "Contours en pointillés des murs des niveaux voisins, pour aligner les murs et vérifier un toit par rapport aux murs situés dessous." + # English: "Delete guide image" +# Source-Hash: cdf3f12939ba108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteGuideImage" msgstr "Supprimer l’image guide" +# English: "Delete scan" # Source-Hash: 6add06abbafa8289 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete scan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteScan" msgstr "Supprimer le relevé 3D" +# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" # Source-Hash: 6f5e541cb5fb502f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideCount" msgstr "{count, plural, one {# image guide sur ce niveau} many {# images guides sur ce niveau} other {# images guides sur ce niveau}}" +# English: "Guide image {number}" # Source-Hash: 8a832a2f33757bf8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageNumber" msgstr "Image guide {number}" +# English: "Guide images" # Source-Hash: 7f3aaccfe8d1af1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImages" msgstr "Images guides" +# English: "Guide image settings" # Source-Hash: 9b1de02505434d74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageSettings" msgstr "Réglages des images guides" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guides" msgstr "Guides" +# English: "Guides: Hidden" # Source-Hash: ccfb05c78d47d6f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesHidden" msgstr "Guides : masqués" +# English: "Guides: Visible" # Source-Hash: 6d7ca85c4f389991 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesVisible" msgstr "Guides : visibles" +# English: "Hide guide images" # Source-Hash: 5f6264ac469f57c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideGuideImages" msgstr "Masquer les images guides" +# English: "Hide reference floor" # Source-Hash: cae234c23229c80b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideReferenceFloor" msgstr "Masquer le niveau de référence" +# English: "Hide scans" # Source-Hash: 469a8416de876e0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideScans" msgstr "Masquer les relevés 3D" +# English: "{count} below" # Source-Hash: 873620c284a5651f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count} below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.levelsBelow" msgstr "{count} en dessous" -# Source-Hash: 22a59ae72d8c9504 +# English: "No level above" +# Source-Hash: 6661c6766fdd1a7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelAbove" +msgstr "Aucun niveau au-dessus" + +# English: "No level below" +# Source-Hash: e34d9ec372c823ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelBelow" +msgstr "Aucun niveau en dessous" + # English: "No lower floor available." +# Source-Hash: 22a59ae72d8c9504 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.noLowerFloorAvailable" msgstr "Aucun niveau inférieur disponible." +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.opacity" msgstr "Opacité" +# English: "Reference floor" # Source-Hash: 8bbd9bc90ac70a4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloor" msgstr "Niveau de référence" +# English: "Reference floor: {selectedLevelName}" # Source-Hash: 45493691a23c7690 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor: {selectedLevelName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSelectedlevelname" msgstr "Niveau de référence : {selectedLevelName}" +# English: "Reference floor settings" # Source-Hash: 7178c7b5a4581367 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSettings" msgstr "Réglages du niveau de référence" +# English: "References" # Source-Hash: a52848021f822f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.references" msgstr "Références" +# English: "Upload GLB meshes as scan references or blueprint images as guide references." # Source-Hash: e59c9eec7dbd6ba1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload GLB meshes as scan references or blueprint images as guide references." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesEmpty" msgstr "Importez des maillages GLB comme relevés 3D de référence ou des plans en image comme guides." +# English: "Reference settings" # Source-Hash: 3e73d06c3e96efba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceSettings" msgstr "Réglages des références" +# English: "References: Hidden" # Source-Hash: aa0df2c2f00672e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesHidden" msgstr "Références : masquées" +# English: "References: Visible" # Source-Hash: 9976708fc92bfa91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesVisible" msgstr "Références : visibles" +# English: "Riser diagram" # Source-Hash: 320a5f1ec012900b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser diagram" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.riserDiagram" msgstr "Schéma des colonnes" +# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" # Source-Hash: 357b3d3eaf878a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanCount" msgstr "{count, plural, one {# relevé 3D sur ce niveau} many {# relevés 3D sur ce niveau} other {# relevés 3D sur ce niveau}}" +# English: "Scan {number}" # Source-Hash: f09f12547484c888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanNumber" msgstr "Relevé 3D {number}" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scans" msgstr "Relevés 3D" +# English: "Scan settings" # Source-Hash: c9c52015095997e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanSettings" msgstr "Réglages des relevés 3D" +# English: "Scans: Hidden" # Source-Hash: 60abc9f1a8aec65c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansHidden" msgstr "Relevés 3D : masqués" +# English: "Scans: Visible" # Source-Hash: 2d2ff119a7dd7911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansVisible" msgstr "Relevés 3D : visibles" +# English: "Show guide images" # Source-Hash: 8211234b205fd9a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showGuideImages" msgstr "Afficher les images guides" +# English: "Show reference floor" # Source-Hash: 7786cae6acb04cfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showReferenceFloor" msgstr "Afficher le niveau de référence" +# English: "Show scans" # Source-Hash: 6066f0b6cff8a910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showScans" msgstr "Afficher les relevés 3D" -# Source-Hash: def6dff836767bda +# English: "Show walls below and above" +# Source-Hash: c100768fc929ccf4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.showWallsBelowAndAbove" +msgstr "Afficher les murs du dessous et du dessus" + # English: "Upload scan or guide image" +# Source-Hash: def6dff836767bda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.uploadScanOrGuideImage" msgstr "Importer un relevé 3D ou une image guide" -# Source-Hash: 6aee0962fc18c0f1 +# English: "Walls above" +# Source-Hash: e9ac75feeb58a5be +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsAbove" +msgstr "Murs du dessus" + +# English: "Walls below" +# Source-Hash: 790a0b5c6dbf0e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelow" +msgstr "Murs du dessous" + +# English: "Walls below and above" +# Source-Hash: e74e28937511bce9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAbove" +msgstr "Murs du dessous et du dessus" + +# English: "Walls below and above settings" +# Source-Hash: 7c7bd2a996dfd5ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveSettings" +msgstr "Réglages des murs du dessous et du dessus" + +# English: "Walls below and above: shown" +# Source-Hash: 43bc80f62aeab442 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveShown" +msgstr "Murs du dessous et du dessus : affichés" + # English: "Back to home" +# Source-Hash: 6aee0962fc18c0f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.backToHome" msgstr "Retour à l’accueil" +# English: "Advanced" # Source-Hash: f571cb2d06ae01ae # Review: human -# English: "Advanced" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.advanced" msgstr "Avancé" +# English: "{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: 7afac05841af7b0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.caption" +msgstr "{lead} chaque mur prend le type extérieur, où qu’il se trouve. Échap ou un double-clic termine le tracé." + +# English: "Exterior wall" +# Source-Hash: 0a7c3607e2011f54 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.label" +msgstr "Mur extérieur" + +# English: "Exterior walls: every wall drawn takes the exterior type and cladding" +# Source-Hash: 76fd5dab80b9d731 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.title" +msgstr "Murs extérieurs : chaque mur tracé prend le type et le parement extérieurs" + +# English: "walls · floor · ceiling" # Source-Hash: fb40866c2d6db5fb # Review: human -# English: "walls · floor · ceiling" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.hint" msgstr "murs · sol · plafond" +# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." # Source-Hash: 271039bd919dc4c1 # Review: human -# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.idle" msgstr "Choisissez une forme pour commencer. Chaque pièce crée ses murs, son sol et son plafond." +# English: "{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: f1fddcf6d6b5c32b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.caption" +msgstr "{lead} chaque mur prend le type intérieur, où qu’il se trouve. Échap ou un double-clic termine le tracé." + +# English: "Interior wall" +# Source-Hash: b9e25ba9781c9848 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.label" +msgstr "Mur intérieur" + +# English: "Interior walls: every wall drawn takes the interior type and paint" +# Source-Hash: 9e4a942fd52a06c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.title" +msgstr "Murs intérieurs : chaque mur tracé prend le type intérieur et la peinture" + +# English: "{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run." +# Source-Hash: 3f519112d88b3ba6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.caption" +msgstr "{lead} chaque ligne est un mur invisible : les pièces se ferment dessus, rien n’est construit. Fermée à l’extérieur de la maison, une pièce devient un porche. Échap ou un double-clic termine le tracé." + +# English: "Invisible wall" +# Source-Hash: 5b0317bd807d560e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.label" +msgstr "Mur invisible" + +# English: "Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall." +# Source-Hash: 72fbf62dac3603b5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.title" +msgstr "Murs invisibles : divisez une pièce ou fermez un porche sans construire de mur. Les pièces se ferment dessus ; ils ne coupent jamais un mur." + +# English: "{lead} then the first one again to close. For L-shapes and angled rooms." # Source-Hash: ae2800b6c9e2b6ed # Review: human -# English: "{lead} then the first one again to close. For L-shapes and angled rooms." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.caption" msgstr "{lead} puis de nouveau sur le premier pour fermer. Pour les pièces en L et les angles obliques." +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: human -# English: "Polygon" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.label" msgstr "Polygone" +# English: "Click each corner," # Source-Hash: 345e3aca3879b5c5 # Review: human -# English: "Click each corner," #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.lead" msgstr "Cliquez sur chaque angle," +# English: "Polygon room" # Source-Hash: 8ec6152e139b0e3f # Review: human -# English: "Polygon room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.title" msgstr "Pièce polygonale" +# English: "{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run." +# Source-Hash: 18773160bb84f069 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.caption" +msgstr "{lead} chaque ligne est un garde-corps sur lequel les pièces se ferment, sans montants, bardage ni semelle. Placez-y une porte pour créer une ouverture. Échap ou un double-clic termine le tracé." + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.label" +msgstr "Garde-corps" + +# English: "Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel." +# Source-Hash: 161fbdfe5f09b81a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.title" +msgstr "Garde-corps : une protection de 36 in de haut tracée comme les murs, avec des poteaux espacés de 6 ft au plus. Les pièces se ferment dessus et elle accepte des ouvertures ; style et hauteur dans le panneau du mur." + +# English: "{lead} on the grid. Walls, floor and ceiling are created together." # Source-Hash: 9b46295eedcbedae # Review: human -# English: "{lead} on the grid. Walls, floor and ceiling are created together." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.caption" msgstr "{lead} sur la grille. Les murs, le sol et le plafond sont créés ensemble." +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: human -# English: "Rectangle" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.label" msgstr "Rectangle" +# English: "Click two corners" # Source-Hash: 060faa48e299d7c9 # Review: human -# English: "Click two corners" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.lead" msgstr "Cliquez sur deux angles" +# English: "Rectangle room" # Source-Hash: 83a45603ac7f41e8 # Review: human -# English: "Rectangle room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.title" msgstr "Pièce rectangulaire" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.title" msgstr "Pièces" -# Source-Hash: 8a21a03c4acf396b -# Review: human -# English: "{lead} Close a loop and it becomes a room; leave it open for partitions." +# English: "{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room." +# Source-Hash: ee04555fa3ead992 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.caption" -msgstr "{lead} Fermez la boucle pour créer une pièce, ou laissez-la ouverte pour une cloison." +msgstr "{lead} chaque mur part de la fin du précédent. Échap ou un double-clic termine le tracé ; fermez une boucle pour en faire une pièce." +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.label" msgstr "Murs" -# Source-Hash: 9843edd68426fa2d -# Review: human -# English: "Draw one wall at a time." +# English: "Click each corner;" +# Source-Hash: ab5c2c822d968ca2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.lead" -msgstr "Tracez un mur à la fois." +msgstr "Cliquez sur chaque angle ;" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.title" msgstr "Murs" -# Source-Hash: 1f3650da23603008 +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraControlsHint" -msgstr "Aide sur les commandes de caméra" +msgid "editor.buildPanel.wallTypeControls.default" +msgstr "Par défaut" -# Source-Hash: 101b6a54c377049a +# English: "Delete from this project" +# Source-Hash: 1dba2aacc55e0bb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pan" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.pan" -msgstr "Panoramique" +msgid "editor.buildPanel.wallTypeControls.deleteFromThisProject" +msgstr "Supprimer de ce projet" -# Source-Hash: 306d409f8a1cf480 +# English: "Duplicate as a new type…" +# Source-Hash: 58a1b780e877ddc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.rotate" -msgstr "Pivoter" +msgid "editor.buildPanel.wallTypeControls.duplicateAsANewType" +msgstr "Dupliquer comme nouveau type…" -# Source-Hash: 663cecc316eb91b8 +# English: "Edit layers…" +# Source-Hash: 43e7aad9a5f75b6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zoom" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.zoom" -msgstr "Zoom" +msgid "editor.buildPanel.wallTypeControls.editLayers" +msgstr "Modifier les couches…" -# Source-Hash: 34b7ca7ff46c44a9 +# English: "Edit or duplicate {label}" +# Source-Hash: 640331cc307a8421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label}: {finish}" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.ariaLabel" -msgstr "{label} : {finish}" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateLabel" +msgstr "Modifier ou dupliquer {label}" -# Source-Hash: 4ca0e0e73229540c +# English: "Edit or duplicate this wall type" +# Source-Hash: 8f1ea5a4c16ba810 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.default" -msgstr "Par défaut" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType" +msgstr "Modifier ou dupliquer ce type de mur" -# Source-Hash: a2bd204e93e90598 +# English: "New wall type…" +# Source-Hash: 273fcb71b4f9272f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismiss" -msgstr "Fermer" +msgid "editor.buildPanel.wallTypeControls.newWallType" +msgstr "Nouveau type de mur…" -# Source-Hash: 9222f970dce6cc60 +# English: "Presets stay as cited. Duplicate one to change its layers." +# Source-Hash: 079e4005e0093122 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismissCameraControlsHint" -msgstr "Fermer l’aide sur les commandes de caméra" +msgid "editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne" +msgstr "Les préréglages restent conformes à leur source. Dupliquez-en un pour modifier ses couches." -# Source-Hash: aeac665749d3101d +# English: "Add" +# Source-Hash: b071c943d8da1500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.expandSidebar" -msgstr "Développer la barre latérale" +msgid "editor.buildPanel.wallTypeEditorDialog.add" +msgstr "Ajouter" -# Source-Hash: dd86efcab72fc5c7 +# English: "Air space" +# Source-Hash: fcc69acc0fd92288 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis" -msgstr "Axe" +msgid "editor.buildPanel.wallTypeEditorDialog.airSpace" +msgstr "Lame d’air" -# Source-Hash: 5232a826549f915e +# English: "A wall type holds at most {limit, plural, one {# layer} other {# layers}}." +# Source-Hash: 46fb2832f979d656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⌥ axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis2" -msgstr "⌥ axe" +msgid "editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost" +msgstr "Un type de mur compte au plus {limit, plural, one {# couche} many {# couches} other {# couches}}." -# Source-Hash: 7001600001844c92 +# English: "Brick veneer" +# Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⚠ no equipment" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.noEquipment" -msgstr "⚠ aucun équipement" +msgid "editor.buildPanel.wallTypeEditorDialog.brickVeneer" +msgstr "Parement brique" -# Source-Hash: eae0194029f96edb +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# register} other {# registers}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.registerCount" -msgstr "{count, plural, one {# bouche} many {# bouches} other {# bouches}}" +msgid "editor.buildPanel.wallTypeEditorDialog.cancel" +msgstr "Annuler" -# Source-Hash: 8fb180251a321cef +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "R/T rotate" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.rTRotate" -msgstr "R/T pivoter" +msgid "editor.buildPanel.wallTypeEditorDialog.drywall" +msgstr "Plaque de plâtre" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 6fb660d4eabe8d6a +# English: "Duplicate wall type" +# Source-Hash: 1f2e15ce909adf0c # Review: required -# English: "{count, plural, one {{number} run} other {{number} runs}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.runCount" -msgstr "{count, plural, one {{number} tronçon} many {{number} tronçons} other {{number} tronçons}}" +msgid "editor.buildPanel.wallTypeEditorDialog.duplicateWallType" +msgstr "Dupliquer le type de mur" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Edit wall type" +# Source-Hash: 832628bc67f36778 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.system" -msgstr "Système" +msgid "editor.buildPanel.wallTypeEditorDialog.editWallType" +msgstr "Modifier le type de mur" -# Source-Hash: f0e76aea883d4e48 +# English: "Every wall type has one framing core" +# Source-Hash: 7d3d13d418b64f1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a floor under the roof" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" -msgstr "Ajouter un niveau sous le toit" +msgid "editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming" +msgstr "Chaque type de mur a un noyau d’ossature" -# Source-Hash: a982ff9b075af829 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level above" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelAbove" -msgstr "Ajouter un niveau au-dessus" +msgid "editor.buildPanel.wallTypeEditorDialog.fiberCement" +msgstr "Fibrociment" -# Source-Hash: 0db7d221df08fa6b +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level below" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelBelow" -msgstr "Ajouter un niveau en dessous" +msgid "editor.buildPanel.wallTypeEditorDialog.finish" +msgstr "Finition" -# Source-Hash: c0774c185060fc13 +# English: "Framing core" +# Source-Hash: 23306d290f066b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.cancel" -msgstr "Annuler" +msgid "editor.buildPanel.wallTypeEditorDialog.framingCore" +msgstr "Noyau d’ossature" -# Source-Hash: b26de372b4b0eaac +# English: "{fullReason} Remove one to add another." +# Source-Hash: b50fa488c2bfb502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.delete" -msgstr "Supprimer" +msgid "editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother" +msgstr "{fullReason} Retirez-en une pour en ajouter une autre." -# Source-Hash: 8e29375893d63408 +# English: "Gypsum sheathing" +# Source-Hash: e3e25fbbf14e97fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevel" -msgstr "Supprimer le niveau" +msgid "editor.buildPanel.wallTypeEditorDialog.gypsumSheathing" +msgstr "Plaque de plâtre extérieure" -# Source-Hash: c3bc69ec52022250 +# English: "Inside" +# Source-Hash: 8a7ff5011275abbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevelConfirm" -msgstr "Voulez-vous vraiment supprimer {levelName} ? Tous les murs, planchers et objets de ce niveau seront définitivement supprimés." +msgid "editor.buildPanel.wallTypeEditorDialog.inside" +msgstr "Intérieur" -# Source-Hash: 5b2826ddac95cd0c +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to reorder" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.dragToReorder" -msgstr "Faire glisser pour réorganiser" +msgid "editor.buildPanel.wallTypeEditorDialog.insulation" +msgstr "Isolant" -# Source-Hash: 933177338f5a03ed +# English: "Lap siding" +# Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateLevel" -msgstr "Dupliquer le niveau" +msgid "editor.buildPanel.wallTypeEditorDialog.lapSiding" +msgstr "Bardage à clin" -# Source-Hash: 9614c6825a7c67bf +# English: "Layer" +# Source-Hash: d7ee1cefa20d0e32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateWithOptions" -msgstr "Dupliquer avec options..." +msgid "editor.buildPanel.wallTypeEditorDialog.layer" +msgstr "Couche" -# Source-Hash: 5ec91bb339806413 +# English: "Layer role" +# Source-Hash: bd00fff893ace820 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit unit focus" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.exitUnitFocus" -msgstr "Quitter le focus sur le logement" +msgid "editor.buildPanel.wallTypeEditorDialog.layerRole" +msgstr "Rôle de la couche" -# Source-Hash: a88c3f8bed58b4fa +# English: "Lining" +# Source-Hash: e5536deeaabfacec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused unit has zones here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" -msgstr "Le logement ciblé a des zones ici" +msgid "editor.buildPanel.wallTypeEditorDialog.lining" +msgstr "Doublage" -# Source-Hash: 655055a310fb5d13 +# English: "Lists under Exterior: it has a cladding side." +# Source-Hash: d4e37e79879efa23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorOverGrade" -msgstr "Le rez-de-chaussée est {distance} au-dessus du sol. Placer le rez-de-chaussée à 8 in au-dessus du sol" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA" +msgstr "Classé sous Extérieur : il a une face de parement." -# Source-Hash: 77548faae4869eec +# English: "Lists under Interior: both faces match." +# Source-Hash: d455f88eb0e9e68e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorUnderGrade" -msgstr "Le rez-de-chaussée est {distance} sous le sol. Placer le rez-de-chaussée à 8 in au-dessus du sol" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch" +msgstr "Classé sous Intérieur : ses deux faces sont identiques." -# Source-Hash: e3763ae2dc62c03f +# English: "List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type." +# Source-Hash: 5454746105949d05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground line: the levels under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" -msgstr "Ligne de sol : les niveaux en dessous sont enterrés" +msgid "editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside" +msgstr "Listez les couches de l’extérieur vers l’intérieur, autour d’un seul noyau d’ossature. Les murs déjà dessinés gardent leurs couches ; ceux dessinés ensuite utilisent le type enregistré." -# Source-Hash: e12035eb47cc61cc +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground · {offset}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundOffset" -msgstr "Sol · {offset}" +msgid "editor.buildPanel.wallTypeEditorDialog.material" +msgstr "Matériau" -# Source-Hash: 5661c11383217661 +# English: "Mineral wool board" +# Source-Hash: 75d79fb9a2e8fcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insert level here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.insertLevelHere" -msgstr "Insérer un niveau ici" +msgid "editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard" +msgstr "Panneau de laine minérale" -# Source-Hash: 0215b36d5a08597a +# English: "Move in" +# Source-Hash: 78acc1bad880ce0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level height" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.levelHeight" -msgstr "Hauteur du niveau" +msgid "editor.buildPanel.wallTypeEditorDialog.moveIn" +msgstr "Déplacer vers l’intérieur" -# Source-Hash: 91af436e81bb0875 +# English: "Move out" +# Source-Hash: 2de6ee30b4e608bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paste copied selection" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.pasteCopiedSelection" -msgstr "Coller la sélection copiée" +msgid "editor.buildPanel.wallTypeEditorDialog.moveOut" +msgstr "Déplacer vers l’extérieur" -# Source-Hash: 68bc52f53240cbec +# English: "Name" +# Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reorder {levelName}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.reorderLevelname" -msgstr "Réorganiser {levelName}" +msgid "editor.buildPanel.wallTypeEditorDialog.name" +msgstr "Nom" -# Source-Hash: 625d53de72ebc2ef +# English: "New wall type" +# Source-Hash: 4b3edeb00b32607e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seat the building so this floor stands at grade; the floors under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" -msgstr "Placer le bâtiment pour que cet étage soit au niveau du sol ; les étages inférieurs sont enterrés" +msgid "editor.buildPanel.wallTypeEditorDialog.newWallType" +msgstr "Nouveau type de mur" -# Source-Hash: 7ac9c00895c30d76 +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set as ground floor" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.setAsGroundFloor" -msgstr "Définir comme rez-de-chaussée" +msgid "editor.buildPanel.wallTypeEditorDialog.osb" +msgstr "OSB" -# Source-Hash: d6b381247066a7aa +# English: "Outside" +# Source-Hash: dc4fe6cb415ed882 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" -msgstr "Le niveau du rez-de-chaussée ne peut pas être supprimé" +msgid "editor.buildPanel.wallTypeEditorDialog.outside" +msgstr "Extérieur" -# Source-Hash: b1122051dce0d7a1 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor stands at grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" -msgstr "Cet étage est au niveau du sol" +msgid "editor.buildPanel.wallTypeEditorDialog.plaster" +msgstr "Enduit plâtre" -# Source-Hash: 062cc0f1afef0c52 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.unit" -msgstr "Lot" +msgid "editor.buildPanel.wallTypeEditorDialog.plywood" +msgstr "Contreplaqué" -# Source-Hash: 5871b7c20478f4a1 +# English: "Remove layer" +# Source-Hash: ca36e9fd7fba1fce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor area" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.area" -msgstr "Surface de plancher" +msgid "editor.buildPanel.wallTypeEditorDialog.removeLayer" +msgstr "Retirer la couche" -# Source-Hash: 8b2e1435272aa86a +# English: "Rigid foam" +# Source-Hash: 7d8f351c35f89b94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{room} floor" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.room" -msgstr "Plancher {room}" +msgid "editor.buildPanel.wallTypeEditorDialog.rigidFoam" +msgstr "Mousse rigide" -# Source-Hash: 4731183fec06cbdf +# English: "Save wall type" +# Source-Hash: e8bc984965d5753d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shared floor" -#: packages/editor/src/components/ui/panels/room-construction-rows.tsx -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.shared" -msgstr "Plancher partagé" +msgid "editor.buildPanel.wallTypeEditorDialog.saveWallType" +msgstr "Enregistrer le type de mur" -# Source-Hash: c0774c185060fc13 +# English: "Sheathing" +# Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.cancel" -msgstr "Annuler" +msgid "editor.buildPanel.wallTypeEditorDialog.sheathing" +msgstr "Contreventement" -# Source-Hash: 4c6ca3441c74da9e +# English: "Stone veneer" +# Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place building" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.placeBuilding" -msgstr "Placer le bâtiment" +msgid "editor.buildPanel.wallTypeEditorDialog.stoneVeneer" +msgstr "Parement pierre" -# Source-Hash: 306d409f8a1cf480 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.rotate" -msgstr "Pivoter" +msgid "editor.buildPanel.wallTypeEditorDialog.stucco" +msgstr "Enduit" -# Source-Hash: 832470d3aeee82fd +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel placement" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.cancelPlacement" -msgstr "Annuler le placement" +msgid "editor.buildPanel.wallTypeEditorDialog.thickness" +msgstr "Épaisseur" -# Source-Hash: e374fcaee33c3e2c +# English: "Total {thickness}" +# Source-Hash: 72e31eecaeb87447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation — click or press C to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" -msgstr "Enchaînement — cliquez ou appuyez sur C pour changer" +msgid "editor.buildPanel.wallTypeEditorDialog.totalThickness" +msgstr "Total {thickness}" -# Source-Hash: b108c7dc2ec2b9de +# English: "Wall type" +# Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation: {label}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationLabel" -msgstr "Enchaînement : {label}" +msgid "editor.buildPanel.wallTypeEditorDialog.wallType" +msgstr "Type de mur" -# Source-Hash: 729d87891cb7dbb1 +# English: "Brick" +# Source-Hash: abff67400a76e5eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to draw fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" -msgstr "Faites glisser pour tracer la clôture" +msgid "editor.buildPanel.wallTypes.brick" +msgstr "Brique" -# Source-Hash: b52e874718af1916 +# English: "Cladding" +# Source-Hash: ecca209317a2d1cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScope" -msgstr "Effacer : {scope}" +msgid "editor.buildPanel.wallTypes.cladding" +msgstr "Parement" -# Source-Hash: 61f7494abc319e66 +# English: "Drawing: Exterior walls." +# Source-Hash: 5c53959decce5dae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" -msgstr "Portée de l’effacement — cliquez ou appuyez sur Maj pour changer" +msgid "editor.buildPanel.wallTypes.drawingExterior" +msgstr "Tracé : murs extérieurs." -# Source-Hash: 59993c163d68cc53 +# English: "Drawing: Interior walls." +# Source-Hash: 5206b8695c329b06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" -msgstr "Portée de l’effacement : {scope}" +msgid "editor.buildPanel.wallTypes.drawingInterior" +msgstr "Tracé : murs intérieurs." -# Source-Hash: a4bdcbc6cab772bf +# English: "Drywall paint" +# Source-Hash: 8a542e796ac8edf0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence type — click or press T to switch between straight, curved and freehand" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" -msgstr "Type de clôture — cliquez ou appuyez sur T pour passer de droite à courbe ou libre" +msgid "editor.buildPanel.wallTypes.drywallPaint" +msgstr "Peinture sur plâtre" -# Source-Hash: 2a9ed258727078ac +# English: "Exterior" +# Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fewer shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" -msgstr "Moins de raccourcis" +msgid "editor.buildPanel.wallTypes.exterior" +msgstr "Extérieur" -# Source-Hash: 698fa6e78af4d4b7 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish curve (or double-click)" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" -msgstr "Terminer la courbe (ou double-clic)" +msgid "editor.buildPanel.wallTypes.fiberCement" +msgstr "Fibrociment" -# Source-Hash: 09f6d79edf5cfd16 +# English: "{drawing} Every wall takes the exterior type until you pick a shape or leave the tool." +# Source-Hash: 5badd399f078d485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridLength" -msgstr "Grille : {length}" +msgid "editor.buildPanel.wallTypes.forcedExterior" +msgstr "{drawing} Chaque mur prend le type extérieur jusqu’à ce que vous choisissiez une forme ou quittiez l’outil." -# Source-Hash: c493b562040c4328 +# English: "{drawing} Every wall takes the interior type until you pick a shape or leave the tool." +# Source-Hash: de2c329c8d03a385 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step — click or tap Ctrl to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" -msgstr "Pas de grille — cliquez ou appuyez sur Ctrl pour changer" +msgid "editor.buildPanel.wallTypes.forcedInterior" +msgstr "{drawing} Chaque mur prend le type intérieur jusqu’à ce que vous choisissiez une forme ou quittiez l’outil." -# Source-Hash: d36a8c93d81b3a36 +# English: "New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint." +# Source-Hash: b70a5d0948632955 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepLength" -msgstr "Pas de grille : {length}" +msgid "editor.buildPanel.wallTypes.hint" +msgstr "Les nouveaux murs prennent le type et le parement extérieurs ; les murs tracés dans une pièce prennent le type intérieur. Chaque face en plaque de plâtre reçoit la peinture." -# Source-Hash: abea993ede7daa7e +# English: "Interior" +# Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a painted surface to erase" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" -msgstr "Survolez une surface peinte pour l’effacer" +msgid "editor.buildPanel.wallTypes.interior" +msgstr "Intérieur" -# Source-Hash: fc1ed0ddc0cfcef5 +# English: "Plain wall" +# Source-Hash: 3744d42a0e4ee161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" -msgstr "Survolez une surface pour la peindre" +msgid "editor.buildPanel.wallTypes.plainWall" +msgstr "Mur simple" -# Source-Hash: 63dec5de4a337015 +# English: "Siding" +# Source-Hash: 8884a1d20caae02f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to pick its material" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" -msgstr "Survolez une surface pour prélever son matériau" +msgid "editor.buildPanel.wallTypes.siding" +msgstr "Bardage" -# Source-Hash: 7ad6dc1b1f03f970 +# English: "Stone" +# Source-Hash: 44c6466e6907a0b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.moreShortcuts" -msgstr "Plus de raccourcis" +msgid "editor.buildPanel.wallTypes.stone" +msgstr "Pierre" -# Source-Hash: 476b8f5f9ee542e6 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScope" -msgstr "Peindre : {scope}" +msgid "editor.buildPanel.wallTypes.stucco" +msgstr "Enduit" -# Source-Hash: 9c09ce4c34122277 +# English: "Camera controls hint" +# Source-Hash: 1f3650da23603008 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" -msgstr "Portée de la peinture — cliquez ou appuyez sur Maj pour changer" +msgid "editor.cameraControlsHint" +msgstr "Aide sur les commandes de caméra" -# Source-Hash: 7890aa81687f6654 +# English: "Pan" +# Source-Hash: 101b6a54c377049a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" -msgstr "Portée de la peinture : {scope}" +msgid "editor.cameraHint.pan" +msgstr "Panoramique" -# Source-Hash: a75b3abc0784b12b +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.pickScope" -msgstr "Prélever : {scope}" +msgid "editor.cameraHint.rotate" +msgstr "Pivoter" -# Source-Hash: 69c1044e6ea87ccf +# English: "Zoom" +# Source-Hash: 663cecc316eb91b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gate on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" -msgstr "Placer le portail sur une clôture" +msgid "editor.cameraHint.zoom" +msgstr "Zoom" -# Source-Hash: 3754255b8affc7a0 +# English: "{label}: {finish}" +# Source-Hash: 34b7ca7ff46c44a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place passage on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" -msgstr "Placer le passage sur une clôture" +msgid "editor.controls.finishSwatch.ariaLabel" +msgstr "{label} : {finish}" -# Source-Hash: a8328c8964e947fa +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a material to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" -msgstr "Choisissez un matériau à peindre" +msgid "editor.controls.finishSwatch.default" +msgstr "Par défaut" -# Source-Hash: 6f95d7116aea5738 +# English: "Dismiss" +# Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Angles" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingAngles" -msgstr "Aimantation : angles" +msgid "editor.dismiss" +msgstr "Fermer" -# Source-Hash: 2d6d8d85d2ebed47 +# English: "Dismiss camera controls hint" +# Source-Hash: 9222f970dce6cc60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Grid" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingGrid" -msgstr "Aimantation : grille" +msgid "editor.dismissCameraControlsHint" +msgstr "Fermer l’aide sur les commandes de caméra" -# Source-Hash: 9096c5f9d8ce0f7c +# English: "Click to edit the foundation’s outline" +# Source-Hash: e314809b6907d9ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Lines" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingLines" -msgstr "Aimantation : lignes" +msgid "editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS" +msgstr "Cliquez pour modifier le contour de la fondation" -# Source-Hash: b663afbf0835cf32 +# English: "Expand sidebar" +# Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping mode — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" -msgstr "Mode d’aimantation — cliquez ou appuyez sur Maj pour changer" +msgid "editor.expandSidebar" +msgstr "Développer la barre latérale" -# Source-Hash: d00e984fc88f71aa +# English: "Axis" +# Source-Hash: dd86efcab72fc5c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Off" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingOff" -msgstr "Aimantation : désactivée" +msgid "editor.floatingActionMenu.axis" +msgstr "Axe" -# Source-Hash: 6be9d0d312200761 +# English: "⌥ axis" +# Source-Hash: 5232a826549f915e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight continuation is unavailable for curved or freehand fences" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" -msgstr "L’enchaînement droit n’est pas disponible pour les clôtures courbes ou libres" +msgid "editor.floatingActionMenu.axis2" +msgstr "⌥ axe" -# Source-Hash: 0d56b1f0a0493302 +# English: "⚠ no equipment" +# Source-Hash: 7001600001844c92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Continuous" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuous" -msgstr "Droite : continue" +msgid "editor.floatingActionMenu.noEquipment" +msgstr "⚠ aucun équipement" -# Source-Hash: c034e85d3e7fd27e +# English: "{count, plural, one {# register} other {# registers}}" +# Source-Hash: eae0194029f96edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight fence continuation — click or press C to toggle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" -msgstr "Enchaînement de clôture droite — cliquez ou appuyez sur C pour basculer" +msgid "editor.floatingActionMenu.registerCount" +msgstr "{count, plural, one {# bouche} many {# bouches} other {# bouches}}" -# Source-Hash: e3f679cdb4a3d603 +# English: "R/T rotate" +# Source-Hash: 8fb180251a321cef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Single" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightSingle" -msgstr "Droite : simple" +msgid "editor.floatingActionMenu.rTRotate" +msgstr "R/T pivoter" -# Source-Hash: 9a56d90d461b4bca +# English: "{count, plural, one {{number} run} other {{number} runs}}" +# Source-Hash: 6fb660d4eabe8d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPoint" -msgstr "Ajouter un point" +msgid "editor.floatingActionMenu.runCount" +msgstr "{count, plural, one {{number} tronçon} many {{number} tronçons} other {{number} tronçons}}" -# Source-Hash: c3dc3918c823a5e6 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point · click the first to close" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPointClickTheFirstTo" -msgstr "Ajouter un point · cliquez sur le premier pour fermer" +msgid "editor.floatingActionMenu.system" +msgstr "Système" -# Source-Hash: d8cf72d482d56cd7 +# English: "Add a floor under the roof" +# Source-Hash: f0e76aea883d4e48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brush size" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.brushSize" -msgstr "Taille du pinceau" +msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" +msgstr "Ajouter un niveau sous le toit" -# Source-Hash: c0774c185060fc13 +# English: "Add level above" +# Source-Hash: a982ff9b075af829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancel" -msgstr "Annuler" +msgid "editor.floatingLevelSelector.addLevelAbove" +msgstr "Ajouter un niveau au-dessus" -# Source-Hash: 104fdab7934ec66c +# English: "Add level below" +# Source-Hash: 0db7d221df08fa6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel picking" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelPicking" -msgstr "Annuler la sélection" +msgid "editor.floatingLevelSelector.addLevelBelow" +msgstr "Ajouter un niveau en dessous" -# Source-Hash: c318cf0ce70b8977 +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel stroke" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelStroke" -msgstr "Annuler le tracé" +msgid "editor.floatingLevelSelector.cancel" +msgstr "Annuler" -# Source-Hash: 16552c2580bbe8b8 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.done" -msgstr "Terminé" +msgid "editor.floatingLevelSelector.delete" +msgstr "Supprimer" -# Source-Hash: e297db9ca0ce6d31 +# English: "Delete level" +# Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the mezzanine" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheMezzanine" -msgstr "Dessiner la mezzanine" +msgid "editor.floatingLevelSelector.deleteLevel" +msgstr "Supprimer le niveau" -# Source-Hash: c8be604718a7a9fd +# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." +# Source-Hash: c3bc69ec52022250 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the opening" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheOpening" -msgstr "Dessiner l’ouverture" +msgid "editor.floatingLevelSelector.deleteLevelConfirm" +msgstr "Voulez-vous vraiment supprimer {levelName} ? Tous les murs, planchers et objets de ce niveau seront définitivement supprimés." -# Source-Hash: 231386ae6d534163 +# English: "Drag to reorder" +# Source-Hash: 5b2826ddac95cd0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the terrace" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheTerrace" -msgstr "Dessiner la terrasse" +msgid "editor.floatingLevelSelector.dragToReorder" +msgstr "Faire glisser pour réorganiser" -# Source-Hash: e092e803eb60af69 +# English: "Duplicate level" +# Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit smart measure" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.exitSmartMeasure" -msgstr "Quitter la mesure intelligente" +msgid "editor.floatingLevelSelector.duplicateLevel" +msgstr "Dupliquer le niveau" -# Source-Hash: b3907da0ad234f10 +# English: "Duplicate with options..." +# Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finish" -msgstr "Terminer" +msgid "editor.floatingLevelSelector.duplicateWithOptions" +msgstr "Dupliquer avec options..." -# Source-Hash: f74bd937ae2e3a40 +# English: "Exit unit focus" +# Source-Hash: 5ec91bb339806413 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish at the edge" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finishAtTheEdge" -msgstr "Terminer au bord" +msgid "editor.floatingLevelSelector.exitUnitFocus" +msgstr "Quitter le focus sur le logement" -# Source-Hash: 21029cf1c45697cc +# English: "Focused unit has zones here" +# Source-Hash: a88c3f8bed58b4fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMove" -msgstr "Déplacement libre" +msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" +msgstr "Le logement ciblé a des zones ici" -# Source-Hash: 3247d8689bed6ff6 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move · slide doors and windows" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" -msgstr "Déplacement libre · faire glisser portes et fenêtres" +msgid "editor.floatingLevelSelector.foundation" +msgstr "Fondation" -# Source-Hash: b5ad765b43e75a5c +# English: "Basement" +# Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to pick a material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.holdToPickAMaterial" -msgstr "Maintenez pour prélever un matériau" +msgid "editor.floatingLevelSelector.foundationBadgeBasement" +msgstr "Sous-sol" -# Source-Hash: 46b53c5449c98964 +# English: "Basement walls cut into the ground" +# Source-Hash: 96878e51aa1f4aac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inspect surface dimensions" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.inspectSurfaceDimensions" -msgstr "Inspecter les dimensions de la surface" +msgid "editor.floatingLevelSelector.foundationBadgeBasementTitle" +msgstr "Murs de sous-sol enterrés" -# Source-Hash: 63f1f7006ae14177 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint with this material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.paintWithThisMaterial" -msgstr "Peindre avec ce matériau" +msgid "editor.floatingLevelSelector.foundationBadgeOff" +msgstr "Aucune" -# Source-Hash: 188f55cc5afbe34f +# English: "No foundation drawn: click to set one up" +# Source-Hash: 44761dadfc126cbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick target height" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pickTargetHeight" -msgstr "Choisir la hauteur cible" +msgid "editor.floatingLevelSelector.foundationBadgeOffTitle" +msgstr "Aucune fondation dessinée : cliquez pour en définir une" -# Source-Hash: 2880fc9752eababa +# English: "Piles" +# Source-Hash: f4031d60a7899a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin measurement lens" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pinMeasurementLens" -msgstr "Épingler la loupe de mesure" +msgid "editor.floatingLevelSelector.foundationBadgePiles" +msgstr "Pieux" -# Source-Hash: 722741535b346e09 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.place" -msgstr "Placer" +msgid "editor.floatingLevelSelector.foundationBadgePilesTitle" +msgstr "Longrine sur pieux" -# Source-Hash: 82010a7fd6350c93 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place inside the room" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.placeInsideTheRoom" -msgstr "Placer dans la pièce" +msgid "editor.floatingLevelSelector.foundationBadgeRaised" +msgstr "Surélevé" -# Source-Hash: 8b97f4503edb27b9 +# English: "Raised floor on a stem wall" +# Source-Hash: 69980d5b1f49e272 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Press anywhere, drag to the opposite corner" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" -msgstr "Appuyez n’importe où, faites glisser jusqu’à l’angle opposé" +msgid "editor.floatingLevelSelector.foundationBadgeRaisedTitle" +msgstr "Plancher surélevé sur mur de soubassement" -# Source-Hash: 786f0b1c81b2a6c0 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise or lower" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.raiseOrLower" -msgstr "Monter ou descendre" +msgid "editor.floatingLevelSelector.foundationBadgeSlab" +msgstr "Dalle" -# Source-Hash: b033f46ae3a0cbb7 +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove a painted part, or reset a surface" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" -msgstr "Retirer une partie peinte ou réinitialiser une surface" +msgid "editor.floatingLevelSelector.foundationBadgeSlabTitle" +msgstr "Dalle sur terre-plein" -# Source-Hash: 0dc6d121791696f7 +# English: "The ground floor stands {distance} over grade. {action}" +# Source-Hash: 6a00e82fda812178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeLastPoint" -msgstr "Retirer le dernier point" +msgid "editor.floatingLevelSelector.groundFloorOverGrade" +msgstr "Le rez-de-chaussée se trouve à {distance} au-dessus du sol. {action}" -# Source-Hash: f85bdac0a1d16603 +# English: "The ground floor stands {distance} under grade. {action}" +# Source-Hash: 61f400e77494df9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.resize" -msgstr "Redimensionner" +msgid "editor.floatingLevelSelector.groundFloorUnderGrade" +msgstr "Le rez-de-chaussée se trouve à {distance} sous le sol. {action}" -# Source-Hash: 8a665ecaeffa9308 +# English: "Ground line: the levels under it are below grade" +# Source-Hash: e3763ae2dc62c03f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotate45" -msgstr "Pivoter de ±45°" +msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" +msgstr "Ligne de sol : les niveaux en dessous sont enterrés" -# Source-Hash: 6099ee99f8c38700 +# English: "Ground · {offset}" +# Source-Hash: e12035eb47cc61cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotateTheSelection45" -msgstr "Pivoter la sélection de ±45°" +msgid "editor.floatingLevelSelector.groundOffset" +msgstr "Sol · {offset}" -# Source-Hash: c93ae898b4d59f6f +# English: "Insert level here" +# Source-Hash: 5661c11383217661 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set one corner, then the opposite" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" -msgstr "Placez un angle, puis l’angle opposé" +msgid "editor.floatingLevelSelector.insertLevelHere" +msgstr "Insérer un niveau ici" -# Source-Hash: c0774c185060fc13 +# English: "Level height" +# Source-Hash: 0215b36d5a08597a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.cancel" -msgstr "Annuler" +msgid "editor.floatingLevelSelector.levelHeight" +msgstr "Hauteur du niveau" -# Source-Hash: ed7f3d8417a3e074 +# English: "Paste copied selection" +# Source-Hash: 91af436e81bb0875 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.forcePlace" -msgstr "Forcer le placement" +msgid "editor.floatingLevelSelector.pasteCopiedSelection" +msgstr "Coller la sélection copiée" -# Source-Hash: 722741535b346e09 +# English: "Reorder {levelName}" +# Source-Hash: 68bc52f53240cbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.place" -msgstr "Placer" +msgid "editor.floatingLevelSelector.reorderLevelname" +msgstr "Réorganiser {levelName}" -# Source-Hash: 306d409f8a1cf480 +# English: "Seat the building so this floor stands at grade; the floors under it are below grade" +# Source-Hash: 625d53de72ebc2ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.rotate" -msgstr "Pivoter" +msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" +msgstr "Placer le bâtiment pour que cet étage soit au niveau du sol ; les étages inférieurs sont enterrés" -# Source-Hash: e9dfc2a178bca5f7 +# English: "Set as ground floor" +# Source-Hash: 7ac9c00895c30d76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guided constraints bypassed" -#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" -msgstr "Contraintes guidées ignorées" +msgid "editor.floatingLevelSelector.setAsGroundFloor" +msgstr "Définir comme rez-de-chaussée" -# Source-Hash: c69865ec5d9d1ac9 +# English: "Show the foundation" +# Source-Hash: 46b0734342ab499f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.angle.g10zaw" -msgstr "Angle" +msgid "editor.floatingLevelSelector.showTheFoundation" +msgstr "Afficher la fondation" -# Source-Hash: 9c562983f6fed685 +# English: "The ground level cannot be deleted" +# Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arm length m" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.armLengthM.1jclqgw" -msgstr "Longueur du bras m" +msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" +msgstr "Le niveau du rez-de-chaussée ne peut pas être supprimé" -# Source-Hash: 38461bc364321749 +# English: "This floor stands at grade" +# Source-Hash: b1122051dce0d7a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment side" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.attachmentSide.6n9s5v" -msgstr "Côté de raccordement" +msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" +msgstr "Cet étage est au niveau du sol" -# Source-Hash: 441124187e7da2fc +# English: "Unit" +# Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.unit" +msgstr "Lot" + +# English: "Floor area" +# Source-Hash: 5871b7c20478f4a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.area" +msgstr "Surface de plancher" + +# English: "{room} floor" +# Source-Hash: 8b2e1435272aa86a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.room" +msgstr "Plancher {room}" + +# English: "Shared floor" +# Source-Hash: 4731183fec06cbdf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.shared" +msgstr "Plancher partagé" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.cancel" +msgstr "Annuler" + +# English: "Place building" +# Source-Hash: 4c6ca3441c74da9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.placeBuilding" +msgstr "Placer le bâtiment" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.rotate" +msgstr "Pivoter" + +# English: "Cancel placement" +# Source-Hash: 832470d3aeee82fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.cancelPlacement" +msgstr "Annuler le placement" + +# English: "Continuation — click or press C to cycle" +# Source-Hash: e374fcaee33c3e2c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" +msgstr "Enchaînement — cliquez ou appuyez sur C pour changer" + +# English: "Continuation: {label}" +# Source-Hash: b108c7dc2ec2b9de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationLabel" +msgstr "Enchaînement : {label}" + +# English: "Drag to draw fence" +# Source-Hash: 729d87891cb7dbb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" +msgstr "Faites glisser pour tracer la clôture" + +# English: "Erase: {scope}" +# Source-Hash: b52e874718af1916 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScope" +msgstr "Effacer : {scope}" + +# English: "Erase scope — click or press Shift to cycle" +# Source-Hash: 61f7494abc319e66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" +msgstr "Portée de l’effacement — cliquez ou appuyez sur Maj pour changer" + +# English: "Erase scope: {scope}" +# Source-Hash: 59993c163d68cc53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" +msgstr "Portée de l’effacement : {scope}" + +# English: "Fence type — click or press T to switch between straight, curved and freehand" +# Source-Hash: a4bdcbc6cab772bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" +msgstr "Type de clôture — cliquez ou appuyez sur T pour passer de droite à courbe ou libre" + +# English: "Fewer shortcuts" +# Source-Hash: 2a9ed258727078ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" +msgstr "Moins de raccourcis" + +# English: "Finish curve (or double-click)" +# Source-Hash: 698fa6e78af4d4b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" +msgstr "Terminer la courbe (ou double-clic)" + +# English: "Grid: {length}" +# Source-Hash: 09f6d79edf5cfd16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridLength" +msgstr "Grille : {length}" + +# English: "Grid step — click or tap Ctrl to cycle" +# Source-Hash: c493b562040c4328 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" +msgstr "Pas de grille — cliquez ou appuyez sur Ctrl pour changer" + +# English: "Grid step: {length}" +# Source-Hash: d36a8c93d81b3a36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepLength" +msgstr "Pas de grille : {length}" + +# English: "Hover a painted surface to erase" +# Source-Hash: abea993ede7daa7e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" +msgstr "Survolez une surface peinte pour l’effacer" + +# English: "Hover a surface to paint" +# Source-Hash: fc1ed0ddc0cfcef5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" +msgstr "Survolez une surface pour la peindre" + +# English: "Hover a surface to pick its material" +# Source-Hash: 63dec5de4a337015 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" +msgstr "Survolez une surface pour prélever son matériau" + +# English: "More shortcuts" +# Source-Hash: 7ad6dc1b1f03f970 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.moreShortcuts" +msgstr "Plus de raccourcis" + +# English: "Paint: {scope}" +# Source-Hash: 476b8f5f9ee542e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScope" +msgstr "Peindre : {scope}" + +# English: "Paint scope — click or press Shift to cycle" +# Source-Hash: 9c09ce4c34122277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" +msgstr "Portée de la peinture — cliquez ou appuyez sur Maj pour changer" + +# English: "Paint scope: {scope}" +# Source-Hash: 7890aa81687f6654 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" +msgstr "Portée de la peinture : {scope}" + +# English: "Pick: {scope}" +# Source-Hash: a75b3abc0784b12b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.pickScope" +msgstr "Prélever : {scope}" + +# English: "Place gate on a fence" +# Source-Hash: 69c1044e6ea87ccf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" +msgstr "Placer le portail sur une clôture" + +# English: "Place passage on a fence" +# Source-Hash: 3754255b8affc7a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" +msgstr "Placer le passage sur une clôture" + +# English: "Select a material to paint" +# Source-Hash: a8328c8964e947fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" +msgstr "Choisissez un matériau à peindre" + +# English: "Snapping: Angles" +# Source-Hash: 6f95d7116aea5738 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingAngles" +msgstr "Aimantation : angles" + +# English: "Snapping: Grid" +# Source-Hash: 2d6d8d85d2ebed47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingGrid" +msgstr "Aimantation : grille" + +# English: "Snapping: Lines" +# Source-Hash: 9096c5f9d8ce0f7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingLines" +msgstr "Aimantation : lignes" + +# English: "Snapping mode — click or press Shift to cycle" +# Source-Hash: b663afbf0835cf32 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" +msgstr "Mode d’aimantation — cliquez ou appuyez sur Maj pour changer" + +# English: "Snapping: Off" +# Source-Hash: d00e984fc88f71aa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingOff" +msgstr "Aimantation : désactivée" + +# English: "Straight continuation is unavailable for curved or freehand fences" +# Source-Hash: 6be9d0d312200761 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" +msgstr "L’enchaînement droit n’est pas disponible pour les clôtures courbes ou libres" + +# English: "Straight: Continuous" +# Source-Hash: 0d56b1f0a0493302 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuous" +msgstr "Droite : continue" + +# English: "Straight fence continuation — click or press C to toggle" +# Source-Hash: c034e85d3e7fd27e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" +msgstr "Enchaînement de clôture droite — cliquez ou appuyez sur C pour basculer" + +# English: "Straight: Single" +# Source-Hash: e3f679cdb4a3d603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightSingle" +msgstr "Droite : simple" + +# English: "Add point" +# Source-Hash: 9a56d90d461b4bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPoint" +msgstr "Ajouter un point" + +# English: "Add point · click the first to close" +# Source-Hash: c3dc3918c823a5e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPointClickTheFirstTo" +msgstr "Ajouter un point · cliquez sur le premier pour fermer" + +# English: "Brush size" +# Source-Hash: d8cf72d482d56cd7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.brushSize" +msgstr "Taille du pinceau" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancel" +msgstr "Annuler" + +# English: "Cancel picking" +# Source-Hash: 104fdab7934ec66c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelPicking" +msgstr "Annuler la sélection" + +# English: "Cancel stroke" +# Source-Hash: c318cf0ce70b8977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelStroke" +msgstr "Annuler le tracé" + +# English: "Done" +# Source-Hash: 16552c2580bbe8b8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.done" +msgstr "Terminé" + +# English: "Draw the mezzanine" +# Source-Hash: e297db9ca0ce6d31 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheMezzanine" +msgstr "Dessiner la mezzanine" + +# English: "Draw the opening" +# Source-Hash: c8be604718a7a9fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheOpening" +msgstr "Dessiner l’ouverture" + +# English: "Draw the terrace" +# Source-Hash: 231386ae6d534163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheTerrace" +msgstr "Dessiner la terrasse" + +# English: "Exit smart measure" +# Source-Hash: e092e803eb60af69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.exitSmartMeasure" +msgstr "Quitter la mesure intelligente" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finish" +msgstr "Terminer" + +# English: "Finish at the edge" +# Source-Hash: f74bd937ae2e3a40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finishAtTheEdge" +msgstr "Terminer au bord" + +# English: "Free move" +# Source-Hash: 21029cf1c45697cc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMove" +msgstr "Déplacement libre" + +# English: "Free move · slide doors and windows" +# Source-Hash: 3247d8689bed6ff6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" +msgstr "Déplacement libre · faire glisser portes et fenêtres" + +# English: "Hold to pick a material" +# Source-Hash: b5ad765b43e75a5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.holdToPickAMaterial" +msgstr "Maintenez pour prélever un matériau" + +# English: "Inspect surface dimensions" +# Source-Hash: 46b53c5449c98964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.inspectSurfaceDimensions" +msgstr "Inspecter les dimensions de la surface" + +# English: "Paint with this material" +# Source-Hash: 63f1f7006ae14177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.paintWithThisMaterial" +msgstr "Peindre avec ce matériau" + +# English: "Pick target height" +# Source-Hash: 188f55cc5afbe34f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pickTargetHeight" +msgstr "Choisir la hauteur cible" + +# English: "Pin measurement lens" +# Source-Hash: 2880fc9752eababa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pinMeasurementLens" +msgstr "Épingler la loupe de mesure" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.place" +msgstr "Placer" + +# English: "Place inside the room" +# Source-Hash: 82010a7fd6350c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.placeInsideTheRoom" +msgstr "Placer dans la pièce" + +# English: "Press anywhere, drag to the opposite corner" +# Source-Hash: 8b97f4503edb27b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" +msgstr "Appuyez n’importe où, faites glisser jusqu’à l’angle opposé" + +# English: "Raise or lower" +# Source-Hash: 786f0b1c81b2a6c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.raiseOrLower" +msgstr "Monter ou descendre" + +# English: "Remove a painted part, or reset a surface" +# Source-Hash: b033f46ae3a0cbb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" +msgstr "Retirer une partie peinte ou réinitialiser une surface" + +# English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeLastPoint" +msgstr "Retirer le dernier point" + +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.resize" +msgstr "Redimensionner" + +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotate45" +msgstr "Pivoter de ±45°" + +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotateTheSelection45" +msgstr "Pivoter la sélection de ±45°" + +# English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" +msgstr "Placez un angle, puis l’angle opposé" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.cancel" +msgstr "Annuler" + +# English: "Force place" +# Source-Hash: ed7f3d8417a3e074 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.forcePlace" +msgstr "Forcer le placement" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.place" +msgstr "Placer" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.rotate" +msgstr "Pivoter" + +# English: "Guided constraints bypassed" +# Source-Hash: e9dfc2a178bca5f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +#, fuzzy +msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" +msgstr "Contraintes guidées ignorées" + +# English: "Angle" +# Source-Hash: c69865ec5d9d1ac9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.angle.g10zaw" +msgstr "Angle" + +# English: "Arm length m" +# Source-Hash: 9c562983f6fed685 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.armLengthM.1jclqgw" +msgstr "Longueur du bras m" + +# English: "Attachment side" +# Source-Hash: 38461bc364321749 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.attachmentSide.6n9s5v" +msgstr "Côté de raccordement" + +# English: "Auto hangers" +# Source-Hash: 441124187e7da2fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoHangers.10554ka" +msgstr "Suspentes auto" + +# English: "Auto miter corners" +# Source-Hash: e20033e4b0045f1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoMiterCorners.zxr5lt" +msgstr "Angles en onglet auto" + +# English: "Auto span" +# Source-Hash: 92f3a8b63963daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoSpan.1gas9r6" +msgstr "Portée auto" + +# English: "Back ratio" +# Source-Hash: 8b2dfb9b9137fda0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.backRatio.1ughd99" +msgstr "Ratio arrière" + +# English: "Base height" +# Source-Hash: 75dfae8d9f03fa0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseHeight.1hs4zdf" +msgstr "Hauteur de base" + +# English: "Base style" +# Source-Hash: 1cc2da12e8463e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseStyle.24vty5" +msgstr "Style de base" + +# English: "Beam height" +# Source-Hash: f9d95c6df06b6223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamHeight.1kq0oh1" +msgstr "Hauteur de poutre" + +# English: "Beam width" +# Source-Hash: a133f8d23b1274dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamWidth.1q1mzbe" +msgstr "Largeur de poutre" + +# English: "Body shape" +# Source-Hash: b98c0d0189526335 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bodyShape.arvkuw" +msgstr "Forme du corps" + +# English: "Bracket style" +# Source-Hash: 2251ecab18467631 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bracketStyle.1vvoptm" +msgstr "Style de console" + +# English: "Branch angle" +# Source-Hash: 5e9d4a4fcea481ca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.branchAngle.v56zme" +msgstr "Angle de piquage" + +# English: "Cab height" +# Source-Hash: dec709f1f9c714ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cabHeight.1yn34mk" +msgstr "Hauteur de cabine" + +# English: "Canopy form" +# Source-Hash: 87863f8366e1c88a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.canopyForm.1czzggp" +msgstr "Forme de l’auvent" + +# English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cap.s0thej" +msgstr "Chapeau" + +# English: "Cap overhang" +# Source-Hash: 2f2e49598ec8188e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capOverhang.kxxz9j" +msgstr "Débord du chapeau" + +# English: "Cap shape" +# Source-Hash: 8523c086e95b83e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capShape.17mbxhu" +msgstr "Forme du chapeau" + +# English: "Cap thickness" +# Source-Hash: acd98600a518dd95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capThickness.u835q9" +msgstr "Épaisseur du chapeau" + +# English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.carcassHeight.1aks0zy" +msgstr "Hauteur du caisson" + +# English: "Cleanout style" +# Source-Hash: 827709caa79db37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cleanoutStyle.127zz6z" +msgstr "Style de regard" + +# English: "Collar diameter" +# Source-Hash: a549dc98e2a1bd07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarDiameter.1d5k28r" +msgstr "Diamètre du collier" + +# English: "Collar height" +# Source-Hash: aa0232fc6f3eeadc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarHeight.1dqwsrf" +msgstr "Hauteur du collier" + +# English: "Collar shape" +# Source-Hash: cbd8079c3f540a4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarShape.1gacv11" +msgstr "Forme du collier" + +# English: "Collar width" +# Source-Hash: 557cde7f95616454 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarWidth.qvc9ik" +msgstr "Largeur du collier" + +# English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.columns.1sy73ks" +msgstr "Colonnes" + +# English: "Conical full circle" +# Source-Hash: cd5b2f70924b7c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" +msgstr "Conique cercle complet" + +# English: "Connection mode" +# Source-Hash: dc90f3abb935e7e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionMode.1nty478" +msgstr "Mode de raccordement" + +# English: "Connection offset" +# Source-Hash: 1a9a49d7135d4f15 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionOffset.2m70s8" +msgstr "Décalage de raccordement" + +# English: "Corner bevel" +# Source-Hash: 69be301fcb8030e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cornerBevel.16iniho" +msgstr "Chanfrein d’angle" + +# English: "Covering type" +# Source-Hash: b997a96a9dc7e450 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.coveringType.95yd3k" +msgstr "Type de revêtement" + +# English: "Cricket height" +# Source-Hash: 03b33160d8baa966 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketHeight.pok31n" +msgstr "Hauteur de la costière" + +# English: "Cricket length" +# Source-Hash: 21552eee05ccb5d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketLength.i2aht8" +msgstr "Longueur de la costière" + +# English: "Cricket side" +# Source-Hash: c13b707bfdeac855 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketSide.1xnwil7" +msgstr "Côté de la costière" + +# English: "Cricket style" +# Source-Hash: 4ee7089fa1b3e63b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketStyle.10j0lat" +msgstr "Style de costière" + +# English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curb.5xi2ol" +msgstr "Costière" + +# English: "Curb height" +# Source-Hash: 8da648df210dbc0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curbHeight.1yn97sg" +msgstr "Hauteur de costière" + +# English: "Curve offset" +# Source-Hash: 0c01b9848f5d9673 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curveOffset.l4xhvr" +msgstr "Flèche de courbure" + +# English: "Damper angle" +# Source-Hash: 69179f4caa384585 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.damperAngle.h5fxn7" +msgstr "Angle du registre" + +# English: "Deck thickness" +# Source-Hash: 15cff91231e806a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.deckThickness.4jmqze" +msgstr "Épaisseur de la terrasse" + +# English: "Default level id" +# Source-Hash: eff1e7dc0901b9d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.defaultLevelId.xmrjzp" +msgstr "ID de niveau par défaut" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.depth.1lu11u2" +msgstr "Profondeur" + +# English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter.s3robe" +msgstr "Diamètre" + +# English: "Diameter2" +# Source-Hash: dab4100e83e4e6b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter2.pm6cl4" +msgstr "Diamètre 2" + +# English: "Door duration ms" +# Source-Hash: 5747145349432396 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorDurationMs.13nx909" +msgstr "Durée de porte ms" + +# English: "Door height" +# Source-Hash: 6f4aca26196c221f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorHeight.92r9k4" +msgstr "Hauteur de porte" + +# English: "Door panel style" +# Source-Hash: 990ae81752236e3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorPanelStyle.2qog4q" +msgstr "Style de panneau de porte" + +# English: "Door style" +# Source-Hash: c94e002123bb1199 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorStyle.gerpos" +msgstr "Style de porte" + +# English: "Door width" +# Source-Hash: 91aeb04b0b7b605e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorWidth.sy732l" +msgstr "Largeur de porte" + +# English: "Downspout enabled" +# Source-Hash: baad1238f70f79ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutEnabled.1gn00nv" +msgstr "Descente activée" + +# English: "Downspout position" +# Source-Hash: e288531858c4c44d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutPosition.fxcsop" +msgstr "Position de la descente" + +# English: "Duct material" +# Source-Hash: 861d28317325cf61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ductMaterial.i8eonq" +msgstr "Matériau de gaine" + +# English: "Dutch gablet rake" +# Source-Hash: ad8ab4ae38c3299c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchGabletRake.6paydp" +msgstr "Rive du pignon à demi-croupe" + +# English: "Dutch hip height ratio" +# Source-Hash: 913d25cf2c5ff179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" +msgstr "Ratio de hauteur de demi-croupe" + +# English: "Dutch hip width ratio" +# Source-Hash: f4a80233d49f2b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" +msgstr "Ratio de largeur de demi-croupe" + +# English: "Dutch top rake thickness" +# Source-Hash: 92021b0e37f9a25a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" +msgstr "Épaisseur de rive haute" + +# English: "Dutch waist length ratio" +# Source-Hash: d0a2ec13d71ce659 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" +msgstr "Ratio de longueur de taille" + +# English: "Dwell ms" +# Source-Hash: 993396ccca2cd1d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dwellMs.1lkk0o5" +msgstr "Arrêt ms" + +# English: "Edge inset" +# Source-Hash: 9cf550eb019bca1b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.edgeInset.1obeimx" +msgstr "Retrait de bord" + +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.elevation.4jbp44" +msgstr "Altitude" + +# English: "End cap left" +# Source-Hash: 1fc91958e30dbeaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapLeft.19jel4v" +msgstr "Embout gauche" + +# English: "End cap right" +# Source-Hash: 4c9ae62e0ed70635 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapRight.lhse9c" +msgstr "Embout droit" + +# English: "End caps" +# Source-Hash: a9cba06250228a55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCaps.4fsb5b" +msgstr "Embouts" + +# English: "End post" +# Source-Hash: ebad74353b5a7610 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endPost.1guwsk0" +msgstr "Poteau d’extrémité" + +# English: "Equipment type" +# Source-Hash: ecbe36df4238439e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.equipmentType.1mc2ywt" +msgstr "Type d’équipement" + +# English: "Fill to floor" +# Source-Hash: 66f1018b50c2e426 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fillToFloor.1oppnkf" +msgstr "Remplir jusqu’au sol" + +# English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.finial.1ertf4s" +msgstr "Épi de faîtage" + +# English: "Fitting type" +# Source-Hash: 28cd69e506709f16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fittingType.18mz17s" +msgstr "Type de raccord" + +# English: "Flashing height" +# Source-Hash: 3c1aec5e6fb180cf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingHeight.3e8nn6" +msgstr "Hauteur du solin" + +# English: "Flashing projection" +# Source-Hash: 327ba762101db94d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingProjection.hhf0fk" +msgstr "Saillie du solin" + +# English: "Flue count" +# Source-Hash: 9473e2ab53370b40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueCount.j3t2ci" +msgstr "Nombre de conduits" + +# English: "Flue diameter" +# Source-Hash: fd0b65425ed3df8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueDiameter.cjhubg" +msgstr "Diamètre du conduit" + +# English: "Flue height" +# Source-Hash: 07ebe5e3780c624b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueHeight.1dhc4y4" +msgstr "Hauteur du conduit" + +# English: "Flue shape" +# Source-Hash: ff8319792830d591 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueShape.1ealiy4" +msgstr "Forme du conduit" + +# English: "Flue spacing" +# Source-Hash: 5acf77854a0030ec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueSpacing.exhzco" +msgstr "Espacement des conduits" + +# English: "Footing style" +# Source-Hash: 0a64209c0edacaa1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.footingStyle.e3btg6" +msgstr "Style de semelle" + +# English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameDepth.1934a5n" +msgstr "Profondeur du cadre" + +# English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameThickness.1agxzd8" +msgstr "Épaisseur du cadre" + +# English: "Framing strategy" +# Source-Hash: 9a3495a1e3181b56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.framingStrategy.3jx9qe" +msgstr "Stratégie d’ossature" + +# English: "From level id" +# Source-Hash: 49f329aab535fd3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fromLevelId.15ieejs" +msgstr "ID du niveau de départ" + +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gableOverhang.2z0obw" +msgstr "Débord de rive" + +# English: "Gambrel lower height ratio" +# Source-Hash: 517caa8af48afb22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" +msgstr "Ratio de hauteur du brisis" + +# English: "Gambrel lower width ratio" +# Source-Hash: 03ecb8f3b625d49d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" +msgstr "Ratio de largeur du brisis" + +# English: "Gap x" +# Source-Hash: f3ca5555e8c54375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapX.wjxa7" +msgstr "Écart x" + +# English: "Gap y" +# Source-Hash: 2736f7c5dfc7d917 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapY.mkbl8" +msgstr "Écart y" + +# English: "Glass thickness" +# Source-Hash: 5d03ca50d712339b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.glassThickness.s97b0x" +msgstr "Épaisseur du vitrage" + +# English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.groundClearance.4os7f0" +msgstr "Garde au sol" + +# English: "Guard infill" +# Source-Hash: 6a22a09b67309545 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.guardInfill.16heiig" +msgstr "Remplissage du garde-corps" + +# English: "Gutter enabled" +# Source-Hash: 98dfdac04af3c762 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterEnabled.1oxiltf" +msgstr "Gouttière activée" + +# English: "Gutter profile" +# Source-Hash: 4bdc3cdf0fe0ac3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterProfile.1ngzqi3" +msgstr "Profil de gouttière" + +# English: "Gutter size" +# Source-Hash: 76555670cba9bb13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterSize.jgavtz" +msgstr "Taille de gouttière" + +# English: "Hanger max reach" +# Source-Hash: c14d3aa4a98efa98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerMaxReach.1869epv" +msgstr "Portée max. du support" + +# English: "Hanger spacing" +# Source-Hash: dfbc357fb556559b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerSpacing.i2b5d7" +msgstr "Espacement des supports" + +# English: "Hanger style" +# Source-Hash: 3ce5e50c572644ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerStyle.l7v7gb" +msgstr "Style de support" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height.7puwki" +msgstr "Hauteur" + +# English: "Height2" +# Source-Hash: d0b836103c795726 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height2.7b3jlc" +msgstr "Hauteur 2" + +# English: "Height above ridge" +# Source-Hash: 681fd794e601ace5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.heightAboveRidge.1k99brk" +msgstr "Hauteur au-dessus du faîtage" + +# English: "High edge height" +# Source-Hash: b4905e5ec62a170f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highEdgeHeight.1s7v90t" +msgstr "Hauteur du bord haut" + +# English: "High overhang" +# Source-Hash: c136d5cd7f3ce04a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highOverhang.67w55d" +msgstr "Débord haut" + +# English: "High side mode" +# Source-Hash: 8b4cd537cc650dac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highSideMode.1rc1r5j" +msgstr "Mode du côté haut" + +# English: "Hood overhang" +# Source-Hash: c38e4e73ed9779f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hoodOverhang.1q1hcin" +msgstr "Débord du capot" + +# English: "Infill placement" +# Source-Hash: 9884ee52a0e84c0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.infillPlacement.11dxzpo" +msgstr "Position du remplissage" + +# English: "Inlet shape" +# Source-Hash: 37fde9f9290ddc59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.inletShape.ypayh2" +msgstr "Forme d’entrée" + +# English: "Inner radius" +# Source-Hash: d840917007332b94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.innerRadius.1fkmg0j" +msgstr "Rayon intérieur" + +# English: "Insulated" +# Source-Hash: c8ce389856a90ae2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulated.iwziey" +msgstr "Isolé" + +# English: "Insulation r" +# Source-Hash: 043a7a10b6c91f4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulationR.663ufx" +msgstr "Isolation R" + +# English: "Lantern height" +# Source-Hash: fe661655ba41b709 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternHeight.oz7zpm" +msgstr "Hauteur du lanterneau" + +# English: "Lantern top scale" +# Source-Hash: ab22022e7d5597af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternTopScale.hkmpp2" +msgstr "Échelle du haut du lanterneau" + +# English: "Ledger depth" +# Source-Hash: 17f854a76ebc033a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerDepth.7av8k9" +msgstr "Profondeur de la lisse" + +# English: "Ledger height" +# Source-Hash: 547d3a8a9e3e24f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerHeight.zo2oif" +msgstr "Hauteur de la lisse" + +# English: "Ledger vertical offset" +# Source-Hash: 88603f884243c81e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" +msgstr "Décalage vertical de la lisse" + +# English: "Left end condition" +# Source-Hash: 6519f495202b830d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftEndCondition.1syqlaw" +msgstr "Condition d’extrémité gauche" + +# English: "Left overhang" +# Source-Hash: d041b8217c356977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftOverhang.1ydl9ma" +msgstr "Débord gauche" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.length.1kpdcfp" +msgstr "Longueur" + +# English: "Liquid diameter" +# Source-Hash: d6deaf0ceb9f43af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.liquidDiameter.w3pk7c" +msgstr "Diamètre liquide" + +# English: "Louver count" +# Source-Hash: 45aca762426392fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.louverCount.lhw2gr" +msgstr "Nombre de lames" + +# English: "Low beam inset" +# Source-Hash: c645a86c55eb0075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowBeamInset.1g7yw5l" +msgstr "Retrait de la poutre basse" + +# English: "Low edge height" +# Source-Hash: 7e7b896682c78c5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowEdgeHeight.47mae1" +msgstr "Hauteur du bord bas" + +# English: "Low overhang" +# Source-Hash: 9deb775fc64854b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowOverhang.t4nz1h" +msgstr "Débord bas" + +# English: "Mansard steep height ratio" +# Source-Hash: 9aa621d270fe64b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" +msgstr "Ratio de hauteur du brisis mansardé" + +# English: "Mansard steep width ratio" +# Source-Hash: 7cb03b27c632949b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" +msgstr "Ratio de largeur du brisis mansardé" + +# English: "Match host roof material" +# Source-Hash: 95772435857a7424 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" +msgstr "Reprendre le matériau du toit porteur" + +# English: "Match host roof structure" +# Source-Hash: dc22618e31654bde +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofStructure.bojht3" +msgstr "Reprendre la structure du toit porteur" + +# English: "Mount" +# Source-Hash: 06b36a09a84fa8b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mount.h80tay" +msgstr "Fixation" + +# English: "Mounting type" +# Source-Hash: a288700f96d2b362 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mountingType.1xvin1e" +msgstr "Type de montage" + +# English: "Neck height" +# Source-Hash: 3087bc9b219af9ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.neckHeight.19ym8n1" +msgstr "Hauteur du col" + +# English: "Opacity" +# Source-Hash: 1c0b8a5503c5daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.opacity.1pmpfd2" +msgstr "Opacité" + +# English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingAngle.brhi7c" +msgstr "Angle d’ouverture" + +# English: "Opening offset" +# Source-Hash: 1d714b576a057902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingOffset.wpoqt6" +msgstr "Décalage de l’ouverture" + +# English: "Opening side" +# Source-Hash: 55e5629865efd03d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingSide.18l2iwe" +msgstr "Côté d’ouverture" + +# English: "Operation state" +# Source-Hash: 9824d357692ecb8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.operationState.1lwrz07" +msgstr "État d’ouverture" + +# English: "Outlet shape" +# Source-Hash: 4f1cbc14dcfb8f3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.outletShape.lgpqhx" +msgstr "Forme de sortie" + +# English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.overhang.b5ljpv" +msgstr "Débord" + +# English: "Panel height" +# Source-Hash: 50d7e5e370667f63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelHeight.1kf31vk" +msgstr "Hauteur du panneau" + +# English: "Panel width" +# Source-Hash: 068d9e0adabc4578 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelWidth.946581" +msgstr "Largeur du panneau" + +# English: "Pattern alignment" +# Source-Hash: aca94a3adc0b0f01 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternAlignment.jd1ht0" +msgstr "Alignement du motif" + +# English: "Pattern count" +# Source-Hash: 9e9f9408ec2c8f0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternCount.1r37pve" +msgstr "Nombre de motifs" + +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternDistribution.fmam51" +msgstr "Répartition du motif" + +# English: "Pattern remainder" +# Source-Hash: 63f833f219b9a592 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternRemainder.i47p5y" +msgstr "Reste du motif" + +# English: "Picket profile" +# Source-Hash: e0b8f766411329ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketProfile.1xgzuyc" +msgstr "Profil des lattes" + +# English: "Picket rail count" +# Source-Hash: dfac7d68ea16e8a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketRailCount.atgt6w" +msgstr "Nombre de lisses" + +# English: "Picket spacing" +# Source-Hash: cd2652254a81d288 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketSpacing.p8d98w" +msgstr "Espacement des lattes" + +# English: "Picket top" +# Source-Hash: 462f6cb03b23d773 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTop.1tskplu" +msgstr "Haut des lattes" + +# English: "Picket top clearance" +# Source-Hash: 0a4dbbe1cf592326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTopClearance.16eq1k0" +msgstr "Dégagement en haut des lattes" + +# English: "Picket variation" +# Source-Hash: c99ccea380ca41bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketVariation.f7vl1c" +msgstr "Variation des lattes" + +# English: "Picket width" +# Source-Hash: 798382373625fecd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketWidth.3fr3rh" +msgstr "Largeur des lattes" + +# English: "Pipe material" +# Source-Hash: 48ce215c3be034c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pipeMaterial.1ggc2wy" +msgstr "Matériau du tuyau" + +# English: "Pitch" +# Source-Hash: 75e3449fc1776d09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pitch.xe6ekx" +msgstr "Pente" + +# English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.position.1quewx6" +msgstr "Position" + +# English: "Post bracing" +# Source-Hash: d090baf9f08c2280 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postBracing.crq8nn" +msgstr "Contreventement des poteaux" + +# English: "Post cap" +# Source-Hash: 6467b580eed1d333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCap.1xcvxrv" +msgstr "Chapeau de poteau" + +# English: "Post count" +# Source-Hash: e1d91cdd16cc6593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCount.22r56c" +msgstr "Nombre de poteaux" + +# English: "Post depth" +# Source-Hash: d6d8b8653cb43624 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postDepth.1ko6j1m" +msgstr "Profondeur du poteau" + +# English: "Post inset" +# Source-Hash: dd071e84e259ee48 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postInset.le36li" +msgstr "Retrait du poteau" + +# English: "Post layout mode" +# Source-Hash: 88c5d586822e58f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postLayoutMode.16jre62" +msgstr "Mode de répartition des poteaux" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSize.1jodxek" +msgstr "Taille du poteau" + +# English: "Post spacing" +# Source-Hash: c8e8490c957e01f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSpacing.voppfi" +msgstr "Espacement des poteaux" + +# English: "Post through" +# Source-Hash: b98cbe8478508d41 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postThrough.1abgu4a" +msgstr "Poteau traversant" + +# English: "Post width" +# Source-Hash: d1492df1fbb22c29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postWidth.rk26fz" +msgstr "Largeur du poteau" + +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.profile.1kwlci6" +msgstr "Profil" + +# English: "Projection" +# Source-Hash: d01cd36d3939abab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.projection.jhtig0" +msgstr "Saillie" + +# English: "Purlin height" +# Source-Hash: 800a7a9159519b46 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinHeight.eo9cjm" +msgstr "Hauteur de panne" + +# English: "Purlin spacing" +# Source-Hash: da4d0d12715d6c33 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinSpacing.4gbzb2" +msgstr "Espacement des pannes" + +# English: "Purlin width" +# Source-Hash: b4774bcbdf599812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinWidth.r6tu3j" +msgstr "Largeur de panne" + +# English: "Rafter end inset" +# Source-Hash: 2a4dd54f8a8c15ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoHangers.10554ka" -msgstr "Suspentes auto" +msgid "editor.inspector.field.rafterEndInset.14c0wy5" +msgstr "Retrait d’about de chevron" -# Source-Hash: e20033e4b0045f1c +# English: "Rafter height" +# Source-Hash: 5a80a11b97c0e043 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto miter corners" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoMiterCorners.zxr5lt" -msgstr "Angles en onglet auto" +msgid "editor.inspector.field.rafterHeight.1e1n99w" +msgstr "Hauteur de chevron" -# Source-Hash: 92f3a8b63963daeb +# English: "Rafter spacing" +# Source-Hash: b6348d554660af5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoSpan.1gas9r6" -msgstr "Portée auto" +msgid "editor.inspector.field.rafterSpacing.19subc0" +msgstr "Espacement des chevrons" -# Source-Hash: 8b2dfb9b9137fda0 +# English: "Rafter width" +# Source-Hash: 112c447946c3beaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.backRatio.1ughd99" -msgstr "Ratio arrière" +msgid "editor.inspector.field.rafterWidth.eksj4t" +msgstr "Largeur de chevron" -# Source-Hash: 75dfae8d9f03fa0e +# English: "Railing height" +# Source-Hash: 7020d720876a2106 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseHeight.1hs4zdf" -msgstr "Hauteur de base" +msgid "editor.inspector.field.railingHeight.6wf4bm" +msgstr "Hauteur du garde-corps" -# Source-Hash: 1cc2da12e8463e85 +# English: "Railing mode" +# Source-Hash: 3e89ce193885b5b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseStyle.24vty5" -msgstr "Style de base" +msgid "editor.inspector.field.railingMode.ov7xpu" +msgstr "Mode du garde-corps" -# Source-Hash: f9d95c6df06b6223 +# English: "Railing style" +# Source-Hash: 174342bb31a86afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamHeight.1kq0oh1" -msgstr "Hauteur de poutre" +msgid "editor.inspector.field.railingStyle.1nyd2u" +msgstr "Style de garde-corps" -# Source-Hash: a133f8d23b1274dc +# English: "Resize lock" +# Source-Hash: a44eedc895b95474 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamWidth.1q1mzbe" -msgstr "Largeur de poutre" +msgid "editor.inspector.field.resizeLock.b2hbr0" +msgstr "Verrouiller les proportions" -# Source-Hash: b98c0d0189526335 +# English: "Return diameter" +# Source-Hash: edd319917231def8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bodyShape.arvkuw" -msgstr "Forme du corps" +msgid "editor.inspector.field.returnDiameter.166ozhe" +msgstr "Diamètre de reprise" -# Source-Hash: 2251ecab18467631 +# English: "Return height" +# Source-Hash: dafa151b183daa77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bracket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bracketStyle.1vvoptm" -msgstr "Style de console" +msgid "editor.inspector.field.returnHeight.b9sq6y" +msgstr "Hauteur de reprise" -# Source-Hash: 5e9d4a4fcea481ca +# English: "Return shape" +# Source-Hash: cd55fd029ad2803b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Branch angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.branchAngle.v56zme" -msgstr "Angle de piquage" +msgid "editor.inspector.field.returnShape.1e3a27i" +msgstr "Forme de reprise" -# Source-Hash: dec709f1f9c714ee +# English: "Return width" +# Source-Hash: 6a7c411667124177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cabHeight.1yn34mk" -msgstr "Hauteur de cabine" +msgid "editor.inspector.field.returnWidth.z9r3h3" +msgstr "Largeur de reprise" -# Source-Hash: 87863f8366e1c88a +# English: "Right end condition" +# Source-Hash: 460bd11bfc2e5697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy form" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.canopyForm.1czzggp" -msgstr "Forme de l’auvent" +msgid "editor.inspector.field.rightEndCondition.tjlw3l" +msgstr "Condition d’extrémité droite" -# Source-Hash: d52b53241789ee63 +# English: "Right overhang" +# Source-Hash: 1ed35f8d27ae5583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cap.s0thej" -msgstr "Chapeau" +msgid "editor.inspector.field.rightOverhang.yqbeft" +msgstr "Débord droit" -# Source-Hash: 2f2e49598ec8188e +# English: "Roof height" +# Source-Hash: 39c02aa2d97a62ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capOverhang.kxxz9j" -msgstr "Débord du chapeau" +msgid "editor.inspector.field.roofHeight.1uhy6qy" +msgstr "Hauteur du toit" -# Source-Hash: 8523c086e95b83e2 +# English: "Roof style" +# Source-Hash: d62f8833d77ca8a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capShape.17mbxhu" -msgstr "Forme du chapeau" +msgid "editor.inspector.field.roofStyle.pawqfy" +msgstr "Style de toit" -# Source-Hash: acd98600a518dd95 +# English: "Roof thickness" +# Source-Hash: 0ddd561a4126b826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capThickness.u835q9" -msgstr "Épaisseur du chapeau" +msgid "editor.inspector.field.roofThickness.1xlr3rx" +msgstr "Épaisseur du toit" -# Source-Hash: 7af3b69b4b5aeb19 +# English: "Roof type" +# Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carcass height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.carcassHeight.1aks0zy" -msgstr "Hauteur du caisson" +msgid "editor.inspector.field.roofType.1m9kv5h" +msgstr "Type de toit" -# Source-Hash: 827709caa79db37f +# English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cleanoutStyle.127zz6z" -msgstr "Style de regard" +msgid "editor.inspector.field.rotation.ly2rj" +msgstr "Rotation" -# Source-Hash: a549dc98e2a1bd07 +# English: "Rows" +# Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarDiameter.1d5k28r" -msgstr "Diamètre du collier" +msgid "editor.inspector.field.rows.1ba1neg" +msgstr "Rangées" -# Source-Hash: aa0232fc6f3eeadc +# English: "Scale" +# Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarHeight.1dqwsrf" -msgstr "Hauteur du collier" +msgid "editor.inspector.field.scale.b6rpxt" +msgstr "Échelle" -# Source-Hash: cbd8079c3f540a4c +# English: "Seam detail" +# Source-Hash: adb6face4bac5245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarShape.1gacv11" -msgstr "Forme du collier" +msgid "editor.inspector.field.seamDetail.197za48" +msgstr "Détail de joint" -# Source-Hash: 557cde7f95616454 +# English: "Segment type" +# Source-Hash: 4b2aed9d56cc4dee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarWidth.qvc9ik" -msgstr "Largeur du collier" +msgid "editor.inspector.field.segmentType.l667sy" +msgstr "Type de segment" -# Source-Hash: 30edd1c180aac9b4 +# English: "Shaft style" +# Source-Hash: a7794a81141b4de4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.columns.1sy73ks" -msgstr "Colonnes" +msgid "editor.inspector.field.shaftStyle.1i5ampa" +msgstr "Style de gaine" -# Source-Hash: cd5b2f70924b7c2d +# English: "Shaft wall thickness" +# Source-Hash: 8eb6438543b18cdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical full circle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" -msgstr "Conique cercle complet" +msgid "editor.inspector.field.shaftWallThickness.bduslr" +msgstr "Épaisseur des parois de gaine" -# Source-Hash: dc90f3abb935e7e2 +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionMode.1nty478" -msgstr "Mode de raccordement" +msgid "editor.inspector.field.shape.iq9t52" +msgstr "Forme" -# Source-Hash: 1a9a49d7135d4f15 +# English: "Shape2" +# Source-Hash: 44a8ab89c854cad1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionOffset.2m70s8" -msgstr "Décalage de raccordement" +msgid "editor.inspector.field.shape2.fi0nx8" +msgstr "Forme 2" -# Source-Hash: 69be301fcb8030e2 +# English: "Shed high side" +# Source-Hash: fcee9712879570ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner bevel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cornerBevel.16iniho" -msgstr "Chanfrein d’angle" +msgid "editor.inspector.field.shedHighSide.bzzddg" +msgstr "Côté haut du monopente" -# Source-Hash: b997a96a9dc7e450 +# English: "Shingle thickness" +# Source-Hash: f5a931cd6e97e5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.coveringType.95yd3k" -msgstr "Type de revêtement" +msgid "editor.inspector.field.shingleThickness.1hn5wxr" +msgstr "Épaisseur des bardeaux" -# Source-Hash: 03b33160d8baa966 +# English: "Shoulder extent" +# Source-Hash: b14990b3e4222dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketHeight.pok31n" -msgstr "Hauteur de la costière" +msgid "editor.inspector.field.shoulderExtent.11j8uiv" +msgstr "Étendue de l’épaulement" -# Source-Hash: 21552eee05ccb5d6 +# English: "Shoulder height" +# Source-Hash: 505de343ec5010e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketLength.i2aht8" -msgstr "Longueur de la costière" +msgid "editor.inspector.field.shoulderHeight.xyqn6g" +msgstr "Hauteur de l’épaulement" -# Source-Hash: c13b707bfdeac855 +# English: "Shoulder style" +# Source-Hash: 58d48c01b7ec1a40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketSide.1xnwil7" -msgstr "Côté de la costière" +msgid "editor.inspector.field.shoulderStyle.1hkcv2w" +msgstr "Style d’épaulement" -# Source-Hash: 4ee7089fa1b3e63b +# English: "Show center column" +# Source-Hash: af84db1026bf2e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketStyle.10j0lat" -msgstr "Style de costière" +msgid "editor.inspector.field.showCenterColumn.1uggg45" +msgstr "Afficher la colonne centrale" -# Source-Hash: b8ff053666ee326b +# English: "Show infill" +# Source-Hash: 80214dc086ebc1b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curb.5xi2ol" -msgstr "Costière" +msgid "editor.inspector.field.showInfill.33rv7u" +msgstr "Afficher le remplissage" -# Source-Hash: 8da648df210dbc0d +# English: "Show step supports" +# Source-Hash: f02d6cdfb4c6ad08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curbHeight.1yn97sg" -msgstr "Hauteur de costière" +msgid "editor.inspector.field.showStepSupports.1kg8h8m" +msgstr "Afficher les supports de marche" -# Source-Hash: 0c01b9848f5d9673 +# English: "Side flashing" +# Source-Hash: 5d1147c774718b76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curveOffset.l4xhvr" -msgstr "Flèche de courbure" +msgid "editor.inspector.field.sideFlashing.cv744o" +msgstr "Solin latéral" -# Source-Hash: 69179f4caa384585 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.damperAngle.h5fxn7" -msgstr "Angle du registre" +msgid "editor.inspector.field.size.1a4x3zw" +msgstr "Taille" -# Source-Hash: 15cff91231e806a4 +# English: "Skylight type" +# Source-Hash: fbdbc6c97c75b030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.deckThickness.4jmqze" -msgstr "Épaisseur de la terrasse" +msgid "editor.inspector.field.skylightType.1a5vj3i" +msgstr "Type de puits de lumière" -# Source-Hash: eff1e7dc0901b9d0 +# English: "Slab opening mode" +# Source-Hash: c9a6561cb3c3da82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.defaultLevelId.xmrjzp" -msgstr "ID de niveau par défaut" +msgid "editor.inspector.field.slabOpeningMode.g4f730" +msgstr "Mode de trémie de dalle" -# Source-Hash: 99ac4d03c3061f3a +# English: "Slat gap" +# Source-Hash: ef0efc845ccdba78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.depth.1lu11u2" -msgstr "Profondeur" +msgid "editor.inspector.field.slatGap.slmk93" +msgstr "Écart entre lames" -# Source-Hash: 016d8e70618916d4 +# English: "Slide direction" +# Source-Hash: 4daab7a2ce2ecef3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter.s3robe" -msgstr "Diamètre" +msgid "editor.inspector.field.slideDirection.1527q13" +msgstr "Sens de coulissement" -# Source-Hash: dab4100e83e4e6b3 +# English: "Span" +# Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter2.pm6cl4" -msgstr "Diamètre 2" +msgid "editor.inspector.field.span.1bmmuw1" +msgstr "Portée" -# Source-Hash: 5747145349432396 +# English: "Speed" +# Source-Hash: 3b006be0b1f0493f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door duration ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorDurationMs.13nx909" -msgstr "Durée de porte ms" +msgid "editor.inspector.field.speed.2w2xwg" +msgstr "Vitesse" -# Source-Hash: 6f4aca26196c221f +# English: "Spin speed" +# Source-Hash: c8cfb8336a240cb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorHeight.92r9k4" -msgstr "Hauteur de porte" +msgid "editor.inspector.field.spinSpeed.1ozx746" +msgstr "Vitesse de rotation" -# Source-Hash: 990ae81752236e3a +# English: "Stair type" +# Source-Hash: c9f54c3b258961c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door panel style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorPanelStyle.2qog4q" -msgstr "Style de panneau de porte" +msgid "editor.inspector.field.stairType.1u05e02" +msgstr "Type d’escalier" -# Source-Hash: c94e002123bb1199 +# English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorStyle.gerpos" -msgstr "Style de porte" +msgid "editor.inspector.field.standoff.4wv2g0" +msgstr "Entretoise" -# Source-Hash: 91aeb04b0b7b605e +# English: "Standoff height" +# Source-Hash: 0464d4efa1e80c62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorWidth.sy732l" -msgstr "Largeur de porte" +msgid "editor.inspector.field.standoffHeight.1qdnff" +msgstr "Hauteur d’entretoise" -# Source-Hash: baad1238f70f79ea +# English: "Start post" +# Source-Hash: 85da28b0b26b8c2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutEnabled.1gn00nv" -msgstr "Descente activée" +msgid "editor.inspector.field.startPost.1ood9kv" +msgstr "Poteau de départ" -# Source-Hash: e288531858c4c44d +# English: "Step count" +# Source-Hash: d5dc1ba8cca3193d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutPosition.fxcsop" -msgstr "Position de la descente" +msgid "editor.inspector.field.stepCount.nzorrw" +msgstr "Nombre de marches" -# Source-Hash: 861d28317325cf61 +# English: "Strap spacing" +# Source-Hash: cadbb9cadd6a5e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ductMaterial.i8eonq" -msgstr "Matériau de gaine" +msgid "editor.inspector.field.strapSpacing.1cyt3dc" +msgstr "Espacement des feuillards" -# Source-Hash: ad8ab4ae38c3299c +# English: "Strap style" +# Source-Hash: c017cd56b3ae5865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch gablet rake" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchGabletRake.6paydp" -msgstr "Rive du pignon à demi-croupe" +msgid "editor.inspector.field.strapStyle.fh1oy4" +msgstr "Style de feuillard" -# Source-Hash: 913d25cf2c5ff179 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" -msgstr "Ratio de hauteur de demi-croupe" +msgid "editor.inspector.field.style.nf3vcm" +msgstr "Style" -# Source-Hash: f4a80233d49f2b20 +# English: "Suction diameter" +# Source-Hash: 7b3f016989c631a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" -msgstr "Ratio de largeur de demi-croupe" +msgid "editor.inspector.field.suctionDiameter.nutxtj" +msgstr "Diamètre d’aspiration" -# Source-Hash: 92021b0e37f9a25a +# English: "Supply diameter" +# Source-Hash: 3f1e0fd30e5b1f39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch top rake thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" -msgstr "Épaisseur de rive haute" +msgid "editor.inspector.field.supplyDiameter.h5ldh5" +msgstr "Diamètre de soufflage" -# Source-Hash: d0a2ec13d71ce659 +# English: "Supply height" +# Source-Hash: 05c9b608538e181d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch waist length ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" -msgstr "Ratio de longueur de taille" +msgid "editor.inspector.field.supplyHeight.zy6ru1" +msgstr "Hauteur de soufflage" -# Source-Hash: 993396ccca2cd1d5 +# English: "Supply shape" +# Source-Hash: 3c9400722baa474d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dwell ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dwellMs.1lkk0o5" -msgstr "Arrêt ms" +msgid "editor.inspector.field.supplyShape.k39wnb" +msgstr "Forme de soufflage" -# Source-Hash: 9cf550eb019bca1b +# English: "Supply width" +# Source-Hash: af7619025438f995 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edge inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.edgeInset.1obeimx" -msgstr "Retrait de bord" +#, fuzzy +msgid "editor.inspector.field.supplyWidth.g66axi" +msgstr "Largeur de soufflage" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Support offset" +# Source-Hash: 617e198cf3f214d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.elevation.4jbp44" -msgstr "Altitude" +msgid "editor.inspector.field.supportOffset.1w4cocr" +msgstr "Décalage du support" -# Source-Hash: 1fc91958e30dbeaf +# English: "Sweep angle" +# Source-Hash: 2686abc093f3dfb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapLeft.19jel4v" -msgstr "Embout gauche" +msgid "editor.inspector.field.sweepAngle.ucmmlw" +msgstr "Angle de balayage" -# Source-Hash: 4c9ae62e0ed70635 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapRight.lhse9c" -msgstr "Embout droit" +msgid "editor.inspector.field.system.13qbhrw" +msgstr "Système" -# Source-Hash: a9cba06250228a55 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCaps.4fsb5b" -msgstr "Embouts" +msgid "editor.inspector.field.terminal.1501knl" +msgstr "Terminal" -# Source-Hash: ebad74353b5a7610 +# English: "Terminal type" +# Source-Hash: de1777c521046f7d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endPost.1guwsk0" -msgstr "Poteau d’extrémité" +msgid "editor.inspector.field.terminalType.1ne8lgb" +msgstr "Type de terminal" -# Source-Hash: ecbe36df4238439e +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.equipmentType.1mc2ywt" -msgstr "Type d’équipement" +msgid "editor.inspector.field.thickness.1l8hu45" +msgstr "Épaisseur" -# Source-Hash: 66f1018b50c2e426 +# English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fillToFloor.1oppnkf" -msgstr "Remplir jusqu’au sol" +msgid "editor.inspector.field.tiltAngle.1312yw1" +msgstr "Angle d’inclinaison" -# Source-Hash: 1cfe33235b30b1d8 +# English: "To level id" +# Source-Hash: 7057972ff4c78dc7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.finial.1ertf4s" -msgstr "Épi de faîtage" +msgid "editor.inspector.field.toLevelId.8i5qx7" +msgstr "ID du niveau d’arrivée" -# Source-Hash: 28cd69e506709f16 +# English: "Top landing depth" +# Source-Hash: fa8fc3d5c612e774 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fittingType.18mz17s" -msgstr "Type de raccord" +msgid "editor.inspector.field.topLandingDepth.ki7gk2" +msgstr "Profondeur du palier d’arrivée" -# Source-Hash: 3c1aec5e6fb180cf +# English: "Top landing mode" +# Source-Hash: 6e4a0b98089ec2cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingHeight.3e8nn6" -msgstr "Hauteur du solin" +msgid "editor.inspector.field.topLandingMode.zk0h7e" +msgstr "Mode du palier d’arrivée" -# Source-Hash: 327ba762101db94d +# English: "Top rail height" +# Source-Hash: c182bb3c8a701085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingProjection.hhf0fk" -msgstr "Saillie du solin" +msgid "editor.inspector.field.topRailHeight.fscisv" +msgstr "Hauteur de la lisse haute" -# Source-Hash: 9473e2ab53370b40 +# English: "Transition mode" +# Source-Hash: 8c5502f1d8087b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueCount.j3t2ci" -msgstr "Nombre de conduits" +msgid "editor.inspector.field.transitionMode.f3evgt" +msgstr "Mode de transition" -# Source-Hash: fd0b65425ed3df8b +# English: "Transition width" +# Source-Hash: 96eb5997f0aa2dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueDiameter.cjhubg" -msgstr "Diamètre du conduit" +msgid "editor.inspector.field.transitionWidth.rtgz3m" +msgstr "Largeur de transition" -# Source-Hash: 07ebe5e3780c624b +# English: "Vane count" +# Source-Hash: 812d27902509339f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueHeight.1dhc4y4" -msgstr "Hauteur du conduit" +msgid "editor.inspector.field.vaneCount.55vtye" +msgstr "Nombre d’ailettes" -# Source-Hash: ff8319792830d591 +# English: "Wall height" +# Source-Hash: 1e7dcd717c8ac239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueShape.1ealiy4" -msgstr "Forme du conduit" +msgid "editor.inspector.field.wallHeight.18vmh5c" +msgstr "Hauteur du mur" -# Source-Hash: 5acf77854a0030ec +# English: "Wall shell" +# Source-Hash: f14bdaed6101896a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueSpacing.exhzco" -msgstr "Espacement des conduits" +msgid "editor.inspector.field.wallShell.vqsyhv" +msgstr "Enveloppe murale" -# Source-Hash: 0a64209c0edacaa1 +# English: "Wall skirt height" +# Source-Hash: 801268585c9a679b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.footingStyle.e3btg6" -msgstr "Style de semelle" +msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" +msgstr "Hauteur du soubassement" -# Source-Hash: fd413477f1a9e5ed +# English: "Wall thickness" +# Source-Hash: 6b377d0ba1565477 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameDepth.1934a5n" -msgstr "Profondeur du cadre" +msgid "editor.inspector.field.wallThickness.17wn4iv" +msgstr "Épaisseur du mur" -# Source-Hash: 6aa2318fa815e5f2 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameThickness.1agxzd8" -msgstr "Épaisseur du cadre" +msgid "editor.inspector.field.width.gfxz9b" +msgstr "Largeur" -# Source-Hash: 9a3495a1e3181b56 +# English: "Width2" +# Source-Hash: de05070474f589b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing strategy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.framingStrategy.3jx9qe" -msgstr "Stratégie d’ossature" +msgid "editor.inspector.field.width2.1nqawvr" +msgstr "Largeur 2" -# Source-Hash: 49f329aab535fd3a +# English: "With back" +# Source-Hash: 6199b641888a83fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fromLevelId.15ieejs" -msgstr "ID du niveau de départ" +msgid "editor.inspector.field.withBack.1ay8nus" +msgstr "Avec fond" -# Source-Hash: dbd062efc7f4fd7f +# English: "With bottom" +# Source-Hash: 699934d8b9571b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gableOverhang.2z0obw" -msgstr "Débord de rive" +msgid "editor.inspector.field.withBottom.1x1z1vo" +msgstr "Avec fond inférieur" -# Source-Hash: 517caa8af48afb22 +# English: "With sides" +# Source-Hash: 03987b9aadaab50f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" -msgstr "Ratio de hauteur du brisis" +msgid "editor.inspector.field.withSides.uq066x" +msgstr "Avec côtés" -# Source-Hash: 03ecb8f3b625d49d +# English: "Advanced" +# Source-Hash: f571cb2d06ae01ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" -msgstr "Ratio de largeur du brisis" +msgid "editor.inspector.group.advanced.qwfkor" +msgstr "Avancé" -# Source-Hash: f3ca5555e8c54375 +# English: "Air" +# Source-Hash: a886619d15bae47e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap x" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapX.wjxa7" -msgstr "Écart x" +msgid "editor.inspector.group.air.15jotdj" +msgstr "Air" -# Source-Hash: 2736f7c5dfc7d917 +# English: "Appearance" +# Source-Hash: b69fc1f8128958fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap y" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapY.mkbl8" -msgstr "Écart y" +msgid "editor.inspector.group.appearance.dli9wf" +msgstr "Apparence" -# Source-Hash: 5d03ca50d712339b +# English: "Body" +# Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.glassThickness.s97b0x" -msgstr "Épaisseur du vitrage" +msgid "editor.inspector.group.body.ign4wl" +msgstr "Corps" -# Source-Hash: 4fc3e60be28f60d5 +# English: "Cab" +# Source-Hash: fdf7108a011bd94b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.groundClearance.4os7f0" -msgstr "Garde au sol" +msgid "editor.inspector.group.cab.o4z1ox" +msgstr "Cabine" -# Source-Hash: 6a22a09b67309545 +# English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.guardInfill.16heiig" -msgstr "Remplissage du garde-corps" +msgid "editor.inspector.group.cabinet.rzvymr" +msgstr "Meuble" -# Source-Hash: 98dfdac04af3c762 +# English: "Cap" +# Source-Hash: d52b53241789ee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterEnabled.1oxiltf" -msgstr "Gouttière activée" +msgid "editor.inspector.group.cap.s0thej" +msgstr "Chapeau" -# Source-Hash: 4bdc3cdf0fe0ac3c +# English: "Collar" +# Source-Hash: 93a545688c917e69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterProfile.1ngzqi3" -msgstr "Profil de gouttière" +msgid "editor.inspector.group.collar.1am2iqo" +msgstr "Collier" -# Source-Hash: 76555670cba9bb13 +# English: "Connection" +# Source-Hash: 7a9c55b1c5a6e61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterSize.jgavtz" -msgstr "Taille de gouttière" +msgid "editor.inspector.group.connection.2r1h4p" +msgstr "Raccordement" -# Source-Hash: c14d3aa4a98efa98 +# English: "Connections" +# Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger max reach" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerMaxReach.1869epv" -msgstr "Portée max. du support" +msgid "editor.inspector.group.connections.ai4w8u" +msgstr "Raccordements" -# Source-Hash: dfbc357fb556559b +# English: "Construction" +# Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerSpacing.i2b5d7" -msgstr "Espacement des supports" +msgid "editor.inspector.group.construction.j6p9kc" +msgstr "Construction" -# Source-Hash: 3ce5e50c572644ed +# English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerStyle.l7v7gb" -msgstr "Style de support" +msgid "editor.inspector.group.cricket.6iwg8g" +msgstr "Costière" -# Source-Hash: 646ef0fbcbc58edf +# English: "Curb" +# Source-Hash: b8ff053666ee326b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height.7puwki" -msgstr "Hauteur" +msgid "editor.inspector.group.curb.5xi2ol" +msgstr "Costière" -# Source-Hash: d0b836103c795726 +# English: "Curve points" +# Source-Hash: 3b9c59e996dcc9c5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height2.7b3jlc" -msgstr "Hauteur 2" +msgid "editor.inspector.group.curvePoints.ztdcnn" +msgstr "Points de courbe" -# Source-Hash: 681fd794e601ace5 +# English: "Dimensions" +# Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height above ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.heightAboveRidge.1k99brk" -msgstr "Hauteur au-dessus du faîtage" +msgid "editor.inspector.group.dimensions.pewa8g" +msgstr "Dimensions" -# Source-Hash: b4905e5ec62a170f +# English: "Doors" +# Source-Hash: 20f60b98de67849b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highEdgeHeight.1s7v90t" -msgstr "Hauteur du bord haut" +msgid "editor.inspector.group.doors.18w2kpa" +msgstr "Portes" -# Source-Hash: c136d5cd7f3ce04a +# English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highOverhang.67w55d" -msgstr "Débord haut" +msgid "editor.inspector.group.dormer.1fjs5tq" +msgstr "Lucarne" -# Source-Hash: 8b4cd537cc650dac +# English: "Dormer roof" +# Source-Hash: cda262793b4cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High side mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highSideMode.1rc1r5j" -msgstr "Mode du côté haut" +msgid "editor.inspector.group.dormerRoof.u9uy2g" +msgstr "Toit de lucarne" -# Source-Hash: c38e4e73ed9779f4 +# English: "Drainage" +# Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hood overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hoodOverhang.1q1hcin" -msgstr "Débord du capot" +msgid "editor.inspector.group.drainage.7easy0" +msgstr "Évacuation" -# Source-Hash: 9884ee52a0e84c0d +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.infillPlacement.11dxzpo" -msgstr "Position du remplissage" +msgid "editor.inspector.group.elevation.4jbp44" +msgstr "Altitude" -# Source-Hash: 37fde9f9290ddc59 +# English: "End caps" +# Source-Hash: a9cba06250228a55 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.inletShape.ypayh2" -msgstr "Forme d’entrée" +msgid "editor.inspector.group.endCaps.4fsb5b" +msgstr "Embouts" -# Source-Hash: d840917007332b94 +# English: "Equipment" +# Source-Hash: 228f2206862e41cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.innerRadius.1fkmg0j" -msgstr "Rayon intérieur" +msgid "editor.inspector.group.equipment.1xtq0ev" +msgstr "Équipement" -# Source-Hash: c8ce389856a90ae2 +# English: "Face" +# Source-Hash: a19276baa3688cbf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulated.iwziey" -msgstr "Isolé" +msgid "editor.inspector.group.face.1531fr0" +msgstr "Face" -# Source-Hash: 043a7a10b6c91f4f +# English: "Fitting" +# Source-Hash: fb34ee7c59cb84ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation r" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulationR.663ufx" -msgstr "Isolation R" +msgid "editor.inspector.group.fitting.171z1r4" +msgstr "Raccord" -# Source-Hash: fe661655ba41b709 +# English: "Flues" +# Source-Hash: f817c09ef0bb6d69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternHeight.oz7zpm" -msgstr "Hauteur du lanterneau" +msgid "editor.inspector.group.flues.nq6pye" +msgstr "Conduits" -# Source-Hash: ab22022e7d5597af +# English: "Footprint" +# Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern top scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternTopScale.hkmpp2" -msgstr "Échelle du haut du lanterneau" +msgid "editor.inspector.group.footprint.g8q2pg" +msgstr "Emprise" -# Source-Hash: 17f854a76ebc033a +# English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerDepth.7av8k9" -msgstr "Profondeur de la lisse" +msgid "editor.inspector.group.frame.q7q9fq" +msgstr "Cadre" -# Source-Hash: 547d3a8a9e3e24f5 +# English: "Geometry" +# Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerHeight.zo2oif" -msgstr "Hauteur de la lisse" +msgid "editor.inspector.group.geometry.a21os1" +msgstr "Géométrie" -# Source-Hash: 88603f884243c81e +# English: "Grid" +# Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger vertical offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" -msgstr "Décalage vertical de la lisse" +msgid "editor.inspector.group.grid.d8gwxd" +msgstr "Grille" -# Source-Hash: 6519f495202b830d +# English: "Hangers" +# Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftEndCondition.1syqlaw" -msgstr "Condition d’extrémité gauche" +msgid "editor.inspector.group.hangers.agnfzn" +msgstr "Supports" -# Source-Hash: d041b8217c356977 +# English: "Hardware" +# Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftOverhang.1ydl9ma" -msgstr "Débord gauche" +msgid "editor.inspector.group.hardware.1nygwm1" +msgstr "Quincaillerie" -# Source-Hash: 643687bc3adaac76 +# English: "Hung wall" +# Source-Hash: 2a47557eb4a5f766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.length.1kpdcfp" -msgstr "Longueur" +msgid "editor.inspector.group.hungWall.1ftr1gz" +msgstr "Mur suspendu" -# Source-Hash: d6deaf0ceb9f43af +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.liquidDiameter.w3pk7c" -msgstr "Diamètre liquide" +msgid "editor.inspector.group.insulation.1kyo5e7" +msgstr "Isolation" -# Source-Hash: 45aca762426392fa +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louver count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.louverCount.lhw2gr" -msgstr "Nombre de lames" +msgid "editor.inspector.group.lantern.1h7o2xb" +msgstr "Lanterneau" -# Source-Hash: c645a86c55eb0075 +# English: "Levels" +# Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low beam inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowBeamInset.1g7yw5l" -msgstr "Retrait de la poutre basse" +msgid "editor.inspector.group.levels.1s6yaru" +msgstr "Niveaux" -# Source-Hash: 7e7b896682c78c5b +# English: "Line" +# Source-Hash: 1a849e52a46d5087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowEdgeHeight.47mae1" -msgstr "Hauteur du bord bas" +msgid "editor.inspector.group.line.166mv5z" +msgstr "Ligne" -# Source-Hash: 9deb775fc64854b2 +# English: "Lines" +# Source-Hash: 9ab4f7a738ebee13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowOverhang.t4nz1h" -msgstr "Débord bas" +msgid "editor.inspector.group.lines.12bghr0" +msgstr "Lignes" -# Source-Hash: 9aa621d270fe64b1 +# English: "Motion" +# Source-Hash: c0ff16d77ebc9593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" -msgstr "Ratio de hauteur du brisis mansardé" +msgid "editor.inspector.group.motion.1be7sjr" +msgstr "Mouvement" -# Source-Hash: 7cb03b27c632949b +# English: "Mounting" +# Source-Hash: 81d365ccef73068c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" -msgstr "Ratio de largeur du brisis mansardé" +msgid "editor.inspector.group.mounting.feutd2" +msgstr "Montage" -# Source-Hash: 95772435857a7424 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" -msgstr "Reprendre le matériau du toit porteur" +msgid "editor.inspector.group.opening.dfet3" +msgstr "Ouverture" -# Source-Hash: dc22618e31654bde +# English: "Panel dimensions" +# Source-Hash: f46314c54627858d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofStructure.bojht3" -msgstr "Reprendre la structure du toit porteur" +msgid "editor.inspector.group.panelDimensions.b59qe6" +msgstr "Dimensions des panneaux" -# Source-Hash: 06b36a09a84fa8b1 +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mount.h80tay" -msgstr "Fixation" +msgid "editor.inspector.group.patternDistribution.fmam51" +msgstr "Répartition du motif" -# Source-Hash: a288700f96d2b362 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mountingType.1xvin1e" -msgstr "Type de montage" +msgid "editor.inspector.group.placement.13lcvts" +msgstr "Placement" -# Source-Hash: 3087bc9b219af9ef +# English: "Position" +# Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Neck height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.neckHeight.19ym8n1" -msgstr "Hauteur du col" +msgid "editor.inspector.group.position.1quewx6" +msgstr "Position" -# Source-Hash: 1c0b8a5503c5daeb +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.opacity.1pmpfd2" -msgstr "Opacité" +msgid "editor.inspector.group.profile.1kwlci6" +msgstr "Profil" -# Source-Hash: 51b7ab6a0a78d90a +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingAngle.brhi7c" -msgstr "Angle d’ouverture" +msgid "editor.inspector.group.railing.19a5bnr" +msgstr "Garde-corps" -# Source-Hash: 1d714b576a057902 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingOffset.wpoqt6" -msgstr "Décalage de l’ouverture" +msgid "editor.inspector.group.return.1klrjrz" +msgstr "Reprise" -# Source-Hash: 55e5629865efd03d +# English: "Settings" +# Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingSide.18l2iwe" -msgstr "Côté d’ouverture" +msgid "editor.inspector.group.settings.ktdb8o" +msgstr "Paramètres" -# Source-Hash: 9824d357692ecb8a +# English: "Shaft" +# Source-Hash: 4fc63ad21de30103 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Operation state" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.operationState.1lwrz07" -msgstr "État d’ouverture" +msgid "editor.inspector.group.shaft.mad9cv" +msgstr "Gaine" -# Source-Hash: 4f1cbc14dcfb8f3b +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.outletShape.lgpqhx" -msgstr "Forme de sortie" +msgid "editor.inspector.group.shape.iq9t52" +msgstr "Forme" -# Source-Hash: 88540def2ce16ac5 +# English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.overhang.b5ljpv" -msgstr "Débord" +msgid "editor.inspector.group.shoulder.1nyfx25" +msgstr "Épaulement" -# Source-Hash: 50d7e5e370667f63 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelHeight.1kf31vk" -msgstr "Hauteur du panneau" +msgid "editor.inspector.group.size.1a4x3zw" +msgstr "Taille" -# Source-Hash: 068d9e0adabc4578 +# English: "Structure" +# Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelWidth.946581" -msgstr "Largeur du panneau" +msgid "editor.inspector.group.structure.gezdra" +msgstr "Structure" -# Source-Hash: aca94a3adc0b0f01 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternAlignment.jd1ht0" -msgstr "Alignement du motif" +msgid "editor.inspector.group.style.nf3vcm" +msgstr "Style" -# Source-Hash: 9e9f9408ec2c8f0b +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternCount.1r37pve" -msgstr "Nombre de motifs" +msgid "editor.inspector.group.supply.1oy2zwu" +msgstr "Soufflage" -# Source-Hash: 4712c449e9a42d3b +# English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternDistribution.fmam51" -msgstr "Répartition du motif" +msgid "editor.inspector.group.surface.wtamqk" +msgstr "Surface" -# Source-Hash: 63f833f219b9a592 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern remainder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternRemainder.i47p5y" -msgstr "Reste du motif" +msgid "editor.inspector.group.terminal.1501knl" +msgstr "Terminal" -# Source-Hash: e0b8f766411329ff +# English: "Topology" +# Source-Hash: 399fc3e62f491f08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketProfile.1xgzuyc" -msgstr "Profil des lattes" +msgid "editor.inspector.group.topology.18t8dyc" +msgstr "Topologie" -# Source-Hash: dfac7d68ea16e8a8 +# English: "Transform" +# Source-Hash: f425799b82e085ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket rail count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketRailCount.atgt6w" -msgstr "Nombre de lisses" +msgid "editor.inspector.group.transform.1c55ayj" +msgstr "Transformation" -# Source-Hash: cd2652254a81d288 +# English: "Trap" +# Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketSpacing.p8d98w" -msgstr "Espacement des lattes" +msgid "editor.inspector.group.trap.1qc7vhe" +msgstr "Siphon" -# Source-Hash: 462f6cb03b23d773 +# English: "Type" +# Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTop.1tskplu" -msgstr "Haut des lattes" +msgid "editor.inspector.group.type.1m2zofh" +msgstr "Type" -# Source-Hash: 0a4dbbe1cf592326 +# English: "Abs" +# Source-Hash: c8fd17853839516f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTopClearance.16eq1k0" -msgstr "Dégagement en haut des lattes" +msgid "editor.inspector.option.abs.132mz8b" +msgstr "ABS" -# Source-Hash: c99ccea380ca41bf +# English: "Access panel" +# Source-Hash: fd9eb1a16cb6b996 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket variation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketVariation.f7vl1c" -msgstr "Variation des lattes" +msgid "editor.inspector.option.accessPanel.13sptb5" +msgstr "Trappe de visite" -# Source-Hash: 798382373625fecd +# English: "Air handler" +# Source-Hash: db8064add8c27a5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketWidth.3fr3rh" -msgstr "Largeur des lattes" +msgid "editor.inspector.option.airHandler.1crnyg3" +msgstr "Centrale de traitement d’air" -# Source-Hash: 48ce215c3be034c7 +# English: "Alternating" +# Source-Hash: d8234782848be927 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pipeMaterial.1ggc2wy" -msgstr "Matériau du tuyau" +msgid "editor.inspector.option.alternating.5jashi" +msgstr "Alterné" -# Source-Hash: 75e3449fc1776d09 +# English: "Arched" +# Source-Hash: 63cbda7b09662368 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pitch.xe6ekx" -msgstr "Pente" +msgid "editor.inspector.option.arched.14188w0" +msgstr "Cintré" -# Source-Hash: 9da0cbfad144d737 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.position.1quewx6" -msgstr "Position" +msgid "editor.inspector.option.auto.540b06" +msgstr "Auto" -# Source-Hash: d090baf9f08c2280 +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post bracing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postBracing.crq8nn" -msgstr "Contreventement des poteaux" +msgid "editor.inspector.option.automatic.r122ro" +msgstr "Automatique" -# Source-Hash: 6467b580eed1d333 +# English: "Back" +# Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCap.1xcvxrv" -msgstr "Chapeau de poteau" +msgid "editor.inspector.option.back.1hzmxtu" +msgstr "Retour" -# Source-Hash: e1d91cdd16cc6593 +# English: "Balusters" +# Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCount.22r56c" -msgstr "Nombre de poteaux" +msgid "editor.inspector.option.balusters.1j3w6ng" +msgstr "Balustres" -# Source-Hash: d6d8b8653cb43624 +# English: "Band" +# Source-Hash: f211248ec8619e4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postDepth.1ko6j1m" -msgstr "Profondeur du poteau" +msgid "editor.inspector.option.band.160umau" +msgstr "Bandeau" -# Source-Hash: dd071e84e259ee48 +# English: "Base plate" +# Source-Hash: 8f9ff05c6a3b08a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postInset.le36li" -msgstr "Retrait du poteau" +msgid "editor.inspector.option.basePlate.17fcg8k" +msgstr "Platine" -# Source-Hash: 88c5d586822e58f0 +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post layout mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postLayoutMode.16jre62" -msgstr "Mode de répartition des poteaux" +msgid "editor.inspector.option.boards.u14hig" +msgstr "Planches" -# Source-Hash: faa36308c1ecb2a9 +# English: "Bookshelf" +# Source-Hash: c0fee00bdfb09d37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSize.1jodxek" -msgstr "Taille du poteau" +msgid "editor.inspector.option.bookshelf.ls0zqk" +msgstr "Bibliothèque" -# Source-Hash: c8e8490c957e01f3 +# English: "Both" +# Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSpacing.voppfi" -msgstr "Espacement des poteaux" +msgid "editor.inspector.option.both.mkyb42" +msgstr "Les deux" -# Source-Hash: b98cbe8478508d41 +# English: "Bottom" +# Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post through" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postThrough.1abgu4a" -msgstr "Poteau traversant" +msgid "editor.inspector.option.bottom.9mpruy" +msgstr "Bas" -# Source-Hash: d1492df1fbb22c29 +# English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postWidth.rk26fz" -msgstr "Largeur du poteau" +msgid "editor.inspector.option.box.1nbjcuq" +msgstr "Boîte" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Break" +# Source-Hash: 2c2815df4f724abe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.profile.1kwlci6" -msgstr "Profil" +msgid "editor.inspector.option.break.17spnvc" +msgstr "Rupture" -# Source-Hash: d01cd36d3939abab +# English: "Butterfly" +# Source-Hash: 0d5a25303d3d8058 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.projection.jhtig0" -msgstr "Saillie" +msgid "editor.inspector.option.butterfly.pvv0g4" +msgstr "Papillon" -# Source-Hash: 800a7a9159519b46 +# English: "Cable" +# Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinHeight.eo9cjm" -msgstr "Hauteur de panne" +msgid "editor.inspector.option.cable.fp50zq" +msgstr "Câble" -# Source-Hash: da4d0d12715d6c33 +# English: "Cast iron" +# Source-Hash: 69129e514347672a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinSpacing.4gbzb2" -msgstr "Espacement des pannes" +msgid "editor.inspector.option.castIron.1q2z4qk" +msgstr "Fonte" -# Source-Hash: b4774bcbdf599812 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinWidth.r6tu3j" -msgstr "Largeur de panne" +msgid "editor.inspector.option.ceiling.1t2zaqo" +msgstr "Plafond" -# Source-Hash: 2a4dd54f8a8c15ef +# English: "Center" +# Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter end inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterEndInset.14c0wy5" -msgstr "Retrait d’about de chevron" +msgid "editor.inspector.option.center.kzdxno" +msgstr "Centre" -# Source-Hash: 5a80a11b97c0e043 +# English: "Center opening" +# Source-Hash: 65f410261609060b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterHeight.1e1n99w" -msgstr "Hauteur de chevron" +msgid "editor.inspector.option.centerOpening.bn1rvw" +msgstr "Ouverture centrale" -# Source-Hash: b6348d554660af5b +# English: "Cleanout" +# Source-Hash: cf5f9756cc866f3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterSpacing.19subc0" -msgstr "Espacement des chevrons" +msgid "editor.inspector.option.cleanout.lww8bm" +msgstr "Regard de nettoyage" -# Source-Hash: 112c447946c3beaa +# English: "Concrete pad" +# Source-Hash: e71d1d949f1d1b41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterWidth.eksj4t" -msgstr "Largeur de chevron" +msgid "editor.inspector.option.concretePad.m1zdij" +msgstr "Dalle béton" -# Source-Hash: 7020d720876a2106 +# English: "Condenser" +# Source-Hash: a3804c4f5e6aefbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingHeight.6wf4bm" -msgstr "Hauteur du garde-corps" +msgid "editor.inspector.option.condenser.a74nps" +msgstr "Condenseur" -# Source-Hash: 3e89ce193885b5b4 +# English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingMode.ov7xpu" -msgstr "Mode du garde-corps" +msgid "editor.inspector.option.corbeled.bovs4v" +msgstr "En encorbellement" -# Source-Hash: 174342bb31a86afc +# English: "Count" +# Source-Hash: 8a4beb9a57edc26e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingStyle.1nyd2u" -msgstr "Style de garde-corps" +msgid "editor.inspector.option.count.1qoi4r8" +msgstr "Nombre" -# Source-Hash: a44eedc895b95474 +# English: "Coupling" +# Source-Hash: db1f8f141714b363 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.resizeLock.b2hbr0" -msgstr "Verrouiller les proportions" +msgid "editor.inspector.option.coupling.111aap2" +msgstr "Manchon" -# Source-Hash: edd319917231def8 +# English: "Covering specific" +# Source-Hash: 7a9a92d2e59a3bbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnDiameter.166ozhe" -msgstr "Diamètre de reprise" +msgid "editor.inspector.option.coveringSpecific.1h9fo8w" +msgstr "Spécifique au revêtement" -# Source-Hash: dafa151b183daa77 +# English: "Cross" +# Source-Hash: d500c4b7827d3390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnHeight.b9sq6y" -msgstr "Hauteur de reprise" +msgid "editor.inspector.option.cross.1elubzv" +msgstr "Croix" -# Source-Hash: cd55fd029ad2803b +# English: "Cubby" +# Source-Hash: 7ca7f00b3ba531f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnShape.1e3a27i" -msgstr "Forme de reprise" +msgid "editor.inspector.option.cubby.ivq4qc" +msgstr "Casier" -# Source-Hash: 6a7c411667124177 +# English: "Curved" +# Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnWidth.z9r3h3" -msgstr "Largeur de reprise" +msgid "editor.inspector.option.curved.6noz1s" +msgstr "Courbe" -# Source-Hash: 460bd11bfc2e5697 +# English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightEndCondition.tjlw3l" -msgstr "Condition d’extrémité droite" +msgid "editor.inspector.option.cylinder.eol6vj" +msgstr "Cylindre" -# Source-Hash: 1ed35f8d27ae5583 +# English: "Damper" +# Source-Hash: 53f224e2828d5ab7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightOverhang.yqbeft" -msgstr "Débord droit" +msgid "editor.inspector.option.damper.wgri80" +msgstr "Registre" -# Source-Hash: 39c02aa2d97a62ba +# English: "Destination" +# Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofHeight.1uhy6qy" -msgstr "Hauteur du toit" +msgid "editor.inspector.option.destination.gfzm1r" +msgstr "Destination" -# Source-Hash: d62f8833d77ca8a1 +# English: "Diffuser" +# Source-Hash: bfb3561e28d92a39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofStyle.pawqfy" -msgstr "Style de toit" +msgid "editor.inspector.option.diffuser.1igj0jh" +msgstr "Diffuseur" -# Source-Hash: 0ddd561a4126b826 +# English: "Dog ear" +# Source-Hash: 6cff941e10ff22b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofThickness.1xlr3rx" -msgstr "Épaisseur du toit" +msgid "editor.inspector.option.dogEar.xhtvjt" +msgstr "Oreille de chien" -# Source-Hash: 3a08ede9e51d5395 +# English: "Dome" +# Source-Hash: de47e23eb81efe02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofType.1m9kv5h" -msgstr "Type de toit" +msgid "editor.inspector.option.dome.1c87e56" +msgstr "Dôme" -# Source-Hash: 01989595ee7c1dc0 +# English: "Double" +# Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rotation.ly2rj" -msgstr "Rotation" +msgid "editor.inspector.option.double.13h5354" +msgstr "Double" -# Source-Hash: c913b494c3901a17 +# English: "Duct board" +# Source-Hash: 72a84a9762461745 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rows.1ba1neg" -msgstr "Rangées" +msgid "editor.inspector.option.ductBoard.lc3o4t" +msgstr "Panneau de gaine" -# Source-Hash: 8210f376c0d29acc +# English: "Dutch" +# Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.scale.b6rpxt" -msgstr "Échelle" +msgid "editor.inspector.option.dutch.1djx429" +msgstr "Demi-croupe" -# Source-Hash: adb6face4bac5245 +# English: "Elbow" +# Source-Hash: e6637bddef6aabd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seam detail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.seamDetail.197za48" -msgstr "Détail de joint" +msgid "editor.inspector.option.elbow.1flup74" +msgstr "Coude" -# Source-Hash: 4b2aed9d56cc4dee +# English: "End" +# Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.segmentType.l667sy" -msgstr "Type de segment" +msgid "editor.inspector.option.end.1llf32i" +msgstr "Fin" -# Source-Hash: a7794a81141b4de4 +# English: "End cap" +# Source-Hash: ada9fc2eec2c7d61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftStyle.1i5ampa" -msgstr "Style de gaine" +msgid "editor.inspector.option.endCap.1p4v4na" +msgstr "Embout" -# Source-Hash: 8eb6438543b18cdb +# English: "Equal fit" +# Source-Hash: 73916b8395208874 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftWallThickness.bduslr" -msgstr "Épaisseur des parois de gaine" +msgid "editor.inspector.option.equalFit.11vw52c" +msgstr "Ajustement égal" -# Source-Hash: 85ea1d3c8b749680 +# English: "Fixed count" +# Source-Hash: 83579a6e420df2ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape.iq9t52" -msgstr "Forme" +msgid "editor.inspector.option.fixedCount.1o3dznu" +msgstr "Nombre fixe" -# Source-Hash: 44a8ab89c854cad1 +# English: "Fixed spacing" +# Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape2.fi0nx8" -msgstr "Forme 2" +msgid "editor.inspector.option.fixedSpacing.hl4dr4" +msgstr "Espacement fixe" -# Source-Hash: fcee9712879570ae +# English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed high side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shedHighSide.bzzddg" -msgstr "Côté haut du monopente" +msgid "editor.inspector.option.flat.ly0bei" +msgstr "Plat" -# Source-Hash: f5a931cd6e97e5e2 +# English: "Flex" +# Source-Hash: a9cad0badcc16693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shingleThickness.1hn5wxr" -msgstr "Épaisseur des bardeaux" +msgid "editor.inspector.option.flex.ktpamq" +msgstr "Flexible" -# Source-Hash: b14990b3e4222dd3 +# English: "Floating" +# Source-Hash: 7843c5142b7d066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder extent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderExtent.11j8uiv" -msgstr "Étendue de l’épaulement" +msgid "editor.inspector.option.floating.1oadpln" +msgstr "Flottant" -# Source-Hash: 505de343ec5010e6 +# English: "Floor" +# Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderHeight.xyqn6g" -msgstr "Hauteur de l’épaulement" +msgid "editor.inspector.option.floor.q9caf1" +msgstr "Niveau" -# Source-Hash: 58d48c01b7ec1a40 +# English: "Flush" +# Source-Hash: eaaa20f3b603140f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderStyle.1hkcv2w" -msgstr "Style d’épaulement" +msgid "editor.inspector.option.flush.olwwu5" +msgstr "Affleurant" -# Source-Hash: af84db1026bf2e6c +# English: "Front" +# Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show center column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showCenterColumn.1uggg45" -msgstr "Afficher la colonne centrale" +msgid "editor.inspector.option.front.uhugl4" +msgstr "Avant" -# Source-Hash: 80214dc086ebc1b1 +# English: "Furnace" +# Source-Hash: bccfc0565157dae6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showInfill.33rv7u" -msgstr "Afficher le remplissage" +msgid "editor.inspector.option.furnace.w0mzpf" +msgstr "Chaudière à air chaud" -# Source-Hash: f02d6cdfb4c6ad08 +# English: "Gable" +# Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show step supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showStepSupports.1kg8h8m" -msgstr "Afficher les supports de marche" +msgid "editor.inspector.option.gable.1ekyx6" +msgstr "Deux pans" -# Source-Hash: 5d1147c774718b76 +# English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side flashing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sideFlashing.cv744o" -msgstr "Solin latéral" +msgid "editor.inspector.option.gambrel.1yo90d9" +msgstr "Brisé" -# Source-Hash: 60bb3338e0ad558c +# English: "Generic" +# Source-Hash: 3c4900155c96b139 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.size.1a4x3zw" -msgstr "Taille" +#, fuzzy +msgid "editor.inspector.option.generic.1074oey" +msgstr "Générique" -# Source-Hash: fbdbc6c97c75b030 +# English: "Glass" +# Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.skylightType.1a5vj3i" -msgstr "Type de puits de lumière" +msgid "editor.inspector.option.glass.163tc5n" +msgstr "Vitrage" -# Source-Hash: c9a6561cb3c3da82 +# English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab opening mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slabOpeningMode.g4f730" -msgstr "Mode de trémie de dalle" +msgid "editor.inspector.option.glassFrame.jp9s3m" +msgstr "Cadre vitré" -# Source-Hash: ef0efc845ccdba78 +# English: "Globe" +# Source-Hash: 2d4223f80dc3945c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slatGap.slmk93" -msgstr "Écart entre lames" +msgid "editor.inspector.option.globe.1avkqoy" +msgstr "Globe" -# Source-Hash: 4daab7a2ce2ecef3 +# English: "Grounded" +# Source-Hash: 398af4f45ba60c85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slideDirection.1527q13" -msgstr "Sens de coulissement" +msgid "editor.inspector.option.grounded.jwoq73" +msgstr "Au sol" -# Source-Hash: 93b34a5b546664c4 +# English: "Guard" +# Source-Hash: 4972a0e68d69be58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.span.1bmmuw1" -msgstr "Portée" +msgid "editor.inspector.option.guard.1m575se" +msgstr "Garde-corps" -# Source-Hash: 3b006be0b1f0493f +# English: "Half round" +# Source-Hash: 67be98efb58a7767 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.speed.2w2xwg" -msgstr "Vitesse" +msgid "editor.inspector.option.halfRound.m5hob0" +msgstr "Demi-rond" -# Source-Hash: c8cfb8336a240cb1 +# English: "Hidden" +# Source-Hash: 229199467e9cf2db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spin speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.spinSpeed.1ozx746" -msgstr "Vitesse de rotation" +msgid "editor.inspector.option.hidden.gp48rd" +msgstr "Masqué" -# Source-Hash: c9f54c3b258961c7 +# English: "Hip" +# Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stairType.1u05e02" -msgstr "Type d’escalier" +msgid "editor.inspector.option.hip.r0jnjy" +msgstr "Croupe" -# Source-Hash: 0af6194a8b8d8be6 +# English: "Horizontal" +# Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoff.4wv2g0" -msgstr "Entretoise" +msgid "editor.inspector.option.horizontal.tyapah" +msgstr "Horizontal" -# Source-Hash: 0464d4efa1e80c62 +# English: "Include" +# Source-Hash: 6a9b4142ab179557 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoffHeight.1qdnff" -msgstr "Hauteur d’entretoise" +msgid "editor.inspector.option.include.1a27vnr" +msgstr "Inclure" -# Source-Hash: 85da28b0b26b8c2a +# English: "Independent high beam" +# Source-Hash: 8061b426b9d1b5fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.startPost.1ood9kv" -msgstr "Poteau de départ" +msgid "editor.inspector.option.independentHighBeam.1tn0zvy" +msgstr "Poutre haute indépendante" -# Source-Hash: d5dc1ba8cca3193d +# English: "Industrial" +# Source-Hash: 1dc686f2068f976b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stepCount.nzorrw" -msgstr "Nombre de marches" +msgid "editor.inspector.option.industrial.8sd9u" +msgstr "Industriel" -# Source-Hash: cadbb9cadd6a5e6f +# English: "Inline" +# Source-Hash: 0c9b1ec8947ee177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapSpacing.1cyt3dc" -msgstr "Espacement des feuillards" +msgid "editor.inspector.option.inline.1uh05lg" +msgstr "En ligne" -# Source-Hash: c017cd56b3ae5865 +# English: "Integrated" +# Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapStyle.fh1oy4" -msgstr "Style de feuillard" +msgid "editor.inspector.option.integrated.1xd9lv0" +msgstr "Intégré" -# Source-Hash: 1a79a485d91f3f28 +# English: "Joined" +# Source-Hash: bb197b9219f32603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.style.nf3vcm" -msgstr "Style" +msgid "editor.inspector.option.joined.1x2o8ds" +msgstr "Joint" -# Source-Hash: 7b3f016989c631a7 +# English: "Kickout" +# Source-Hash: 0299cc0ee05d17fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Suction diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.suctionDiameter.nutxtj" -msgstr "Diamètre d’aspiration" +msgid "editor.inspector.option.kickout.vt8s6p" +msgstr "Déflecteur" -# Source-Hash: 3f1e0fd30e5b1f39 +# English: "Knee" +# Source-Hash: a0712c8461bb0758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyDiameter.h5ldh5" -msgstr "Diamètre de soufflage" +msgid "editor.inspector.option.knee.186hcui" +msgstr "Jambette" -# Source-Hash: 05c9b608538e181d +# English: "K style" +# Source-Hash: 083febe9876348ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyHeight.zy6ru1" -msgstr "Hauteur de soufflage" +msgid "editor.inspector.option.kStyle.m8kfwz" +msgstr "Style K" -# Source-Hash: 3c9400722baa474d +# English: "Landing" +# Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyShape.k39wnb" -msgstr "Forme de soufflage" +msgid "editor.inspector.option.landing.tyqarg" +msgstr "Palier" -# Source-Hash: af7619025438f995 +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyWidth.g66axi" -msgstr "Largeur de soufflage" +msgid "editor.inspector.option.lantern.1h7o2xb" +msgstr "Lanterneau" -# Source-Hash: 617e198cf3f214d3 +# English: "Leave" +# Source-Hash: 9477f663422698ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Support offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supportOffset.1w4cocr" -msgstr "Décalage du support" +msgid "editor.inspector.option.leave.1xf0sbk" +msgstr "Quitter" -# Source-Hash: 2686abc093f3dfb1 +# English: "Left" +# Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sweepAngle.ucmmlw" -msgstr "Angle de balayage" +msgid "editor.inspector.option.left.14n06mo" +msgstr "Gauche" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Level" +# Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.system.13qbhrw" -msgstr "Système" +msgid "editor.inspector.option.level.i4lhtp" +msgstr "Niveau" -# Source-Hash: ccb3afc48bd9a284 +# English: "Low profile" +# Source-Hash: a281a83a9fef6b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminal.1501knl" -msgstr "Terminal" +msgid "editor.inspector.option.lowProfile.1h9lgzg" +msgstr "Profil bas" -# Source-Hash: de1777c521046f7d +# English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminalType.1ne8lgb" -msgstr "Type de terminal" +msgid "editor.inspector.option.mansard.dwzck9" +msgstr "Mansarde" -# Source-Hash: fdbb503f0941bd56 +# English: "Manual" +# Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.thickness.1l8hu45" -msgstr "Épaisseur" +msgid "editor.inspector.option.manual.1wu7r43" +msgstr "Manuel" -# Source-Hash: 58cb60c14826f1d0 +# English: "Maximum spacing" +# Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.tiltAngle.1312yw1" -msgstr "Angle d’inclinaison" +msgid "editor.inspector.option.maximumSpacing.n1fzl4" +msgstr "Espacement maximal" -# Source-Hash: 7057972ff4c78dc7 +# English: "Metal" +# Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.toLevelId.8i5qx7" -msgstr "ID du niveau d’arrivée" +msgid "editor.inspector.option.metal.1az9fgc" +msgstr "Métal" -# Source-Hash: fa8fc3d5c612e774 +# English: "Metal panel" +# Source-Hash: c6185ecb219a9435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingDepth.ki7gk2" -msgstr "Profondeur du palier d’arrivée" +msgid "editor.inspector.option.metalPanel.1xdz476" +msgstr "Panneau métallique" -# Source-Hash: 6e4a0b98089ec2cd +# English: "Minimal" +# Source-Hash: 2fd92342f3986843 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingMode.zk0h7e" -msgstr "Mode du palier d’arrivée" +msgid "editor.inspector.option.minimal.fwmiwg" +msgstr "Minimal" -# Source-Hash: c182bb3c8a701085 +# English: "Mono" +# Source-Hash: 5d8a5912056f83ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top rail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topRailHeight.fscisv" -msgstr "Hauteur de la lisse haute" +msgid "editor.inspector.option.mono.oszvd6" +msgstr "Monopente" -# Source-Hash: 8c5502f1d8087b7b +# English: "None" +# Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionMode.f3evgt" -msgstr "Mode de transition" +msgid "editor.inspector.option.none.deku7v" +msgstr "Aucun" -# Source-Hash: 96eb5997f0aa2dde +# English: "Omit" +# Source-Hash: 7b6e4a6b86ba5112 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionWidth.rtgz3m" -msgstr "Largeur de transition" +msgid "editor.inspector.option.omit.okcs7w" +msgstr "Omettre" -# Source-Hash: 812d27902509339f +# English: "Open" +# Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vane count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.vaneCount.55vtye" -msgstr "Nombre d’ailettes" +msgid "editor.inspector.option.open.n6hn1l" +msgstr "Ouvrir" -# Source-Hash: 1e7dcd717c8ac239 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallHeight.18vmh5c" -msgstr "Hauteur du mur" +msgid "editor.inspector.option.opening.dfet3" +msgstr "Ouverture" -# Source-Hash: f14bdaed6101896a +# English: "Open rack" +# Source-Hash: cc72b9bda6b30b5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallShell.vqsyhv" -msgstr "Enveloppe murale" +msgid "editor.inspector.option.openRack.mvt1x0" +msgstr "Étagère ouverte" -# Source-Hash: 801268585c9a679b +# English: "Oval" +# Source-Hash: ed6d7b3dd9d5fc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall skirt height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" -msgstr "Hauteur du soubassement" +msgid "editor.inspector.option.oval.1jiqzxx" +msgstr "Ovale" -# Source-Hash: 6b377d0ba1565477 +# English: "Picket" +# Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallThickness.17wn4iv" -msgstr "Épaisseur du mur" +msgid "editor.inspector.option.picket.zyeoy1" +msgstr "Piquets" -# Source-Hash: 3a0ccd6c46877a87 +# English: "Pointed" +# Source-Hash: 180ffb8b75c977aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width.gfxz9b" -msgstr "Largeur" +msgid "editor.inspector.option.pointed.effedk" +msgstr "Pointu" -# Source-Hash: de05070474f589b4 +# English: "Post and rail" +# Source-Hash: fca7919fb0c0669c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width2.1nqawvr" -msgstr "Largeur 2" +msgid "editor.inspector.option.postAndRail.lajk2u" +msgstr "Poteaux et lisses" -# Source-Hash: 6199b641888a83fa +# English: "Preserve high edge" +# Source-Hash: a78b28a428b27e20 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBack.1ay8nus" -msgstr "Avec fond" +msgid "editor.inspector.option.preserveHighEdge.y6ywf0" +msgstr "Conserver le bord haut" -# Source-Hash: 699934d8b9571b64 +# English: "Preserve low edge" +# Source-Hash: 325c6f53f6dc7634 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBottom.1x1z1vo" -msgstr "Avec fond inférieur" +msgid "editor.inspector.option.preserveLowEdge.196m5pk" +msgstr "Conserver le bord bas" -# Source-Hash: 03987b9aadaab50f +# English: "Preserve pitch" +# Source-Hash: 95c0b860d22a62cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With sides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withSides.uq066x" -msgstr "Avec côtés" +msgid "editor.inspector.option.preservePitch.1uq973r" +msgstr "Conserver la pente" -# Source-Hash: f571cb2d06ae01ae +# English: "Privacy" +# Source-Hash: d4e11841abf22593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Advanced" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.advanced.qwfkor" -msgstr "Avancé" +msgid "editor.inspector.option.privacy.1th4hrb" +msgstr "Intimité" -# Source-Hash: a886619d15bae47e +# English: "Purlins" +# Source-Hash: 1a8c46c7712a83b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.air.15jotdj" -msgstr "Air" +msgid "editor.inspector.option.purlins.od124c" +msgstr "Pannes" -# Source-Hash: b69fc1f8128958fc +# English: "Pvc" +# Source-Hash: a9650f6f94de9247 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.appearance.dli9wf" -msgstr "Apparence" +msgid "editor.inspector.option.pvc.1oc4u2w" +msgstr "PVC" -# Source-Hash: faaa4f6b40001667 +# English: "Pyramid" +# Source-Hash: 47587b825e9a696b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.body.ign4wl" -msgstr "Corps" +msgid "editor.inspector.option.pyramid.17ucsqv" +msgstr "Pyramide" -# Source-Hash: fdf7108a011bd94b +# English: "Rafters" +# Source-Hash: a9cb232546779779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cab.o4z1ox" -msgstr "Cabine" +msgid "editor.inspector.option.rafters.16tzbby" +msgstr "Chevrons" -# Source-Hash: 5737c4567e0f6863 +# English: "Rail" +# Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cabinet.rzvymr" -msgstr "Meuble" +msgid "editor.inspector.option.rail.39zqc9" +msgstr "Lisse" -# Source-Hash: d52b53241789ee63 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cap.s0thej" -msgstr "Chapeau" +msgid "editor.inspector.option.raised.4kyx9h" +msgstr "Surélevé" -# Source-Hash: 93a545688c917e69 +# English: "Rect" +# Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.collar.1am2iqo" -msgstr "Collier" +msgid "editor.inspector.option.rect.tpfvaf" +msgstr "Rect." -# Source-Hash: 7a9c55b1c5a6e61d +# English: "Reducer" +# Source-Hash: 68206f70689b2340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connection.2r1h4p" -msgstr "Raccordement" +msgid "editor.inspector.option.reducer.18byt67" +msgstr "Réduction" -# Source-Hash: d613df280aa13b42 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connections.ai4w8u" -msgstr "Raccordements" +msgid "editor.inspector.option.return.1klrjrz" +msgstr "Reprise" -# Source-Hash: ce1429556f57d7ee +# English: "Return grille" +# Source-Hash: 63b39b1681f74fe4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.construction.j6p9kc" -msgstr "Construction" +msgid "editor.inspector.option.returnGrille.r28ncm" +msgstr "Grille de reprise" -# Source-Hash: f8ba7d73d3006d57 +# English: "Right" +# Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cricket.6iwg8g" -msgstr "Costière" +msgid "editor.inspector.option.right.8humpx" +msgstr "Droite" -# Source-Hash: b8ff053666ee326b +# English: "Round" +# Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curb.5xi2ol" -msgstr "Costière" +msgid "editor.inspector.option.round.1vx0wyj" +msgstr "Rond" -# Source-Hash: 3b9c59e996dcc9c5 +# English: "Rounded" +# Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve points" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curvePoints.ztdcnn" -msgstr "Points de courbe" +msgid "editor.inspector.option.rounded.199nwpm" +msgstr "Arrondi" -# Source-Hash: 12df69f42beb0643 +# English: "Sanitary tee" +# Source-Hash: dc67a1cdb2482f23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dimensions.pewa8g" -msgstr "Dimensions" +msgid "editor.inspector.option.sanitaryTee.oro3d4" +msgstr "Té sanitaire" -# Source-Hash: 20f60b98de67849b +# English: "Scalloped" +# Source-Hash: beb3fec22c287185 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Doors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.doors.18w2kpa" -msgstr "Portes" +msgid "editor.inspector.option.scalloped.1eltba0" +msgstr "Festonné" -# Source-Hash: 6b6f8068bbc4c902 +# English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormer.1fjs5tq" -msgstr "Lucarne" +msgid "editor.inspector.option.scoop.qy5zdv" +msgstr "Écope" -# Source-Hash: cda262793b4cc754 +# English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormerRoof.u9uy2g" -msgstr "Toit de lucarne" +msgid "editor.inspector.option.segmentedPanel.5r3hwl" +msgstr "Panneau segmenté" -# Source-Hash: 00a4416e0a33d856 +# English: "Shed" +# Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.drainage.7easy0" -msgstr "Évacuation" +msgid "editor.inspector.option.shed.6t994v" +msgstr "Appentis" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Sheet metal" +# Source-Hash: 7fbc26cd497cfd12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.elevation.4jbp44" -msgstr "Altitude" +msgid "editor.inspector.option.sheetMetal.l164lv" +msgstr "Tôle" -# Source-Hash: a9cba06250228a55 +# English: "Shingle" +# Source-Hash: 26e2c261223cbb46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.endCaps.4fsb5b" -msgstr "Embouts" +msgid "editor.inspector.option.shingle.pfmynh" +msgstr "Bardeau" -# Source-Hash: 228f2206862e41cf +# English: "Shingled" +# Source-Hash: e9046e9823b400a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.equipment.1xtq0ev" -msgstr "Équipement" +msgid "editor.inspector.option.shingled.1nm9inf" +msgstr "En bardeaux" -# Source-Hash: a19276baa3688cbf +# English: "Simple" +# Source-Hash: a05a390ae205fdd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.face.1531fr0" -msgstr "Face" +msgid "editor.inspector.option.simple.12mfu73" +msgstr "Simple" -# Source-Hash: fb34ee7c59cb84ac +# English: "Single" +# Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.fitting.171z1r4" -msgstr "Raccord" +msgid "editor.inspector.option.single.1uzxurt" +msgstr "Simple" -# Source-Hash: f817c09ef0bb6d69 +# English: "Single left" +# Source-Hash: aee3b2bd78c87d0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flues" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.flues.nq6pye" -msgstr "Conduits" +msgid "editor.inspector.option.singleLeft.1atdh02" +msgstr "Simple gauche" -# Source-Hash: a3c10560fa3c1612 +# English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.footprint.g8q2pg" -msgstr "Emprise" +msgid "editor.inspector.option.singleRight.1axbu67" +msgstr "Simple droite" -# Source-Hash: ce3a6c03765dbbd8 +# English: "Slant box" +# Source-Hash: 7672d2764445c110 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.frame.q7q9fq" -msgstr "Cadre" +msgid "editor.inspector.option.slantBox.1ta99vy" +msgstr "Boîte inclinée" -# Source-Hash: 523b9912e283b644 +# English: "Slat" +# Source-Hash: 405e07a8cc2c88e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.geometry.a21os1" -msgstr "Géométrie" +msgid "editor.inspector.option.slat.1p9njbz" +msgstr "Lame" -# Source-Hash: 7e4a0a38f694be6b +# English: "Sliding" +# Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.grid.d8gwxd" -msgstr "Grille" +msgid "editor.inspector.option.sliding.cd0jox" +msgstr "Coulissante" -# Source-Hash: 30d8dba84c42040e +# English: "Slope" +# Source-Hash: e6d82e606b8c9301 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hangers.agnfzn" -msgstr "Supports" +msgid "editor.inspector.option.slope.1bn2qw" +msgstr "Pente" -# Source-Hash: 8c1bc3a078056ff7 +# English: "Sloped" +# Source-Hash: f6998707123fe72d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hardware.1nygwm1" -msgstr "Quincaillerie" +msgid "editor.inspector.option.sloped.lo4sh0" +msgstr "En pente" -# Source-Hash: 2a47557eb4a5f766 +# English: "Solid" +# Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hung wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hungWall.1ftr1gz" -msgstr "Mur suspendu" +msgid "editor.inspector.option.solid.o4uy6s" +msgstr "Plein" -# Source-Hash: 7d6ed24c02ab0c5c +# English: "Solid panel" +# Source-Hash: 8008b6f17089beaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.insulation.1kyo5e7" -msgstr "Isolation" +msgid "editor.inspector.option.solidPanel.2yaw62" +msgstr "Panneau plein" -# Source-Hash: d4c6c2495c233bb5 +# English: "Spiral" +# Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lantern.1h7o2xb" -msgstr "Lanterneau" +msgid "editor.inspector.option.spiral.91hcl4" +msgstr "Hélicoïdal" -# Source-Hash: cc3d601d59e2e26c +# English: "Splash" +# Source-Hash: beef1cf79388b77b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.levels.1s6yaru" -msgstr "Niveaux" +msgid "editor.inspector.option.splash.1uke4cm" +msgstr "Pare-éclaboussures" -# Source-Hash: 1a849e52a46d5087 +# English: "Spread" +# Source-Hash: 324677a79ab15e22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.line.166mv5z" -msgstr "Ligne" +msgid "editor.inspector.option.spread.1g1zysu" +msgstr "Écartement" -# Source-Hash: 9ab4f7a738ebee13 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lines.12bghr0" -msgstr "Lignes" +msgid "editor.inspector.option.square.1xl1lk6" +msgstr "Carré" -# Source-Hash: c0ff16d77ebc9593 +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Motion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.motion.1be7sjr" -msgstr "Mouvement" +msgid "editor.inspector.option.stair.1xf86e" +msgstr "Escalier" -# Source-Hash: 81d365ccef73068c +# English: "Standard" +# Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.mounting.feutd2" -msgstr "Montage" +msgid "editor.inspector.option.standard.vuco" +msgstr "Standard" -# Source-Hash: e04298381837252f +# English: "Start" +# Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.opening.dfet3" -msgstr "Ouverture" +msgid "editor.inspector.option.start.30xvgf" +msgstr "Début" -# Source-Hash: f46314c54627858d +# English: "Step" +# Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.panelDimensions.b59qe6" -msgstr "Dimensions des panneaux" +msgid "editor.inspector.option.step.jtn9kf" +msgstr "Gradin" -# Source-Hash: 4712c449e9a42d3b +# English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.patternDistribution.fmam51" -msgstr "Répartition du motif" +msgid "editor.inspector.option.stepped.naxzh0" +msgstr "En gradins" -# Source-Hash: b3be684ac62fbf53 +# English: "Straight" +# Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.placement.13lcvts" -msgstr "Placement" +msgid "editor.inspector.option.straight.lxqbfr" +msgstr "Droit" -# Source-Hash: 9da0cbfad144d737 +# English: "Strap" +# Source-Hash: b9b45ac5ba1daeee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.position.1quewx6" -msgstr "Position" +msgid "editor.inspector.option.strap.14q0ps9" +msgstr "Feuillard" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.profile.1kwlci6" -msgstr "Profil" +msgid "editor.inspector.option.supply.1oy2zwu" +msgstr "Soufflage" -# Source-Hash: 240ed9c77627b9a1 +# English: "Supply register" +# Source-Hash: 00e7a2b3b2aa36ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.railing.19a5bnr" -msgstr "Garde-corps" +msgid "editor.inspector.option.supplyRegister.1lzdbbn" +msgstr "Bouche de soufflage" -# Source-Hash: 2e0aa7113b021c67 +# English: "Tapered" +# Source-Hash: f86f1f22230596b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.return.1klrjrz" -msgstr "Reprise" +msgid "editor.inspector.option.tapered.3y5blo" +msgstr "Effilé" -# Source-Hash: 83909f82446ee9c3 +# English: "Target spacing" +# Source-Hash: 5fed23c2217b83fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.settings.ktdb8o" -msgstr "Paramètres" +msgid "editor.inspector.option.targetSpacing.14uibc9" +msgstr "Espacement cible" -# Source-Hash: 4fc63ad21de30103 +# English: "Tee" +# Source-Hash: f3ceed7f867f090f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shaft.mad9cv" -msgstr "Gaine" +msgid "editor.inspector.option.tee.7d6tjp" +msgstr "Té" -# Source-Hash: 85ea1d3c8b749680 +# English: "Tilted" +# Source-Hash: 787005b26aec58a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shape.iq9t52" -msgstr "Forme" +msgid "editor.inspector.option.tilted.6n0u5j" +msgstr "Incliné" -# Source-Hash: 8c6e3985459dacf9 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shoulder.1nyfx25" -msgstr "Épaulement" +msgid "editor.inspector.option.top.2nypcs" +msgstr "Haut" -# Source-Hash: 60bb3338e0ad558c +# English: "Transition" +# Source-Hash: e6657000554dfe53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.size.1a4x3zw" -msgstr "Taille" +msgid "editor.inspector.option.transition.1rziz9e" +msgstr "Transition" -# Source-Hash: a20c89205a4448d2 +# English: "Vent" +# Source-Hash: 04bc0909a133a7e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.structure.gezdra" -msgstr "Structure" +msgid "editor.inspector.option.vent.xda5j2" +msgstr "Ventilation" -# Source-Hash: 1a79a485d91f3f28 +# English: "Walk on" +# Source-Hash: 7eee9aff0dd520b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.style.nf3vcm" -msgstr "Style" +msgid "editor.inspector.option.walkOn.1n5sg9l" +msgstr "Marcher sur" -# Source-Hash: b45f382a67d6c0eb +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.supply.1oy2zwu" -msgstr "Soufflage" +msgid "editor.inspector.option.wall.blj585" +msgstr "Mur" -# Source-Hash: 78a454b7ac7a3cd9 +# English: "Wall abutment" +# Source-Hash: f3578f7177ad0054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.surface.wtamqk" -msgstr "Surface" +msgid "editor.inspector.option.wallAbutment.lza941" +msgstr "Appui contre le mur" -# Source-Hash: ccb3afc48bd9a284 +# English: "Wall ledger" +# Source-Hash: 8dcf182f91ba3245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.terminal.1501knl" -msgstr "Terminal" +msgid "editor.inspector.option.wallLedger.5m8ana" +msgstr "Lisse murale" -# Source-Hash: 399fc3e62f491f08 +# English: "Wall shelf" +# Source-Hash: 0adda30be5f17975 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Topology" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.topology.18t8dyc" -msgstr "Topologie" +msgid "editor.inspector.option.wallShelf.yiozfl" +msgstr "Étagère murale" -# Source-Hash: f425799b82e085ed +# English: "Waste" +# Source-Hash: 29d8590334492601 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transform" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.transform.1c55ayj" -msgstr "Transformation" +msgid "editor.inspector.option.waste.3xrp79" +msgstr "Eaux usées" -# Source-Hash: b778a315e82065a0 +# English: "Wye" +# Source-Hash: 00249876bb37079f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.trap.1qc7vhe" -msgstr "Siphon" +msgid "editor.inspector.option.wye.14vupc8" +msgstr "Culotte" -# Source-Hash: c44f7ab8bbd24858 +# English: "Collapse" +# Source-Hash: 179b025e25f9a091 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.group.type.1m2zofh" -msgstr "Type" +msgid "editor.layers.collapse" +msgstr "Réduire" -# Source-Hash: c8fd17853839516f +# English: "Ceiling ({area})" +# Source-Hash: dbeae4c43f49327f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Abs" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.abs.132mz8b" -msgstr "ABS" +msgid "editor.layers.defaultName.ceiling" +msgstr "Plafond ({area})" -# Source-Hash: fd9eb1a16cb6b996 +# English: "Collection" +# Source-Hash: d29165c9d4bfc3cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Access panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.accessPanel.13sptb5" -msgstr "Trappe de visite" +msgid "editor.layers.defaultName.collection" +msgstr "Collection" -# Source-Hash: db8064add8c27a5c +# English: "Flight ({width}×{length}m)" +# Source-Hash: 8f536b859be662a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air handler" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.airHandler.1crnyg3" -msgstr "Centrale de traitement d’air" +msgid "editor.layers.defaultName.flight" +msgstr "Volée ({width}×{length}m)" -# Source-Hash: d8234782848be927 +# English: "Landing ({width}×{length}m)" +# Source-Hash: 46dcbf4548374073 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alternating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.alternating.5jashi" -msgstr "Alterné" +msgid "editor.layers.defaultName.landing" +msgstr "Palier ({width}×{length}m)" -# Source-Hash: 63cbda7b09662368 +# English: "Node" +# Source-Hash: 3ea44453fcd08a6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arched" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.arched.14188w0" -msgstr "Cintré" +msgid "editor.layers.defaultName.node" +msgstr "Nœud" -# Source-Hash: 05b65242d8c48791 +# English: "Roof ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 93f3a04e15b6fb49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.auto.540b06" -msgstr "Auto" +msgid "editor.layers.defaultName.roof" +msgstr "Toit ({count, plural, one {# segment} many {# segments} other {# segments}})" -# Source-Hash: d18036b75b2c3e4f +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" +# Source-Hash: a730fab01c172704 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.automatic.r122ro" -msgstr "Automatique" +msgid "editor.layers.defaultName.roofSegment" +msgstr "{roofType, select, hip {Croupe} gable {Pignon} shed {Monopente} gambrel {Gambrel} dutch {Demi-croupe} mansard {Mansarde} flat {Plat} conical {Conique} barrel {Voûte} other {Toit}} ({width}×{depth}m)" -# Source-Hash: 7ceade8f25b58fe9 +# English: "Room ({area})" +# Source-Hash: 54efcb0756c2f2c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.back.1hzmxtu" -msgstr "Retour" +msgid "editor.layers.defaultName.room" +msgstr "Pièce ({area})" -# Source-Hash: 3eda29328f91485d +# English: "Slab ({area})" +# Source-Hash: e38a4b9986a9d21a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.balusters.1j3w6ng" -msgstr "Balustres" +msgid "editor.layers.defaultName.slab" +msgstr "Dalle ({area})" -# Source-Hash: f211248ec8619e4c +# English: "Staircase ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 57398365d4b9066f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Band" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.band.160umau" -msgstr "Bandeau" +msgid "editor.layers.defaultName.staircase" +msgstr "Escalier ({count, plural, one {# segment} many {# segments} other {# segments}})" -# Source-Hash: 8f9ff05c6a3b08a6 +# English: "Zone ({area})" +# Source-Hash: a17f8fc29aae896e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base plate" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.basePlate.17fcg8k" -msgstr "Platine" +msgid "editor.layers.defaultName.zone" +msgstr "Zone ({area})" -# Source-Hash: 19ae2360207ecf5a +# English: "Expand" +# Source-Hash: 5cabcabb8bc14809 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.option.boards.u14hig" -msgstr "Planches" +msgid "editor.layers.expand" +msgstr "Développer" -# Source-Hash: c0fee00bdfb09d37 +# English: "{levelName} options" +# Source-Hash: 6731e9dd94e8b268 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bookshelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bookshelf.ls0zqk" -msgstr "Bibliothèque" +msgid "editor.layers.levelOptions" +msgstr "Options de {levelName}" -# Source-Hash: 69578b7dd7723fa1 +# English: "Rename" +# Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx #, fuzzy -msgid "editor.inspector.option.both.mkyb42" -msgstr "Les deux" +msgid "editor.layers.rename" +msgstr "Renommer" -# Source-Hash: b3a163b453896bef +# English: "{role, select, room {Room colour} other {Zone colour}}" +# Source-Hash: 21eafa019ad62fea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bottom.9mpruy" -msgstr "Bas" +msgid "editor.layers.spaceColour" +msgstr "{role, select, room {Couleur de la pièce} other {Couleur de la zone}}" -# Source-Hash: e9a89a5e4ac8cd61 +# English: "Unit colour" +# Source-Hash: f5de042416a2c04f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.box.1nbjcuq" -msgstr "Boîte" +msgid "editor.layers.unitColour" +msgstr "Couleur du logement" -# Source-Hash: 2c2815df4f724abe +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Break" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.break.17spnvc" -msgstr "Rupture" +msgid "editor.levelDuplicateDialog.cancel" +msgstr "Annuler" -# Source-Hash: 0d5a25303d3d8058 +# English: "Choose what to copy from {levelName}." +# Source-Hash: 07c11e7e4d4681d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Butterfly" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.butterfly.pvv0g4" -msgstr "Papillon" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" +msgstr "Choisissez ce qu’il faut copier depuis {levelName}." -# Source-Hash: 5e2429ced8144ab4 +# English: "Choose what to copy from this level." +# Source-Hash: 9500d4378a319532 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cable.fp50zq" -msgstr "Câble" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" +msgstr "Choisissez ce qu’il faut copier depuis ce niveau." -# Source-Hash: 69129e514347672a +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cast iron" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.castIron.1q2z4qk" -msgstr "Fonte" +msgid "editor.levelDuplicateDialog.duplicate" +msgstr "Dupliquer" -# Source-Hash: 4275c05172abd7fb +# English: "Duplicate Level" +# Source-Hash: a56e3eb43e60b45c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.ceiling.1t2zaqo" -msgstr "Plafond" +msgid "editor.levelDuplicateDialog.duplicateLevel" +msgstr "Dupliquer le niveau" -# Source-Hash: 5211341ed18f33f6 +# English: "Everything" +# Source-Hash: e4c6ab1ea473a082 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.center.kzdxno" -msgstr "Centre" +msgid "editor.levelDuplicateDialog.everything" +msgstr "Tout" -# Source-Hash: 65f410261609060b +# English: "Structure, finishes, and placed items, without guide references." +# Source-Hash: ac3f119e560388ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.centerOpening.bn1rvw" -msgstr "Ouverture centrale" +msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" +msgstr "Structure, finitions et éléments placés, sans références de calque." -# Source-Hash: cf5f9756cc866f3e +# English: "Structure + furniture" +# Source-Hash: acbdf968391d5273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cleanout.lww8bm" -msgstr "Regard de nettoyage" +msgid "editor.levelDuplicateDialog.structureFurniture" +msgstr "Structure + mobilier" -# Source-Hash: e71d1d949f1d1b41 +# English: "Structure + materials" +# Source-Hash: ce682864a140fe75 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Concrete pad" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.concretePad.m1zdij" -msgstr "Dalle béton" +msgid "editor.levelDuplicateDialog.structureMaterials" +msgstr "Structure + matériaux" -# Source-Hash: a3804c4f5e6aefbc +# English: "Structure, materials, furniture, and references." +# Source-Hash: 5ee44d53847c5ea2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Condenser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.condenser.a74nps" -msgstr "Condenseur" +msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" +msgstr "Structure, matériaux, mobilier et références." -# Source-Hash: 6786ac75cbe9af57 +# English: "Structure only" +# Source-Hash: 95aa1b43dbdb3f02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corbeled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.corbeled.bovs4v" -msgstr "En encorbellement" +msgid "editor.levelDuplicateDialog.structureOnly" +msgstr "Structure seule" -# Source-Hash: 8a4beb9a57edc26e +# English: "Structure with the current material and finish assignments." +# Source-Hash: 2c53b46c9ca9fe2f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.count.1qoi4r8" -msgstr "Nombre" +msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" +msgstr "Structure avec les matériaux et finitions actuellement attribués." -# Source-Hash: db1f8f141714b363 +# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." +# Source-Hash: 3350e9008eaba15d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coupling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.coupling.111aap2" -msgstr "Manchon" +msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" +msgstr "Murs, dalles, toits, escaliers, fenêtres et portes, sans finitions." -# Source-Hash: 7a9a92d2e59a3bbc +# English: "Copy" +# Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering specific" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.coveringSpecific.1h9fo8w" -msgstr "Spécifique au revêtement" +msgid "editor.levelWallCopyPrompt.copy" +msgstr "Copier" -# Source-Hash: d500c4b7827d3390 +# English: "Copy exterior walls from the level {direction}?" +# Source-Hash: 8ccaf4c98e335cc8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cross" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cross.1elubzv" -msgstr "Croix" +msgid "editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel" +msgstr "Copier les murs extérieurs du niveau {direction, select, below {inférieur} above {supérieur} other {{direction}}} ?" -# Source-Hash: 7ca7f00b3ba531f4 +# English: "No thanks" +# Source-Hash: f6ad0e813d2945bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cubby" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cubby.ivq4qc" -msgstr "Casier" +msgid "editor.levelWallCopyPrompt.noThanks" +msgstr "Non merci" -# Source-Hash: a028e08962c388e0 +# English: "{sourceName}’s outside walls, in place, so this level lines up with it." +# Source-Hash: d2aecc3c47120ad5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.curved.6noz1s" -msgstr "Courbe" +msgid "editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace" +msgstr "Les murs extérieurs du niveau {sourceName}, au même emplacement, pour que ce niveau s’y aligne." -# Source-Hash: c65f02bcdc3231d6 +# English: "Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies." +# Source-Hash: b6317f13da65f81e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cylinder" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.cylinder.eol6vj" -msgstr "Cylindre" +msgid "editor.lib.buildingFoundation.basementExcavation" +msgstr "Déblai de {depth} côté haut, creusé à l’intérieur des faces extérieures des murs ; le terrain autour de la maison reste tel quel." -# Source-Hash: 53f224e2828d5ab7 +# English: "The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 16c97d232e4c05dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.damper.wgri80" -msgstr "Registre" +msgid "editor.lib.buildingFoundation.basementTopOverTheGround" +msgstr "Le dessus du mur de sous-sol se trouve à {distance} au-dessus du point haut du terrain ; la fondation est réglée pour {asked} au-dessus." -# Source-Hash: 748877ae309c313c +# English: "The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 9b781eb9749c9af3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.destination.gfzm1r" -msgstr "Destination" +msgid "editor.lib.buildingFoundation.basementTopUnderTheGround" +msgstr "Le dessus du mur de sous-sol se trouve à {distance} sous le point haut du terrain ; la fondation est réglée pour {asked} au-dessus." -# Source-Hash: bfb3561e28d92a39 +# English: "Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1)." +# Source-Hash: 6903234208cb7eb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diffuser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.diffuser.1igj0jh" -msgstr "Diffuseur" +msgid "editor.lib.buildingFoundation.crawlSpaceDugOut" +msgstr "Vide sanitaire : creusé jusqu’à {dug} pour {clearance} sous les solives (IRC R317.1)." -# Source-Hash: 6cff941e10ff22b3 +# English: "Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall." +# Source-Hash: fee27efc9440d33c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dog ear" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dogEar.xhtvjt" -msgstr "Oreille de chien" +msgid "editor.lib.buildingFoundation.crippleWalls" +msgstr "Au-delà de {stem} de soubassement hors du sol, le soubassement redescend à {least} au-dessus du sol : {cripples, plural, one {# muret d’ossature} many {# murets d’ossature} other {# murets d’ossature}} par-dessus, jusqu’à {tallest} de haut." -# Source-Hash: de47e23eb81efe02 +# English: "The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it." +# Source-Hash: d9f9c3b0c3978923 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dome" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dome.1c87e56" -msgstr "Dôme" +msgid "editor.lib.buildingFoundation.daylightWalls" +msgstr "Le terrain descend de {fall} d’un côté à l’autre de la maison : les murs côté bas dépassent du sol jusqu’à {exposed}." -# Source-Hash: 3cd71355956a1821 +# English: "That is {fill} of fill. A raised floor on a stem wall suits a floor this high." +# Source-Hash: a62040ee19eb7321 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.double.13h5354" -msgstr "Double" +msgid "editor.lib.buildingFoundation.deepFill" +msgstr "Cela représente {fill} de remblai. Un plancher surélevé sur mur de soubassement convient à un plancher aussi haut." -# Source-Hash: 72a84a9762461745 +# English: "That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope." +# Source-Hash: 7f8966c9c28a5d64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct board" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.ductBoard.lc3o4t" -msgstr "Panneau de gaine" +msgid "editor.lib.buildingFoundation.deepFillOnASlope" +msgstr "Cela représente {fill} de remblai côté bas. Un plancher surélevé sur mur de soubassement convient à cette pente." -# Source-Hash: 6949d667043bee0b +# English: "The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: a2434c0de9dae5e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dutch.1djx429" -msgstr "Demi-croupe" +msgid "editor.lib.buildingFoundation.floorOverTheGround" +msgstr "Le plancher se trouve à {distance} au-dessus du point haut du terrain ; la fondation est réglée pour {asked} au-dessus." -# Source-Hash: e6637bddef6aabd6 +# English: "The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 6dcf21da285e3910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.elbow.1flup74" -msgstr "Coude" +msgid "editor.lib.buildingFoundation.floorUnderTheGround" +msgstr "Le plancher se trouve à {distance} sous le point haut du terrain ; la fondation est réglée pour {asked} au-dessus." -# Source-Hash: 135874d189176d13 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: b4e4be1da470f91e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.end.1llf32i" -msgstr "Fin" +msgid "editor.lib.buildingFoundation.footingSteps" +msgstr "Le terrain descend de {fall} d’un côté à l’autre de la maison : la semelle forme {steps, plural, one {# redan} many {# redans} other {# redans}} le long de son côté le plus pentu, de {riser} chacun." -# Source-Hash: ada9fc2eec2c7d61 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: bea524db96134378 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.endCap.1p4v4na" -msgstr "Embout" +msgid "editor.lib.buildingFoundation.footingStepsUpTo" +msgstr "Le terrain descend de {fall} d’un côté à l’autre de la maison : la semelle forme {steps, plural, one {# redan} many {# redans} other {# redans}} le long de son côté le plus pentu, jusqu’à {riser} chacun." -# Source-Hash: 73916b8395208874 +# English: "The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1))." +# Source-Hash: 31192a74aa068c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal fit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.equalFit.11vw52c" -msgstr "Ajustement égal" +msgid "editor.lib.buildingFoundation.footingTooNarrow" +msgstr "La semelle fait {width} de large : {storeys, plural, one {un étage exige} many {# étages exigent} other {# étages exigent}} {asked} sur un sol à 1500 psf (IRC Table R403.1(1))." -# Source-Hash: 83579a6e420df2ec +# English: "The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: c9f4ef98e9cd7d9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedCount.1o3dznu" -msgstr "Nombre fixe" +msgid "editor.lib.buildingFoundation.footingTooShallow" +msgstr "Le fond de la semelle est à {depth} sous le sol : gardez-le à au moins {least}, ou à la profondeur hors gel (IRC R403.1.4)." -# Source-Hash: 4fb65101d0b1a222 +# English: "The ground is held {least} under the slab’s top on every side: cut {cut}." +# Source-Hash: 8e9be841759f6e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedSpacing.hl4dr4" -msgstr "Espacement fixe" +msgid "editor.lib.buildingFoundation.gradingCut" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : déblai de {cut}." -# Source-Hash: 9cab4d4c95f63cdd +# English: "The ground is held {least} under the slab’s top on every side: cut {cut} on the high side." +# Source-Hash: 779f127732e5589b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flat.ly0bei" -msgstr "Plat" +msgid "editor.lib.buildingFoundation.gradingCutOnASlope" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : déblai de {cut} côté haut." -# Source-Hash: a9cad0badcc16693 +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}." +# Source-Hash: 954523c1f6ed230c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flex" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flex.ktpamq" -msgstr "Flexible" +msgid "editor.lib.buildingFoundation.gradingFilled" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : remblai de {fill}." -# Source-Hash: 7843c5142b7d066d +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}." +# Source-Hash: b55f69797190bfee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floating.1oadpln" -msgstr "Flottant" +msgid "editor.lib.buildingFoundation.gradingFilledAndCut" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : remblai de {fill}, déblai de {cut}." -# Source-Hash: d7148fa065bc709b +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side." +# Source-Hash: bae5f3ae42970e8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floor.q9caf1" -msgstr "Niveau" +msgid "editor.lib.buildingFoundation.gradingFilledAndCutOnASlope" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : remblai jusqu’à {fill} côté bas, déblai de {cut} côté haut." -# Source-Hash: eaaa20f3b603140f +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side." +# Source-Hash: 4a06302380f3f85d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flush" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flush.olwwu5" -msgstr "Affleurant" +msgid "editor.lib.buildingFoundation.gradingFilledOnASlope" +msgstr "Le terrain est maintenu à {least} sous le dessus de la dalle sur tout le pourtour : remblai jusqu’à {fill} côté bas." -# Source-Hash: e360f89d3c0ec456 +# English: "The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less." +# Source-Hash: 64931230719b598b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.front.uhugl4" -msgstr "Avant" +msgid "editor.lib.buildingFoundation.groundCutRoundTheStem" +msgstr "Le terrain est déblayé de {cut} côté haut pour maintenir le dessus du mur de soubassement à {least} au-dessus (IRC R317.1). Relever le plancher réduit le déblai." -# Source-Hash: bccfc0565157dae6 +# English: "Raise it on a stem wall" +# Source-Hash: 3613643e58e4b6d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.furnace.w0mzpf" -msgstr "Chaudière à air chaud" +msgid "editor.lib.buildingFoundation.raiseItOnAStemWall" +msgstr "Surélever sur un mur de soubassement" -# Source-Hash: deaffa095d126dfe +# English: "Raise the floor" +# Source-Hash: f69b48072be7f6ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gable.1ekyx6" -msgstr "Deux pans" +msgid "editor.lib.buildingFoundation.raiseTheFloor" +msgstr "Relever le plancher" -# Source-Hash: 7f53d9ffca39984a +# English: "Seat the basement wall’s top {asked} over the high side of the ground" +# Source-Hash: c1e5c4d64f7952a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gambrel.1yo90d9" -msgstr "Brisé" +msgid "editor.lib.buildingFoundation.seatBasementWallTop" +msgstr "Placer le dessus du mur de sous-sol à {asked} au-dessus du point haut du terrain" -# Source-Hash: 3c4900155c96b139 +# English: "Seat the floor’s top {asked} over the high side of the ground" +# Source-Hash: 3f82cd72a6654fdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Generic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.generic.1074oey" -msgstr "Générique" +msgid "editor.lib.buildingFoundation.seatFloorTop" +msgstr "Placer le dessus du plancher à {asked} au-dessus du point haut du terrain" -# Source-Hash: 8ab91094cf650204 +# English: "Seat the ground floor {asked} over grade" +# Source-Hash: b4bbe36aae57a0d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glass.163tc5n" -msgstr "Vitrage" +msgid "editor.lib.buildingFoundation.seatGroundFloor" +msgstr "Placer le rez-de-chaussée à {asked} au-dessus du sol" -# Source-Hash: 88810658a2b97bc6 +# English: "Seat it at {asked}" +# Source-Hash: bc5256a4749abb35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass frame" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glassFrame.jp9s3m" -msgstr "Cadre vitré" +msgid "editor.lib.buildingFoundation.seatItAt" +msgstr "Placer à {asked}" -# Source-Hash: 2d4223f80dc3945c +# English: "Seat the raised floor’s top {asked} over the high side of the ground" +# Source-Hash: 2becc7a7c8c48a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Globe" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.globe.1avkqoy" -msgstr "Globe" +msgid "editor.lib.buildingFoundation.seatRaisedFloorTop" +msgstr "Placer le dessus du plancher surélevé à {asked} au-dessus du point haut du terrain" -# Source-Hash: 398af4f45ba60c85 +# English: "Seat the slab’s top {asked} over the high side of the ground" +# Source-Hash: 88e009e2eb3ec2a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.grounded.jwoq73" -msgstr "Au sol" +msgid "editor.lib.buildingFoundation.seatSlabTop" +msgstr "Placer le dessus de la dalle à {asked} au-dessus du point haut du terrain" -# Source-Hash: 4972a0e68d69be58 +# English: "Set it {height}" +# Source-Hash: ccb9193f87264256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.guard.1m575se" -msgstr "Garde-corps" +msgid "editor.lib.buildingFoundation.setIt" +msgstr "Régler à {height}" -# Source-Hash: 67be98efb58a7767 +# English: "Set it {depth} deep" +# Source-Hash: e004c8ca1e6b6117 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.halfRound.m5hob0" -msgstr "Demi-rond" +msgid "editor.lib.buildingFoundation.setItDeep" +msgstr "Approfondir à {depth}" -# Source-Hash: 229199467e9cf2db +# English: "The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1)." +# Source-Hash: e884fcda7ee630ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hidden" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hidden.gp48rd" -msgstr "Masqué" +msgid "editor.lib.buildingFoundation.sillTooNearTheGround" +msgstr "La lisse et l’ossature du plancher sont à {height} au-dessus du sol : gardez-les à au moins {least} du sol (IRC R317.1)." -# Source-Hash: 9eda0ea8d447066d +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: fe154a985943c7ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hip.r0jnjy" -msgstr "Croupe" +msgid "editor.lib.buildingFoundation.slabEdgeSteps" +msgstr "Le terrain descend de {fall} d’un côté à l’autre de la maison : la bêche de la dalle forme {steps, plural, one {# redan} many {# redans} other {# redans}} le long de son côté le plus pentu, de {riser} chacun." -# Source-Hash: ac9c79f08ef130a1 +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: ee469b2069a20e5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.horizontal.tyapah" -msgstr "Horizontal" +msgid "editor.lib.buildingFoundation.slabEdgeStepsUpTo" +msgstr "Le terrain descend de {fall} d’un côté à l’autre de la maison : la bêche de la dalle forme {steps, plural, one {# redan} many {# redans} other {# redans}} le long de son côté le plus pentu, jusqu’à {riser} chacun." -# Source-Hash: 6a9b4142ab179557 +# English: "The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: e45d256982a22d6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.include.1a27vnr" -msgstr "Inclure" +msgid "editor.lib.buildingFoundation.slabEdgeTooShallow" +msgstr "Le fond de la bêche est à {depth} sous le sol : gardez-le à au moins {least}, ou à la profondeur hors gel (IRC R403.1.4)." -# Source-Hash: 8061b426b9d1b5fa +# English: "Widen it to {width}" +# Source-Hash: 70fa99a8b7e7c762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Independent high beam" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.independentHighBeam.1tn0zvy" -msgstr "Poutre haute indépendante" +msgid "editor.lib.buildingFoundation.widenItTo" +msgstr "Élargir à {width}" -# Source-Hash: 1dc686f2068f976b +# English: "Add or remove objects from the selection" +# Source-Hash: 395fd0821b203517 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Industrial" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.industrial.8sd9u" -msgstr "Industriel" +msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" +msgstr "Ajouter ou retirer des objets de la sélection" -# Source-Hash: 0c9b1ec8947ee177 +# English: "Adjust one side’s corner radius" +# Source-Hash: 8428cb0af9c6d6a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inline" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.inline.1uh05lg" -msgstr "En ligne" +msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" +msgstr "Ajuster le rayon d’angle d’un côté" -# Source-Hash: 88d5b9ecb242c650 -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" -#: packages/nodes/src/i18n/definition-messages.ts +# English: "Clear the selection (or click outside)" +# Source-Hash: d63b45c195713993 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.integrated.1xd9lv0" -msgstr "Intégré" +msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" +msgstr "Effacer la sélection (ou cliquer à l’extérieur)" -# Source-Hash: bb197b9219f32603 +# English: "Click a handle dot to show move arrows" +# Source-Hash: 40b1afbeadbd6213 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Joined" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.joined.1x2o8ds" -msgstr "Joint" +msgid "editor.lib.contextualHelp.clickAHandleDotToShow" +msgstr "Cliquez sur une poignée pour afficher les flèches de déplacement" -# Source-Hash: 0299cc0ee05d17fc +# English: "Click or drag the selection to move it as one" +# Source-Hash: 0b82a35c71549580 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kickout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kickout.vt8s6p" -msgstr "Déflecteur" +msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" +msgstr "Cliquez ou faites glisser la sélection pour la déplacer d’un bloc" -# Source-Hash: a0712c8461bb0758 +# English: "Click the handle dot to show move + rotate handles" +# Source-Hash: 73a761f393f69cb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Knee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.knee.186hcui" -msgstr "Jambette" +msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" +msgstr "Cliquez sur la poignée pour afficher les poignées de déplacement et de rotation" -# Source-Hash: 083febe9876348ec +# English: "Detach the joint while dragging an arrow" +# Source-Hash: c0045cef191fee69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "K style" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kStyle.m8kfwz" -msgstr "Style K" +msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" +msgstr "Détacher la jonction en faisant glisser une flèche" -# Source-Hash: de9566487b17a73b +# English: "Drag left or right to rotate selected object" +# Source-Hash: a019c8948c104a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.landing.tyqarg" -msgstr "Palier" +msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" +msgstr "Faites glisser à gauche ou à droite pour faire pivoter l’objet sélectionné" -# Source-Hash: d4c6c2495c233bb5 +# English: "Drag selected movable object" +# Source-Hash: 7db0364deb12f06e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lantern.1h7o2xb" -msgstr "Lanterneau" +msgid "editor.lib.contextualHelp.dragSelectedMovableObject" +msgstr "Faire glisser l’objet mobile sélectionné" -# Source-Hash: 9477f663422698ae +# English: "Group selection (session only)" +# Source-Hash: 0f258dd389afdc73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leave" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.leave.1xf0sbk" -msgstr "Quitter" +msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" +msgstr "Grouper la sélection (session uniquement)" -# Source-Hash: f365c6b6dd9ebab9 +# English: "Hold to rotate freely" +# Source-Hash: 85330d62893a45c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.left.14n06mo" -msgstr "Gauche" +msgid "editor.lib.contextualHelp.holdToRotateFreely" +msgstr "Maintenez pour pivoter librement" -# Source-Hash: 0608743fc1736d86 +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.level.i4lhtp" -msgstr "Niveau" +msgid "editor.lib.contextualHelp.rotate45" +msgstr "Pivoter de ±45°" -# Source-Hash: a281a83a9fef6b88 +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low profile" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lowProfile.1h9lgzg" -msgstr "Profil bas" +msgid "editor.lib.contextualHelp.rotateTheSelection45" +msgstr "Pivoter la sélection de ±45°" -# Source-Hash: 7f7bb0e875af3260 +# English: "Rotating freely (no angle step)" +# Source-Hash: 0f6b418b18ff96f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.mansard.dwzck9" -msgstr "Mansarde" +msgid "editor.lib.contextualHelp.rotatingFreely" +msgstr "Rotation libre (sans pas d’angle)" -# Source-Hash: 387082cd5f118bed +# English: "Switch the rotation axis (Y → X → Z)" +# Source-Hash: 7ed174d6b03563b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.manual.1wu7r43" -msgstr "Manuel" +msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" +msgstr "Changer l’axe de rotation (Y → X → Z)" -# Source-Hash: 321ca5101ab61db7 +# English: "Ungroup session selection" +# Source-Hash: d6f2c5cea683de62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.maximumSpacing.n1fzl4" -msgstr "Espacement maximal" +msgid "editor.lib.contextualHelp.ungroupSessionSelection" +msgstr "Dissocier la sélection de session" -# Source-Hash: 8e464a8f52254f27 +# English: "Continuous" +# Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metal.1az9fgc" -msgstr "Métal" +msgid "editor.lib.continuation.continuous" +msgstr "Continu" -# Source-Hash: c6185ecb219a9435 +# English: "Continuous canopy" +# Source-Hash: ebf0eb7af4636a16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metalPanel.1xdz476" -msgstr "Panneau métallique" +msgid "editor.lib.continuation.continuousCanopy" +msgstr "Auvent continu" -# Source-Hash: 2fd92342f3986843 +# English: "Continuous run" +# Source-Hash: 1d8e87cd807e8d40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Minimal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.minimal.fwmiwg" -msgstr "Minimal" +msgid "editor.lib.continuation.continuousRun" +msgstr "Rangée continue" -# Source-Hash: 5d8a5912056f83ce +# English: "Curved fence" +# Source-Hash: 8990683667567c6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mono" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.mono.oszvd6" -msgstr "Monopente" +msgid "editor.lib.continuation.curvedFence" +msgstr "Clôture courbe" -# Source-Hash: f2953d2931e36d0c +# English: "Freehand fence" +# Source-Hash: edf1b9a2ad3ab910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.none.deku7v" -msgstr "Aucun" +msgid "editor.lib.continuation.freehandFence" +msgstr "Clôture libre" -# Source-Hash: 7b6e4a6b86ba5112 +# English: "Place multiple" +# Source-Hash: 2cdb7c1c7cd7c4fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Omit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.omit.okcs7w" -msgstr "Omettre" +msgid "editor.lib.continuation.placeMultiple" +msgstr "Placer plusieurs" -# Source-Hash: 3e52f04f92e08070 +# English: "Place once" +# Source-Hash: 90bf945df6f9c979 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.open.n6hn1l" -msgstr "Ouvrir" +msgid "editor.lib.continuation.placeOnce" +msgstr "Placer une fois" -# Source-Hash: e04298381837252f +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.opening.dfet3" -msgstr "Ouverture" +msgid "editor.lib.continuation.polygonRoom" +msgstr "Pièce polygonale" -# Source-Hash: cc72b9bda6b30b5c +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rack" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.openRack.mvt1x0" -msgstr "Étagère ouverte" +msgid "editor.lib.continuation.rectangleRoom" +msgstr "Pièce rectangulaire" -# Source-Hash: ed6d7b3dd9d5fc69 +# English: "Single cabinet" +# Source-Hash: 7fd85d0408141157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Oval" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.oval.1jiqzxx" -msgstr "Ovale" +msgid "editor.lib.continuation.singleCabinet" +msgstr "Meuble seul" -# Source-Hash: 9295ddc2805b21e2 +# English: "Single canopy" +# Source-Hash: dd1bdbc26165baa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.picket.zyeoy1" -msgstr "Piquets" +msgid "editor.lib.continuation.singleCanopy" +msgstr "Auvent seul" -# Source-Hash: 180ffb8b75c977aa +# English: "Single fence" +# Source-Hash: 5fb1a69316be983e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pointed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.pointed.effedk" -msgstr "Pointu" +msgid "editor.lib.continuation.singleFence" +msgstr "Clôture simple" -# Source-Hash: fca7919fb0c0669c +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post and rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.postAndRail.lajk2u" -msgstr "Poteaux et lisses" +msgid "editor.lib.continuation.walls" +msgstr "Murs" -# Source-Hash: a78b28a428b27e20 +# English: "Cut opening" +# Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve high edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveHighEdge.y6ywf0" -msgstr "Conserver le bord haut" +msgid "editor.lib.hudTitle.cutOpening" +msgstr "Découper une ouverture" -# Source-Hash: 325c6f53f6dc7634 +# English: "Divide room" +# Source-Hash: 60e8d235f3084a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve low edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveLowEdge.196m5pk" -msgstr "Conserver le bord bas" +msgid "editor.lib.hudTitle.divideRoom" +msgstr "Diviser la pièce" -# Source-Hash: 95c0b860d22a62cb +# English: "Edit {kind}" +# Source-Hash: aa496ec33d21ab03 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve pitch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preservePitch.1uq973r" -msgstr "Conserver la pente" +msgid "editor.lib.hudTitle.editKind" +msgstr "Modifier : {kind}" -# Source-Hash: d4e11841abf22593 +# English: "Erase" +# Source-Hash: f3ea3efa7102844f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Privacy" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.privacy.1th4hrb" -msgstr "Intimité" +msgid "editor.lib.hudTitle.erase" +msgstr "Effacer" -# Source-Hash: 1a8c46c7712a83b6 +# English: "Level terrain" +# Source-Hash: 3f907e76526472bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlins" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.purlins.od124c" -msgstr "Pannes" +msgid "editor.lib.hudTitle.levelTerrain" +msgstr "Niveler le terrain" -# Source-Hash: a9650f6f94de9247 +# English: "Lower terrain" +# Source-Hash: 4e911c300d74eb47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pvc" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pvc.1oc4u2w" -msgstr "PVC" +msgid "editor.lib.hudTitle.lowerTerrain" +msgstr "Abaisser le terrain" -# Source-Hash: 47587b825e9a696b +# English: "Mezzanine" +# Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pyramid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pyramid.17ucsqv" -msgstr "Pyramide" +msgid "editor.lib.hudTitle.mezzanine" +msgstr "Mezzanine" -# Source-Hash: a9cb232546779779 +# English: "Move {kind}" +# Source-Hash: c48c4129c9ed249a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rafters.16tzbby" -msgstr "Chevrons" +msgid "editor.lib.hudTitle.moveKind" +msgstr "Déplacer : {kind}" -# Source-Hash: 947eacd2689daced +# English: "Move room" +# Source-Hash: 89f6b7ca8904c656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rail.39zqc9" -msgstr "Lisse" +msgid "editor.lib.hudTitle.moveRoom" +msgstr "Déplacer la pièce" -# Source-Hash: bd50bd18e682db41 +# English: "Move selection" +# Source-Hash: 9c707069572fb3db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.raised.4kyx9h" -msgstr "Surélevé" +msgid "editor.lib.hudTitle.moveSelection" +msgstr "Déplacer la sélection" -# Source-Hash: 3b6f225112035fde +# English: "Paint" +# Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rect.tpfvaf" -msgstr "Rect." +msgid "editor.lib.hudTitle.paint" +msgstr "Peindre" -# Source-Hash: 68206f70689b2340 +# English: "Paint a rectangle" +# Source-Hash: ca197314cb70aa56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reducer" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.reducer.18byt67" -msgstr "Réduction" +msgid "editor.lib.hudTitle.paintARectangle" +msgstr "Peindre un rectangle" -# Source-Hash: 2e0aa7113b021c67 +# English: "Paint a shape" +# Source-Hash: ea4ce729e17254f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.return.1klrjrz" -msgstr "Reprise" +msgid "editor.lib.hudTitle.paintAShape" +msgstr "Peindre une forme" -# Source-Hash: 63b39b1681f74fe4 +# English: "Pick material" +# Source-Hash: 01359502b07284d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return grille" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.returnGrille.r28ncm" -msgstr "Grille de reprise" +msgid "editor.lib.hudTitle.pickMaterial" +msgstr "Prélever un matériau" -# Source-Hash: b34d5ee0f218221d +# English: "Raise terrain" +# Source-Hash: 4066e8a3f60b1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.right.8humpx" -msgstr "Droite" +msgid "editor.lib.hudTitle.raiseTerrain" +msgstr "Surélever le terrain" -# Source-Hash: ad0457800a1f0eb4 +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.round.1vx0wyj" -msgstr "Rond" +msgid "editor.lib.hudTitle.resize" +msgstr "Redimensionner" -# Source-Hash: fc826da6c99303e6 +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rounded.199nwpm" -msgstr "Arrondi" +msgid "editor.lib.hudTitle.rotate" +msgstr "Pivoter" -# Source-Hash: dc67a1cdb2482f23 +# English: "Select" +# Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sanitary tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.sanitaryTee.oro3d4" -msgstr "Té sanitaire" +msgid "editor.lib.hudTitle.select" +msgstr "Sélectionner" -# Source-Hash: beb3fec22c287185 +# English: "Smart measure" +# Source-Hash: ec1f1d68af469867 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scalloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scalloped.1eltba0" -msgstr "Festonné" +msgid "editor.lib.hudTitle.smartMeasure" +msgstr "Mesure intelligente" -# Source-Hash: e16f85e4bebfefb1 +# English: "Smooth terrain" +# Source-Hash: 5ba5f22bd1a69e09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scoop" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.option.scoop.qy5zdv" -msgstr "Écope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts +#, fuzzy +msgid "editor.lib.hudTitle.smoothTerrain" +msgstr "Lisser le terrain" -# Source-Hash: f9a95d8407fafda4 +# English: "Split {kind}" +# Source-Hash: cbb4687261739397 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segmented panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.segmentedPanel.5r3hwl" -msgstr "Panneau segmenté" +msgid "editor.lib.hudTitle.splitKind" +msgstr "Diviser : {kind}" -# Source-Hash: c8d0df27faa3feaa +# English: "Terrace" +# Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.shed.6t994v" -msgstr "Appentis" +msgid "editor.lib.hudTitle.terrace" +msgstr "Terrasse" -# Source-Hash: 7fbc26cd497cfd12 +# English: "{title} ({exterior} exterior, {interior} interior)" +# Source-Hash: 322452fdf0850c8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheet metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/tool-shortcuts.ts #, fuzzy -msgid "editor.inspector.option.sheetMetal.l164lv" -msgstr "Tôle" +msgid "editor.lib.toolShortcuts.wallsTooltip" +msgstr "{title} (extérieur : {exterior}, intérieur : {interior})" -# Source-Hash: 26e2c261223cbb46 +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingle.pfmynh" -msgstr "Bardeau" +msgid "editor.lib.wallDrawVariant.polygonRoom" +msgstr "Pièce polygonale" -# Source-Hash: e9046e9823b400a0 +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingled.1nm9inf" -msgstr "En bardeaux" +msgid "editor.lib.wallDrawVariant.rectangleRoom" +msgstr "Pièce rectangulaire" -# Source-Hash: a05a390ae205fdd8 +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Simple" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.simple.12mfu73" -msgstr "Simple" +msgid "editor.lib.wallDrawVariant.walls" +msgstr "Murs" -# Source-Hash: 62ab2eb3c775da95 +# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" +# Source-Hash: c2626e1fbf89b3c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/registry-action-contributions.tsx #, fuzzy -msgid "editor.inspector.option.single.1uzxurt" -msgstr "Simple" +msgid "editor.mechanismAction" +msgstr "{action, select, close {Fermer} open {Ouvrir} stop {Arrêter} other {Lancer}}" -# Source-Hash: aee3b2bd78c87d0b +# English: "Curve" +# Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleLeft.1atdh02" -msgstr "Simple gauche" +msgid "editor.nodeActionMenu.curve" +msgstr "Courbe" -# Source-Hash: cc43ffbef4ac7ae0 +# English: "Cut out" +# Source-Hash: 5af05fb6661e4d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleRight.1axbu67" -msgstr "Simple droite" +msgid "editor.nodeActionMenu.cutOut" +msgstr "Découper" -# Source-Hash: 7672d2764445c110 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slant box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slantBox.1ta99vy" -msgstr "Boîte inclinée" +msgid "editor.nodeActionMenu.delete" +msgstr "Supprimer" -# Source-Hash: 405e07a8cc2c88e1 +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slat.1p9njbz" -msgstr "Lame" +msgid "editor.nodeActionMenu.duplicate" +msgstr "Dupliquer" -# Source-Hash: 18cbc73c5e1590aa +# English: "Edit mesh" +# Source-Hash: 94b212bd79c05141 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sliding.cd0jox" -msgstr "Coulissante" +msgid "editor.nodeActionMenu.editMesh" +msgstr "Modifier le maillage" -# Source-Hash: e6d82e606b8c9301 +# English: "Find in catalog" +# Source-Hash: ce90cf37224f73cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slope.1bn2qw" -msgstr "Pente" +msgid "editor.nodeActionMenu.findInCatalog" +msgstr "Trouver dans le catalogue" -# Source-Hash: f6998707123fe72d +# English: "Group selection" +# Source-Hash: d5e550c345e7161c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sloped.lo4sh0" -msgstr "En pente" +msgid "editor.nodeActionMenu.groupSelection" +msgstr "Grouper la sélection" -# Source-Hash: ef3e765e692faf4c +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solid.o4uy6s" -msgstr "Plein" +msgid "editor.nodeActionMenu.move" +msgstr "Déplacer" -# Source-Hash: 8008b6f17089beaf +# English: "Rotate left" +# Source-Hash: 473a9d4463761107 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solidPanel.2yaw62" -msgstr "Panneau plein" +msgid "editor.nodeActionMenu.rotateLeft" +msgstr "Pivoter à gauche" -# Source-Hash: f9a4a870cb3d3c41 +# English: "Rotate right" +# Source-Hash: b1bf4e65c8d8c8ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.spiral.91hcl4" -msgstr "Hélicoïdal" +msgid "editor.nodeActionMenu.rotateRight" +msgstr "Pivoter à droite" -# Source-Hash: beef1cf79388b77b +# English: "Ungroup selection" +# Source-Hash: 6f4dbbc4a3f58fca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Splash" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.splash.1uke4cm" -msgstr "Pare-éclaboussures" +msgid "editor.nodeActionMenu.ungroupSelection" +msgstr "Dissocier la sélection" -# Source-Hash: 324677a79ab15e22 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spread" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.spread.1g1zysu" -msgstr "Écartement" +msgid "editor.nodeDisplay.ceiling" +msgstr "Plafond" -# Source-Hash: 39287346366fc206 +# English: "Column" +# Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Square" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.square.1xl1lk6" -msgstr "Carré" +msgid "editor.nodeDisplay.column" +msgstr "Poteau" -# Source-Hash: 53d7084bc41c1ad6 +# English: "Door" +# Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stair.1xf86e" -msgstr "Escalier" +msgid "editor.nodeDisplay.door" +msgstr "Porte" -# Source-Hash: d998c212030bc7c2 +# English: "Elevator" +# Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.standard.vuco" -msgstr "Standard" +msgid "editor.nodeDisplay.elevator" +msgstr "Ascenseur" -# Source-Hash: 0a26a1e61c825660 +# English: "Fence" +# Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.start.30xvgf" -msgstr "Début" +msgid "editor.nodeDisplay.fence" +msgstr "Clôture" -# Source-Hash: ed3989f23a2d185e +# English: "Guide image" +# Source-Hash: 4c3ff8cdc6feda97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.step.jtn9kf" -msgstr "Gradin" +msgid "editor.nodeDisplay.guide" +msgstr "Image de calque" -# Source-Hash: 2c6b1cf17a973d67 +# English: "Item" +# Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stepped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stepped.naxzh0" -msgstr "En gradins" +msgid "editor.nodeDisplay.item" +msgstr "Élément" -# Source-Hash: 4ee35f740730ae87 +# English: "Roof" +# Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.straight.lxqbfr" -msgstr "Droit" +msgid "editor.nodeDisplay.roof" +msgstr "Toit" -# Source-Hash: b9b45ac5ba1daeee +# English: "Roof segment" +# Source-Hash: a130b4eec084c79a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.strap.14q0ps9" -msgstr "Feuillard" +msgid "editor.nodeDisplay.roofSegment" +msgstr "Segment de toit" -# Source-Hash: b45f382a67d6c0eb +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supply.1oy2zwu" -msgstr "Soufflage" +msgid "editor.nodeDisplay.room" +msgstr "Pièce" -# Source-Hash: 00e7a2b3b2aa36ac +# English: "3D Scan" +# Source-Hash: b22b2f20cc7cd416 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply register" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supplyRegister.1lzdbbn" -msgstr "Bouche de soufflage" +msgid "editor.nodeDisplay.scan" +msgstr "Relevé 3D" -# Source-Hash: f86f1f22230596b7 +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tapered" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tapered.3y5blo" -msgstr "Effilé" +msgid "editor.nodeDisplay.selection" +msgstr "Sélection" -# Source-Hash: 5fed23c2217b83fb +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.targetSpacing.14uibc9" -msgstr "Espacement cible" +msgid "editor.nodeDisplay.slab" +msgstr "Dalle" -# Source-Hash: f3ceed7f867f090f +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tee.7d6tjp" -msgstr "Té" +msgid "editor.nodeDisplay.stair" +msgstr "Escalier" -# Source-Hash: 787005b26aec58a3 +# English: "Stair segment" +# Source-Hash: 52de428c16bb81e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilted" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tilted.6n0u5j" -msgstr "Incliné" +msgid "editor.nodeDisplay.stairSegment" +msgstr "Segment d’escalier" -# Source-Hash: 27644f0497fd9849 +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.top.2nypcs" -msgstr "Haut" +msgid "editor.nodeDisplay.wall" +msgstr "Mur" -# Source-Hash: e6657000554dfe53 +# English: "Window" +# Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.transition.1rziz9e" -msgstr "Transition" +msgid "editor.nodeDisplay.window" +msgstr "Fenêtre" -# Source-Hash: 04bc0909a133a7e9 +# English: "Zone" +# Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vent" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.vent.xda5j2" -msgstr "Ventilation" +msgid "editor.nodeDisplay.zone" +msgstr "Zone" -# Source-Hash: 7eee9aff0dd520b6 +# English: "At the high side of the ground, to scale" +# Source-Hash: 59310bd629d358d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk on" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.walkOn.1n5sg9l" -msgstr "Marcher sur" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe" +msgstr "Au point haut du terrain, à l’échelle" -# Source-Hash: 2210e5ee50b74dc9 +# English: "At the high side of the ground, to scale; the dashed line is the grade" +# Source-Hash: 1d006e84523a03c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wall.blj585" -msgstr "Mur" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe2" +msgstr "Au point haut du terrain, à l’échelle ; le trait pointillé indique le niveau du sol" -# Source-Hash: f3578f7177ad0054 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall abutment" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallAbutment.lza941" -msgstr "Appui contre le mur" +msgid "editor.panels.buildingFoundationSection.auto" +msgstr "Auto" -# Source-Hash: 8dcf182f91ba3245 +# English: "Basement slab" +# Source-Hash: ca97295ab420e54a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall ledger" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallLedger.5m8ana" -msgstr "Lisse murale" +msgid "editor.panels.buildingFoundationSection.basementSlab" +msgstr "Dalle de sous-sol" -# Source-Hash: 0adda30be5f17975 +# English: "Basement wall" +# Source-Hash: 8691c5ac35304434 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallShelf.yiozfl" -msgstr "Étagère murale" +msgid "editor.panels.buildingFoundationSection.basementWall" +msgstr "Mur de sous-sol" -# Source-Hash: 29d8590334492601 +# English: "Poured concrete: 8 in for most basements (IRC Table R404.1.2(1))." +# Source-Hash: fecad6f58f5f6e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.waste.3xrp79" -msgstr "Eaux usées" +msgid "editor.panels.buildingFoundationSection.basementWallHint" +msgstr "Béton coulé : 8 in pour la plupart des sous-sols (IRC Table R404.1.2(1))." -# Source-Hash: 00249876bb37079f +# English: "Basement wall (its own)" +# Source-Hash: 1f0c089c4a7cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wye" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wye.14vupc8" -msgstr "Culotte" +msgid "editor.panels.buildingFoundationSection.basementWallItsOwn" +msgstr "Mur de sous-sol (indépendant)" -# Source-Hash: 179b025e25f9a091 +# English: "Basement wall {thickness}" +# Source-Hash: c672e131662d2766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.collapse" -msgstr "Réduire" +msgid "editor.panels.buildingFoundationSection.basementWallThickness" +msgstr "Mur de sous-sol {thickness}" -# Source-Hash: dbeae4c43f49327f +# English: "Basement wall top over grade" +# Source-Hash: 29a11cb98f792822 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.ceiling" -msgstr "Plafond ({area})" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGrade" +msgstr "Dessus du mur de sous-sol au-dessus du sol" -# Source-Hash: d29165c9d4bfc3cb +# English: "Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement." +# Source-Hash: 4f96c4f9effefccf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.collection" -msgstr "Collection" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGradeHint" +msgstr "Mesuré au-dessus du point le plus haut du sol le long de la maison : la lisse et l’ossature du plancher reposent dessus, à au moins 8 in du sol (IRC R317.1). Relevez-le pour un sous-sol semi-enterré." -# Source-Hash: 8f536b859be662a0 +# English: "Carpet" +# Source-Hash: e653669adc447902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.flight" -msgstr "Volée ({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.carpet" +msgstr "Moquette" -# Source-Hash: 46dcbf4548374073 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.landing" -msgstr "Palier ({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.ceiling" +msgstr "Plafond" -# Source-Hash: 3ea44453fcd08a6e +# English: "Ceramic tile" +# Source-Hash: 299c16d7ad11f5e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Node" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.node" -msgstr "Nœud" +msgid "editor.panels.buildingFoundationSection.ceramicTile" +msgstr "Carrelage céramique" -# Source-Hash: 93f3a04e15b6fb49 +# English: "Close the outside walls into a loop to draw the foundation under them." +# Source-Hash: 317b5b94671d871a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roof" -msgstr "Toit ({count, plural, one {# segment} many {# segments} other {# segments}})" +msgid "editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA" +msgstr "Fermez les murs extérieurs en boucle pour dessiner la fondation dessous." -# Source-Hash: a730fab01c172704 +# English: "Crawl space under joists" +# Source-Hash: 3e736505db2a19c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roofSegment" -msgstr "{roofType, select, hip {Croupe} gable {Pignon} shed {Monopente} gambrel {Gambrel} dutch {Demi-croupe} mansard {Mansarde} flat {Plat} conical {Conique} barrel {Voûte} other {Toit}} ({width}×{depth}m)" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoists" +msgstr "Vide sanitaire sous solives" -# Source-Hash: 54efcb0756c2f2c1 +# English: "Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it." +# Source-Hash: 61f898724893d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.room" -msgstr "Pièce ({area})" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint" +msgstr "Mesuré depuis le dessous des solives, qui portent sur la lisse (IRC R317.1 : 18 in jusqu’au sol). Le vide sanitaire est creusé jusqu’à cette cote." -# Source-Hash: e38a4b9986a9d21a +# English: "Custom" +# Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.slab" -msgstr "Dalle ({area})" +msgid "editor.panels.buildingFoundationSection.custom" +msgstr "Personnalisé" -# Source-Hash: 57398365d4b9066f +# English: "Delete the basement and it stands on its raised floor again." +# Source-Hash: 8629f9746eac5bfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.staircase" -msgstr "Escalier ({count, plural, one {# segment} many {# segments} other {# segments}})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor" +msgstr "Supprimez le sous-sol et la maison repose de nouveau sur son plancher surélevé." -# Source-Hash: a17f8fc29aae896e +# English: "Delete the basement and it stands on its slab again." +# Source-Hash: f99636a38ec14785 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.zone" -msgstr "Zone ({area})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementSlab" +msgstr "Supprimez le sous-sol et la maison repose de nouveau sur sa dalle." -# Source-Hash: 5cabcabb8bc14809 +# English: "{depth} deep" +# Source-Hash: 09f12189bbbf8cbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.expand" -msgstr "Développer" +msgid "editor.panels.buildingFoundationSection.depthDeep" +msgstr "{depth} de profondeur" -# Source-Hash: 6731e9dd94e8b268 +# English: "{depth} down" +# Source-Hash: 164f8aea5d46f232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{levelName} options" -#: packages/editor/src/components/ui/floating-level-selector.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.levelOptions" -msgstr "Options de {levelName}" +msgid "editor.panels.buildingFoundationSection.depthDown" +msgstr "{depth} sous le sol" -# Source-Hash: d6952b4e54d246dd +# English: "{depth} I-joist" +# Source-Hash: a6010b7488316ac1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.rename" -msgstr "Renommer" +msgid "editor.panels.buildingFoundationSection.depthIJoist" +msgstr "Poutrelle en I de {depth}" -# Source-Hash: 21eafa019ad62fea +# English: "Draw the foundation" +# Source-Hash: 4db57e6b9b2a22f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{role, select, room {Room colour} other {Zone colour}}" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx -#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.spaceColour" -msgstr "{role, select, room {Couleur de la pièce} other {Couleur de la zone}}" +msgid "editor.panels.buildingFoundationSection.drawTheFoundation" +msgstr "Dessiner la fondation" -# Source-Hash: f5de042416a2c04f +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit colour" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.unitColour" -msgstr "Couleur du logement" +msgid "editor.panels.buildingFoundationSection.drywall" +msgstr "Plaque de plâtre" -# Source-Hash: c0774c185060fc13 +# English: "Drywall ceiling" +# Source-Hash: cea6df381e71f25f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.cancel" -msgstr "Annuler" +msgid "editor.panels.buildingFoundationSection.drywallCeiling" +msgstr "Plafond en plaques de plâtre" -# Source-Hash: 07c11e7e4d4681d6 +# English: "Edge under grade" +# Source-Hash: 027b063ba3959c44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from {levelName}." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" -msgstr "Choisissez ce qu’il faut copier depuis {levelName}." +msgid "editor.panels.buildingFoundationSection.edgeUnderGrade" +msgstr "Bêche sous le sol" -# Source-Hash: 9500d4378a319532 +# English: "IRC R403.1.4: at least 12 in, or the frost line." +# Source-Hash: a114cbbf9fff17fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from this level." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" -msgstr "Choisissez ce qu’il faut copier depuis ce niveau." +msgid "editor.panels.buildingFoundationSection.edgeUnderGradeHint" +msgstr "IRC R403.1.4 : au moins 12 in, ou la profondeur hors gel." -# Source-Hash: 34dc96fe77a31e51 +# English: "Edge width" +# Source-Hash: ea863dda5621adfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicate" -msgstr "Dupliquer" +msgid "editor.panels.buildingFoundationSection.edgeWidth" +msgstr "Largeur de bêche" -# Source-Hash: a56e3eb43e60b45c +# English: "Edge {width} wide" +# Source-Hash: 542f041267191a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate Level" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicateLevel" -msgstr "Dupliquer le niveau" +msgid "editor.panels.buildingFoundationSection.edgeWidthWide" +msgstr "Bêche de {width} de large" -# Source-Hash: e4c6ab1ea473a082 +# English: "Engineered wood" +# Source-Hash: 5c0c1a6c73eb46e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Everything" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.everything" -msgstr "Tout" +msgid "editor.panels.buildingFoundationSection.engineeredWood" +msgstr "Parquet contrecollé" -# Source-Hash: ac3f119e560388ab +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, finishes, and placed items, without guide references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" -msgstr "Structure, finitions et éléments placés, sans références de calque." +msgid "editor.panels.buildingFoundationSection.finish" +msgstr "Finition" -# Source-Hash: acbdf968391d5273 +# English: "Finish floor over grade" +# Source-Hash: b97e6e3652b2ea63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + furniture" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFurniture" -msgstr "Structure + mobilier" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGrade" +msgstr "Plancher fini au-dessus du sol" -# Source-Hash: ce682864a140fe75 +# English: "Measured over the highest grade under the outside walls. The foundation grows down the slope from there." +# Source-Hash: 326641cd3bd5968a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + materials" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterials" -msgstr "Structure + matériaux" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGradeHint" +msgstr "Mesuré au-dessus du point le plus haut du sol sous les murs extérieurs. La fondation descend avec la pente à partir de là." -# Source-Hash: 5ee44d53847c5ea2 +# English: "Floor framing" +# Source-Hash: bcc3c6e3240bd6da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, materials, furniture, and references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" -msgstr "Structure, matériaux, mobilier et références." +msgid "editor.panels.buildingFoundationSection.floorFraming" +msgstr "Ossature de plancher" -# Source-Hash: 95aa1b43dbdb3f02 +# English: "Floor framing and sill {depth}" +# Source-Hash: 2d7eaba6b2a7787b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure only" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureOnly" -msgstr "Structure seule" +msgid "editor.panels.buildingFoundationSection.floorFramingAndSill" +msgstr "Ossature de plancher et lisse {depth}" -# Source-Hash: 2c53b46c9ca9fe2f +# English: "Floor top down to the stem: flooring, subfloor, joists, sill." +# Source-Hash: 60bbbc62b12ee1b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure with the current material and finish assignments." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" -msgstr "Structure avec les matériaux et finitions actuellement attribués." +msgid "editor.panels.buildingFoundationSection.floorFramingHint" +msgstr "Du dessus du plancher jusqu’au soubassement : revêtement de sol, sous-plancher, solives, lisse." -# Source-Hash: 3350e9008eaba15d +# English: "Floor framing {depth}, sill {sill} included" +# Source-Hash: b3f975c3aa4056d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" -msgstr "Murs, dalles, toits, escaliers, fenêtres et portes, sans finitions." +msgid "editor.panels.buildingFoundationSection.floorFramingSillIncluded" +msgstr "Ossature de plancher {depth}, lisse {sill} comprise" -# Source-Hash: 395fd0821b203517 +# English: "Flooring" +# Source-Hash: 3e6a58401ab2fc64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add or remove objects from the selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" -msgstr "Ajouter ou retirer des objets de la sélection" +msgid "editor.panels.buildingFoundationSection.flooring" +msgstr "Revêtement de sol" -# Source-Hash: 8428cb0af9c6d6a0 +# English: "Footing thickness" +# Source-Hash: e7ca47bb438592ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Adjust one side’s corner radius" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" -msgstr "Ajuster le rayon d’angle d’un côté" +msgid "editor.panels.buildingFoundationSection.footingThickness" +msgstr "Épaisseur de semelle" -# Source-Hash: d63b45c195713993 +# English: "Footing under grade" +# Source-Hash: 3b481ad3c192b6a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear the selection (or click outside)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" -msgstr "Effacer la sélection (ou cliquer à l’extérieur)" +msgid "editor.panels.buildingFoundationSection.footingUnderGrade" +msgstr "Semelle sous le sol" -# Source-Hash: 40b1afbeadbd6213 +# English: "IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line." +# Source-Hash: d1ed081aa7773257 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a handle dot to show move arrows" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickAHandleDotToShow" -msgstr "Cliquez sur une poignée pour afficher les flèches de déplacement" +msgid "editor.panels.buildingFoundationSection.footingUnderGradeHint" +msgstr "IRC R403.1.4 : au moins 12 in sous le sol adjacent, à l’extérieur comme dans le vide sanitaire creusé ; plus bas jusqu’à la profondeur hors gel." -# Source-Hash: 0b82a35c71549580 +# English: "Footing width" +# Source-Hash: a4bdf4be3447baec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click or drag the selection to move it as one" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" -msgstr "Cliquez ou faites glisser la sélection pour la déplacer d’un bloc" +msgid "editor.panels.buildingFoundationSection.footingWidth" +msgstr "Largeur de semelle" -# Source-Hash: 73a761f393f69cb9 +# English: "Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys." +# Source-Hash: bfb55823e6b7d61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click the handle dot to show move + rotate handles" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" -msgstr "Cliquez sur la poignée pour afficher les poignées de déplacement et de rotation" +msgid "editor.panels.buildingFoundationSection.footingWidthHint" +msgstr "Table R403.1(1), sol à 1500 psf : 12, 15, 23 in pour 1, 2, 3 étages." -# Source-Hash: c0045cef191fee69 +# English: "Footing {width} × {thickness}" +# Source-Hash: dd973bd2c5207dd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detach the joint while dragging an arrow" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" -msgstr "Détacher la jonction en faisant glisser une flèche" +msgid "editor.panels.buildingFoundationSection.footingWidthThickness" +msgstr "Semelle {width} × {thickness}" -# Source-Hash: a019c8948c104a9e +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag left or right to rotate selected object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" -msgstr "Faites glisser à gauche ou à droite pour faire pivoter l’objet sélectionné" +msgid "editor.panels.buildingFoundationSection.foundation" +msgstr "Fondation" -# Source-Hash: 7db0364deb12f06e +# English: "Grade beam depth" +# Source-Hash: c4460990c10fbb11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag selected movable object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragSelectedMovableObject" -msgstr "Faire glisser l’objet mobile sélectionné" +msgid "editor.panels.buildingFoundationSection.gradeBeamDepth" +msgstr "Hauteur de longrine" -# Source-Hash: 0f258dd389afdc73 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection (session only)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" -msgstr "Grouper la sélection (session uniquement)" +msgid "editor.panels.buildingFoundationSection.gradeBeamOnPiles" +msgstr "Longrine sur pieux" -# Source-Hash: 85330d62893a45c3 +# English: "Grade beam width" +# Source-Hash: 2208ea6b7967afc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to rotate freely" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.holdToRotateFreely" -msgstr "Maintenez pour pivoter librement" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidth" +msgstr "Largeur de longrine" -# Source-Hash: 8a665ecaeffa9308 +# English: "Grade beam {width} × {depth}" +# Source-Hash: e70be1cfffd2567a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotate45" -msgstr "Pivoter de ±45°" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidthDepth" +msgstr "Longrine {width} × {depth}" -# Source-Hash: 6099ee99f8c38700 +# English: "Half the edge’s depth under the slab, 6 to 12 in" +# Source-Hash: 08e07ba612a2515a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotateTheSelection45" -msgstr "Pivoter la sélection de ±45°" +msgid "editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder" +msgstr "La moitié de la profondeur de la bêche sous la dalle, de 6 à 12 in" -# Source-Hash: 0f6b418b18ff96f3 +# English: "Hardwood" +# Source-Hash: af3644ad05550f7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotating freely (no angle step)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotatingFreely" -msgstr "Rotation libre (sans pas d’angle)" +msgid "editor.panels.buildingFoundationSection.hardwood" +msgstr "Parquet massif" -# Source-Hash: 7ed174d6b03563b1 +# English: "{height} crawl" +# Source-Hash: d0ab8d2735f2e1a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch the rotation axis (Y → X → Z)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" -msgstr "Changer l’axe de rotation (Y → X → Z)" +msgid "editor.panels.buildingFoundationSection.heightCrawl" +msgstr "Vide sanitaire {height}" -# Source-Hash: d6f2c5cea683de62 +# English: "{height} FF" +# Source-Hash: 70a0363496dbb627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup session selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.ungroupSessionSelection" -msgstr "Dissocier la sélection de session" +msgid "editor.panels.buildingFoundationSection.heightFinishFloor" +msgstr "Plancher fini {height}" -# Source-Hash: 384842ae1e1c6825 +# English: "{height} top" +# Source-Hash: 28fbe28f080c2e49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuous" -msgstr "Continu" +msgid "editor.panels.buildingFoundationSection.heightTop" +msgstr "Dessus {height}" -# Source-Hash: ebf0eb7af4636a16 +# English: "I-joists" +# Source-Hash: 61dc716260daf09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousCanopy" -msgstr "Auvent continu" +msgid "editor.panels.buildingFoundationSection.iJoists" +msgstr "Poutrelles en I" -# Source-Hash: 1d8e87cd807e8d40 +# English: "Its layers make {depth} with the sill; the framing follows them once a layer is set." +# Source-Hash: dcaf89ca7f4ba36a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous run" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousRun" -msgstr "Rangée continue" +msgid "editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill" +msgstr "Ses couches font {depth} avec la lisse ; l’ossature les suit dès qu’une couche est réglée." -# Source-Hash: 8990683667567c6b +# English: "Its walls are poured under the house’s outside walls." +# Source-Hash: 47f3b8abf6c71c0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.curvedFence" -msgstr "Clôture courbe" +msgid "editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe" +msgstr "Les murs du sous-sol sont coulés sous les murs extérieurs de la maison." -# Source-Hash: edf1b9a2ad3ab910 +# English: "Its walls are the basement’s own, on their footing." +# Source-Hash: 2ad8c11c90166f43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Freehand fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.freehandFence" -msgstr "Clôture libre" +msgid "editor.panels.buildingFoundationSection.itsWallsAreTheBasementS" +msgstr "Les murs du sous-sol lui sont propres, sur leur semelle." -# Source-Hash: 2cdb7c1c7cd7c4fd +# English: "Joists" +# Source-Hash: c7aa596536ceeecb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place multiple" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeMultiple" -msgstr "Placer plusieurs" +msgid "editor.panels.buildingFoundationSection.joists" +msgstr "Solives" -# Source-Hash: 90bf945df6f9c979 +# English: "Kind" +# Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place once" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeOnce" -msgstr "Placer une fois" +msgid "editor.panels.buildingFoundationSection.kind" +msgstr "Type" -# Source-Hash: 8ec6152e139b0e3f +# English: "{label} (auto)" +# Source-Hash: bc4b8b385cd50d18 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.polygonRoom" -msgstr "Pièce polygonale" +msgid "editor.panels.buildingFoundationSection.labelAuto" +msgstr "{label} (auto)" -# Source-Hash: 83a45603ac7f41e8 +# English: "Laminate" +# Source-Hash: 826e189e08abac28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.rectangleRoom" -msgstr "Pièce rectangulaire" +msgid "editor.panels.buildingFoundationSection.laminate" +msgstr "Stratifié" -# Source-Hash: 7fd85d0408141157 +# English: "Least depth under grade" +# Source-Hash: 6ace3330ae71b24d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single cabinet" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCabinet" -msgstr "Meuble seul" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGrade" +msgstr "Profondeur min. sous le sol" -# Source-Hash: dd1bdbc26165baa5 +# English: "The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further." +# Source-Hash: 5e390c119e4d6da5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCanopy" -msgstr "Auvent seul" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGradeHint" +msgstr "La longrine descend au moins à cette profondeur sous le sol (IRC R403.1.4 : 12 in, ou la profondeur hors gel) ; une longrine plus haute descend plus bas." -# Source-Hash: 5fb1a69316be983e +# English: "Least step run" +# Source-Hash: 2e918ebc24201841 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleFence" -msgstr "Clôture simple" +msgid "editor.panels.buildingFoundationSection.leastStepRun" +msgstr "Longueur min. entre redans" -# Source-Hash: aac9a732d2159ccc +# English: "IRC R403.1.5: the top of a footing is level; it steps where the ground falls." +# Source-Hash: 95c2ee3d275f29cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.walls" -msgstr "Murs" +msgid "editor.panels.buildingFoundationSection.leastStepRunHint" +msgstr "IRC R403.1.5 : le dessus d’une semelle est de niveau ; elle forme des redans là où le terrain descend." -# Source-Hash: 4e4f1a24e4a9d61e +# English: "Lumber" +# Source-Hash: 73dcfae611575500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.cutOpening" -msgstr "Découper une ouverture" +msgid "editor.panels.buildingFoundationSection.lumber" +msgstr "Bois massif" -# Source-Hash: 60e8d235f3084a9e +# English: "Name the material" +# Source-Hash: 3053c459862812ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divide room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.divideRoom" -msgstr "Diviser la pièce" +msgid "editor.panels.buildingFoundationSection.nameTheMaterial" +msgstr "Nommer le matériau" -# Source-Hash: aa496ec33d21ab03 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.editKind" -msgstr "Modifier : {kind}" +msgid "editor.panels.buildingFoundationSection.off" +msgstr "Désactivé" -# Source-Hash: f3ea3efa7102844f +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.erase" -msgstr "Effacer" +msgid "editor.panels.buildingFoundationSection.osb" +msgstr "OSB" -# Source-Hash: 3f907e76526472bf +# English: "OSB subfloor" +# Source-Hash: 097c64d90d9fb848 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.levelTerrain" -msgstr "Niveler le terrain" +msgid "editor.panels.buildingFoundationSection.osbSubfloor" +msgstr "Sous-plancher OSB" -# Source-Hash: 4e911c300d74eb47 +# English: "Panel" +# Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lower terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.lowerTerrain" -msgstr "Abaisser le terrain" +msgid "editor.panels.buildingFoundationSection.panel" +msgstr "Panneau" -# Source-Hash: 194a147d4618a994 +# English: "Pile diameter" +# Source-Hash: 1372cffe6283ee90 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.mezzanine" -msgstr "Mezzanine" +msgid "editor.panels.buildingFoundationSection.pileDiameter" +msgstr "Diamètre des pieux" -# Source-Hash: c48c4129c9ed249a +# English: "Piles are sized by the engineer." +# Source-Hash: 58bdc4ad2c360017 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveKind" -msgstr "Déplacer : {kind}" +msgid "editor.panels.buildingFoundationSection.pileDiameterHint" +msgstr "Les pieux sont dimensionnés par l’ingénieur." -# Source-Hash: 89f6b7ca8904c656 +# English: "Pile {diameter} @ {spacing}, {length} deep" +# Source-Hash: 2e583e4622318f58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveRoom" -msgstr "Déplacer la pièce" +msgid "editor.panels.buildingFoundationSection.pileDiameterSpacingDeep" +msgstr "Pieu {diameter} tous les {spacing}, {length} de profondeur" -# Source-Hash: 9c707069572fb3db +# English: "Pile length" +# Source-Hash: 5e99bac8b7ba3af9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move selection" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveSelection" -msgstr "Déplacer la sélection" +msgid "editor.panels.buildingFoundationSection.pileLength" +msgstr "Longueur des pieux" -# Source-Hash: cf2e870fce0f2f51 +# English: "Pile spacing" +# Source-Hash: 7dfd658f6006b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paint" -msgstr "Peindre" +msgid "editor.panels.buildingFoundationSection.pileSpacing" +msgstr "Espacement des pieux" -# Source-Hash: ca197314cb70aa56 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a rectangle" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintARectangle" -msgstr "Peindre un rectangle" +msgid "editor.panels.buildingFoundationSection.plaster" +msgstr "Plâtre" -# Source-Hash: ea4ce729e17254f8 +# English: "Plaster ceiling" +# Source-Hash: a1f03abd876ee26f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a shape" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintAShape" -msgstr "Peindre une forme" +msgid "editor.panels.buildingFoundationSection.plasterCeiling" +msgstr "Plafond en plâtre" -# Source-Hash: 01359502b07284d4 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick material" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.pickMaterial" -msgstr "Prélever un matériau" +msgid "editor.panels.buildingFoundationSection.plywood" +msgstr "Contreplaqué" -# Source-Hash: 4066e8a3f60b1711 +# English: "Plywood subfloor" +# Source-Hash: ded7bab2a8fe5793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.raiseTerrain" -msgstr "Surélever le terrain" +msgid "editor.panels.buildingFoundationSection.plywoodSubfloor" +msgstr "Sous-plancher en contreplaqué" -# Source-Hash: f85bdac0a1d16603 +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.resize" -msgstr "Redimensionner" +msgid "editor.panels.buildingFoundationSection.preset" +msgstr "Préréglage" -# Source-Hash: 306d409f8a1cf480 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.rotate" -msgstr "Pivoter" +msgid "editor.panels.buildingFoundationSection.raisedFloor" +msgstr "Plancher surélevé" -# Source-Hash: a4395168488bb476 +# English: "Foundation section: basement, its wall's top {top} over grade, its floor {floor} under it" +# Source-Hash: f17c359b6460adc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.select" -msgstr "Sélectionner" +msgid "editor.panels.buildingFoundationSection.sectionBasement" +msgstr "Coupe de la fondation : sous-sol, dessus du mur à {top} au-dessus du sol, plancher à {floor} sous le sol" -# Source-Hash: ec1f1d68af469867 +# English: "Foundation section: raised floor on a stem wall, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: d292ee971f5b54a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart measure" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smartMeasure" -msgstr "Mesure intelligente" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloor" +msgstr "Coupe de la fondation : plancher surélevé sur mur de soubassement, plancher fini à {floor} au-dessus du sol, vide sanitaire de {crawl} sous les solives" -# Source-Hash: 5ba5f22bd1a69e09 +# English: "Foundation section: raised floor on a stem wall on piles, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: 786e74b8b53f3dab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smooth terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smoothTerrain" -msgstr "Lisser le terrain" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles" +msgstr "Coupe de la fondation : plancher surélevé sur mur de soubassement sur pieux, plancher fini à {floor} au-dessus du sol, vide sanitaire de {crawl} sous les solives" -# Source-Hash: cbb4687261739397 +# English: "Foundation section: slab on grade, finish floor {floor} over grade" +# Source-Hash: bfb5167b838b50c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.splitKind" -msgstr "Diviser : {kind}" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGrade" +msgstr "Coupe de la fondation : dalle sur terre-plein, plancher fini à {floor} au-dessus du sol" -# Source-Hash: 96281876ce53ba08 +# English: "Foundation section: slab on grade on piles, finish floor {floor} over grade" +# Source-Hash: 5a7458213c388c8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.terrace" -msgstr "Terrasse" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles" +msgstr "Coupe de la fondation : dalle sur terre-plein sur pieux, plancher fini à {floor} au-dessus du sol" -# Source-Hash: 8ec6152e139b0e3f +# English: "Sill" +# Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.polygonRoom" -msgstr "Pièce polygonale" +msgid "editor.panels.buildingFoundationSection.sill" +msgstr "Lisse d’assise" -# Source-Hash: 83a45603ac7f41e8 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.rectangleRoom" -msgstr "Pièce rectangulaire" +msgid "editor.panels.buildingFoundationSection.size" +msgstr "Taille" -# Source-Hash: aac9a732d2159ccc +# English: "{size} joists" +# Source-Hash: 2406f7fa46d251de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.walls" -msgstr "Murs" +msgid "editor.panels.buildingFoundationSection.sizeJoists" +msgstr "Solives {size}" +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# Source-Hash: c2626e1fbf89b3c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" -#: packages/editor/src/components/editor/registry-action-contributions.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.mechanismAction" -msgstr "{action, select, close {Fermer} open {Ouvrir} stop {Arrêter} other {Lancer}}" +msgid "editor.panels.buildingFoundationSection.slab" +msgstr "Dalle" +# English: "IRC R506.1 asks 3½ in at least." +# Source-Hash: 97296dd13f940848 # Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.curve" -msgstr "Courbe" +msgid "editor.panels.buildingFoundationSection.slabHint" +msgstr "L’IRC R506.1 exige au moins 3½ in." +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# Source-Hash: 5af05fb6661e4d6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut out" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.cutOut" -msgstr "Découper" +msgid "editor.panels.buildingFoundationSection.slabOnGrade" +msgstr "Dalle sur terre-plein" +# English: "Slab {thickness}" +# Source-Hash: 714bbb73fd4e68b9 # Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.delete" -msgstr "Supprimer" +msgid "editor.panels.buildingFoundationSection.slabThickness" +msgstr "Dalle {thickness}" +# English: "Slab top over grade" +# Source-Hash: 4e6765b0d6a9bbe5 # Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.duplicate" -msgstr "Dupliquer" +msgid "editor.panels.buildingFoundationSection.slabTopOverGrade" +msgstr "Dessus de dalle au-dessus du sol" +# English: "The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in)." +# Source-Hash: 7a6001a64711e175 # Review: required -# Source-Hash: 94b212bd79c05141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit mesh" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.editMesh" -msgstr "Modifier le maillage" +msgid "editor.panels.buildingFoundationSection.slabTopOverGradeHint" +msgstr "Le terrain autour de la dalle est réglé à cette cote de tous côtés : remblayé là où il descend, déblayé là où il monte (IRC R317.1 : 8 in)." +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c # Review: required -# Source-Hash: ce90cf37224f73cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in catalog" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.findInCatalog" -msgstr "Trouver dans le catalogue" +msgid "editor.panels.buildingFoundationSection.spacing" +msgstr "Espacement" +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 # Review: required -# Source-Hash: d5e550c345e7161c -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.groupSelection" -msgstr "Grouper la sélection" +msgid "editor.panels.buildingFoundationSection.spacingOnCentre" +msgstr "{spacing} d’entraxe" +# English: "Stem top over grade" +# Source-Hash: 097812a3bcb6d256 # Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.move" -msgstr "Déplacer" +msgid "editor.panels.buildingFoundationSection.stemTopOverGrade" +msgstr "Dessus du soubassement au-dessus du sol" +# English: "IRC R317.1 keeps wood framing 8 in off the ground." +# Source-Hash: eabab1d380a0b860 # Review: required -# Source-Hash: 473a9d4463761107 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate left" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateLeft" -msgstr "Pivoter à gauche" +msgid "editor.panels.buildingFoundationSection.stemTopOverGradeHint" +msgstr "L’IRC R317.1 maintient l’ossature bois à 8 in du sol." +# English: "Stem wall" +# Source-Hash: ce9da5096b1feb02 # Review: required -# Source-Hash: b1bf4e65c8d8c8ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate right" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateRight" -msgstr "Pivoter à droite" +msgid "editor.panels.buildingFoundationSection.stemWall" +msgstr "Mur de soubassement" +# English: "Stem wall and footing" +# Source-Hash: b0a49e954f1e5a99 # Review: required -# Source-Hash: 6f4dbbc4a3f58fca -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.ungroupSelection" -msgstr "Dissocier la sélection" +msgid "editor.panels.buildingFoundationSection.stemWallAndFooting" +msgstr "Soubassement et semelle" -# Source-Hash: 4275c05172abd7fb +# English: "Table R404.1.2: 6 in for one storey, 8 in for more." +# Source-Hash: 4ea0e2b0609f6137 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.ceiling" -msgstr "Plafond" +msgid "editor.panels.buildingFoundationSection.stemWallHint" +msgstr "Table R404.1.2 : 6 in pour un étage, 8 in au-delà." -# Source-Hash: 3530ac1f4046cd22 +# English: "Stem wall {thickness}" +# Source-Hash: 965e29b358d63d58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.column" -msgstr "Poteau" +msgid "editor.panels.buildingFoundationSection.stemWallThickness" +msgstr "Mur de soubassement {thickness}" -# Source-Hash: 787e10f5db16904e +# English: "Step down the slope" +# Source-Hash: 2a131b783b8e35ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.door" -msgstr "Porte" +msgid "editor.panels.buildingFoundationSection.stepDownTheSlope" +msgstr "Suivre la pente en redans" -# Source-Hash: ab5cbab7be160a1c +# English: "Step height" +# Source-Hash: 2d15d12859e8f169 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.elevator" -msgstr "Ascenseur" +msgid "editor.panels.buildingFoundationSection.stepHeight" +msgstr "Hauteur de redan" -# Source-Hash: 02042fde61b5aa11 +# English: "Subfloor sheathing" +# Source-Hash: 438e69e35a342a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.fence" -msgstr "Clôture" +msgid "editor.panels.buildingFoundationSection.subfloorSheathing" +msgstr "Sous-plancher" -# Source-Hash: 4c3ff8cdc6feda97 +# English: "Tallest stem before stepping" +# Source-Hash: 1170f2affa3a3f4b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.guide" -msgstr "Image de calque" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeStepping" +msgstr "Soubassement max. avant redan" -# Source-Hash: 2743a0e9d2743807 +# English: "A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it." +# Source-Hash: 9485def39ac25b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.item" -msgstr "Élément" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint" +msgstr "Un côté dont le soubassement dépasserait davantage du sol descend la pente en redans : son soubassement redescend à la hauteur de dessus réglée au-dessus du sol, surmonté d’un muret d’ossature." -# Source-Hash: f5f730969df63d1d +# English: "Taper" +# Source-Hash: 6063ae2877faa35d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roof" -msgstr "Toit" +msgid "editor.panels.buildingFoundationSection.taper" +msgstr "Biais" -# Source-Hash: a130b4eec084c79a +# English: "Taper {taper} at 45°" +# Source-Hash: 4c008bc3ff2c404c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roofSegment" -msgstr "Segment de toit" +msgid "editor.panels.buildingFoundationSection.taperAt45" +msgstr "Biais {taper} à 45°" -# Source-Hash: 612dcd5bf06a79c4 +# English: "The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider." +# Source-Hash: c2d8477f6b02f2a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.room" -msgstr "Pièce" +msgid "editor.panels.buildingFoundationSection.taperHint" +msgstr "La face intérieure de la bêche monte d’aplomb depuis le fond, puis s’incline à 45° jusqu’à la dalle. Auto règle le biais à la moitié de la profondeur de la bêche sous la dalle, de 6 à 12 in : une bêche plus profonde gagne en hauteur d’aplomb, pas en largeur." -# Source-Hash: b22b2f20cc7cd416 +# English: "The house stands on a basement: its walls are the foundation walls, so only their footing is drawn." +# Source-Hash: 8d646e2d19be2e57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D Scan" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.scan" -msgstr "Relevé 3D" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnABasement" +msgstr "La maison repose sur un sous-sol : les murs du sous-sol sont les murs de fondation, seule leur semelle est donc dessinée." -# Source-Hash: 170b2049ef0f9131 +# English: "The house stands on its basement, cut into the ground: the ground round it is left as it lies." +# Source-Hash: 82f250b688c1079e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selection" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.selection" -msgstr "Sélection" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement" +msgstr "La maison repose sur son sous-sol, creusé dans le terrain : le terrain autour reste tel quel." -# Source-Hash: 8775bc8dc9593964 +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.slab" -msgstr "Dalle" +msgid "editor.panels.buildingFoundationSection.thickness" +msgstr "Épaisseur" -# Source-Hash: 53d7084bc41c1ad6 +# English: "{title} material" +# Source-Hash: 4cf16973c8dd006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stair" -msgstr "Escalier" +msgid "editor.panels.buildingFoundationSection.titleMaterial" +msgstr "Matériau ({title})" -# Source-Hash: 52de428c16bb81e6 +# English: "Turned-down edge" +# Source-Hash: 4707e4ae34281fd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stairSegment" -msgstr "Segment d’escalier" +msgid "editor.panels.buildingFoundationSection.turnedDownEdge" +msgstr "Bêche périphérique" -# Source-Hash: 2210e5ee50b74dc9 +# English: "Vinyl plank" +# Source-Hash: 385c128fc1026239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.wall" -msgstr "Mur" +msgid "editor.panels.buildingFoundationSection.vinylPlank" +msgstr "Vinyle en lames" -# Source-Hash: aa3c35ed33e181e5 +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.window" -msgstr "Fenêtre" +msgid "editor.panels.buildingFoundationSection.woodPlank" +msgstr "Lambris" -# Source-Hash: a107b4d76d6972e3 +# English: "Wood plank ceiling" +# Source-Hash: 931afc430cda5de8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.zone" -msgstr "Zone" +msgid "editor.panels.buildingFoundationSection.woodPlankCeiling" +msgstr "Plafond en lambris" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.cancel" msgstr "Annuler" +# English: "Collection name…" # Source-Hash: 6dbabf32ff8c6f37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection name…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collectionName" msgstr "Nom de la collection…" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collections" msgstr "Collections" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.delete" msgstr "Supprimer" +# English: "Delete \"{name}\"?" # Source-Hash: c53d71b05b8163b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete \"{name}\"?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.deleteName" msgstr "Supprimer « {name} » ?" +# English: "New" # Source-Hash: e9f7cceb39860c56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.new" msgstr "Nouveau" +# English: "No collections yet. Create one to group elements together." # Source-Hash: 649bc1b1799f2311 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No collections yet. Create one to group elements together." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" msgstr "Aucune collection pour l’instant. Créez-en une pour regrouper des éléments." +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.rename" msgstr "Renommer" -# Source-Hash: 2000cf124b4e1139 +# English: "Along house" +# Source-Hash: 0a605a51555bb514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.alongHouse" +msgstr "Parallèle" + +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boards" +msgstr "Lames" + +# English: "{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." +# Source-Hash: 2bc05aaf1fc289b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boardsSummary" +msgstr "Lames {label} de {thickness} × {width} in, espacées de {gap} in, {direction, select, parallel {parallèles à la maison, sur des solives perpendiculaires à celle-ci} diagonal {à 45° par rapport à la maison} other {perpendiculaires à la maison, sur des solives parallèles à celle-ci, portées par des poutres perpendiculaires à la maison}} ; solives à {spacing} in d’entraxe." + +# English: "Deck boards" +# Source-Hash: 24c6255bfa1abd8f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.deckBoards" +msgstr "Lames de terrasse" + +# English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.diagonal" +msgstr "Diagonale" + +# English: "Gap" +# Source-Hash: df708ece3529bcba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.gap" +msgstr "Joint" + +# English: "Out from house" +# Source-Hash: 3660fcdd12159277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.outFromHouse" +msgstr "Perpendiculaire" + +# English: "Run" +# Source-Hash: 9ac7e4955593cc34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.run" +msgstr "Sens" + # English: "A thicker floor grows upward: the rooms below keep their height." +# Source-Hash: 2000cf124b4e1139 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" msgstr "Un plancher plus épais croît vers le haut : les pièces du dessous gardent leur hauteur." -# Source-Hash: 0d57958b99b6b352 +# English: "Its layers make {thickness}; the thickness follows them on the first change." +# Source-Hash: 767f8969959d1c48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.buildUpFollows" +msgstr "Ses couches font {thickness} ; l’épaisseur les suit dès la première modification." + # English: "Edge finish" +# Source-Hash: 0d57958b99b6b352 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.edgeFinish" msgstr "Finition de rive" -# Source-Hash: 8e458d8b21f26f09 +# English: "Floor" +# Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floor" +msgstr "Plancher" + +# English: "Floor build-up" +# Source-Hash: 097b19040c031ae4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floorBuildUp" +msgstr "Composition du plancher" + # English: "Floor plate" +# Source-Hash: 8e458d8b21f26f09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlate" msgstr "Plancher" +# English: "Floor plate {thicknessLabel} thick, resting on the walls below" # Source-Hash: 2bb71e5633801afb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate {thicknessLabel} thick, resting on the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn" msgstr "Plancher de {thicknessLabel} d’épaisseur, reposant sur les murs du dessous" -# Source-Hash: 73614b8faa059cd6 +# English: "Floor thickness {thickness}" +# Source-Hash: 68a6aff9bf44f9c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy -msgid "editor.panels.floorFoundationPanel.floorThickness" -msgstr "Épaisseur du plancher" +msgid "editor.panels.floorFoundationPanel.floorThicknessValue" +msgstr "Épaisseur du plancher {thickness}" +# English: "Floor top +{height}" # Source-Hash: a40a1fbacd5d2551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top +{height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopAbove" msgstr "Dessus du plancher +{height}" +# English: "Floor top {height}" # Source-Hash: 5c3849e15933737f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopValue" msgstr "Dessus du plancher {height}" +# English: "Foundation finish" # Source-Hash: 19802dd369749ea7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationFinish" msgstr "Finition de la fondation" +# English: "Foundation height" # Source-Hash: 3a4599be1ad687be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationHeight" msgstr "Hauteur de fondation" +# English: "Floor plate" # Source-Hash: 8e458d8b21f26f09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kicker" msgstr "Plancher" +# English: "Floor plate · {level}" # Source-Hash: 66d52bfbb94f7bb3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerLevel" msgstr "Plancher · {level}" +# English: "Floor plate · {level} · rests on {below}" # Source-Hash: 0189e5a1987b4ec0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level} · rests on {below}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerRestsOn" msgstr "Plancher · {level} · repose sur {below}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" # Source-Hash: 648ef10535b0af73 # Review: required -# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.lowerTogether" msgstr "{count, plural, =2 {Abaisser les deux} one {Abaisser les {number}} many {Abaisser les {number}} other {Abaisser les {number}}}" +# English: "Not to scale" # Source-Hash: c2fcc43bb7f1c5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not to scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.notToScale" msgstr "Pas à l’échelle" +# English: "On the ground" # Source-Hash: e9440fa9f770b7a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.onTheGround" msgstr "Au sol" +# English: "Raised on a foundation" # Source-Hash: 7c3692a361075ecf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised on a foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raisedOnAFoundation" msgstr "Surélevé sur une fondation" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" # Source-Hash: 769c3ca1ca0cd4f0 # Review: required -# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raiseTogether" msgstr "{count, plural, =2 {Relever les deux} one {Relever les {number}} many {Relever les {number}} other {Relever les {number}}}" +# English: "Floor plate · {thickness}" # Source-Hash: 719ae422da204ca7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {thickness}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorPlate" msgstr "Plancher · {thickness}" +# English: "Floor top" # Source-Hash: 069d64adffa05db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorTop" msgstr "Dessus du plancher" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundation" msgstr "Fondation" +# English: "Foundation · {foundation}" # Source-Hash: 7759a59b8685c885 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation · {foundation}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundationKind" msgstr "Fondation · {foundation}" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.ground" msgstr "Sol" -# Source-Hash: 51e274b2c90e2cf3 +# English: "{layer} · {size}" +# Source-Hash: 1a0f891c672247b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.section.layer" +msgstr "{layer} · {size}" + # English: "Slab · {thickness}" +# Source-Hash: 51e274b2c90e2cf3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.slab" msgstr "Dalle · {thickness}" +# English: "Walls below" # Source-Hash: 790a0b5c6dbf0e9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.wallsBelow" msgstr "Murs du dessous" -# Source-Hash: 90e96ae5f3d3f69c +# English: "Floor plate {thickness} thick: {layers}; resting on the walls below" +# Source-Hash: 7c1a423934a360f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayers" +msgstr "Plancher de {thickness} d’épaisseur : {layers} ; reposant sur les murs du dessous" + +# English: "{layer} {size}" +# Source-Hash: 0c5b48217a0cf663 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayerSize" +msgstr "{layer} {size}" + # English: "Slab {thickness} thick on the ground, floor top {top} above the ground" +# Source-Hash: 90e96ae5f3d3f69c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionOnGround" msgstr "Dalle de {thickness} d’épaisseur sur le sol, dessus du plancher à {top} au-dessus du sol" +# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" # Source-Hash: 71ed2295d9932ed0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionRaised" msgstr "Dalle de {thickness} d’épaisseur sur une fondation {foundation}, dessus du plancher à {top} au-dessus du sol" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slab" msgstr "Dalle" +# English: "Slab thickness" # Source-Hash: ca51d643691c29ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slabThickness" msgstr "Épaisseur de dalle" +# English: "The floor's outside edge, all around the footprint." # Source-Hash: 678c39468593275b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The floor's outside edge, all around the footprint." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll" msgstr "Le bord extérieur du plancher, tout autour de l’emprise." +# English: "The foundation stands on the ground; the slab sits on it." # Source-Hash: 6638ed5caa43bdb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The foundation stands on the ground; the slab sits on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround" msgstr "La fondation repose sur le sol ; la dalle repose sur elle." +# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." # Source-Hash: 5321416b442f23b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation" msgstr "La dalle repose sur la fondation (ou le sol) : une dalle plus épaisse relève le dessus du plancher et tout ce qui s’y trouve." +# English: "Under the floor" # Source-Hash: 044ccae8bec574e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Under the floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.underTheFloor" msgstr "Sous le plancher" +# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." # Source-Hash: 6aca44df3e582a81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt" msgstr "Trappe dans la mezzanine : elle ne coupe que le plancher de la mezzanine." +# English: "Also cut the ceiling below" # Source-Hash: 2ce79639d42da658 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the ceiling below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow" msgstr "Couper aussi le plafond en dessous" +# English: "Also cut the floor above" # Source-Hash: e360814f64792300 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the floor above" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheFloorAbove" msgstr "Couper aussi le plancher au-dessus" +# English: "{area} · Drag its corners to change the shape." # Source-Hash: c25cddf8b831c2b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Drag its corners to change the shape." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaDragCorners" msgstr "{area} · Faites glisser ses angles pour changer la forme." +# English: "{area} · Shape follows its owner." # Source-Hash: 32b168cf010a5daa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Shape follows its owner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaOwned" msgstr "{area} · La forme suit son propriétaire." -# Source-Hash: b26de372b4b0eaac +# English: "{area} · Drag its sides or corners to resize it; it moves with the stair." +# Source-Hash: 71b5c7184cbd2e5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-opening-panel.tsx +#, fuzzy +msgid "editor.panels.floorOpeningPanel.areaStairResize" +msgstr "{area} · Faites glisser ses côtés ou ses angles pour la redimensionner ; elle suit l’escalier." + # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.delete" msgstr "Supprimer" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.done" msgstr "Terminé" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kicker" msgstr "Ouverture" +# English: "Opening · {level}" # Source-Hash: 079ad92dac7947cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerLevel" msgstr "Ouverture · {level}" +# English: "Opening · in {room}" # Source-Hash: baf2c2c209637e5f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoom" msgstr "Ouverture · dans {room}" +# English: "Opening · in {room} · {level}" # Source-Hash: 1712c164335ecd86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room} · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoomLevel" msgstr "Ouverture · dans {room} · {level}" +# English: "On {level}, as one void with this opening." # Source-Hash: 88e673123b9dbfe1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On {level}, as one void with this opening." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.oneVoidWith" msgstr "Sur {level}, en un seul vide avec cette ouverture." +# English: "Owned by Elevator" # Source-Hash: c38959fc8fd3e59d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByElevator" msgstr "Appartient à l’ascenseur" +# English: "Owned by Staircase" # Source-Hash: 01494805cf60d78d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByStaircase" msgstr "Appartient à l’escalier" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.room" msgstr "Pièce" +# English: "There is no floor above this storey." # Source-Hash: 1405b914a324b153 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no floor above this storey." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis" msgstr "Il n’y a pas de plancher au-dessus de cet étage." +# English: "There is no storey below." # Source-Hash: 776170dcae88e969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no storey below." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoStoreyBelow" msgstr "Il n’y a pas d’étage en dessous." +# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" # Source-Hash: 0fdd1877a0f2d777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.title" msgstr "{source, select, stair {Trémie d’escalier} elevator {Trémie d’ascenseur} ceiling {Ouverture de plafond} other {Trémie de plancher}}" +# English: "A raised floor on a stem wall of its own." +# Source-Hash: 1839fff3aba02d40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aRaisedFloorOnAStem" +msgstr "Un plancher surélevé sur son propre mur de soubassement." + +# English: "A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round." +# Source-Hash: 52eead98a170fd6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aSlabOfItsOwn" +msgstr "Une dalle indépendante (porche, patio, dalle de garage), le sol maintenu à 8 in sous son dessus tout autour." + +# English: "Click its corners; click the first again, or press Enter, to close it." +# Source-Hash: 96853854f04b1514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.clickItsCornersClickTheFirst" +msgstr "Cliquez sur ses angles ; cliquez de nouveau sur le premier, ou appuyez sur Entrée, pour fermer le contour." + +# English: "Close the outside walls into a loop first, or draw one" +# Source-Hash: 4655068154039021 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA" +msgstr "Fermez d’abord les murs extérieurs en boucle, ou dessinez un contour" + +# English: "Delete this outline" +# Source-Hash: 8b8a23b925c30027 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.deleteThisOutline" +msgstr "Supprimer ce contour" + +# English: "Draw" +# Source-Hash: b5c56dd8c68de309 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.draw" +msgstr "Dessiner" + +# English: "Edit it myself" +# Source-Hash: e9c386def6e2dadf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.editItMyself" +msgstr "Modifier moi-même" + +# English: "Follow the walls" +# Source-Hash: 86c8ecac2691c892 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.followTheWalls" +msgstr "Suivre les murs" + +# English: "{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it." +# Source-Hash: ee78cc2aa3f77ca1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.footingsDrawn" +msgstr "{footings, plural, one {# semelle dessinée} many {# semelles dessinées} other {# semelles dessinées}} : cliquez sur une semelle dans le plan pour la dimensionner ou la déplacer." + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.foundation" +msgstr "Fondation" + +# English: "House foundation" +# Source-Hash: 5ec26bb3cea817e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.houseFoundation" +msgstr "Fondation de la maison" + +# English: "How far its top sits under the house’s floor: a porch slab steps down from the door" +# Source-Hash: 0140833a30e08d70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.howFarItsTopSitsUnder" +msgstr "Distance entre son dessus et le plancher de la maison : une dalle de porche est une marche plus bas que la porte" + +# English: "Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be." +# Source-Hash: 3b101db8d8839574 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd" +msgstr "Indépendant : faites glisser ses angles et ses côtés dans le plan ou en 3D. Déplacer les murs ne le modifie pas." + +# English: "Outline" +# Source-Hash: 3e6d52c82419517d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outline" +msgstr "Contour" + +# English: "Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts." +# Source-Hash: 78d4f6a686211e44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors" +msgstr "Contours de dalles et de planchers surélevés (porches, patios, dalle de garage), et semelles sous les murs porteurs intérieurs et les poteaux." + +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.raisedFloor" +msgstr "Plancher surélevé" + +# English: "Re-trace from walls" +# Source-Hash: e6eaea778b041b10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.reTraceFromWalls" +msgstr "Retracer d’après les murs" + +# English: "Set the outline back to the outside faces of the walls, keeping it your own" +# Source-Hash: 5181a67d6940cccb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.setTheOutlineBackToThe" +msgstr "Ramener le contour sur les faces extérieures des murs, en le gardant indépendant" + +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.slab" +msgstr "Dalle" + +# English: "The house’s" +# Source-Hash: 523f999e5b2d497e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own. +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHouses" +msgstr "De la maison" + +# English: "The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline." +# Source-Hash: 3e4d5b90c1ab8eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHousesOwnFoundation" +msgstr "La fondation de la maison elle-même, coulée selon les réglages de la maison ci-dessous, à la place du contour des murs." + +# English: "This outline" +# Source-Hash: 51be023b5f1983f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.thisOutline" +msgstr "Ce contour" + +# English: "Top under the floor" +# Source-Hash: 85eca682e0102861 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.topUnderTheFloor" +msgstr "Dessus sous le plancher" + +# English: "Traced from the outside walls: it moves when they do." +# Source-Hash: 1f7c9880395d456a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt" +msgstr "Tracé d’après les murs extérieurs : il se déplace avec eux." + +# English: "One of them already wraps another corner. Unwrap it first." # Source-Hash: fa35c7653e1f649e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them already wraps another corner. Unwrap it first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.alreadyJoined" msgstr "L’une d’elles occupe déjà un autre angle. Détachez-la d’abord." +# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" # Source-Hash: dd87eb1983876459 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.between" msgstr "{kind, select, item {Un objet se trouve entre elles.} door {Une porte se trouve entre elles.} window {Une fenêtre se trouve entre elles.} shelf {Une étagère se trouve entre elles.} other {Il y a quelque chose entre elles.}}" +# English: "These windows cannot be merged." # Source-Hash: 58aea0b9789ae280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "These windows cannot be merged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cannotMerge" msgstr "Ces fenêtres ne peuvent pas être fusionnées." +# English: "Their walls meet at too sharp or too flat an angle for a corner window." # Source-Hash: 15170cfd9d666c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls meet at too sharp or too flat an angle for a corner window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cornerAngle" msgstr "Leurs murs se rejoignent à un angle trop aigu ou trop plat pour une fenêtre d’angle." +# English: "The window selected first is taller than the other wall. Select the other one first." # Source-Hash: f2818a1e20ebd6ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window selected first is taller than the other wall. Select the other one first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.heightExceedsWall" msgstr "La fenêtre sélectionnée en premier est plus haute que l’autre mur. Sélectionnez d’abord l’autre." +# English: "They are too far apart. Windows merge with at most {reach} between them." # Source-Hash: 7c97fb4af929d4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "They are too far apart. Windows merge with at most {reach} between them." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAdjacent" msgstr "Elles sont trop éloignées. Les fenêtres fusionnent avec au plus {reach} entre elles." +# English: "One of them is too far from the corner. A window joins one within {reach} of it." # Source-Hash: 57a281bdc4229b6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them is too far from the corner. A window joins one within {reach} of it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAtCorner" msgstr "L’une d’elles est trop loin de l’angle. Une fenêtre en rejoint une autre à moins de {reach}." +# English: "A roof or dormer window cannot join another window." # Source-Hash: e7d41ce7879ec075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A roof or dormer window cannot join another window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notOnWall" msgstr "Une fenêtre de toit ou de lucarne ne peut pas rejoindre une autre fenêtre." +# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" # Source-Hash: eaad567d5e4257dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.overlap" msgstr "{kind, select, item {Les faire glisser jusqu’à l’angle chevaucherait un objet.} door {Les faire glisser jusqu’à l’angle chevaucherait une porte.} window {Les faire glisser jusqu’à l’angle chevaucherait une fenêtre.} shelf {Les faire glisser jusqu’à l’angle chevaucherait une étagère.} other {Les faire glisser jusqu’à l’angle chevaucherait un autre élément.}}" +# English: "Their walls do not meet at a corner." # Source-Hash: ecb092821ed5b5e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls do not meet at a corner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.wallsNotMeeting" msgstr "Leurs murs ne se rejoignent pas en angle." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.mixed" msgstr "Mixte" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-panel-sheet.tsx #, fuzzy msgid "editor.panels.mobilePanelSheet.close" msgstr "Fermer" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.delete" msgstr "Supprimer" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.duplicate" msgstr "Dupliquer" +# English: "Edit properties" # Source-Hash: 7eb955d866c2668b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit properties" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editProperties" msgstr "Modifier les propriétés" +# English: "Edit {resolvedLabel}" # Source-Hash: 89f25aa13e8192bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {resolvedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editResolvedlabel" msgstr "Modifier {resolvedLabel}" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.move" msgstr "Déplacer" +# English: "Currently {currentLabel}" # Source-Hash: 72be05e1796526c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {currentLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.currentlyCurrentlabel" msgstr "Actuellement {currentLabel}" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.customHeight" msgstr "Hauteur personnalisée" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.followsLevel" msgstr "Suit le niveau" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.height" msgstr "Hauteur" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.top" msgstr "Haut" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx #, fuzzy msgid "editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects" msgstr "{label} (session uniquement). Un clic simple resélectionne tous les membres. Non enregistré avec le projet." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.delete" msgstr "Supprimer" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.duplicate" msgstr "Dupliquer" +# English: "Group" # Source-Hash: b0e7e3b47dfb5a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.group" msgstr "Grouper" +# English: "Group (Ctrl/Cmd+G)" # Source-Hash: ca0a95fe0b08b8b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group (Ctrl/Cmd+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.groupCtrlCmdG" msgstr "Grouper (Ctrl/Cmd+G)" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects" msgstr "{label} (session uniquement). Un clic simple resélectionne tous les membres. Non enregistré avec le projet." +# English: "{length} selected" # Source-Hash: fc98ef29c2842341 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length} selected" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.lengthSelected" msgstr "{length} sélectionnés" +# English: "Merge windows" # Source-Hash: 323aaf1ebd04ada7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindows" msgstr "Fusionner les fenêtres" +# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" # Source-Hash: 7855d03a6140af77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow" msgstr "Fusionner en fenêtre d’angle : les deux prennent la hauteur, l’appui et le type de la première sélectionnée" +# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" # Source-Hash: b79da96502d2e1c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth" msgstr "Fusionner en une seule fenêtre sur les deux : elle garde le type et le style de la première sélectionnée" +# English: "Select building" # Source-Hash: f409934681a046e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectBuilding" msgstr "Sélectionner le bâtiment" +# English: "Select the whole building to move all its levels" # Source-Hash: c62a73873bd0164d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select the whole building to move all its levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove" msgstr "Sélectionnez tout le bâtiment pour déplacer tous ses niveaux" +# English: "Ungroup" # Source-Hash: 7bd5f29203a28efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroup" msgstr "Dissocier" +# English: "Ungroup (Ctrl/Cmd+Shift+G)" # Source-Hash: aec9c3983a440d4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup (Ctrl/Cmd+Shift+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG" msgstr "Dissocier (Ctrl/Cmd+Maj+G)" +# English: "Unwrap the corner" # Source-Hash: 420385f27c627cee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCorner" msgstr "Détacher l’angle" +# English: "Unwrap the corner: both windows stay, each framed at the corner" # Source-Hash: 7906c9f4baebc70b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner: both windows stay, each framed at the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay" msgstr "Détacher l’angle : les deux fenêtres restent, chacune avec un cadre à l’angle" -# Source-Hash: 170b2049ef0f9131 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/panel-manager.tsx +#, fuzzy +msgid "editor.panels.panelManager.foundation" +msgstr "Fondation" + # English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-manager.tsx #, fuzzy msgid "editor.panels.panelManager.selection" msgstr "Sélection" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.back" msgstr "Retour" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.close" msgstr "Fermer" +# English: "Close {title}" # Source-Hash: 2dff25ca30c80559 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.closeTitle" msgstr "Fermer {title}" +# English: "Collapse panel" # Source-Hash: 4f15c3e0fe6178b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collapsePanel" msgstr "Réduire le panneau" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collections" msgstr "Collections" +# English: "Expand panel" # Source-Hash: 64916cc9ea80f43f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.expandPanel" msgstr "Développer le panneau" +# English: "Manage collections…" # Source-Hash: 8a9a25621f200922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manage collections…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.manageCollections" msgstr "Gérer les collections…" +# English: "Open {title}" # Source-Hash: 2ff02ed91f54235c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.openTitle" msgstr "Ouvrir {title}" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.reset" msgstr "Réinitialiser" +# English: "“{title}” hit an error and was unloaded for this session." # Source-Hash: 04a212d6317f6787 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "“{title}” hit an error and was unloaded for this session." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.titleHitAnErrorAndWas" msgstr "« {title} » a rencontré une erreur et a été déchargé pour cette session." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.parametricFieldControl.mixed" msgstr "Mixte" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.actions" msgstr "Actions" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.delete" msgstr "Supprimer" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.deleteWithContents" msgstr "Supprimer avec le contenu" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.move" msgstr "Déplacer" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.cancel" msgstr "Annuler" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.capture" msgstr "Capture" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.center" msgstr "Centre" +# English: "Clear Scale" # Source-Hash: a5b965dfb0bf2cd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clearScale" msgstr "Effacer l’échelle" +# English: "Click both ends of a known distance on the plan, then type its real length." # Source-Hash: 4edc6e45b0c19447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click both ends of a known distance on the plan, then type its real length." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clickBothEndsOfAKnown" msgstr "Cliquez les deux extrémités d’une distance connue sur le plan, puis saisissez sa longueur réelle." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.delete" msgstr "Supprimer" +# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." # Source-Hash: 59f7d81e8d36a157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.drawALineOverAKnown" msgstr "Tracez une ligne sur une cote connue du plan, puis saisissez sa longueur réelle pour mettre l’image exactement à l’échelle." +# English: "Edit Scale" # Source-Hash: 42f6d0e4f065f114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.editScale" msgstr "Modifier l’échelle" +# English: "Guide Image" # Source-Hash: e156c84cbc90e0a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.guideImage" msgstr "Image de calque" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hide" msgstr "Masquer" +# English: "Hide Scale" # Source-Hash: dd282ce13b9085ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hideScale" msgstr "Masquer l’échelle" +# English: "Image" # Source-Hash: fc6eaa65315f601d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.image" msgstr "Image" +# English: "Lock" # Source-Hash: 241399e11d93bd7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.lock" msgstr "Verrouiller" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.move" msgstr "Déplacer" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.opacity" msgstr "Opacité" +# English: "Overlay image unavailable. Replace the image to restore it." # Source-Hash: 6ca213a6b299f19f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overlay image unavailable. Replace the image to restore it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage" msgstr "Image superposée indisponible. Remplacez l’image pour la rétablir." +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.position" msgstr "Position" +# English: "Quick Actions" # Source-Hash: 8b003432b36230c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Quick Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.quickActions" msgstr "Actions rapides" +# English: "Reference Scale" # Source-Hash: dcd4728e9ca90181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.referenceScale" msgstr "Échelle de référence" +# English: "Replace" # Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replace" msgstr "Remplacer" +# English: "Replacing..." # Source-Hash: 08b45f9ba66eab3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replacing..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replacing" msgstr "Remplacement..." +# English: "Reset Image Scale" # Source-Hash: 2714b5a81cedb625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Image Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetImageScale" msgstr "Réinitialiser l’échelle de l’image" +# English: "Reset Rotation" # Source-Hash: a05d3e429ae259ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetRotation" msgstr "Réinitialiser la rotation" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rot" msgstr "rot" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rotation" msgstr "Rotation" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scale" msgstr "échelle" +# English: "Scaled · {scale}" # Source-Hash: e7d0e7d41a775fd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaled" msgstr "Mis à l’échelle · {scale}" +# English: "Scaled (hidden) · {scale}" # Source-Hash: cd92eebfb30e708f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled (hidden) · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaledHidden" msgstr "Mis à l’échelle (masqué) · {scale}" +# English: "Scale & Opacity" # Source-Hash: bfd20999de7f216a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale & Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaleOpacity" msgstr "Échelle et opacité" +# English: "Set Scale" # Source-Hash: 8d8777505acdf78e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.setScale" msgstr "Définir l’échelle" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.show" msgstr "Afficher" +# English: "Show Scale" # Source-Hash: 78be2337f0e81973 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.showScale" msgstr "Afficher l’échelle" +# English: "Uncalibrated" # Source-Hash: ad85a193442c8f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncalibrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.uncalibrated" msgstr "Non calibré" +# English: "Unlock" # Source-Hash: 6c500cc253ab3fd4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unlock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.unlock" msgstr "Déverrouiller" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.xyz" msgstr "XYZ" +# English: "+ Add mezzanine" # Source-Hash: e01549105012f8d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.addMezzanine" msgstr "+ Ajouter une mezzanine" +# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." # Source-Hash: 167b0fe7fdbcd51d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness" msgstr "Dalle dessinée : définissez ici son épaisseur et ses finitions. Elle n’a ni hauteur d’emprise ni fondation." +# English: "At floor level" # Source-Hash: 6cb0969b65fb60bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At floor level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.atFloorLevel" msgstr "Au niveau du plancher" -# Source-Hash: e8563d5ae1ee7390 +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beam" +msgstr "Poutre" + +# English: "Beam size" +# Source-Hash: 4b03c226d5f88a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beamSize" +msgstr "Taille de la poutre" + # English: "Built on" +# Source-Hash: e8563d5ae1ee7390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtOn" msgstr "Construit sur" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtWith" msgstr "Construit avec" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceiling" msgstr "Plafond" +# English: "At {height}" # Source-Hash: 9bf177f59f971033 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceilingAt" msgstr "À {height}" +# English: "Close with walls" # Source-Hash: 48768dc0fc7b86a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close with walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.closeWithWalls" msgstr "Fermer avec des murs" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" # Source-Hash: 75c84b4f21aefde4 # Review: required -# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloor" msgstr "{ambiguous, select, yes {Le plancher est composé de plusieurs dalles dessinées, surtout {name}} other {Le plancher est une dalle dessinée : {name}}}{shared, plural, =0 {} one { (partagée avec {sharedCount} pièce)} many { (partagée avec {sharedCount} pièces)} other { (partagée avec {sharedCount} pièces)}}" +# English: "This floor is a drawn slab: select it to edit or delete it." # Source-Hash: 97e0fab8ffda6d6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor is a drawn slab: select it to edit or delete it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloorSwitchHint" msgstr "Ce plancher est une dalle dessinée : sélectionnez-la pour la modifier ou la supprimer." +# English: "Drawn slab" # Source-Hash: ad5426620188242c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlab" msgstr "Dalle dessinée" +# English: "Drawn slab at {height}" # Source-Hash: 0da0d104eaefbbad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab at {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlabAt" msgstr "Dalle dessinée à {height}" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.editShape" msgstr "Modifier la forme" +# English: "Flat, at wall height" # Source-Hash: fadf264be55063e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat, at wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.flatAtWallHeight" msgstr "Plat, à hauteur des murs" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floor" msgstr "Niveau" +# English: "{distance} down" # Source-Hash: 996e8191c069e779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorDown" msgstr "{distance} plus bas" +# English: "Floor thickness" # Source-Hash: 73614b8faa059cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorThickness" msgstr "Épaisseur du plancher" +# English: "{distance} up" # Source-Hash: 79cdde853c3e8f61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorUp" msgstr "{distance} plus haut" -# Source-Hash: 77bc12b7aba0eb60 +# English: "{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: b6b724f9f4db6d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutoHint" +msgstr "{posts, plural, one {{postCount} poteau} many {{postCount} poteaux} other {{postCount} poteaux}} et {beams, plural, one {{beamCount} poutre} many {{beamCount} poutres} other {{beamCount} poutres}}, un poteau à chaque angle extérieur, rattachés {room, select, deck {à la terrasse} patio {au patio} other {au porche}}." + +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutomatic" +msgstr "Automatique" + +# English: "Manual" +# Source-Hash: 387082cd5f118bed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManual" +msgstr "Manuel" + +# English: "Kept as you edited them. Automatic rebuilds them from these settings." +# Source-Hash: 287400b1b4b46abd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManualHint" +msgstr "Conservés tels que vous les avez modifiés. Automatique les reconstruit à partir de ces réglages." + +# English: "Off" +# Source-Hash: 50accd740fa2e736 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOff" +msgstr "Désactivé" + +# English: "None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: 4dfa7082b7e30280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOffHint" +msgstr "Aucun. Automatique place un poteau à chaque angle extérieur et une poutre dessus, pour porter un toit au-dessus {room, select, deck {de la terrasse} patio {du patio} other {du porche}}." + # English: "Hand-drawn ceiling" +# Source-Hash: 77bc12b7aba0eb60 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.handDrawnCeiling" msgstr "Plafond dessiné à la main" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.height" msgstr "Hauteur" +# English: "Include floor" # Source-Hash: 4d35023efe090b82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeFloor" msgstr "Inclure le plancher" +# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" # Source-Hash: 6398ac77644eb5f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includePart" msgstr "{part, select, floor {Inclure le plancher} walls {Inclure les murs} other {Inclure le plafond}}" +# English: "Include walls" # Source-Hash: 3629e64a6e796ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeWalls" msgstr "Inclure les murs" -# Source-Hash: f6d07fc7f239278b +# English: "Keep outside dimensions" +# Source-Hash: f6d07fc7f239278b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" +msgstr "Conserver les dimensions extérieures" + +# English: "Lowered {lift}" +# Source-Hash: 544410e3cb0e0b07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.lowered" +msgstr "Abaissé de {lift}" + +# English: "+ New floor" +# Source-Hash: c2daf78b740e4397 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.newFloor" +msgstr "+ Nouveau plancher" + +# English: "No floor" +# Source-Hash: ba9edc7910a60cea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.noFloor" +msgstr "Pas de plancher" + +# English: "Nothing to change." +# Source-Hash: ea2675041df74f40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.nothingToChange" +msgstr "Rien à modifier." + +# English: "Not included" +# Source-Hash: ebe0d375d7938c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.notIncluded" +msgstr "Non inclus" + +# English: "On the foundation" +# Source-Hash: f084b3795e3edfb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheFoundation" +msgstr "Sur la fondation" + +# English: "On the ground" +# Source-Hash: e9440fa9f770b7a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheGround" +msgstr "Au sol" + +# English: "On the walls below" +# Source-Hash: a5deb36c485d91bd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheWallsBelow" +msgstr "Sur les murs du dessous" + +# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: 2256aec0d5bdc3ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.openSides" +msgstr "{openSides, plural, one {{openSideCount} côté ouvert} many {{openSideCount} côtés ouverts} other {{openSideCount} côtés ouverts}}" + +# English: "Partial · {coverage, number, percent} covered" +# Source-Hash: f704520c5e5b92b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialCovered" +msgstr "Partiel · {coverage, number, percent} couvert" + +# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: b648bee0231a41cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialWalls" +msgstr "Partiel · {walls, plural, one {{wallCount} mur} many {{wallCount} murs} other {{wallCount} murs}}, {openSides, plural, one {{openSideCount} côté ouvert} many {{openSideCount} côtés ouverts} other {{openSideCount} côtés ouverts}}" + +# English: "Posts and beam" +# Source-Hash: 4b2a51c39b382184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postsAndBeam" +msgstr "Poteaux et poutre" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postSize" +msgstr "Taille des poteaux" + +# English: "Post spacing (max)" +# Source-Hash: cc25439729a97f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep outside dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" -msgstr "Conserver les dimensions extérieures" +msgid "editor.panels.roomConstructionRows.postSpacingMax" +msgstr "Espacement des poteaux (max.)" -# Source-Hash: 544410e3cb0e0b07 +# English: "Raised {lift}" +# Source-Hash: e503250e8ecb0030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lowered {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.lowered" -msgstr "Abaissé de {lift}" +msgid "editor.panels.roomConstructionRows.raised" +msgstr "Surélevé de {lift}" -# Source-Hash: c2daf78b740e4397 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ New floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.newFloor" -msgstr "+ Nouveau plancher" +msgid "editor.panels.roomConstructionRows.raisedFloor" +msgstr "Plancher surélevé" -# Source-Hash: ba9edc7910a60cea +# English: "Replace" +# Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.noFloor" -msgstr "Pas de plancher" +msgid "editor.panels.roomConstructionRows.replace" +msgstr "Remplacer" -# Source-Hash: ea2675041df74f40 +# English: "Room type" +# Source-Hash: be1c56bb3ddc63c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nothing to change." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.nothingToChange" -msgstr "Rien à modifier." +msgid "editor.panels.roomConstructionRows.roomType" +msgstr "Type de pièce" -# Source-Hash: ebe0d375d7938c93 +# English: "Deck" +# Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not included" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.notIncluded" -msgstr "Non inclus" +msgid "editor.panels.roomConstructionRows.roomTypeDeck" +msgstr "Terrasse" -# Source-Hash: e9440fa9f770b7a4 +# English: "Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling." +# Source-Hash: f0028986efecf7d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheGround" -msgstr "Au sol" +msgid "editor.panels.roomConstructionRows.roomTypeDeckHint" +msgstr "Extérieur : lames de terrasse sur ossature bois et poteaux, 1 in plus bas que la maison ; pas de plafond." -# Source-Hash: a5deb36c485d91bd +# English: "Patio" +# Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheWallsBelow" -msgstr "Sur les murs du dessous" +msgid "editor.panels.roomConstructionRows.roomTypePatio" +msgstr "Patio" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 2256aec0d5bdc3ea +# English: "Outdoors and open: a concrete slab 1½ in down from the house, no ceiling." +# Source-Hash: 5e4ea6a084f67808 # Review: required -# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.openSides" -msgstr "{openSides, plural, one {{openSideCount} côté ouvert} many {{openSideCount} côtés ouverts} other {{openSideCount} côtés ouverts}}" +msgid "editor.panels.roomConstructionRows.roomTypePatioHint" +msgstr "Extérieur ouvert : dalle béton 1½ in plus bas que la maison, sans plafond." -# Source-Hash: f704520c5e5b92b2 +# English: "Porch" +# Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partial · {coverage, number, percent} covered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialCovered" -msgstr "Partiel · {coverage, number, percent} couvert" +msgid "editor.panels.roomConstructionRows.roomTypePorch" +msgstr "Porche" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: b648bee0231a41cd +# English: "Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s." +# Source-Hash: af2c941e8ef874bf # Review: required -# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialWalls" -msgstr "Partiel · {walls, plural, one {{wallCount} mur} many {{wallCount} murs} other {{wallCount} murs}}, {openSides, plural, one {{openSideCount} côté ouvert} many {{openSideCount} côtés ouverts} other {{openSideCount} côtés ouverts}}" +msgid "editor.panels.roomConstructionRows.roomTypePorchHint" +msgstr "Extérieur couvert : dalle béton 1½ in plus bas que la maison, plafond à la hauteur de celui de la maison." -# Source-Hash: e503250e8ecb0030 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.raised" -msgstr "Surélevé de {lift}" +msgid "editor.panels.roomConstructionRows.roomTypeRoom" +msgstr "Pièce" -# Source-Hash: 0cb87327b8076151 +# English: "An ordinary room inside the house." +# Source-Hash: fb0a6cc444058631 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.replace" -msgstr "Remplacer" +msgid "editor.panels.roomConstructionRows.roomTypeRoomHint" +msgstr "Une pièce ordinaire, à l’intérieur de la maison." +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.select" msgstr "Sélectionner" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.shape" msgstr "Forme" +# English: "Turning walls off keeps the ones shared with {rooms}." # Source-Hash: aafd84823715d698 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turning walls off keeps the ones shared with {rooms}." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sharedWallsNote" msgstr "Désactiver les murs conserve ceux partagés avec {rooms}." +# English: "Sits on: {name} · {preset}" # Source-Hash: c98df900d43f77bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sits on: {name} · {preset}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sitsOnPreset" msgstr "Repose sur : {name} · {preset}" -# Source-Hash: a17a79e835fa2064 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.slab" +msgstr "Dalle" + # English: "the next room" +# Source-Hash: a17a79e835fa2064 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.theNextRoom" msgstr "la pièce voisine" +# English: "Use existing" # Source-Hash: ebae699e0cb18ea6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use existing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.useExisting" msgstr "Utiliser l’existant" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" # Source-Hash: 9976021d9162cd5e # Review: required -# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallCount" msgstr "{walls, plural, one {{wallCount} mur} many {{wallCount} murs} other {{wallCount} murs}}" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.walls" msgstr "Murs" +# English: "Walls thicken inward, so the building's outer size stays the same." # Source-Hash: 6c69eb46a0c40d52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls thicken inward, so the building's outer size stays the same." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding" msgstr "Les murs s’épaississent vers l’intérieur : la taille extérieure du bâtiment reste la même." +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.cutOpening" msgstr "Découper une ouverture" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.done" msgstr "Terminé" +# English: "Edit floor" # Source-Hash: 2c89abb1778a3f9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.editFloor" msgstr "Modifier le plancher" +# English: "Elevator opening" # Source-Hash: ca18a6c5398fc8ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.elevatorOpening" msgstr "Trémie d’ascenseur" +# English: "No openings yet. Cut one for a stair void or a hatch." # Source-Hash: eafea43e795f3708 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No openings yet. Cut one for a stair void or a hatch." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.noOpeningsYetCutOneFor" msgstr "Aucune ouverture pour l’instant. Découpez-en une pour une trémie d’escalier ou une trappe." +# English: "Opening {number}" # Source-Hash: 60fac94ffa614238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openingNumber" msgstr "Ouverture {number}" +# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" # Source-Hash: 7aafbf90d659dcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openings" msgstr "{count, plural, =0 {Aucune ouverture} one {{number} ouverture} many {{number} ouvertures} other {{number} ouvertures}}" +# English: "Stair opening" # Source-Hash: c1a1794d31990f5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.stairOpening" msgstr "Trémie d’escalier" +# English: "Add stairs" # Source-Hash: f0a46329fef44d94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addStairs" msgstr "Ajouter un escalier" +# English: "Add to a unit" # Source-Hash: 8a2ab34a61b9d181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to a unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addToAUnit" msgstr "Ajouter à un logement" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.area" msgstr "Surface" +# English: "Browse" # Source-Hash: 88f1a727e5528c10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.browse" msgstr "Parcourir" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.builtWith" msgstr "Construit avec" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.ceiling" msgstr "Plafond" +# English: "Delete room" # Source-Hash: a5e4fe4960deced9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.deleteRoom" msgstr "Supprimer la pièce" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.editShape" msgstr "Modifier la forme" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.floor" msgstr "Niveau" +# English: "{height} headroom" # Source-Hash: d17c3b63449c052b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{height} headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.headroom" msgstr "Échappée de {height}" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.height" msgstr "Hauteur" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.hideCode" msgstr "Masquer le code" +# English: "Room · {level}" # Source-Hash: e93969f5a7cbb140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerLevel" msgstr "Pièce · {level}" +# English: "Mezzanine · in {room}" # Source-Hash: f4ad56b4e0c014b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerMezzanineIn" msgstr "Mezzanine · dans {room}" +# English: "Room · not enclosed" # Source-Hash: de37205633316011 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · not enclosed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerNotEnclosed" msgstr "Pièce · non fermée" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.loading" msgstr "Chargement…" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanine" msgstr "Mezzanine" +# English: "Mezzanine name" # Source-Hash: 97de3df03c4a0be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineName" msgstr "Nom de la mezzanine" +# English: "Mezzanine names" # Source-Hash: 42cfb1bcb80d09e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineNames" msgstr "Noms de mezzanine" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.newUnit" msgstr "Nouveau logement" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.none" msgstr "Aucun" +# English: "Not enough headroom" # Source-Hash: e92d6108b57df9e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not enough headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.notEnoughHeadroom" msgstr "Échappée insuffisante" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" # Source-Hash: 81a6770092b05d48 # Review: required -# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openingCounts" msgstr "{doors, plural, one {{doorCount} porte} many {{doorCount} portes} other {{doorCount} portes}} · {windows, plural, one {{windowCount} fenêtre} many {{windowCount} fenêtres} other {{windowCount} fenêtres}}" +# English: "{area} · Close its walls to build its floor and ceiling." # Source-Hash: a8cd9507ce472a48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Close its walls to build its floor and ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openRoomArea" msgstr "{area} · Fermez ses murs pour construire son plancher et son plafond." +# English: "Over the mezzanine" # Source-Hash: f8aa48ec39a15616 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Over the mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.overTheMezzanine" msgstr "Au-dessus de la mezzanine" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.perimeter" msgstr "Périmètre" +# English: "Pick" # Source-Hash: ed2aaf0a8d5a9b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pick" msgstr "Choisir" +# English: "Picking it adds a number" # Source-Hash: 7baaebbd411cc837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picking it adds a number" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pickingItAddsANumber" msgstr "Le choisir ajoute un numéro" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.remove" msgstr "Retirer" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnit" msgstr "Retirer du logement" +# English: "Remove from {unit}" # Source-Hash: a11d13b92b3cefdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from {unit}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnitNamed" msgstr "Retirer de {unit}" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.rename" msgstr "Renommer" +# English: "{name} · Rename" # Source-Hash: 3562f9bea0a4a8f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{name} · Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.renameNamed" msgstr "{name} · Renommer" +# English: "Revert" # Source-Hash: d0d4e919bb58a977 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Revert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.revert" msgstr "Rétablir" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.room" msgstr "Pièce" +# English: "Room colour — used in the plan and labels" # Source-Hash: 15ae260fae44f42f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room colour — used in the plan and labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomColourUsedInThePlan" msgstr "Couleur de la pièce — utilisée dans le plan et les étiquettes" +# English: "Room name" # Source-Hash: bf75e739dd7e5877 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomName" msgstr "Nom de la pièce" +# English: "Room names" # Source-Hash: 2ecf8c059646873b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomNames" msgstr "Noms des pièces" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.scriptedFinish" msgstr "Finition scriptée" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.shape" msgstr "Forme" +# English: "Show mezzanine names" # Source-Hash: e51144ca1759a7bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showMezzanineNames" msgstr "Afficher les noms de mezzanine" +# English: "Show room names" # Source-Hash: 875b7303c47df33f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showRoomNames" msgstr "Afficher les noms des pièces" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.stairs" msgstr "Escalier" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.unit" msgstr "Lot" +# English: "Use" # Source-Hash: 91436d6b374e8263 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.use" msgstr "Utiliser" +# English: "{used} on this level" # Source-Hash: 61880aff6cd698c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{used} on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.usedOnThisLevel" msgstr "{used} sur ce niveau" +# English: "{use} “{name}”" # Source-Hash: 611604740ed4549c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{use} “{name}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.useTyped" msgstr "{use} « {name} »" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.viewCode" msgstr "Voir le code" +# English: "The script could not be read." # Source-Hash: ba15840e60ce672b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The script could not be read." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "editor.panels.scriptUnreadable" msgstr "Le script n’a pas pu être lu." +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.polygon" msgstr "Polygone" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.rectangle" msgstr "Rectangle" +# English: "Delete region" # Source-Hash: 5dc88f90e6aa4129 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete region" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.deleteRegion" msgstr "Supprimer la zone" +# English: "Paint regions" # Source-Hash: d81d8cbadf5d4f33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint regions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.paintRegions" msgstr "Zones de peinture" -# Review: required -# Source-Hash: 89ee318aad485f83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Adjust design" +# Source-Hash: 89ee318aad485f83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.adjustDesign" msgstr "Ajuster le modèle" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.back" msgstr "Retour" -# Review: required -# Source-Hash: ba9b68175315e177 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit parts" +# Source-Hash: ba9b68175315e177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.editParts" msgstr "Modifier les pièces" -# Review: required -# Source-Hash: 07c62820f014ed6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid attachment" +# Source-Hash: 07c62820f014ed6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidAttachment" msgstr "Fixation non valide" -# Review: required -# Source-Hash: f862aea8d7957c55 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid edit" +# Source-Hash: f862aea8d7957c55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidEdit" msgstr "Modification non valide" -# Review: required -# Source-Hash: 190cf30b8edd8dd1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid placement" +# Source-Hash: 190cf30b8edd8dd1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPlacement" msgstr "Placement non valide" -# Review: required -# Source-Hash: 45bd2ec690cc48dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid position" +# Source-Hash: 45bd2ec690cc48dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPosition" msgstr "Position non valide" -# Review: required -# Source-Hash: faccab895717c598 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid reset" +# Source-Hash: faccab895717c598 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidReset" msgstr "Réinitialisation non valide" -# Review: required -# Source-Hash: 28fe6e9deddde6e8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "pos" +# Source-Hash: 28fe6e9deddde6e8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.pos" msgstr "pos" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.position" msgstr "Position" -# Review: required -# Source-Hash: 3fce677837d5a876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reset this part" +# Source-Hash: 3fce677837d5a876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.resetThisPart" msgstr "Réinitialiser cette pièce" +# English: "Reload editor" # Source-Hash: 5eab376947d29a50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload editor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.reloadEditor" msgstr "Recharger l’éditeur" -# Review: required -# Source-Hash: 1063f1e315258b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close riser diagram" +# Source-Hash: 1063f1e315258b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.close" msgstr "Fermer le schéma de colonnes" -# Review: required -# Source-Hash: c2d59ee62035974c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV plumbing isometric" +# Source-Hash: c2d59ee62035974c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvPlumbingIsometric" msgstr "Isométrie d'évacuation et de ventilation" -# Review: required -# Source-Hash: 3501a7c8a93d3b19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV riser diagram" +# Source-Hash: 3501a7c8a93d3b19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvRiserDiagram" msgstr "Schéma des colonnes d'évacuation" -# Review: required -# Source-Hash: 225e3e3dc3bb01ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram." +# Source-Hash: 225e3e3dc3bb01ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.noDrainWasteOrVentPipes" msgstr "Aucun tuyau d'évacuation ou de ventilation pour l'instant. Dessinez la plomberie pour voir le schéma de colonnes." -# Review: required -# Source-Hash: bc8e172ba55160ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Riser Diagram" +# Source-Hash: bc8e172ba55160ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.riserDiagram" msgstr "Schéma de colonnes" -# Review: required -# Source-Hash: 6a1132961f17f320 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" P-trap" +# Source-Hash: 6a1132961f17f320 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.trap" msgstr "Siphon {diameter}\"" -# Review: required -# Source-Hash: 04bc0909a133a7e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vent" +# Source-Hash: 04bc0909a133a7e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.vent" msgstr "Ventilation" -# Review: required -# Source-Hash: 02f3eef9f0fb6d9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" VTR" +# Source-Hash: 02f3eef9f0fb6d9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Vent through roof: where a plumbing vent stack exits the roof. #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.ventTermination" msgstr "Sortie de toit {diameter}\"" -# Review: required -# Source-Hash: 29d8590334492601 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waste" +# Source-Hash: 29d8590334492601 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.waste" msgstr "Eaux usées" +# English: "{current} · {distance} above {shared}" # Source-Hash: 0f8d128255bf4afa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} above {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.above" msgstr "{current} · {distance} au-dessus de {shared}" +# English: "{current} · {distance} below {shared}" # Source-Hash: 8416f433385a2936 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} below {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.below" msgstr "{current} · {distance} sous {shared}" +# English: "{current} · level with {shared}" # Source-Hash: 6dc18e9fada391f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · level with {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.levelWith" msgstr "{current} · au niveau de {shared}" -# Source-Hash: 612dcd5bf06a79c4 +# English: "Cannot read \"{input}\"" +# Source-Hash: 782bc47cb30d5c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.cannotReadInput" +msgstr "Impossible de lire « {input} »" + +# English: "Dimension value" +# Source-Hash: 70ca08939d758332 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.dimensionValue" +msgstr "Valeur de la cote" + +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.floor" +msgstr "Plancher" + +# English: "Type a size to move the wall" +# Source-Hash: de281c37906e7d14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.typeASizeToMoveThe" +msgstr "Saisissez une cote pour déplacer le mur" + # English: "Room" +# Source-Hash: 612dcd5bf06a79c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.room" msgstr "Pièce" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.zone" msgstr "Zone" +# English: "Attic" # Source-Hash: 7da15e7c88dab485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.attic" msgstr "Combles" +# English: "Balcony" # Source-Hash: 52adf6ccf54fd772 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balcony" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.balcony" msgstr "Balcon" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Basement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.basement" msgstr "Sous-sol" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bathroom" msgstr "Salle de bain" +# English: "Bedroom" # Source-Hash: f25239fe3891ea8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bedroom" msgstr "Chambre" +# English: "Boiler room" # Source-Hash: 77d15570e2eba719 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boiler room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.boilerRoom" msgstr "Chaufferie" +# English: "Carport" # Source-Hash: 5fbe77592f8f0ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carport" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.carport" msgstr "Abri de voiture" +# English: "Cellar" # Source-Hash: f31dc305ce3a803a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cellar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.cellar" msgstr "Cave" +# English: "Closet" # Source-Hash: e9af4141e3f8bdd1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.closet" msgstr "Placard" +# English: "Corridor" # Source-Hash: 388530b7b9c6647d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corridor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.corridor" msgstr "Couloir" +# English: "Courtyard" # Source-Hash: 4d4a23b87e911d2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Courtyard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.courtyard" msgstr "Cour" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.deck" msgstr "Terrasse" +# English: "Den" # Source-Hash: 18d7f85ff03c43c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Den" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.den" msgstr "Petit salon" +# English: "Dining room" # Source-Hash: 4424343cad1f08b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dining room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.diningRoom" msgstr "Salle à manger" +# English: "Dressing room" # Source-Hash: 5efd0185e89ee84e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dressing room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.dressingRoom" msgstr "Dressing" +# English: "En-suite" # Source-Hash: f45768e27780dc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "En-suite" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.enSuite" msgstr "Salle d’eau attenante" +# English: "Entrance" # Source-Hash: 51c664d19e5ea703 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Entrance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.entrance" msgstr "Entrée" +# English: "Family room" # Source-Hash: 4b32a81a5abf006a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Family room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.familyRoom" msgstr "Salle familiale" +# English: "Gallery" # Source-Hash: e87e6bd9a7d6d667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gallery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gallery" msgstr "Galerie" +# English: "Games room" # Source-Hash: cf7cc0ba06d5eea1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Games room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gamesRoom" msgstr "Salle de jeux" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garage" msgstr "Garage" +# English: "Garden" # Source-Hash: 5073b3c2989a626d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garden" msgstr "Jardin" +# English: "Garden shed" # Source-Hash: 52fe13434cdb7b27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gardenShed" msgstr "Abri de jardin" +# English: "Guest room" # Source-Hash: a2712156dcaec1ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guest room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.guestRoom" msgstr "Chambre d’amis" +# English: "Gym" # Source-Hash: a1128cf188432bb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gym" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gym" msgstr "Salle de sport" +# English: "Hallway" # Source-Hash: 18664ee5a1abd06d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hallway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.hallway" msgstr "Couloir" +# English: "Home cinema" # Source-Hash: 7dd957fc73d4d73a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Home cinema" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.homeCinema" msgstr "Home cinéma" +# English: "Kids' room" # Source-Hash: e1579515f44233ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kids' room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kidsRoom" msgstr "Chambre d’enfant" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchen" msgstr "Cuisine" +# English: "Kitchenette" # Source-Hash: 06eb4c431b9b09b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchenette" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchenette" msgstr "Kitchenette" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.landing" msgstr "Palier" +# English: "Laundry" # Source-Hash: 558bcafa2ec70ded # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Laundry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.laundry" msgstr "Buanderie" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.library" msgstr "Bibliothèque" +# English: "Living room" # Source-Hash: 2204538bab0e77ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.livingRoom" msgstr "Salon" +# English: "Loft" # Source-Hash: bb14c08f0c3db4ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.loft" msgstr "Loft" +# English: "Lounge" # Source-Hash: a6a0e8bf816cbcb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lounge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.lounge" msgstr "Salon" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mezzanine" msgstr "Mezzanine" +# English: "Mudroom" # Source-Hash: 6b2c82e5fbccb28b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mudroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mudroom" msgstr "Vestiaire d’entrée" +# English: "Nursery" # Source-Hash: e3c3b9b917744680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nursery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.nursery" msgstr "Chambre de bébé" +# English: "Office" # Source-Hash: c1f1fcd9cb418340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Office" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.office" msgstr "Bureau" +# English: "Pantry" # Source-Hash: 5d98d41ec10bd05e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pantry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.pantry" msgstr "Garde-manger" +# English: "Patio" # Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Patio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.patio" msgstr "Patio" +# English: "Playroom" # Source-Hash: c3cfcccc167b1c19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Playroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.playroom" msgstr "Salle de jeux" +# English: "Pool area" # Source-Hash: 2e6324beaed18dd5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pool area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.poolArea" msgstr "Espace piscine" +# English: "Porch" # Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Porch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.porch" msgstr "Porche" +# English: "Powder room" # Source-Hash: 04cb81f4a4ae4ff3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Powder room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.powderRoom" msgstr "Toilettes invités" +# English: "Primary bedroom" # Source-Hash: 599c44d857832cd0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Primary bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.primaryBedroom" msgstr "Chambre principale" +# English: "Reading nook" # Source-Hash: 9a6ac3f1cdd8de2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reading nook" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.readingNook" msgstr "Coin lecture" +# English: "Bathrooms" # Source-Hash: 01f8cc22989822c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathrooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.bathrooms" msgstr "Salles de bain" +# English: "Circulation" # Source-Hash: 9ec82561de8cb788 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Circulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.circulation" msgstr "Circulations" +# English: "Living" # Source-Hash: f74a48b6b49644d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.living" msgstr "Séjour" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.mezzanine" msgstr "Mezzanine" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.outdoor" msgstr "Extérieur" +# English: "Sleeping" # Source-Hash: 5df703d5b5e6ef7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sleeping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.sleeping" msgstr "Nuit" +# English: "Utility & storage" # Source-Hash: c9d15ec19f814524 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility & storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.utility" msgstr "Service et rangement" +# English: "Work & hobbies" # Source-Hash: fbe13f7ab45e4cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Work & hobbies" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.work" msgstr "Travail et loisirs" +# English: "Shower room" # Source-Hash: 10ccb6591f6ad207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shower room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.showerRoom" msgstr "Salle de douche" +# English: "Stairwell" # Source-Hash: 5254168cc5712cb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairwell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.stairwell" msgstr "Cage d’escalier" +# English: "Storage" # Source-Hash: d1596647cd84ade3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.storage" msgstr "Rangement" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Studio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.studio" msgstr "Studio" +# English: "Study" # Source-Hash: ebe68e83e73021ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Study" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.study" msgstr "Bureau" +# English: "Sunroom" # Source-Hash: d69a0244649771ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.sunroom" msgstr "Véranda" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.terrace" msgstr "Terrasse" +# English: "Toilet" # Source-Hash: a0cb3e6f5cf4fbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Toilet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.toilet" msgstr "WC" +# English: "Utility room" # Source-Hash: 6c01526f89f3106b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.utilityRoom" msgstr "Cellier" +# English: "Veranda" # Source-Hash: fc42d89621ae7bac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veranda" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.veranda" msgstr "Véranda" +# English: "Walk-in closet" # Source-Hash: b45d7730e896fd99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk-in closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.walkInCloset" msgstr "Dressing" +# English: "Workshop" # Source-Hash: 399f27253ef956cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Workshop" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.workshop" msgstr "Atelier" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.settings" msgstr "Paramètres" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.site" msgstr "Terrain" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.all" msgstr "Tout" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.community" msgstr "Communauté" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.all" msgstr "Tout" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.community" msgstr "Communauté" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.library" msgstr "Bibliothèque" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.mine" msgstr "Les miens" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch" msgstr "Aucun résultat pour « {search} »" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.search" msgstr "Rechercher..." +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.library" msgstr "Bibliothèque" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.mine" msgstr "Les miens" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.noResultsForSearch" msgstr "Aucun résultat pour « {search} »" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.search" msgstr "Rechercher..." -# Source-Hash: 3c67cde0578ac829 +# English: "Build Tools" +# Source-Hash: fb66c49eee49a3bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools" +msgstr "Outils de construction" + +# English: "Camera" +# Source-Hash: 14622745dbf93f4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera" +msgstr "Caméra" + +# English: "Direct Manipulation" +# Source-Hash: 677783232bea72a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation" +msgstr "Manipulation directe" + +# English: "Drawing Tools" +# Source-Hash: 8e67947d43b76596 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools" +msgstr "Outils de dessin" + +# English: "Editor Navigation" +# Source-Hash: 8cdda30f3be2bac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation" +msgstr "Navigation dans l’éditeur" + +# English: "Item Placement" +# Source-Hash: 9a996e3570d615ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement" +msgstr "Placement des éléments" + +# English: "Keyboard shortcuts" +# Source-Hash: fc18926f9bd9eebd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts" +msgstr "Raccourcis clavier" + +# English: "Modes & History" +# Source-Hash: cbb4c3e007d70aa0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory" +msgstr "Modes et historique" + +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection" +msgstr "Sélection" + +# English: "Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely." +# Source-Hash: 8a708ce860d787e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints" +msgstr "Les raccourcis dépendent du contexte. Les contraintes guidées sont activées par défaut ; maintenez Maj pendant un geste en cours pour construire librement." + # English: "Add level" +# Source-Hash: 3c67cde0578ac829 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addLevel" msgstr "Ajouter un niveau" +# English: "Add point" # Source-Hash: 9a56d90d461b4bca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addPoint" msgstr "Ajouter un point" +# English: "Area:" # Source-Hash: fedccec504a9ae33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.area" msgstr "Surface :" +# English: "Base elevation" # Source-Hash: 87b6880f6de7a45a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.baseElevation" msgstr "Altitude de base" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.building" msgstr "Bâtiment" +# English: "Add new level" # Source-Hash: 7c90f458259d5cd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add new level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel" msgstr "Ajouter un nouveau niveau" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.building" msgstr "Bâtiment" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.cameraSnapshot" msgstr "Instantané de caméra" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.capture" msgstr "Capture" +# English: "Clear selection" # Source-Hash: 71f2343818c55e0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSelection" msgstr "Effacer la sélection" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSnapshot" msgstr "Effacer l’instantané" +# English: "Close walls around a space to make a room." # Source-Hash: def517c77fda49ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close walls around a space to make a room." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo" msgstr "Fermez des murs autour d’un espace pour créer une pièce." +# English: "Collection colour" # Source-Hash: 2b9af8da4b34cbfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection colour" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour" msgstr "Couleur de la collection" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collections" msgstr "Collections" +# English: "Delete collection" # Source-Hash: 6f4751c26f12aef9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete collection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection" msgstr "Supprimer la collection" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.delete" msgstr "Supprimer" +# English: "Delete level" # Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.deleteLevel" msgstr "Supprimer le niveau" +# English: "Draw a zone" # Source-Hash: 6552e45eecc0a227 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.drawAZone" msgstr "Dessiner une zone" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicate" msgstr "Dupliquer" +# English: "Duplicate level" # Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevel" msgstr "Dupliquer le niveau" +# English: "Duplicate level with options" # Source-Hash: c038947f317cabba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level with options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevelWithOptions" msgstr "Dupliquer le niveau avec options" +# English: "Duplicate with options..." # Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateWithOptions" msgstr "Dupliquer avec options..." +# English: "Elements" # Source-Hash: 7c4196b9e791c195 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.elements" msgstr "Éléments" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.guide" msgstr "Guide" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.hide" msgstr "Masquer" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.level" msgstr "Niveau" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.newUnit" msgstr "Nouveau logement" +# English: "No buildings yet" # Source-Hash: 0192748d50f0e5f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No buildings yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noBuildingsYet" msgstr "Aucun bâtiment pour l’instant" +# English: "No elements on this level" # Source-Hash: 1be310702a12e433 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No elements on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noElementsOnThisLevel" msgstr "Aucun élément sur ce niveau" +# English: "No levels yet" # Source-Hash: 0956af88ced36992 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No levels yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noLevelsYet" msgstr "Aucun niveau pour l’instant" +# English: "{count, plural, one {# object selected} other {# objects selected}}" # Source-Hash: 45c41bfb2ae19627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# object selected} other {# objects selected}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.objectsSelected" msgstr "{count, plural, one {# objet sélectionné} many {# objets sélectionnés} other {# objets sélectionnés}}" +# English: "Other zones" # Source-Hash: a5434e26e5cb1b26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.otherZones" msgstr "Autres zones" +# English: "Perimeter:" # Source-Hash: 2aa38d84079d4dba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.perimeter" msgstr "Périmètre :" +# English: "Property Line" # Source-Hash: 4004b9a8e4f8c9d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Property Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.propertyLine" msgstr "Limite de propriété" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.rooms" msgstr "Pièces" +# English: "Select a level to view content" # Source-Hash: ca39c0581c7318e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view content" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.selectALevelToViewContent" msgstr "Sélectionnez un niveau pour voir son contenu" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.show" msgstr "Afficher" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.site" msgstr "Terrain" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.takeSnapshot" msgstr "Prendre un instantané" +# English: "The ground level cannot be deleted" # Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted" msgstr "Le niveau du rez-de-chaussée ne peut pas être supprimé" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot" msgstr "Instantané de caméra" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot" msgstr "Effacer l’instantané" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.hide" msgstr "Masquer" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.show" msgstr "Afficher" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot" msgstr "Prendre un instantané" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot" msgstr "Mettre à jour l’instantané" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot" msgstr "Voir l’instantané" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.units" msgstr "Unités" +# English: "Delete unit" # Source-Hash: 149fec3fd1f08042 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit" msgstr "Supprimer le logement" +# English: "Focused" # Source-Hash: 930399d3fb247a7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.focused" msgstr "Ciblé" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit" msgstr "Retirer du logement" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.room" msgstr "Pièce" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.zone" msgstr "Zone" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.updateSnapshot" msgstr "Mettre à jour l’instantané" +# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" # Source-Hash: 28a12e6806c091b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadingProgress" msgstr "{assetType, select, guide {Import du calque... {progress} %} other {Import du relevé 3D... {progress} %}}" +# English: "Upload scan/floorplan" # Source-Hash: de389a2a46d53384 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload scan/floorplan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadScanFloorplan" msgstr "Importer un relevé 3D ou un plan" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.viewSnapshot" msgstr "Voir l’instantané" +# English: "In {unitName}" # Source-Hash: ba9f809cb032ee57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "In {unitName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname" msgstr "Dans {unitName}" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.actions" msgstr "Actions" +# English: "Add one" # Source-Hash: fcc43f22937d1536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add one" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.addOne" msgstr "En ajouter une" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.cameraSnapshot" msgstr "Instantané de caméra" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.clearSnapshot" msgstr "Effacer l’instantané" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.delete" msgstr "Supprimer" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.deleteWithContents" msgstr "Supprimer avec le contenu" +# English: "No zones on this level." # Source-Hash: 22ca3935e8754072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones on this level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.noZonesOnThisLevel" msgstr "Aucune zone sur ce niveau." +# English: "Save to catalog" # Source-Hash: 941ea30f406dcfa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save to catalog" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.saveToCatalog" msgstr "Enregistrer dans le catalogue" +# English: "Select a level to view and create zones" # Source-Hash: 98662dfb371cac13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view and create zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.selectALevelToViewAnd" msgstr "Sélectionnez un niveau pour voir et créer des zones" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.takeSnapshot" msgstr "Prendre un instantané" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.updateSnapshot" msgstr "Mettre à jour l’instantané" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.viewSnapshot" msgstr "Voir l’instantané" +# English: "\"{label}\" plugin crashed" # Source-Hash: 58b05af5b5724efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "\"{label}\" plugin crashed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.labelPluginCrashed" msgstr "Le plugin « {label} » a planté" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.loading" msgstr "Chargement…" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.plugins" msgstr "Plugins" +# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." # Source-Hash: c6312309378aaaea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd" msgstr "Ce panneau a rencontré une erreur et a été déchargé pour cette session. Le reste de l’éditeur n’est pas affecté — rechargez pour réessayer." -# Review: required -# Source-Hash: 2ab3b6983e384ecc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ceiling attachment" +# Source-Hash: 2ab3b6983e384ecc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.ceiling" msgstr "Fixation au plafond" -# Review: required -# Source-Hash: 54a00bbb699e3778 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof attachment" +# Source-Hash: 54a00bbb699e3778 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.roof" msgstr "Fixation à la toiture" -# Review: required -# Source-Hash: e45fee9c27f6ae69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall attachment" +# Source-Hash: e45fee9c27f6ae69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.wall" msgstr "Fixation murale" +# English: "The editor scene failed to render" # Source-Hash: 834de1a7027c4da1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The editor scene failed to render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.theEditorSceneFailedToRender" msgstr "La scène de l’éditeur n’a pas pu s’afficher" +# English: "A curtain wall takes no corner window" # Source-Hash: 151f7c7e4110016f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curtain wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i" msgstr "Un mur-rideau n’accepte pas de fenêtre d’angle" +# English: "A curved wall takes no corner window" # Source-Hash: 97f5e044d46501fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curved wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5" msgstr "Un mur courbe n’accepte pas de fenêtre d’angle" +# English: "Add a corner · click the first to close" # Source-Hash: 776d8ebece7fe1bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a corner · click the first to close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.addACornerClickTheFirst.o4r67t" msgstr "Ajouter un angle · cliquez sur le premier pour fermer" -# Source-Hash: 6a0745dd047b149a +# English: "Add a wall · each one starts where the last ended" +# Source-Hash: d0ddef756fb49fbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.addAWallEachOneStarts.4zkf12" +msgstr "Ajouter un mur · chacun part de la fin du précédent" + # English: "Another opening is in the way at the corner" +# Source-Hash: 6a0745dd047b149a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0" msgstr "Une autre ouverture gêne à l’angle" +# English: "Auto hangers" # Source-Hash: 441124187e7da2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangers.10554ka" msgstr "Suspentes auto" +# English: "Auto hangers - click or press H to toggle" # Source-Hash: 47a7f9a6a81156c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers - click or press H to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersClickOrPressH.rwa3ka" msgstr "Suspentes auto - cliquez ou appuyez sur H pour basculer" +# English: "Auto hangers: Off" # Source-Hash: d7944f31cba90e26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOff.7yx01x" msgstr "Suspentes auto : désactivées" +# English: "Auto hangers: On" # Source-Hash: 295882d015d08742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOn.10b1umh" msgstr "Suspentes auto : activées" +# English: "Bypass snapping" # Source-Hash: 8c039e5182b01c4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bypass snapping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.bypassSnapping.w5lmdv" msgstr "Ignorer l’aimantation" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancel.ew9em3" msgstr "Annuler" +# English: "Cancel move" # Source-Hash: 5b33b576fd30b74e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelMove.lvp31c" msgstr "Annuler le déplacement" +# English: "Cancel run / exit" # Source-Hash: 136fa29b9ae419bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel run / exit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelRunExit.zqjf3h" msgstr "Annuler le tronçon / quitter" +# English: "Cancel start point" # Source-Hash: 75b406824be91ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel start point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelStartPoint.1xbq2lh" msgstr "Annuler le point de départ" -# Source-Hash: ffa5aaa1746303c4 +# English: "Close the outline" +# Source-Hash: e9b0a295122e7ad4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.closeTheOutline.f6cvzi" +msgstr "Fermer le contour" + # English: "Corner window" +# Source-Hash: ffa5aaa1746303c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cornerWindow.1avkzpo" msgstr "Fenêtre d’angle" +# English: "Cuts" # Source-Hash: b0afdd855a02e4a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cuts.1y0axps" msgstr "Coupes" +# English: "Cuts: {value}" # Source-Hash: dc9fe7601cc525f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValue.ouzhvb" msgstr "Coupes : {value}" +# English: "Cuts: {value}, even" # Source-Hash: a2e13bc4dc82624c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}, even" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValueEven.1ngsz3" msgstr "Coupes : {value}, régulières" +# English: "Cycle mono / gable / butterfly" # Source-Hash: 04152b4cec726f34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle mono / gable / butterfly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleMonoGableButterfly.1poga3m" msgstr "Basculer un pan / deux pans / papillon" +# English: "Cycle snapping mode" # Source-Hash: 4fb6c83edb6652b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle snapping mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleSnappingMode.1ct0gn6" msgstr "Changer le mode d’aimantation" +# English: "Door style — click or press L to cycle" # Source-Hash: 5d550a3ac76279de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorStyleClickOrPressL.33pe65" msgstr "Style de porte — cliquez ou appuyez sur L pour changer" +# English: "Door type — click or press O to cycle" # Source-Hash: 3272a7df5d7c6a9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorTypeClickOrPressO.1hhh8gt" msgstr "Type de porte — cliquez ou appuyez sur O pour changer" +# English: "Drop downspout from outlet" # Source-Hash: 366222b4435eefda # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drop downspout from outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.dropDownspoutFromOutlet.1k4h1m4" msgstr "Descendre la descente depuis la sortie" +# English: "Duct diameter down / up" # Source-Hash: 03f306371f345007 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct diameter down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.ductDiameterDownUp.1wdz5vq" msgstr "Diamètre de gaine moins / plus" +# English: "End run / cancel" # Source-Hash: 7b4137e27f8327d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End run / cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.endRunCancel.59dh2w" msgstr "Terminer le tronçon / annuler" -# Source-Hash: 938aacb119b29dc1 +# English: "End the run" +# Source-Hash: b714e91c31108a71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.endTheRun.1c1raig" +msgstr "Terminer le tracé" + # English: "Exit" +# Source-Hash: 938aacb119b29dc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exit.engmg5" msgstr "Quitter" +# English: "Exit drawing" # Source-Hash: 7cb4e0688cabcb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit drawing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exitDrawing.9noxyb" msgstr "Quitter le tracé" +# English: "Finish and continue" # Source-Hash: 47025a103c456125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish and continue" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishAndContinue.1gpfsks" msgstr "Terminer et continuer" +# English: "Finish ceiling" # Source-Hash: 000918429b74128f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishCeiling.1c1lfel" msgstr "Terminer le plafond" +# English: "Finish grid axis" # Source-Hash: 51a80a0dba5a6f30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishGridAxis.1wvrfhr" msgstr "Terminer l’axe de trame" +# English: "Finish measurement" # Source-Hash: 87c7b98fc2e010e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMeasurement.1e4gv16" msgstr "Terminer la mesure" +# English: "Finish multi-point witnesses" # Source-Hash: 5e1a0af7dc506826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish multi-point witnesses" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMultiPointWitnesses.8wzskz" msgstr "Terminer les repères multipoints" +# English: "Finish slab" # Source-Hash: 30fc0862501ba2e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishSlab.xmnm8e" msgstr "Terminer la dalle" -# Source-Hash: 66ed7c7598d3aa29 +# English: "Finish the run" +# Source-Hash: 54ae97157eacbc56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.finishTheRun.ayliww" +msgstr "Terminer le tracé" + # English: "Flip side" +# Source-Hash: 66ed7c7598d3aa29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.flipSide.172xku9" msgstr "Inverser le côté" +# English: "Follow: trace a lineset" # Source-Hash: f5717f6a0905ffab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow: trace a lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.followTraceALineset.mgquwe" msgstr "Suivre : longer une liaison frigorifique" +# English: "Force place" # Source-Hash: ed7f3d8417a3e074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.forcePlace.1hq1nr" msgstr "Forcer le placement" -# Source-Hash: cc0c14065951444d +# English: "Free angle" +# Source-Hash: 64b4ac6d3da25183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freeAngle.1ptblho" +msgstr "Angle libre" + +# English: "Free place" +# Source-Hash: 446b8aac1e5e8422 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freePlace.1s1uusw" +msgstr "Placement libre" + # English: "Free placement" +# Source-Hash: cc0c14065951444d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.freePlacement.1nztdac" msgstr "Placement libre" +# English: "Go vertical ↕, click to place" # Source-Hash: f098a27096a9c1ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Go vertical ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.goVerticalClickToPlace.dg8g6m" msgstr "Passer en vertical ↕, cliquez pour placer" +# English: "Highlight outlet" # Source-Hash: fd47f1820d8799c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Highlight outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.highlightOutlet.65hoq8" msgstr "Mettre en évidence la sortie" +# English: "Join as a corner window" # Source-Hash: eb90058d0f856f67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Join as a corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.joinAsACornerWindow.jvdp4j" msgstr "Joindre en fenêtre d’angle" +# English: "Alt + drag" # Source-Hash: 225b3161f2e59757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alt + drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.altDrag.ai3wn9" msgstr "Alt + glisser" +# English: "Backspace" # Source-Hash: dd66f883b173fc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Backspace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.backspace.qy6qx8" msgstr "Retour arrière" +# English: "Click" # Source-Hash: a18387bc0be54c32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.click.m8sun" msgstr "Clic" +# English: "Click again" # Source-Hash: db9e60c1d30b8499 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click again" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.clickAgain.190ws9j" msgstr "Cliquer à nouveau" +# English: "Command" # Source-Hash: f400330b9c9c12e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Command" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.command.1j2mkia" msgstr "Commande" +# English: "Control" # Source-Hash: 976c63285a510b09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Control" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.control.10fbl8e" msgstr "Contrôle" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.delete.oay2cq" msgstr "Supprimer" +# English: "Double click" # Source-Hash: 88888c9d2719fa92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.doubleClick.vlma6o" msgstr "Double-clic" +# English: "Drag" # Source-Hash: dfb85b2addb7b9fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.drag.8pwtpf" msgstr "Glisser" +# English: "Enter" # Source-Hash: ed9140d42148c99b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.enter.1ctpui5" msgstr "Entrée" +# English: "Hover" # Source-Hash: 9195fa8b9c8ebdfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hover.6xw79p" msgstr "Survoler" +# English: "Hover a duct end" # Source-Hash: 2d78441b741bc562 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a duct end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverADuctEnd.19axr3b" msgstr "Survolez une extrémité de gaine" +# English: "Hover a pipe end" # Source-Hash: 6083405086f61c76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a pipe end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverAPipeEnd.1jbum5f" msgstr "Survolez une extrémité de tuyau" +# English: "Hover gutter" # Source-Hash: a5a337eb76fdb066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverGutter.1dwcsno" msgstr "Survolez la gouttière" +# English: "Left click" # Source-Hash: a58533594411e088 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.leftClick.s4gwfc" msgstr "Clic gauche" +# English: "Middle click" # Source-Hash: bc4ab608ec3d3b4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Middle click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.middleClick.1jbaan4" msgstr "Clic central" +# English: "Right click" # Source-Hash: 6791879c651108c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rightClick.1ovld0h" msgstr "Clic droit" -# Source-Hash: c836b21b42198868 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.key.room.1f3jkpm" +msgstr "Pièce" + # English: "R / Shift+R" +# Source-Hash: c836b21b42198868 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rShiftR.13bnobh" msgstr "R / Maj+R" +# English: "Scroll" # Source-Hash: 9812aa06d8bb7544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scroll.ai6wr0" msgstr "Défilement" +# English: "Scroll wheel" # Source-Hash: f2174536230657c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll wheel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scrollWheel.18j0ix7" msgstr "Molette" +# English: "Space" # Source-Hash: 82dd231086d87f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Space" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.space.1hrh82t" msgstr "Espace" +# English: "Tab" # Source-Hash: 9e68aa2f472cad56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.tab.1xsal0s" msgstr "Tab" +# English: "Mount: floor / ceiling / wall" # Source-Hash: 4b221b73f9496a49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount: floor / ceiling / wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.mountFloorCeilingWall.32dxpv" msgstr "Pose : sol / plafond / mur" +# English: "No room for the matching window on the other wall" # Source-Hash: 225edfb1e45aed95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No room for the matching window on the other wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noRoomForTheMatchingWindow.1755948" msgstr "Pas de place pour la fenêtre correspondante sur l’autre mur" -# Source-Hash: a7045d6b678bcd13 +# English: "Not a rectangle: draw this roof instead" +# Source-Hash: e367d28a655f1cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.notARectangleDrawThisRoof.100pa5g" +msgstr "Non rectangulaire : dessinez plutôt ce toit" + # English: "No wall meets here" +# Source-Hash: a7045d6b678bcd13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noWallMeetsHere.19n99yc" msgstr "Aucun mur ne se rejoint ici" +# English: "Number of cuts — scroll or click to change" # Source-Hash: a639341dc21fed2c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Number of cuts — scroll or click to change" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.numberOfCutsScrollOrClick.1kqe84b" msgstr "Nombre de coupes — faites défiler ou cliquez pour modifier" +# English: "Pick witness point" # Source-Hash: af01bde3ae4b7e59 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick witness point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pickWitnessPoint.k3hgh1" msgstr "Choisir un point de repère" +# English: "Pipe size down / up" # Source-Hash: ecfd58576b28a9f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe size down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pipeSizeDownUp.wp04og" msgstr "Diamètre de tuyau moins / plus" -# Source-Hash: 7a1eb7b64f06bad0 +# English: "Place a corner of the foundation" +# Source-Hash: e0162c885a961919 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j" +msgstr "Placer un angle de la fondation" + # English: "Place and continue" +# Source-Hash: 7a1eb7b64f06bad0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeAndContinue.qfwkrq" msgstr "Placer et continuer" +# English: "Place block" # Source-Hash: c6cda39e2e4b5d3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBlock.1aylwrx" msgstr "Placer le bloc" +# English: "Place box vent on roof" # Source-Hash: 45a743c52a1d72de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place box vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBoxVentOnRoof.kf88e5" msgstr "Placer l’aérateur sur le toit" +# English: "Place cabinet" # Source-Hash: 75142dd1d557efe2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCabinet.b0ouyk" msgstr "Placer le meuble" +# English: "Place canopy or set the next run point" # Source-Hash: da603a713d4c9909 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place canopy or set the next run point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCanopyOrSetTheNext.12l7zf8" msgstr "Placer l’auvent ou le point suivant" +# English: "Place chimney on roof" # Source-Hash: 8b3a30c6b7a5612d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place chimney on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeChimneyOnRoof.19dbqu" msgstr "Placer la cheminée sur le toit" +# English: "Place column" # Source-Hash: 710d41e4e2f259fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeColumn.1w2vq86" msgstr "Placer le poteau" +# English: "Place cupola on roof" # Source-Hash: ee1da4e12a71d9a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cupola on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCupolaOnRoof.ofx3bn" msgstr "Placer le lanterneau sur le toit" +# English: "Place dimension line when needed" # Source-Hash: 19c1fe8c46d213a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dimension line when needed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj" msgstr "Placer la ligne de cote si nécessaire" +# English: "Place door on wall" # Source-Hash: 58f3e7efb6ea120b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place door on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDoorOnWall.1jwadf" msgstr "Placer la porte sur le mur" +# English: "Place dormer on roof" # Source-Hash: de04e9d03d937c97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dormer on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDormerOnRoof.1w518a6" msgstr "Placer la lucarne sur le toit" +# English: "Place elevator" # Source-Hash: c581645f2ee447c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeElevator.1f65zva" msgstr "Placer l’ascenseur" +# English: "Place eyebrow vent on roof" # Source-Hash: 096b8d6482717358 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place eyebrow vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn" msgstr "Placer le chien-assis sur le toit" +# English: "Place fitting" # Source-Hash: e8d42c1c8370773f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeFitting.1pmx3vn" msgstr "Placer le raccord" +# English: "Place gutter on roof eave" # Source-Hash: 2340a4b70cb0d9ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gutter on roof eave" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeGutterOnRoofEave.llwzf9" msgstr "Placer la gouttière sur l’égout du toit" +# English: "Place item" # Source-Hash: 706a8eafb6212833 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItem.teynf" msgstr "Placer l’élément" +# English: "Place it (locked to 45°)" # Source-Hash: 1815c482e249932e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place it (locked to 45°)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItLockedTo45.16e4csk" msgstr "Placer (verrouillé à 45°)" +# English: "Place measurement point" # Source-Hash: 4a14340657532838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place measurement point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeMeasurementPoint.bsatli" msgstr "Placer le point de mesure" +# English: "Placement" # Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placement.13lcvts" msgstr "Placement" +# English: "Placement: Auto" # Source-Hash: 2bb2ce6276c69b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementAuto.1caa52d" msgstr "Placement : auto" +# English: "Placement: Ground" # Source-Hash: ed78ac5354a1dcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementGround.1ruz145" msgstr "Placement : sol" +# English: "Placement: Roof" # Source-Hash: a066a2c9d5e65a1a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementRoof.renkw8" msgstr "Placement : toit" +# English: "Placement surface - click or press P to cycle" # Source-Hash: 8f74dbe5064c4c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement surface - click or press P to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementSurfaceClickOrPressP.w56h19" msgstr "Surface de placement - cliquez ou appuyez sur P pour changer" +# English: "Placement type" # Source-Hash: fe119955c925feaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementType.1mdpl60" msgstr "Type de placement" +# English: "Placement type — click or press I to toggle" # Source-Hash: 33043a35500168f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type — click or press I to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementTypeClickOrPressI.10cgxh2" msgstr "Type de placement — cliquez ou appuyez sur I pour basculer" +# English: "Place on a fence" # Source-Hash: 9945f412e6a9a446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place on a fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeOnAFence.1dptstr" msgstr "Placer sur une clôture" +# English: "Place register" # Source-Hash: 6708118775a7da7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRegister.1uebycd" msgstr "Placer la bouche" +# English: "Place ridge vent on roof" # Source-Hash: ebd2731f5c79306e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place ridge vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRidgeVentOnRoof.2tpd8r" msgstr "Placer la ventilation de faîtage sur le toit" +# English: "Place shelf" # Source-Hash: 9b3c66a867c23326 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeShelf.y4l3iw" msgstr "Placer l’étagère" +# English: "Place skylight on roof" # Source-Hash: f11cf5c1c9fe862d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place skylight on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSkylightOnRoof.e215pe" msgstr "Placer la fenêtre de toit" +# English: "Place solar panel array on roof" # Source-Hash: e989a58d6709cc16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place solar panel array on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn" msgstr "Placer les panneaux solaires sur le toit" +# English: "Place spawn point" # Source-Hash: b67f7036d5180cf5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSpawnPoint.205o25" msgstr "Placer le point d’apparition" +# English: "Place stairs" # Source-Hash: 6c7f32a17e5636c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeStairs.zwxxu2" msgstr "Placer l’escalier" +# English: "Place trap" # Source-Hash: 641c0d3debd798a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTrap.1gxemn3" msgstr "Placer le siphon" +# English: "Place turbine vent on roof" # Source-Hash: 6f0a6f6c4b7588a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place turbine vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTurbineVentOnRoof.1iqeluf" msgstr "Placer l’extracteur éolien sur le toit" +# English: "Place unit" # Source-Hash: e65cf5edb4f51ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeUnit.8zkx4q" msgstr "Placer l’unité" +# English: "Place window on wall" # Source-Hash: 86ce1c730154f68b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place window on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWindowOnWall.1k3qnfb" msgstr "Placer la fenêtre sur le mur" +# English: "Place without the corner snap" # Source-Hash: 2b612180a7617901 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place without the corner snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWithoutTheCornerSnap.1nifqew" msgstr "Placer sans l’aimantation d’angle" -# Source-Hash: 0dc6d121791696f7 +# English: "Point inside a room, porch or deck" +# Source-Hash: dd2ea442152dca5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.pointInsideARoomPorchOr.58w64l" +msgstr "Pointez dans une pièce, un porche ou une terrasse" + # English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastPoint.2dmdcx" msgstr "Retirer le dernier point" +# English: "Remove last witness" # Source-Hash: 8b74b1d962be10fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last witness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastWitness.1b6vvzw" msgstr "Retirer le dernier repère" -# Source-Hash: 306d409f8a1cf480 +# English: "Ridge direction" +# Source-Hash: 1185bd2407dd9172 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirection.5m1369" +msgstr "Sens du faîtage" + +# English: "Ridge direction - click or press R to turn it 90°" +# Source-Hash: 4352028e50450055 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk" +msgstr "Sens du faîtage - cliquez ou appuyez sur R pour le tourner de 90°" + +# English: "Ridge: Long side" +# Source-Hash: 96c889f047db9de5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeLongSide.ew0euv" +msgstr "Faîtage : côté long" + +# English: "Ridge: Short side" +# Source-Hash: 58143198d3f2a265 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeShortSide.gye8pb" +msgstr "Faîtage : côté court" + # English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate.1thqa0a" msgstr "Pivoter" +# English: "Rotate ±45°" # Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45.9zq68u" msgstr "Pivoter de ±45°" +# English: "Rotate ±45° (floor / ceiling)" # Source-Hash: be2240289aa74df5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45° (floor / ceiling)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45FloorCeiling.m9t57d" msgstr "Pivoter de ±45° (sol / plafond)" +# English: "Rotate ghost ±15°" # Source-Hash: d5fb78b07c0f8138 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ghost ±15°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateGhost15.rsa52c" msgstr "Pivoter l’aperçu de ±15°" +# English: "Rotate or flip the run side" # Source-Hash: 912d340cd16e27fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate or flip the run side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateOrFlipTheRunSide.t5ior7" msgstr "Pivoter ou inverser le côté de la rangée" -# Source-Hash: ad1dc0b5536cceef -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate roof direction 90°" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.rotateRoofDirection90.tvbji2" -msgstr "Pivoter l’orientation du toit de 90°" - +# English: "Rotate spawn point" # Source-Hash: fa24651e9e5f595a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateSpawnPoint.1opcuu7" msgstr "Pivoter le point d’apparition" +# English: "Round / rectangular / oval" # Source-Hash: 2d693d87fe9b59e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round / rectangular / oval" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.roundRectangularOval.e4nh6x" msgstr "Rond / rectangulaire / oblong" -# Source-Hash: f7d2c9cdcc637c97 +# English: "Set beam start / end; the next beam starts there" +# Source-Hash: ee4ca2612f1ea085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setBeamStartEndTheNext.di3bzj" +msgstr "Définir le début / la fin de la poutre ; la suivante part de là" + # English: "Set fence start / end" +# Source-Hash: f7d2c9cdcc637c97 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setFenceStartEnd.1phbwsq" msgstr "Définir le début / la fin de la clôture" -# Source-Hash: c93ae898b4d59f6f +# English: "Set footing start / end, or place a pad" +# Source-Hash: b9693a8a1f7cd6c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setFootingStartEndOrPlace.nnv1av" +msgstr "Définir le début / la fin de la semelle, ou placer une semelle isolée" + # English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setOneCornerThenTheOpposite.8tsl49" msgstr "Placez un angle, puis l’angle opposé" +# English: "Set roof footprint" # Source-Hash: 4f55171f10c6ceeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set roof footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setRoofFootprint.p50n0o" msgstr "Définir l’emprise du toit" -# Source-Hash: 0243859b1d64c21e -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set wall start / end" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.setWallStartEnd.1r8ubcx" -msgstr "Définir le début / la fin du mur" - +# English: "Slope / level" # Source-Hash: c2806cce9612fa50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope / level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.slopeLevel.ftqp67" msgstr "Pente / à plat" +# English: "Snap onto the run" # Source-Hash: 06c0ec40606db679 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap onto the run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.snapOntoTheRun.86yhmp" msgstr "Aimanter sur le tronçon" +# English: "Split at the marks" # Source-Hash: e214cccbac9c4fd2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split at the marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.splitAtTheMarks.x5tpdd" msgstr "Diviser aux repères" +# English: "Start grid axis" # Source-Hash: 94455c35bfeac6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startGridAxis.k92yza" msgstr "Commencer l’axe de trame" +# English: "Start lineset" # Source-Hash: 2edb4c29bbe7c513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLineset.23q8dh" msgstr "Commencer la liaison frigorifique" +# English: "Start liquid line" # Source-Hash: 9e50f1b30e325208 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start liquid line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLiquidLine.1rlxsgd" msgstr "Commencer la ligne liquide" +# English: "Start run" # Source-Hash: 877ddcb778c211cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startRun.r6fc5u" msgstr "Commencer le tronçon" +# English: "Start segment" # Source-Hash: 8a62bee64174bbc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startSegment.rolym6" msgstr "Commencer le segment" +# English: "Step back or cancel" # Source-Hash: 6cc5dc51a8e66779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step back or cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stepBackOrCancel.zbfdkn" msgstr "Revenir en arrière ou annuler" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.style.nf3vcm" msgstr "Style" +# English: "Style: Barn" # Source-Hash: 81f595b1c47c0080 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleBarn.k9n1x" msgstr "Style : grange" +# English: "Style: Casement" # Source-Hash: 3a82c718fe215273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCasement.ziklec" msgstr "Style : battant" +# English: "Style: Craftsman" # Source-Hash: 072e3331d1d105c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCraftsman.52e6a9" msgstr "Style : artisan" +# English: "Style: Double-hung" # Source-Hash: b64d07557dec1714 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleDoubleHung.1lg8pkg" msgstr "Style : guillotine double" +# English: "Style: French" # Source-Hash: 55ef8761a537637f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleFrench.1ue3j64" msgstr "Style : à la française" +# English: "Style: Glass" # Source-Hash: bc8a751d87536dc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGlass.qudnb4" msgstr "Style : vitré" +# English: "Style: Grid" # Source-Hash: 40c1eb0bc83e207d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGrid.1vuq4po" msgstr "Style : petits bois" +# English: "Style: Half-louvered" # Source-Hash: f516886bab2e7f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHalfLouvered.13b1few" msgstr "Style : demi-persienne" +# English: "Style: Horizontal bands" # Source-Hash: 64e4389a695371a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHorizontalBands.12ethxk" msgstr "Style : bandes horizontales" +# English: "Style: Modern" # Source-Hash: ff6d226a425b00c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleModern.1fh8hhn" msgstr "Style : moderne" +# English: "Style: Panel" # Source-Hash: c6d17ba078ff47e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePanel.922jze" msgstr "Style : panneau" +# English: "Style: Paneled glass" # Source-Hash: 5e6baad0d2ce3a62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePaneledGlass.17a17en" msgstr "Style : vitré à panneaux" +# English: "Style: Shaker" # Source-Hash: 6314388e6832c252 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleShaker.49z17k" msgstr "Style : shaker" +# English: "Style: Single" # Source-Hash: a0f8e524f3ca121f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSingle.6f3cm0" msgstr "Style : simple" +# English: "Style: Six-panel" # Source-Hash: 368279df3a472c86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSixPanel.u49laj" msgstr "Style : six panneaux" +# English: "Style: Sliding" # Source-Hash: 8f3c2571d8168fee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSliding.q81s0i" msgstr "Style : coulissant" +# English: "Style: Tall grid" # Source-Hash: aa9d9eb137ca38d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTallGrid.1eotg3x" msgstr "Style : petits bois hauts" +# English: "Style: Transom" # Source-Hash: bc164648722b63e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTransom.kb9bh6" msgstr "Style : imposte" +# English: "Style: Triple-hung" # Source-Hash: 9a218f0c9ceb4697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTripleHung.pxnqi3" msgstr "Style : guillotine triple" +# English: "Style: Wide grid" # Source-Hash: 9f6df1344bd3d694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleWideGrid.12n27et" msgstr "Style : petits bois larges" +# English: "Switch rotation axis (Y → X → Z)" # Source-Hash: 711d4050c0cb95d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch rotation axis (Y → X → Z)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.switchRotationAxisYXZ.15lohm6" msgstr "Changer l’axe de rotation (Y → X → Z)" +# English: "The walls meet at too sharp or too flat an angle" # Source-Hash: d650c71e042c2598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The walls meet at too sharp or too flat an angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWallsMeetAtTooSharp.kh3wi3" msgstr "Les murs se rejoignent selon un angle trop aigu ou trop plat" +# English: "The window waiting there is taller than this wall" # Source-Hash: ab206ac56e65f190 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window waiting there is taller than this wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4" msgstr "La fenêtre prévue à cet endroit est plus haute que ce mur" +# English: "Three or more walls meet here" # Source-Hash: 9c1f47138ed5c7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Three or more walls meet here" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.threeOrMoreWallsMeetHere.rgifif" msgstr "Trois murs ou plus se rejoignent ici" +# English: "Trace ceiling outline" # Source-Hash: c3ca56fa28e15f07 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace ceiling outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceCeilingOutline.1b639wv" msgstr "Tracer le contour du plafond" +# English: "Trace slab outline" # Source-Hash: bdf71499d30a29de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace slab outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceSlabOutline.z0j2lc" msgstr "Tracer le contour de la dalle" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.type.1m2zofh" msgstr "Type" +# English: "Type: Awning" # Source-Hash: b54eaaff3349dcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeAwning.1cj59al" msgstr "Type : à l’italienne" +# English: "Type: Barn" # Source-Hash: f399572a27417c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBarn.sbnygq" msgstr "Type : grange" +# English: "Type: Bay" # Source-Hash: a1df9782200b5f1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBay.1200hc1" msgstr "Type : oriel" +# English: "Type: Bow" # Source-Hash: ff069a390e8e3f66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBow.14s2t9p" msgstr "Type : bow-window" +# English: "Type: Cabinet" # Source-Hash: bdac5f3f2ea70fff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCabinet.1r68je5" msgstr "Type : placard" +# English: "Type: Casement" # Source-Hash: e0754fc03997cbf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCasement.176uo9j" msgstr "Type : battant" +# English: "Type: Double" # Source-Hash: 47d873446d6e173b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDouble.af7w4i" msgstr "Type : double" +# English: "Type: Double Hung" # Source-Hash: 5942e484513aaab5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDoubleHung.t482fu" msgstr "Type : guillotine double" +# English: "Type: Fixed" # Source-Hash: f9497fbfceefda27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFixed.v1cjez" msgstr "Type : fixe" +# English: "Type: Folding" # Source-Hash: 0dcf07acc9cbf000 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFolding.sxxyng" msgstr "Type : pliante" +# English: "Type: French" # Source-Hash: bff7331f87f279aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFrench.1y57z1b" msgstr "Type : à la française" +# English: "Type: Hinged" # Source-Hash: 9d2c3b82079eb769 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeHinged.1xrcfju" msgstr "Type : battante" +# English: "Type: Island" # Source-Hash: b8001b90ff522838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Island" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeIsland.ufr3ce" msgstr "Type : îlot" +# English: "Type: Louvered" # Source-Hash: 516833c5160d412e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeLouvered.oxl1cz" msgstr "Type : à persiennes" +# English: "Type: Pocket" # Source-Hash: 291272bf848ad6ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typePocket.hc65x9" msgstr "Type : galandage" +# English: "Type: Single Hung" # Source-Hash: 46d0e53d6ae07c72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSingleHung.p53pyl" msgstr "Type : guillotine simple" +# English: "Type: Sliding" # Source-Hash: b11115b50df8faac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSliding.nwmr3f" msgstr "Type : coulissante" +# English: "Vertical stack ↕, click to place" # Source-Hash: bff0c1694ee0a37e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical stack ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.verticalStackClickToPlace.ijcn4q" msgstr "Colonne verticale ↕, cliquez pour placer" +# English: "Waste / vent" # Source-Hash: bac471ee67af6412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste / vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.wasteVent.1hvi3vj" msgstr "Évacuation / ventilation" +# English: "Window style — click or press L to cycle" # Source-Hash: 1c744b7e24fc786e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowStyleClickOrPressL.ah7qm9" msgstr "Style de fenêtre — cliquez ou appuyez sur L pour changer" +# English: "Window type — click or press O to cycle" # Source-Hash: dd6f0ab314fea26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowTypeClickOrPressO.wv4x35" msgstr "Type de fenêtre — cliquez ou appuyez sur O pour changer" -# Review: required -# Source-Hash: a48c4b2e4d71b08d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length: {field} m" +# Source-Hash: a48c4b2e4d71b08d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.lengthField" msgstr "Longueur : {field} m" -# Review: required -# Source-Hash: e53695d6669245c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tab: direction" +# Source-Hash: e53695d6669245c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.tabDirection" msgstr "Tab : direction" -# Review: required -# Source-Hash: abb1d8c8bfb41acf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "type a length" +# Source-Hash: abb1d8c8bfb41acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.typeALength" msgstr "saisir une longueur" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.plugins" msgstr "Plugins" +# English: "2D" # Source-Hash: c54f9bac358893d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "2D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view2d" msgstr "2D" +# English: "3D" # Source-Hash: f78f2fa035f25f85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view3d" msgstr "3D" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.auto" msgstr "Auto" +# English: "Browse plugins" # Source-Hash: 7890c110650ad1e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.browsePlugins" msgstr "Parcourir les plugins" +# English: "Close {label}" # Source-Hash: 3b11e7af681a9dcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeLabel" msgstr "Fermer {label}" +# English: "Close split (\\)" # Source-Hash: 3f00e92517e3f4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close split (\\)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeSplit" msgstr "Fermer la vue partagée (\\)" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.ground" msgstr "Sol" +# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" # Source-Hash: 0d472502a4d36e86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundSolid" msgstr "{standing, select, partly {Partiellement sous le terrain · sol plein} below {Sous le terrain · sol plein} other {Sol plein}}" +# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" # Source-Hash: 1b6798c307ac1b96 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundVeiled" msgstr "{standing, select, partly {Partiellement sous le terrain · sol voilé} below {Sous le terrain · sol voilé} other {Sol voilé}}" +# English: "Keep the ground solid" # Source-Hash: ebdc976a98e82626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep the ground solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.keepTheGroundSolid" msgstr "Garder le sol plein" +# English: "More views" # Source-Hash: 5f827686a006d88c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.moreViews" msgstr "Plus de vues" +# English: "Pane {pane} views" # Source-Hash: f175bbcf457ab73c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane {pane} views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.paneViews" msgstr "Vues du volet {pane}" +# English: "Pin {label}" # Source-Hash: 33c20cc33dce9a9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinLabel" msgstr "Épingler {label}" +# English: "Pin to the view bar" # Source-Hash: 3f7231b1eb2f93f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin to the view bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.pinToTheViewBar" +msgstr "Épingler à la barre de vues" + +# English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.solid" +msgstr "Plein" + +# English: "Split (\\)" +# Source-Hash: 98f22f3f89f17686 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split" +msgstr "Vue partagée (\\)" + +# English: "Split" +# Source-Hash: e4cd64b1680ac0d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split2" +msgstr "Partager" + +# English: "Swap panes" +# Source-Hash: 15b0adc705923a27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.swapPanes" +msgstr "Inverser les volets" + +# English: "Unpin from the view bar" +# Source-Hash: 9781bcdb7a8549e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinFromTheViewBar" +msgstr "Désépingler de la barre de vues" + +# English: "Unpin {label}" +# Source-Hash: f667358dec4abdea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinLabel" +msgstr "Désépingler {label}" + +# English: "Veil the ground over a level below grade" +# Source-Hash: e96581f0bbf9fa91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy -msgid "editor.views.viewBar.pinToTheViewBar" -msgstr "Épingler à la barre de vues" +msgid "editor.views.viewBar.veilTheGroundOverALevel" +msgstr "Voiler le sol au-dessus d’un niveau sous le terrain" + +# English: "Views" +# Source-Hash: fb82f09f8ed3059e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.views" +msgstr "Vues" + +# English: "You can retry the scene or return home without reloading the whole app shell." +# Source-Hash: fdece5c2158dfe88 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.youCanRetryTheSceneOr" +msgstr "Vous pouvez relancer la scène ou revenir à l’accueil sans recharger toute l’application." + +# English: "A framing beam drawn end to end: a porch header, a deck beam, a girder." +# Source-Hash: bafa390e07160f3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.description" +msgstr "Une poutre de charpente tracée d’une extrémité à l’autre : linteau de porche, poutre de terrasse, poutre maîtresse." + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.label" +msgstr "Poutre" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.actions" +msgstr "Actions" + +# English: "Add posts" +# Source-Hash: 59f845b250e0df6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.addPosts" +msgstr "Ajouter des poteaux" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beam" +msgstr "Poutre" + +# English: "Beam {size}" +# Source-Hash: 26e977da636cc882 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beamSize" +msgstr "Poutre {size}" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.delete" +msgstr "Supprimer" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.depth" +msgstr "Hauteur" + +# English: "Dropped" +# Source-Hash: 73694f3f45cf2ee1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.dropped" +msgstr "En retombée" + +# English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.flush" +msgstr "Noyée" + +# English: "Follow the ceiling plane" +# Source-Hash: 0ee3edea19fd153e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.followTheCeilingPlane" +msgstr "Suivre le plan du plafond" + +# English: "{length} long; its underside {underside} over the floor, the ceiling plane {plane}." +# Source-Hash: 193b75ac869c251d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.lengthLongItsUnderside" +msgstr "{length} de long ; sous-face à {underside} au-dessus du plancher, plan du plafond à {plane}." -# Source-Hash: ef3e765e692faf4c +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.solid" -msgstr "Plein" +msgid "node.beam.panel.material" +msgstr "Matériau" -# Source-Hash: 98f22f3f89f17686 +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split (\\)" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split" -msgstr "Vue partagée (\\)" +msgid "node.beam.panel.move" +msgstr "Déplacer" -# Source-Hash: e4cd64b1680ac0d2 +# English: "1 ply" +# Source-Hash: fa876c632c284d81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split2" -msgstr "Partager" +msgid "node.beam.panel.n1Ply" +msgstr "1 pli" -# Source-Hash: 15b0adc705923a27 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swap panes" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.swapPanes" -msgstr "Inverser les volets" +msgid "node.beam.panel.placement" +msgstr "Position" -# Source-Hash: 9781bcdb7a8549e2 +# English: "{plies} plies" +# Source-Hash: 6ac7987d650b9742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin from the view bar" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinFromTheViewBar" -msgstr "Désépingler de la barre de vues" +msgid "node.beam.panel.pliesPlies" +msgstr "{plies} plis" -# Source-Hash: f667358dec4abdea +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin {label}" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinLabel" -msgstr "Désépingler {label}" +msgid "node.beam.panel.size" +msgstr "Taille" -# Source-Hash: e96581f0bbf9fa91 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veil the ground over a level below grade" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.veilTheGroundOverALevel" -msgstr "Voiler le sol au-dessus d’un niveau sous le terrain" +msgid "node.beam.panel.top" +msgstr "Dessus" -# Source-Hash: fb82f09f8ed3059e +# English: "Top elevation" +# Source-Hash: 5b4c960036b3d05b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Views" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.views" -msgstr "Vues" +msgid "node.beam.panel.topElevation" +msgstr "Cote du dessus" -# Source-Hash: fdece5c2158dfe88 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "You can retry the scene or return home without reloading the whole app shell." -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.youCanRetryTheSceneOr" -msgstr "Vous pouvez relancer la scène ou revenir à l’accueil sans recharger toute l’application." +msgid "node.beam.panel.width" +msgstr "Largeur" +# English: "A topology-backed solid edited directly in the canvas." # Source-Hash: a273dacdf51b26f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A topology-backed solid edited directly in the canvas." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.description" msgstr "Solide à topologie, modifiable directement dans le canevas." +# English: "Block" # Source-Hash: 279f456ebb2f5da0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.label" msgstr "Bloc" +# English: "Small louvered exhaust vent that sits on a roof slope." # Source-Hash: b2a371b4041b5e38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Small louvered exhaust vent that sits on a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.description" msgstr "Petit aérateur d’extraction à persiennes posé sur un versant." +# English: "Box Vent" # Source-Hash: f736ca24e84825c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.label" msgstr "Aérateur de toit" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: c0e9837e67038e92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Flange" +# Source-Hash: c0e9837e67038e92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseFlange" msgstr "Bride de base" -# Review: required -# Source-Hash: fa9118f6f4f020d7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Height" +# Source-Hash: fa9118f6f4f020d7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseHeight" msgstr "Hauteur de base" -# Review: required -# Source-Hash: 0869f5a92dabb4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Inset" +# Source-Hash: 0869f5a92dabb4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseInset" msgstr "Retrait de base" -# Review: required -# Source-Hash: e9a89a5e4ac8cd61 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.box" msgstr "Boîte" -# Review: required -# Source-Hash: f736ca24e84825c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Vent" +# Source-Hash: f736ca24e84825c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.boxVent" msgstr "Aérateur de toit" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cap" msgstr "Chapeau" -# Review: required -# Source-Hash: 0ce3b53766b550e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap Height" +# Source-Hash: 0ce3b53766b550e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.capHeight" msgstr "Hauteur du chapeau" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cornerBevel" msgstr "Chanfrein d'angle" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dome" msgstr "Dôme" -# Review: required -# Source-Hash: 1c5c81aa21193d07 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome Curvature" +# Source-Hash: 1c5c81aa21193d07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.domeCurvature" msgstr "Courbure du dôme" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: 8cac057f7ae6804d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Height" +# Source-Hash: 8cac057f7ae6804d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.gapHeight" msgstr "Hauteur de l'interstice" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 8a9c2ea0783a0e53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hood Overhang" +# Source-Hash: 8a9c2ea0783a0e53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.hoodOverhang" msgstr "Débord du capot" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.style" msgstr "Style" -# Review: required -# Source-Hash: 71d82d47e11d2564 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Taper" +# Source-Hash: 71d82d47e11d2564 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.topTaper" msgstr "Rétrécissement du haut" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.width" msgstr "Largeur" +# English: "A building container holding one or more levels." # Source-Hash: f49d8836ae037dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A building container holding one or more levels." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.description" msgstr "Conteneur de bâtiment contenant un ou plusieurs niveaux." +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.label" msgstr "Bâtiment" -# Review: required -# Source-Hash: 5a8274cbe3034558 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Baskets" +# Source-Hash: 5a8274cbe3034558 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.baskets" msgstr "Paniers" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottom" msgstr "Bas" -# Review: required -# Source-Hash: 486501f027037732 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Freezer" +# Source-Hash: 486501f027037732 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottomFreezer" msgstr "Congélateur en bas" -# Review: required -# Source-Hash: 375acc0016b3aa14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bowls" +# Source-Hash: 375acc0016b3aa14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bowls" msgstr "Bacs" -# Review: required -# Source-Hash: 8449b59bf054c27d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Burners on" +# Source-Hash: 8449b59bf054c27d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.burnersOn" msgstr "Feux allumés" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.chimney" msgstr "Cheminée" -# Review: required -# Source-Hash: a6b76187db231ae7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Decrease {label}" +# Source-Hash: a6b76187db231ae7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.decrease" msgstr "Diminuer {label}" -# Review: required -# Source-Hash: 787e10f5db16904e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door" +# Source-Hash: 787e10f5db16904e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.door" msgstr "Porte" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.double" msgstr "Double" -# Review: required -# Source-Hash: 63fe49d73892aac8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer" +# Source-Hash: 63fe49d73892aac8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawer" msgstr "Tiroir" -# Review: required -# Source-Hash: f98242a5dacd33f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawers" +# Source-Hash: f98242a5dacd33f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawers" msgstr "Tiroirs" -# Review: required -# Source-Hash: 3af5347c94ae6443 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip hinge" +# Source-Hash: 3af5347c94ae6443 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipHinge" msgstr "Inverser la charnière" -# Review: required -# Source-Hash: c930f681e61c029c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip the door hinge to the opposite side" +# Source-Hash: c930f681e61c029c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipTheDoorHingeToThe" msgstr "Placer la charnière de la porte du côté opposé" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.fridge" msgstr "Réfrigérateur" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.gas" msgstr "Gaz" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.glass" msgstr "Vitrage" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 6f434b84c8b59b8c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hob" +# Source-Hash: 6f434b84c8b59b8c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.hob" msgstr "Plaque" -# Review: required -# Source-Hash: d52570d41b4df776 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase {label}" +# Source-Hash: d52570d41b4df776 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.increase" msgstr "Augmenter {label}" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.induction" msgstr "Induction" -# Review: required -# Source-Hash: 54800340a0eeab23 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Layout" +# Source-Hash: 54800340a0eeab23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.layout" msgstr "Disposition" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.left" msgstr "Gauche" -# Review: required -# Source-Hash: 12ade82be3830a0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Micro" +# Source-Hash: 12ade82be3830a0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.micro" msgstr "Micro-ondes" -# Review: required -# Source-Hash: a06a50e63f0c240e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move down" +# Source-Hash: a06a50e63f0c240e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveDown" msgstr "Descendre" -# Review: required -# Source-Hash: d49ea60a2312df2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move up" +# Source-Hash: d49ea60a2312df2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveUp" msgstr "Monter" -# Review: required -# Source-Hash: 596023c8b9a8f2e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven" +# Source-Hash: 596023c8b9a8f2e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.oven" msgstr "Four" -# Review: required -# Source-Hash: 83b20797fb0a01a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pullout" +# Source-Hash: 83b20797fb0a01a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.pullout" msgstr "Coulissant" -# Review: required -# Source-Hash: 43c251f465b647ce -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rack" +# Source-Hash: 43c251f465b647ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.rack" msgstr "Rangement" -# Review: required -# Source-Hash: 3c2b3026d37cbcc2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove compartment" +# Source-Hash: 3c2b3026d37cbcc2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.removeCompartment" msgstr "Supprimer le compartiment" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.right" msgstr "Droite" -# Review: required -# Source-Hash: d7145f84f8e0e3fe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelf" +# Source-Hash: d7145f84f8e0e3fe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelf" msgstr "Étagère" -# Review: required -# Source-Hash: 5fbb88f1f079b6db -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves" +# Source-Hash: 5fbb88f1f079b6db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelves" msgstr "Étagères" -# Review: required -# Source-Hash: 47173085127029f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves inside" +# Source-Hash: 47173085127029f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelvesInside" msgstr "Étagères intérieures" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.single" msgstr "Simple" -# Review: required -# Source-Hash: 522a7535fc93fb4f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink" +# Source-Hash: 522a7535fc93fb4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.sink" msgstr "Évier" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.style" msgstr "Style" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.surface" msgstr "Surface" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.top" msgstr "Haut" -# Review: required -# Source-Hash: eaadfd2d19b27d87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Freezer" +# Source-Hash: eaadfd2d19b27d87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topFreezer" msgstr "Congélateur en haut" -# Review: required -# Source-Hash: 04f3a3462ef5d247 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top grate" +# Source-Hash: 04f3a3462ef5d247 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topGrate" msgstr "Grille supérieure" -# Review: required -# Source-Hash: 4a616e1d2ca55bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tray" +# Source-Hash: 4a616e1d2ca55bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.tray" msgstr "Plateau" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.type" msgstr "Type" -# Review: required -# Source-Hash: 6c4de540d19316ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Washer" +# Source-Hash: 6c4de540d19316ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.washer" msgstr "Lave-vaisselle" -# Review: required -# Source-Hash: 5211a0caf3518125 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wire" +# Source-Hash: 5211a0caf3518125 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.wire" msgstr "Fil" +# English: "A configurable parametric base cabinet." # Source-Hash: 693a0d8896f5cc43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable parametric base cabinet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.description" msgstr "Meuble bas paramétrique configurable." -# Review: required -# Source-Hash: b0568ea943addd5b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom freezer" +# Source-Hash: b0568ea943addd5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.bottomFreezer" msgstr "Congélateur en bas" -# Review: required -# Source-Hash: da78819903ae0863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cooktop type" +# Source-Hash: da78819903ae0863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.cooktopType" msgstr "Type de plaque" -# Review: required -# Source-Hash: c608188f58ed1cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door direction" +# Source-Hash: c608188f58ed1cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.doorDirection" msgstr "Sens d'ouverture" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.double" msgstr "Double" -# Review: required -# Source-Hash: f7fb8f6393cc4cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge type" +# Source-Hash: f7fb8f6393cc4cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.fridgeType" msgstr "Type de réfrigérateur" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.gas" msgstr "Gaz" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.glass" msgstr "Vitrage" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.induction" msgstr "Induction" -# Review: required -# Source-Hash: 76df477d42cec629 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Item options" +# Source-Hash: 76df477d42cec629 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.itemOptions" msgstr "Options de l'élément" -# Review: required -# Source-Hash: bca1b184af1c0c4b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left hinge" +# Source-Hash: bca1b184af1c0c4b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.leftHinge" msgstr "Charnière à gauche" -# Review: required -# Source-Hash: e32da7429c85f1a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right hinge" +# Source-Hash: e32da7429c85f1a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.rightHinge" msgstr "Charnière à droite" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.single" msgstr "Simple" -# Review: required -# Source-Hash: 4bc3042055df1e85 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink layout" +# Source-Hash: 4bc3042055df1e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.sinkLayout" msgstr "Configuration de l'évier" -# Review: required -# Source-Hash: 29cffe75281dbea6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top freezer" +# Source-Hash: 29cffe75281dbea6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.topFreezer" msgstr "Congélateur en haut" -# Review: required -# Source-Hash: 09c2cb2ea7b1be3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose {label}" +# Source-Hash: 09c2cb2ea7b1be3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.chooseLabel" msgstr "Choisir {label}" -# Review: required -# Source-Hash: 1dd2cac6488ddc30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No kitchen items match. Try “fridge”, “sink”, or “cabinet”." +# Source-Hash: 1dd2cac6488ddc30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge" msgstr "Aucun élément de cuisine ne correspond. Essayez « réfrigérateur », « évier » ou « meuble »." -# Review: required -# Source-Hash: 6752675ca6280ddd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search fridge, sink, cabinets…" +# Source-Hash: 6752675ca6280ddd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchFridgeSinkCabinets" msgstr "Rechercher réfrigérateur, évier, meubles…" -# Review: required -# Source-Hash: fe3c6d57a07c323d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search kitchen items" +# Source-Hash: fe3c6d57a07c323d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchKitchenItems" msgstr "Rechercher des éléments de cuisine" -# Review: required -# Source-Hash: d44a46146db2e44c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliances & fixtures" +# Source-Hash: d44a46146db2e44c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.appliances" msgstr "Électroménager et équipements" -# Review: required -# Source-Hash: d4d3ad7c099b6513 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinets" +# Source-Hash: d4d3ad7c099b6513 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.cabinets" msgstr "Meubles" -# Review: required -# Source-Hash: 465db52f98c2d94f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside" +# Source-Hash: 465db52f98c2d94f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBeside" msgstr "Ajouter à côté" -# Review: required -# Source-Hash: 9733e2ad85deb6d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside selected item" +# Source-Hash: 9733e2ad85deb6d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBesideSelectedItem" msgstr "Ajouter à côté de l'élément sélectionné" -# Review: required -# Source-Hash: 4b2f676fb4c9ec63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add {label}" +# Source-Hash: 4b2f676fb4c9ec63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addLabel" msgstr "Ajouter {label}" -# Review: required -# Source-Hash: 5122b8e9b4ec6a00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "An upper cabinet attached to this item will be removed to make room." +# Source-Hash: 5122b8e9b4ec6a00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis" msgstr "Un meuble haut fixé à cet élément sera supprimé pour faire de la place." -# Review: required -# Source-Hash: 5737c4567e0f6863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cabinet" msgstr "Meuble" -# Review: required -# Source-Hash: c0774c185060fc13 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cancel" msgstr "Annuler" -# Review: required -# Source-Hash: f42912b116015f58 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "This {width} item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again." +# Source-Hash: f42912b116015f58 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cannotFit" msgstr "Cet élément de {width} ne tient pas dans le linéaire actuel et ses limites de mur ou d'angle. Déplacez un meuble d'extrémité ou libérez de l'espace, puis réessayez." -# Review: required -# Source-Hash: b7eeb26a356d03c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Inserts on the left of the selected item.} other {Inserts on the right of the selected item.}}" +# Source-Hash: b7eeb26a356d03c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.insertsBeside" msgstr "{side, select, left {Insère à gauche de l'élément sélectionné.} other {Insère à droite de l'élément sélectionné.}}" -# Review: required -# Source-Hash: f65332177316d11d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The kitchen changed. Select an item again to refresh the preview." +# Source-Hash: f65332177316d11d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenChanged" msgstr "La cuisine a changé. Sélectionnez de nouveau un élément pour actualiser l'aperçu." -# Review: required -# Source-Hash: 5b6990b17111fd6b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run preview, highlighted item is the proposed change" +# Source-Hash: 5b6990b17111fd6b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs" msgstr "Aperçu du linéaire de cuisine ; l’élément en surbrillance est la modification proposée" -# Review: required -# Source-Hash: cb59562ba7eb1d25 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left side" +# Source-Hash: cb59562ba7eb1d25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.leftSide" msgstr "Côté gauche" -# Review: required -# Source-Hash: 766354d53f17d78c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: moves {distance}" +# Source-Hash: 766354d53f17d78c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.moved" msgstr "{name} : se déplace de {distance}" -# Review: required -# Source-Hash: df1083d9ffc2c59d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {width}" +# Source-Hash: df1083d9ffc2c59d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.previewItem" msgstr "{name} : {width}" -# Review: required -# Source-Hash: ada7fbece9ca9e3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace selected item" +# Source-Hash: ada7fbece9ca9e3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceSelectedItem" msgstr "Remplacer l'élément sélectionné" -# Review: required -# Source-Hash: ab8b52d2704537d2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replaces only the selected item." +# Source-Hash: ab8b52d2704537d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem" msgstr "Remplace uniquement l'élément sélectionné." -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWith" msgstr "Remplacer par…" -# Review: required -# Source-Hash: 948fe657641f298c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with {label}" +# Source-Hash: 948fe657641f298c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWithLabel" msgstr "Remplacer par {label}" -# Review: required -# Source-Hash: 154f9fa86b7670ba -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {before} → {after}" +# Source-Hash: 154f9fa86b7670ba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.resized" msgstr "{name} : {before} → {after}" -# Review: required -# Source-Hash: 4ecc493400fb6aa2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right side" +# Source-Hash: 4ecc493400fb6aa2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.rightSide" msgstr "Côté droit" -# Review: required -# Source-Hash: f2c30e7e21cf27e6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to update this kitchen." +# Source-Hash: f2c30e7e21cf27e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen" msgstr "Impossible de mettre à jour cette cuisine." +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.label" msgstr "Meuble modulaire" -# Review: required -# Source-Hash: 740e0a4e4afa78ea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished height extends {overflow} mm above the ceiling." +# Source-Hash: 740e0a4e4afa78ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.aboveCeiling" msgstr "La hauteur finie dépasse le plafond de {overflow} mm." -# Review: required -# Source-Hash: e3b4d2ed4f6f5efb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add chimney" +# Source-Hash: e3b4d2ed4f6f5efb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addChimney" msgstr "Ajouter une hotte" -# Review: required -# Source-Hash: 90bdc92c0a91d060 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add compartment" +# Source-Hash: 90bdc92c0a91d060 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addCompartment" msgstr "Ajouter un compartiment" -# Review: required -# Source-Hash: ece05d9b6f4b616d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add wall cabinet" +# Source-Hash: ece05d9b6f4b616d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addWallCabinet" msgstr "Ajouter un meuble haut" -# Review: required -# Source-Hash: f571cb2d06ae01ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Advanced" +# Source-Hash: f571cb2d06ae01ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.advanced" msgstr "Avancé" -# Review: required -# Source-Hash: 7f4dcb90defe1ac2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance" +# Source-Hash: 7f4dcb90defe1ac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.appliance" msgstr "Électroménager" -# Review: required -# Source-Hash: bd5e3f533412ff6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance front" +# Source-Hash: bd5e3f533412ff6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.applianceFront" msgstr "Façade d'appareil" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.auto" msgstr "Auto" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.bar" msgstr "Barre" -# Review: required -# Source-Hash: 133893b177bd05d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Cabinet" +# Source-Hash: 133893b177bd05d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.baseCabinet" msgstr "Meuble bas" -# Review: required -# Source-Hash: d63f16df2c754126 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet construction, compartments, and opening preview." +# Source-Hash: d63f16df2c754126 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview" msgstr "Construction du meuble, compartiments et aperçu d'ouverture." -# Review: required -# Source-Hash: d62dc35dac835228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet panel" +# Source-Hash: d62dc35dac835228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetPanel" msgstr "Panneau du meuble" -# Review: required -# Source-Hash: 81caf4809d58b5f5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet Type" +# Source-Hash: 81caf4809d58b5f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetType" msgstr "Type de meuble" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.center" msgstr "Centre" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.close" msgstr "Fermer" -# Review: required -# Source-Hash: cf29134333e47184 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close cabinet" +# Source-Hash: cf29134333e47184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.closeCabinet" msgstr "Fermer le meuble" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.commonWallCabinetHeightsUseThe" msgstr "Hauteurs courantes de meubles hauts. Utilisez le curseur ci-dessous pour une hauteur personnalisée." -# Review: required -# Source-Hash: fe9e63d5c5087386 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compartments" +# Source-Hash: fe9e63d5c5087386 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.compartments" msgstr "Compartiments" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cutout" msgstr "Découpe" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: 82c9ea6f403fc934 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit kitchen run" +# Source-Hash: 82c9ea6f403fc934 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.editKitchenRun" msgstr "Modifier le linéaire de cuisine" -# Review: required -# Source-Hash: 7e9bbeb035fc73e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fill to ceiling" +# Source-Hash: 7e9bbeb035fc73e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.fillToCeiling" msgstr "Jusqu'au plafond" -# Review: required -# Source-Hash: b3907da0ad234f10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.finish" msgstr "Terminer" -# Review: required -# Source-Hash: c1796152935221ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front construction" +# Source-Hash: c1796152935221ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.frontConstruction" msgstr "Construction de la façade" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.handles" msgstr "Poignées" - -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.heightPreset" msgstr "Hauteur prédéfinie" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.hole" msgstr "Trou" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.inset" msgstr "Encastré" -# Review: required -# Source-Hash: 1176d2568d093bf1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen" +# Source-Hash: 1176d2568d093bf1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchen" msgstr "Cuisine" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchenRun" msgstr "Linéaire de cuisine" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.knob" msgstr "Bouton" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.mounting" msgstr "Montage" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.none" msgstr "Aucun" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.open" msgstr "Ouvrir" -# Review: required -# Source-Hash: b8ecc16c07d66f42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open Animation" +# Source-Hash: b8ecc16c07d66f42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openAnimation" msgstr "Animation d'ouverture" -# Review: required -# Source-Hash: 2d4adbbd70c3ae6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open cabinet" +# Source-Hash: 2d4adbbd70c3ae6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openCabinet" msgstr "Ouvrir le meuble" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.overlay" msgstr "Recouvrement" -# Review: required -# Source-Hash: a35b7357943a5cae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel-ready" +# Source-Hash: a35b7357943a5cae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.panelReady" msgstr "Prêt à habiller" -# Review: required -# Source-Hash: 3d0d4eee59177824 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Planning checks" +# Source-Hash: 3d0d4eee59177824 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.planningChecks" msgstr "Vérifications de conception" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.play" msgstr "Lancer" -# Review: required -# Source-Hash: c530f8af9dfd846f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play animation" +# Source-Hash: c530f8af9dfd846f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.playAnimation" msgstr "Lancer l'animation" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.position" msgstr "Position" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.raisedArch" msgstr "Arche en relief" -# Review: required -# Source-Hash: a8639a97ab07cd59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove wall cabinet" +# Source-Hash: a8639a97ab07cd59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.removeWallCabinet" msgstr "Supprimer le meuble haut" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.replaceWith" msgstr "Remplacer par…" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.revealGap" msgstr "Jeu entre façades" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.shaker" msgstr "Shaker" -# Review: required -# Source-Hash: c378ed346bf83375 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared countertop, plinth, and finishes" +# Source-Hash: c378ed346bf83375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.sharedCountertopPlinthAndFinishes" msgstr "Plan de travail, socle et finitions communs" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.slab" msgstr "Dalle" -# Review: required -# Source-Hash: 8606fe2c60764a95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler." +# Source-Hash: 8606fe2c60764a95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.solidMatchingFrontBetweenTheCabinet" msgstr "Façade pleine assortie entre le meuble et le mur. Le plan de travail et la plinthe continuent sur cette fileuse." -# Review: required -# Source-Hash: 5a4d5353cc496a63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard width" +# Source-Hash: 5a4d5353cc496a63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.standardWidth" msgstr "Largeur standard" -# Review: required -# Source-Hash: 4a784e3e95a08715 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop" +# Source-Hash: 4a784e3e95a08715 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stop" msgstr "Arrêter" -# Review: required -# Source-Hash: 72c9fcd7a0eb5380 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop animation" +# Source-Hash: 72c9fcd7a0eb5380 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stopAnimation" msgstr "Arrêter l'animation" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.style" msgstr "Style" -# Review: required -# Source-Hash: bfd618690e0c2f24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Cabinet" +# Source-Hash: bfd618690e0c2f24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.tallCabinet" msgstr "Colonne" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.top" msgstr "Haut" -# Review: required -# Source-Hash: d09654ebd12a538a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Cabinet" +# Source-Hash: d09654ebd12a538a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCabinet" msgstr "Meuble haut" -# Review: required -# Source-Hash: bc19e7805b3e3703 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top / Ceiling" +# Source-Hash: bc19e7805b3e3703 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCeiling" msgstr "Haut / Plafond" -# Review: required -# Source-Hash: 29ea8a68eed99925 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top depth" +# Source-Hash: 29ea8a68eed99925 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topDepth" msgstr "Profondeur du haut" -# Review: required -# Source-Hash: 2d40f591e7dcbf5a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top height" +# Source-Hash: 2d40f591e7dcbf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topHeight" msgstr "Hauteur du haut" -# Review: required -# Source-Hash: 3b878d6015c03945 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trim / Soffit" +# Source-Hash: 3b878d6015c03945 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.trimSoffit" msgstr "Bandeau / Soffite" -# Review: required -# Source-Hash: a6e5be20001e8d26 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Uses the cabinet front style, reveal, handle, and front material settings below." +# Source-Hash: a6e5be20001e8d26 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.usesTheCabinetFrontStyleReveal" msgstr "Utilise le style de façade, le jeu, la poignée et le matériau de façade définis ci-dessous." -# Review: required -# Source-Hash: cd55643590c64a42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall filler" +# Source-Hash: cd55643590c64a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.wallFiller" msgstr "Fileuse murale" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: 4ef03cfd6782316b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base cabinet" +# Source-Hash: 4ef03cfd6782316b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.baseCabinet" msgstr "Meuble bas" -# Review: required -# Source-Hash: d353fff221a78228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dishwasher" +# Source-Hash: d353fff221a78228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.dishwasher" msgstr "Lave-vaisselle" -# Review: required -# Source-Hash: ecb596567b8b7179 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer cabinet" +# Source-Hash: ecb596567b8b7179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.drawerCabinet" msgstr "Meuble à tiroirs" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.fridge" msgstr "Réfrigérateur" -# Review: required -# Source-Hash: 72d587d58b645a81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas cooktop" +# Source-Hash: 72d587d58b645a81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.gasCooktop" msgstr "Plaque gaz" -# Review: required -# Source-Hash: 6b02c36e6aac2cd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction cooktop" +# Source-Hash: 6b02c36e6aac2cd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.inductionCooktop" msgstr "Plaque à induction" -# Review: required -# Source-Hash: d5fcb790aef707bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven Tower" +# Source-Hash: d5fcb790aef707bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.ovenTower" msgstr "Colonne four" -# Review: required -# Source-Hash: edd4d1ab7525dcaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink cabinet" +# Source-Hash: edd4d1ab7525dcaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.sinkCabinet" msgstr "Meuble sous-évier" -# Review: required -# Source-Hash: 9b7427a8954f618b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Pantry" +# Source-Hash: 9b7427a8954f618b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.tallPantry" msgstr "Colonne garde-manger" -# Review: required -# Source-Hash: 3bd9629eb17cc572 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall cabinet" +# Source-Hash: 3bd9629eb17cc572 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.wallCabinet" msgstr "Meuble haut" -# Review: required -# Source-Hash: 7c40d26770eede82 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add left" +# Source-Hash: 7c40d26770eede82 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addLeft" msgstr "Ajouter à gauche" -# Review: required -# Source-Hash: 25082cdf77e219c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add right" +# Source-Hash: 25082cdf77e219c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addRight" msgstr "Ajouter à droite" -# Review: required -# Source-Hash: 4b1204c1b706f618 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Align cabinets & handles" +# Source-Hash: 4b1204c1b706f618 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.alignCabinetsHandles" msgstr "Aligner meubles et poignées" -# Review: required -# Source-Hash: 8c73c7a9406db5ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Applies depth, carcass, plinth, and countertop thickness to this run." +# Source-Hash: 8c73c7a9406db5ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop" msgstr "Applique à ce linéaire la profondeur, le caisson, le socle et l'épaisseur du plan de travail." -# Review: required -# Source-Hash: ef576e6c8f9ebe5f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} module copy} other {Create {number} module copies}}" +# Source-Hash: ef576e6c8f9ebe5f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreate" msgstr "{count, plural, one {Créer {number} copie de module} many {Créer {number} copies de module} other {Créer {number} copies de module}}" -# Review: required -# Source-Hash: 0eec3888096306a1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} {name} copy} other {Create {number} {name} copies}}" +# Source-Hash: 0eec3888096306a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreateNamed" msgstr "{count, plural, one {Créer {number} copie de {name}} many {Créer {number} copies de {name}} other {Créer {number} copies de {name}}}" -# Review: required -# Source-Hash: 8cdc0b808b551d3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a copy count from 1 to 20 and spacing from 0 to 2 m" +# Source-Hash: 8cdc0b808b551d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayInvalidOptions" msgstr "Choisissez un nombre de copies de 1 à 20 et un espacement de 0 à 2 m" -# Review: required -# Source-Hash: cb89fe4161e87d68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "There is not enough room in this run for the requested array" +# Source-Hash: cb89fe4161e87d68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoRoom" msgstr "Ce linéaire n'a pas assez de place pour la série demandée" -# Review: required -# Source-Hash: 075d027345689724 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a standard or appliance module as the source" +# Source-Hash: 075d027345689724 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoSource" msgstr "Choisissez un module standard ou un appareil comme source" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.auto" msgstr "Auto" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.back" msgstr "Retour" -# Review: required -# Source-Hash: b2c3a03bf67a97a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Balances standard base cabinets while keeping appliance and corner-filler widths fixed." +# Source-Hash: b2c3a03bf67a97a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping" msgstr "Équilibre les meubles bas standard en gardant fixes les largeurs des appareils et des fileuses d'angle." -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.bar" msgstr "Barre" -# Review: required -# Source-Hash: ccec5da17ca39eee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar counter" +# Source-Hash: ccec5da17ca39eee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barCounter" msgstr "Comptoir de bar" -# Review: required -# Source-Hash: 97c1605fff221752 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar depth" +# Source-Hash: 97c1605fff221752 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barDepth" msgstr "Profondeur du bar" -# Review: required -# Source-Hash: 9514a6ddebdca34e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar height" +# Source-Hash: 9514a6ddebdca34e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barHeight" msgstr "Hauteur du bar" -# Review: required -# Source-Hash: 7af3b69b4b5aeb19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.carcassHeight" msgstr "Hauteur du caisson" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.center" msgstr "Centre" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.commonWallCabinetHeightsUseThe" msgstr "Hauteurs courantes de meubles hauts. Utilisez le curseur ci-dessous pour une hauteur personnalisée." -# Review: required -# Source-Hash: 88ccc87b9b45a464 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies" +# Source-Hash: 88ccc87b9b45a464 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copies" msgstr "Copies" -# Review: required -# Source-Hash: d215c489b8385d9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space." +# Source-Hash: d215c489b8385d9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure" msgstr "Les copies reprennent la structure du meuble source et tout meuble haut fixé. Les modules existants restent en place ; la série demandée doit tenir dans la place disponible." -# Review: required -# Source-Hash: 221d1b64649b08f8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner fillers cannot be used as array sources" +# Source-Hash: 221d1b64649b08f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cornerFillersCannotBeUsedAs" msgstr "Les fileuses d'angle ne peuvent pas servir de source" -# Review: required -# Source-Hash: d8d1bc5a0f9fa0cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop depth" +# Source-Hash: d8d1bc5a0f9fa0cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopDepth" msgstr "Profondeur du plan de travail" -# Review: required -# Source-Hash: 10bba4a11996c84b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop height" +# Source-Hash: 10bba4a11996c84b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopHeight" msgstr "Hauteur du plan de travail" -# Review: required -# Source-Hash: 8abdfbc1798d9eaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Create array" +# Source-Hash: 8abdfbc1798d9eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.createArray" msgstr "Créer la série" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cutout" msgstr "Découpe" -# Review: required -# Source-Hash: 555240ffcca44233 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete {name}" +# Source-Hash: 555240ffcca44233 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.deleteModule" msgstr "Supprimer {name}" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 76cd03300faa9b2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Direction" +# Source-Hash: 76cd03300faa9b2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.direction" msgstr "Direction" -# Review: required -# Source-Hash: 5ca5884d3ff7edf4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate along run" +# Source-Hash: 5ca5884d3ff7edf4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.duplicateAlongRun" msgstr "Dupliquer le long du linéaire" -# Review: required -# Source-Hash: a834f687290afc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize all resizeable standard base cabinets in this run" +# Source-Hash: a834f687290afc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAll" msgstr "Égaliser tous les meubles bas standard redimensionnables de ce linéaire" -# Review: required -# Source-Hash: 24da8a2de2c6e345 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The resizeable cabinet widths are already equal" +# Source-Hash: 24da8a2de2c6e345 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAlreadyEqual" msgstr "Les largeurs des meubles redimensionnables sont déjà égales" -# Review: required -# Source-Hash: c38a4faef2097b73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "At least two standard base cabinets are required" +# Source-Hash: c38a4faef2097b73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNeedsTwo" msgstr "Il faut au moins deux meubles bas standard" -# Review: required -# Source-Hash: 61f15ae24419430c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The available run width cannot satisfy the cabinet width limits" +# Source-Hash: 61f15ae24419430c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNoFit" msgstr "La largeur disponible du linéaire ne respecte pas les limites de largeur des meubles" -# Review: required -# Source-Hash: 7617bb3b455dcdda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize widths" +# Source-Hash: 7617bb3b455dcdda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeWidths" msgstr "Égaliser les largeurs" -# Review: required -# Source-Hash: ad8c948d883a0c9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Left: {width} mm} other {Right: {width} mm}}" +# Source-Hash: ad8c948d883a0c9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fillerPlanSide" msgstr "{side, select, left {Gauche : {width} mm} other {Droite : {width} mm}}" -# Review: required -# Source-Hash: 47ccd8c8b04475cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish & align" +# Source-Hash: 47ccd8c8b04475cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishAlign" msgstr "Finitions et alignement" -# Review: required -# Source-Hash: 855892e18c74153a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished back" +# Source-Hash: 855892e18c74153a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedBack" msgstr "Dos fini" -# Review: required -# Source-Hash: 7ab47aea30c73663 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished exposed ends" +# Source-Hash: 7ab47aea30c73663 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedExposedEnds" msgstr "Côtés apparents finis" -# Review: required -# Source-Hash: c9356f25caf33eec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fits 50–150 mm gaps at run ends beside perpendicular walls." +# Source-Hash: c9356f25caf33eec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fits50150MmGapsAt" msgstr "Comble les écarts de 50 à 150 mm en bout de linéaire contre les murs perpendiculaires." -# Review: required -# Source-Hash: b6f541bc28e38d2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fit wall fillers" +# Source-Hash: b6f541bc28e38d2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fitWallFillers" msgstr "Ajuster les fileuses murales" -# Review: required -# Source-Hash: 2af738f010ce28f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fronts" +# Source-Hash: 2af738f010ce28f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fronts" msgstr "Façades" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.handles" msgstr "Poignées" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.heightPreset" msgstr "Hauteur prédéfinie" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.hole" msgstr "Trou" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.inset" msgstr "Encastré" -# Review: required -# Source-Hash: 2997c230c529590d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Island & Bar" +# Source-Hash: 2997c230c529590d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.islandBar" msgstr "Îlot et bar" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.kitchenRun" msgstr "Linéaire de cuisine" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.knob" msgstr "Bouton" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.left" msgstr "Gauche" -# Review: required -# Source-Hash: 3c88a1840043e15a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments." +# Source-Hash: 3c88a1840043e15a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd" msgstr "Harmonise les hauteurs des meubles bas, les dos, les jeux et les poignées. Aligne les meubles hauts ; conserve les grands appareils et les compartiments personnalisés." -# Review: required -# Source-Hash: b112e31fe44cc94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Metric · 600 mm" +# Source-Hash: b112e31fe44cc94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.metricProfile" msgstr "Métrique · 600 mm" - -# Review: required -# Source-Hash: 4739f6315c5e8680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Module {number}" +# Source-Hash: 4739f6315c5e8680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.moduleNumber" msgstr "Module {number}" -# Review: required -# Source-Hash: b8801184e07e4815 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modules" +# Source-Hash: b8801184e07e4815 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.modules" msgstr "Modules" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.mounting" msgstr "Montage" -# Review: required -# Source-Hash: a3914bd96488175c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No eligible module" +# Source-Hash: a3914bd96488175c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.noEligibleModule" msgstr "Aucun module admissible" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.none" msgstr "Aucun" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.overlay" msgstr "Recouvrement" -# Review: required -# Source-Hash: 49fb05dfafd6001d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth height" +# Source-Hash: 49fb05dfafd6001d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.plinthHeight" msgstr "Hauteur du socle" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.position" msgstr "Position" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.raisedArch" msgstr "Arche en relief" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.revealGap" msgstr "Jeu entre façades" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.right" msgstr "Droite" -# Review: required -# Source-Hash: e022da49ae8b4aef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Seating overhang" +# Source-Hash: e022da49ae8b4aef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.seatingOverhang" msgstr "Débord pour assise" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.shaker" msgstr "Shaker" -# Review: required -# Source-Hash: cba29b18ce666b0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared Plinth & Countertop" +# Source-Hash: cba29b18ce666b0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sharedPlinthCountertop" msgstr "Socle et plan de travail communs" -# Review: required -# Source-Hash: f7d577c4064c16ff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show countertop" +# Source-Hash: f7d577c4064c16ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showCountertop" msgstr "Afficher le plan de travail" -# Review: required -# Source-Hash: 5d98e04dfd2e0a83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show plinth" +# Source-Hash: 5d98e04dfd2e0a83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showPlinth" msgstr "Afficher le socle" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.slab" msgstr "Dalle" -# Review: required -# Source-Hash: 5bdc0a959203c52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Source module" +# Source-Hash: 5bdc0a959203c52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sourceModule" msgstr "Module source" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.spacing" msgstr "Espacement" -# Review: required -# Source-Hash: 40e3105cbe68d163 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard dimensions" +# Source-Hash: 40e3105cbe68d163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.standardDimensions" msgstr "Dimensions standard" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.style" msgstr "Style" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" # Source-Hash: e9d7ad9851d476d9 # Review: required -# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartment" msgstr "{type, select, shelf {Étagère} drawer {Tiroir} door {Porte} oven {Four} microwave {Micro-ondes} dishwasher {Lave-vaisselle} sink {Évier} cooktop_gas {Plaque gaz} cooktop_induction {Plaque à induction} pull_out_pantry {Coulissant garde-manger} fridge_single {Réfrigérateur} fridge_double {Réfrigérateur double} fridge_top_freezer {Réfrigérateur congélateur en haut} fridge_bottom_freezer {Réfrigérateur congélateur en bas} hood_pyramid {Hotte pyramide} hood_curved_glass {Hotte verre incurvé} other {{raw}}}" -# Review: required -# Source-Hash: 98582c257f149d44 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} compartment} other {{number} compartments}}" +# Source-Hash: 98582c257f149d44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartments" msgstr "{count, plural, one {{number} compartiment} many {{number} compartiments} other {{number} compartiments}}" -# Review: required -# Source-Hash: 65b1cbba3b92c5b2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Empty" +# Source-Hash: 65b1cbba3b92c5b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryEmpty" msgstr "Vide" -# Review: required -# Source-Hash: 5db8b868684cf9f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall cabinet" +# Source-Hash: 5db8b868684cf9f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryTall" msgstr "Colonne" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.top" msgstr "Haut" -# Review: required -# Source-Hash: 88c85283afe0c0bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to finish this kitchen run." +# Source-Hash: 88c85283afe0c0bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.unableToFinish" msgstr "Impossible de terminer ce linéaire de cuisine." -# Review: required -# Source-Hash: 8f668fb3e1a1696a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use {name} as array source" +# Source-Hash: 8f668fb3e1a1696a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useAsArraySource" msgstr "Utiliser {name} comme source de la série" -# Review: required -# Source-Hash: 9308a109d017dcd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use this module as the array source" +# Source-Hash: 9308a109d017dcd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useThisModuleAsTheArray" msgstr "Utiliser ce module comme source de la série" -# Review: required -# Source-Hash: 1ce253a4d11bfa68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "US · 24 in" +# Source-Hash: 1ce253a4d11bfa68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.usProfile" msgstr "US · 24 po" -# Review: required -# Source-Hash: a2d852b2c5629796 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waterfall ends" +# Source-Hash: a2d852b2c5629796 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.waterfallEnds" msgstr "Retombées en cascade" +# English: "An editable module inside a modular cabinet run." # Source-Hash: ffc8d80e438f40b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An editable module inside a modular cabinet run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.description" msgstr "Module modifiable dans une rangée de meubles modulaires." +# English: "Cabinet Module" # Source-Hash: d5c19ec529e26a98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet Module" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.label" msgstr "Module de meuble" -# Source-Hash: 4d6d5e9439f40e9b +# English: "Bottom chord" +# Source-Hash: 25be6d614ec09df9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.bottomChord" +msgstr "Entrait" + +# English: "Ceiling joists" +# Source-Hash: a0c71abc0f146a2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.ceilingJoists" +msgstr "Solives de plafond" + +# English: "Decking" +# Source-Hash: 7d286c87375d417e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.decking" +msgstr "Platelage" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.finish" +msgstr "Finition" + +# English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.framing" +msgstr "Ossature" + +# English: "Joists" +# Source-Hash: c7aa596536ceeecb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.joists" +msgstr "Solives" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.layers" +msgstr "Couches" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.none" +msgstr "Aucune" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.preset" +msgstr "Préréglage" + +# English: "The height is the finished underside; the layers rise from it." +# Source-Hash: 2bc7b44c217cb8fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside" +msgstr "La hauteur correspond à la sous-face finie ; les couches montent à partir de là." + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.thickness" +msgstr "Épaisseur" + +# English: "Truss bottom chords" +# Source-Hash: 2cf52770d658d006 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.trussBottomChords" +msgstr "Entraits de fermettes" + # English: "A polygon-bounded ceiling surface that hosts ceiling-mounted items." +# Source-Hash: 4d6d5e9439f40e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.description" msgstr "Surface de plafond délimitée par un polygone, qui accueille les éléments fixés au plafond." +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.label" msgstr "Plafond" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.addHole" msgstr "Ajouter une trémie" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.area" msgstr "Surface" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.auto" msgstr "Auto" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.ceiling" msgstr "Plafond" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.customHeight" msgstr "Hauteur personnalisée" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.cutOpening" msgstr "Découper une ouverture" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.done" msgstr "Terminé" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.followsLevel" msgstr "Suit le niveau" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.height" msgstr "Hauteur" +# English: "High (3.0m)" # Source-Hash: 703f56131ac45e60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (3.0m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high30m" msgstr "Haut (3,0 m)" +# English: "High (9'0\")" # Source-Hash: 4ed40bdab397fe22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (9'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high90" msgstr "Haut (9'0\")" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.holes" msgstr "Trémies" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.info" msgstr "Info" +# English: "Limited by the level to {height} — raise the level height for a taller ceiling." # Source-Hash: 6925a008b1924062 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Limited by the level to {height} — raise the level height for a taller ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.limitedByLevel" msgstr "Limité par le niveau à {height} — augmentez la hauteur du niveau pour un plafond plus haut." +# English: "Low (2.4m)" # Source-Hash: c7410ff087be3a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (2.4m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low24m" msgstr "Bas (2,4 m)" +# English: "Low (8'0\")" # Source-Hash: a59425761b7c9084 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (8'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low80" msgstr "Bas (8'0\")" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.move" msgstr "Déplacer" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.noHoles" msgstr "Aucune trémie" +# English: "Standard (2.5m)" # Source-Hash: 24f5fbd158798197 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2.5m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard25m" msgstr "Standard (2,5 m)" +# English: "Standard (8'6\")" # Source-Hash: 73cf00cf1900c22b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (8'6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard86" msgstr "Standard (8'6\")" +# English: "Taller than this level ({available} available). Raise the level height first." # Source-Hash: 065626498678bc5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Taller than this level ({available} available). Raise the level height first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.tallerThanLevel" msgstr "Plus haut que ce niveau ({available} disponibles). Augmentez d’abord la hauteur du niveau." +# English: "Vertical masonry stack on a roof segment." # Source-Hash: ea12ce3c2b3588e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical masonry stack on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.description" msgstr "Souche maçonnée verticale sur un pan de toit." +# English: "Chimney" # Source-Hash: 95169058fec6db14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.label" msgstr "Cheminée" -# Review: required -# Source-Hash: dc393661f0038c2d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Above Ridge" +# Source-Hash: dc393661f0038c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.aboveRidge" msgstr "Au-dessus du faîtage" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.back" msgstr "Retour" -# Review: required -# Source-Hash: 0d42509b34b05dec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bands" +# Source-Hash: 0d42509b34b05dec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.bands" msgstr "Bandeaux" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cap" msgstr "Chapeau" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimney" msgstr "Cheminée" -# Review: required -# Source-Hash: b619f366d2a1dfda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney Type" +# Source-Hash: b619f366d2a1dfda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimneyType" msgstr "Type de cheminée" -# Review: required -# Source-Hash: 6786ac75cbe9af57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.corbeled" msgstr "En encorbellement" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cornerBevel" msgstr "Chanfrein d'angle" -# Review: required -# Source-Hash: 8a4beb9a57edc26e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Count" +# Source-Hash: 8a4beb9a57edc26e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.count" msgstr "Nombre" -# Review: required -# Source-Hash: f8ba7d73d3006d57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cricket" msgstr "Costière" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cutoutOffset" msgstr "Décalage de la découpe" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.diameter" msgstr "Diamètre" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.double" msgstr "Double" -# Review: required -# Source-Hash: 150340cd05f4a6dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extent" +# Source-Hash: 150340cd05f4a6dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.extent" msgstr "Étendue" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flat" msgstr "Plat" -# Review: required -# Source-Hash: f817c09ef0bb6d69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flues" +# Source-Hash: f817c09ef0bb6d69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flues" msgstr "Conduits" -# Review: required -# Source-Hash: a3c10560fa3c1612 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Footprint" +# Source-Hash: a3c10560fa3c1612 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.footprint" msgstr "Emprise" -# Review: required -# Source-Hash: e360f89d3c0ec456 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front" +# Source-Hash: e360f89d3c0ec456 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.front" msgstr "Avant" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 5b490bc19dddf201 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hollow Depth" +# Source-Hash: 5b490bc19dddf201 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.hollowDepth" msgstr "Profondeur du creux" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.length" msgstr "Longueur" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.none" msgstr "Aucun" -# Review: required -# Source-Hash: 0f0e13abf9b2bb67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Offset" +# Source-Hash: 0f0e13abf9b2bb67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.offset" msgstr "Décalage" -# Review: required -# Source-Hash: 88540def2ce16ac5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.overhang" msgstr "Débord" -# Review: required -# Source-Hash: 6ce059aee1386f72 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panels" +# Source-Hash: 6ce059aee1386f72 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.panels" msgstr "Panneaux" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.position" msgstr "Position" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rectangular" msgstr "Rectangulaire" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.round" msgstr "Rond" -# Review: required -# Source-Hash: 8c6e3985459dacf9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.shoulder" msgstr "Épaulement" -# Review: required -# Source-Hash: 84b9195c73dd7a67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Side Margin" +# Source-Hash: 84b9195c73dd7a67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sideMargin" msgstr "Marge latérale" -# Review: required -# Source-Hash: a05a390ae205fdd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple" +# Source-Hash: a05a390ae205fdd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.simple" msgstr "Simple" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.single" msgstr "Simple" -# Review: required -# Source-Hash: f6998707123fe72d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sloped" +# Source-Hash: f6998707123fe72d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sloped" msgstr "En pente" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.spacing" msgstr "Espacement" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.square" msgstr "Carré" -# Review: required -# Source-Hash: 2c6b1cf17a973d67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.stepped" msgstr "En gradins" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.style" msgstr "Style" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.tapered" msgstr "Effilé" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.thickness" msgstr "Épaisseur" -# Review: required -# Source-Hash: 14b162761ce18fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Offset" +# Source-Hash: 14b162761ce18fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.topOffset" msgstr "Décalage du haut" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.wallThickness" msgstr "Épaisseur de paroi" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: abff67400a76e5eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brick" +# Source-Hash: abff67400a76e5eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.brick" msgstr "Brique" -# Review: required -# Source-Hash: f6cbdaa17e989b91 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modern" +# Source-Hash: f6cbdaa17e989b91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.modern" msgstr "Moderne" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.round" msgstr "Rond" +# English: "A parametric column with configurable cross-section, base, and capital." # Source-Hash: 6a06e628257f3393 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A parametric column with configurable cross-section, base, and capital." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.description" msgstr "Poteau paramétrique avec section, base et chapiteau configurables." +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.label" msgstr "Poteau" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: 14b21685d375581a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "A-Frame" +# Source-Hash: 14b21685d375581a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.aFrame" msgstr "Cadre en A" -# Review: required -# Source-Hash: 4007d88108e25c2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply preset..." +# Source-Hash: 4007d88108e25c2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyPreset" msgstr "Appliquer un préréglage…" -# Review: required -# Source-Hash: dbfe7c561383c964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply proportion..." +# Source-Hash: dbfe7c561383c964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyProportion" msgstr "Appliquer une proportion…" -# Review: required -# Source-Hash: 1f3fd938f0b26c9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Depth" +# Source-Hash: 1f3fd938f0b26c9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomDepth" msgstr "Profondeur du bas" -# Review: required -# Source-Hash: 85576e19cd3da914 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Height" +# Source-Hash: 85576e19cd3da914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomHeight" msgstr "Hauteur du bas" -# Review: required -# Source-Hash: 4df0bbb04744a810 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Spread" +# Source-Hash: 4df0bbb04744a810 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomSpread" msgstr "Écartement en bas" -# Review: required -# Source-Hash: 67479fecb980a441 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Step Spread" +# Source-Hash: 67479fecb980a441 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomStepSpread" msgstr "Écart des gradins du bas" -# Review: required -# Source-Hash: 6ea977c42202ca24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Tiers" +# Source-Hash: 6ea977c42202ca24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomTiers" msgstr "Gradins du bas" -# Review: required -# Source-Hash: 709ca788827f8a94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Width" +# Source-Hash: 709ca788827f8a94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomWidth" msgstr "Largeur du bas" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.boxFrame" msgstr "Cadre caisson" -# Review: required -# Source-Hash: 381b1f481f747677 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Depth" +# Source-Hash: 381b1f481f747677 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceDepth" msgstr "Profondeur du contreventement" -# Review: required -# Source-Hash: 8509b09b4ecdd6b6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Width" +# Source-Hash: 8509b09b4ecdd6b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceWidth" msgstr "Largeur du contreventement" -# Review: required -# Source-Hash: 08558dd786f45281 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulge" +# Source-Hash: 08558dd786f45281 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulge" msgstr "Renflement" -# Review: required -# Source-Hash: 663b9a9e6a469b02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulged" +# Source-Hash: 663b9a9e6a469b02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulged" msgstr "Renflée" -# Review: required -# Source-Hash: 3530ac1f4046cd22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Column" +# Source-Hash: 3530ac1f4046cd22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.column" msgstr "Poteau" -# Review: required -# Source-Hash: ca0a856f04460bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Connector Plates" +# Source-Hash: ca0a856f04460bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.connectorPlates" msgstr "Plaques d'assemblage" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: b963a08f0d540014 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edge Softness" +# Source-Hash: b963a08f0d540014 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.edgeSoftness" msgstr "Adoucissement des arêtes" -# Review: required -# Source-Hash: 82b3db356639ef92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ends" +# Source-Hash: 82b3db356639ef92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ends" msgstr "Extrémités" -# Review: required -# Source-Hash: 646c3cd99df3e285 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Width" +# Source-Hash: 646c3cd99df3e285 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.endWidth" msgstr "Largeur d'extrémité" -# Review: required -# Source-Hash: 02e229bca2a2b5f1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fork Spread" +# Source-Hash: 02e229bca2a2b5f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.forkSpread" msgstr "Écartement de la fourche" -# Review: required -# Source-Hash: fdebfac05413abcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Heavy" +# Source-Hash: fdebfac05413abcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heavy" msgstr "Massive" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 957fae84ed52a2e3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to." +# Source-Hash: 957fae84ed52a2e3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heightAndFootprintAreControlledBy" msgstr "La hauteur et l'emprise sont pilotées par l'appentis. Faites pivoter ou changez le style de support ici ; redimensionnez depuis l'appentis parent." -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.hourglass" msgstr "Sablier" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.kBrace" msgstr "Contreventement en K" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 6dc190abd3f22787 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Width" +# Source-Hash: 6dc190abd3f22787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.neckWidth" msgstr "Largeur du col" -# Review: required -# Source-Hash: 7dba7ed55eeba18f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Bottom" +# Source-Hash: 7dba7ed55eeba18f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noBottom" msgstr "Sans base" -# Review: required -# Source-Hash: 682d71815bb20e94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Top" +# Source-Hash: 682d71815bb20e94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noTop" msgstr "Sans chapiteau" -# Review: required -# Source-Hash: 4da8688437f64db8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth Thickness" +# Source-Hash: 4da8688437f64db8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.plinthThickness" msgstr "Épaisseur de la plinthe" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.portalFrame" msgstr "Portique" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.preset" msgstr "Préréglage" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.rectangular" msgstr "Rectangulaire" -# Review: required -# Source-Hash: 23b9d290b165cd49 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Pairs" +# Source-Hash: 23b9d290b165cd49 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringPairs" msgstr "Paires d'anneaux" -# Review: required -# Source-Hash: 8db82d731a19651e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Spread" +# Source-Hash: 8db82d731a19651e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringSpread" msgstr "Écart des anneaux" -# Review: required -# Source-Hash: bdb524a15d9e1509 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Thickness" +# Source-Hash: bdb524a15d9e1509 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringThickness" msgstr "Épaisseur des anneaux" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.round" msgstr "Rond" -# Review: required -# Source-Hash: baa84a2d17095dcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round Band Width" +# Source-Hash: baa84a2d17095dcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundBandWidth" msgstr "Largeur de la bague ronde" -# Review: required -# Source-Hash: 5f1f67fb0ca59a6d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Bottom" +# Source-Hash: 5f1f67fb0ca59a6d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedBottom" msgstr "Base arrondie" -# Review: required -# Source-Hash: 446816980ed0da27 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Top" +# Source-Hash: 446816980ed0da27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedTop" msgstr "Chapiteau arrondi" - -# Review: required -# Source-Hash: 9e554c26be80f0d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Segment Twist" +# Source-Hash: 9e554c26be80f0d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.segmentTwist" msgstr "Torsion des segments" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaft" msgstr "Gaine" -# Review: required -# Source-Hash: 9bc3a704a9a96b3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Corner Radius" +# Source-Hash: 9bc3a704a9a96b3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftCornerRadius" msgstr "Rayon d'angle du fût" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftWidth" msgstr "Largeur du fût" -# Review: required -# Source-Hash: 85ea1d3c8b749680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shape" +# Source-Hash: 85ea1d3c8b749680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shape" msgstr "Forme" -# Review: required -# Source-Hash: 49a2ce13016d4073 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Short / Stout" +# Source-Hash: 49a2ce13016d4073 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shortStout" msgstr "Courte / Trapue" -# Review: required -# Source-Hash: 4637a9a61638cc66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Block Bottom" +# Source-Hash: 4637a9a61638cc66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleBlockBottom" msgstr "Base en bloc simple" -# Review: required -# Source-Hash: 9278f9b4a48208b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Top" +# Source-Hash: 9278f9b4a48208b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleTop" msgstr "Chapiteau simple" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.singleStrut" msgstr "Jambe de force unique" -# Review: required -# Source-Hash: d87ce271009ce063 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slender" +# Source-Hash: d87ce271009ce063 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.slender" msgstr "Élancée" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.square" msgstr "Carré" -# Review: required -# Source-Hash: 2dc84759d8df709d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Plinth Bottom" +# Source-Hash: 2dc84759d8df709d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.squarePlinthBottom" msgstr "Base à plinthe carrée" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.standard" msgstr "Standard" -# Review: required -# Source-Hash: 01a6393135e0fb10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Bottom" +# Source-Hash: 01a6393135e0fb10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedBottom" msgstr "Base à gradins" -# Review: required -# Source-Hash: 3d7e443797d4146e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Top" +# Source-Hash: 3d7e443797d4146e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedTop" msgstr "Chapiteau à gradins" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.straight" msgstr "Droit" -# Review: required -# Source-Hash: 6063ae2877faa35d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Taper" +# Source-Hash: 6063ae2877faa35d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.taper" msgstr "Fuselage" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tapered" msgstr "Effilé" -# Review: required -# Source-Hash: 3e7ec47626ee3f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Depth" +# Source-Hash: 3e7ec47626ee3f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topDepth" msgstr "Profondeur du haut" -# Review: required -# Source-Hash: 0ca9047731784f56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Height" +# Source-Hash: 0ca9047731784f56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topHeight" msgstr "Hauteur du haut" -# Review: required -# Source-Hash: 3d8a2be83b5b536a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Spread" +# Source-Hash: 3d8a2be83b5b536a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topSpread" msgstr "Écartement en haut" -# Review: required -# Source-Hash: f4ae0dc639dbc220 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Step Spread" +# Source-Hash: f4ae0dc639dbc220 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topStepSpread" msgstr "Écart des gradins du haut" -# Review: required -# Source-Hash: 8acc4e843934b086 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Tiers" +# Source-Hash: 8acc4e843934b086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topTiers" msgstr "Gradins du haut" -# Review: required -# Source-Hash: a50eb6400611a57c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Width" +# Source-Hash: a50eb6400611a57c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topWidth" msgstr "Largeur du haut" -# Review: required -# Source-Hash: f425799b82e085ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Transform" +# Source-Hash: f425799b82e085ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.transform" msgstr "Transformation" -# Review: required -# Source-Hash: 952fd1ef187c6646 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle" +# Source-Hash: 952fd1ef187c6646 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.trestle" msgstr "Tréteau" -# Review: required -# Source-Hash: 7430dad59aaf98f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod" +# Source-Hash: 7430dad59aaf98f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tripod" msgstr "Trépied" -# Review: required -# Source-Hash: 0db559f22647ec73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Twist Segments" +# Source-Hash: 0db559f22647ec73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.twistSegments" msgstr "Segments de torsion" -# Review: required -# Source-Hash: 8038a34ffebf4932 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical" +# Source-Hash: 8038a34ffebf4932 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vertical" msgstr "Vertical" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vSupport" msgstr "Support en V" -# Review: required -# Source-Hash: 44dda7136b93c8ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waist" +# Source-Hash: 44dda7136b93c8ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.waist" msgstr "Paillasse" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.xBrace" msgstr "Croisillon en X" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.yaw" msgstr "Lacet" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ySupport" msgstr "Support en Y" +# English: "A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up." +# Source-Hash: 4d3a89de7073d38d # Review: required -# Source-Hash: bf2baf3d7b55f0df -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.aSizeDeckPostStandsNo" +msgstr "Un poteau de terrasse {size} ne doit pas dépasser {limit} de haut (IRC Table R507.4, selon l’essence et la surface portée) : vérifiez-le, ou passez à la taille supérieure." + +# English: "Custom" +# Source-Hash: 9e9715d52410c86f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.custom" +msgstr "Personnalisé" + +# English: "Fit to the beam over it ({height})" +# Source-Hash: 31f74f6b8e7f077e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheBeamOverIt" +msgstr "Ajuster à la poutre au-dessus ({height})" + +# English: "Fit to the ceiling plane ({height})" +# Source-Hash: f09c0bf0ea50fc47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheCeilingPlane" +msgstr "Ajuster au plan du plafond ({height})" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.height" +msgstr "Hauteur" + +# English: "Keep its shape and style it as a column" +# Source-Hash: b1778491849d76d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.keepItsShapeAndStyleIt" +msgstr "Garder sa forme et le styliser comme une colonne" + +# English: "Make it a column" +# Source-Hash: 151f47e85cf7cc8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.makeItAColumn" +msgstr "En faire une colonne" + +# English: "Post" +# Source-Hash: 5cc981e79d737694 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.post" +msgstr "Poteau bois" + +# English: "Treated" +# Source-Hash: 64ed304f3773f93d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.treated" +msgstr "Traité" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.untreated" +msgstr "Non traité" + # English: "A-Frame Support" +# Source-Hash: bf2baf3d7b55f0df +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.aFrameSupport" msgstr "Support en A" -# Review: required -# Source-Hash: 51cf77bb359cee43 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight Round" +# Source-Hash: 51cf77bb359cee43 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.basicPillar" msgstr "Ronde droite" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.boxFrameSupport" msgstr "Cadre caisson" -# Review: required -# Source-Hash: 4f22058e862d1f81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Soft Bulged" +# Source-Hash: 4f22058e862d1f81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.bulgedPillar" msgstr "Légèrement renflée" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.hourglassPillar" msgstr "Sablier" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.kBraceSupport" msgstr "Contreventement en K" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.portalFrameSupport" msgstr "Portique" - -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.singleStrutSupport" msgstr "Jambe de force unique" -# Review: required -# Source-Hash: 0c13af329b9b57cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Block" +# Source-Hash: 0c13af329b9b57cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.squarePillar" msgstr "Bloc carré" -# Review: required -# Source-Hash: 4c294bad84924665 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered Round" +# Source-Hash: 4c294bad84924665 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.taperedPillar" msgstr "Ronde fuselée" -# Review: required -# Source-Hash: 360e405bfd74a379 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle Frame" +# Source-Hash: 360e405bfd74a379 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.trestleSupport" msgstr "Cadre à tréteau" -# Review: required -# Source-Hash: 4589103259b4fc4d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod Support" +# Source-Hash: 4589103259b4fc4d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.tripodSupport" msgstr "Support trépied" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.vFrameSupport" msgstr "Support en V" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.xBraceSupport" msgstr "Croisillon en X" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.yFrameSupport" msgstr "Support en Y" +# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." # Source-Hash: 895766ad1b766c2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.description" msgstr "Cote de plan associative : linéaire, courbe, circulaire, angulaire ou en coordonnées." +# English: "Construction Dimension" # Source-Hash: 6875b60ee8a7cc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction Dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.label" msgstr "Cote de construction" +# English: "Bottom Left" # Source-Hash: 61a6c6b9bc29c4f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomLeft" msgstr "Bas gauche" +# English: "Bottom Right" # Source-Hash: c5368accab136fa6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomRight" msgstr "Bas droite" +# English: "Top Left" # Source-Hash: c8f86af3f03af6c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topLeft" msgstr "Haut gauche" +# English: "Top Right" # Source-Hash: 0ead7f1ffdbbd892 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topRight" msgstr "Haut droite" +# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." # Source-Hash: a276c9693f01dec7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.description" msgstr "Lanterneau à persiennes avec coiffe en dôme ou pyramide et épi facultatif." +# English: "Cupola" # Source-Hash: e679dd19f96eba0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.label" msgstr "Lanterneau" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: e679dd19f96eba0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cupola" +# Source-Hash: e679dd19f96eba0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.cupola" msgstr "Lanterneau" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dome" msgstr "Dôme" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: 1cfe33235b30b1d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.finial" msgstr "Épi de faîtage" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: b5e0cb318d484d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Finial" +# Source-Hash: b5e0cb318d484d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.noFinial" msgstr "Sans épi de faîtage" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 47587b825e9a696b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pyramid" +# Source-Hash: 47587b825e9a696b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.pyramid" msgstr "Pyramide" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.style" msgstr "Style" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.width" msgstr "Largeur" +# English: "Auto elevator cutout" # Source-Hash: 5d4ccdf13668fab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto elevator cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoElevator" msgstr "Découpe auto d’ascenseur" +# English: "Auto stair cutout" # Source-Hash: 78aa03576e02dda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto stair cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoStair" msgstr "Découpe auto d’escalier" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.opening" msgstr "Ouverture" +# English: "Room cutout" # Source-Hash: ce37dbd7a309940b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.room" msgstr "Découpe de pièce" -# Source-Hash: 6123b20edb9569de +# English: "Swap hinge side" +# Source-Hash: b1757d76048706b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapHingeSide" +msgstr "Inverser le côté des charnières" + +# English: "Swap swing side" +# Source-Hash: d8bbb9fdf69726ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapSwingSide" +msgstr "Inverser le sens d’ouverture" + # English: "A door cut into a wall. Animated open/close state." +# Source-Hash: 6123b20edb9569de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.description" msgstr "Porte découpée dans un mur, avec ouverture et fermeture animées." +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.label" msgstr "Porte" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.actions" msgstr "Actions" +# English: "+ Add Segment" # Source-Hash: 8337b3213849faca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.addSegment" msgstr "+ Ajouter un segment" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.all" msgstr "Tout" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.arch" msgstr "Arc" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.archHeight" msgstr "Hauteur de l’arc" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.auto" msgstr "Auto" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.autoFollowsTheWall" msgstr "Auto suit le mur" +# English: "Bar Height" # Source-Hash: 387773c8aa2820a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bar Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.barHeight" msgstr "Hauteur de la barre" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.columns" msgstr "Colonnes" +# English: "Content Padding" # Source-Hash: f533584833a4a7bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Content Padding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.contentPadding" msgstr "Marge intérieure" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.cornerRadius" msgstr "Rayon d’angle" +# English: "Custom: the segments match no style." # Source-Hash: aa0e8e02212091e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the segments match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.customTheSegmentsMatchNoStyle" msgstr "Personnalisé : les segments ne correspondent à aucun style." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.delete" msgstr "Supprimer" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.depth" msgstr "Profondeur" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.dimensions" msgstr "Dimensions" +# English: "Direction" # Source-Hash: 76cd03300faa9b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.direction" msgstr "Direction" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.divider" msgstr "Séparateur" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.door" msgstr "Porte" +# English: "Door Closer" # Source-Hash: 4a0aa7f647e6461b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door Closer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.doorCloser" msgstr "Ferme-porte" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.duplicate" msgstr "Dupliquer" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.empty" msgstr "Vide" +# English: "Enable Handle" # Source-Hash: 6982301da2dbe186 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableHandle" msgstr "Activer la poignée" +# English: "Enable Threshold" # Source-Hash: 2e96ebf9c6e7fe49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableThreshold" msgstr "Activer le seuil" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.flipSide" msgstr "Inverser le côté" +# English: "Fold" # Source-Hash: 7e8fdd28f9d54d1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.fold" msgstr "Pli" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frame" msgstr "Cadre" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frameDepthAuto" msgstr "Profondeur (auto)" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.garage" msgstr "Garage" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.glass" msgstr "Vitrage" +# English: "Handle" # Source-Hash: 41483b645efc0c65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handle" msgstr "Poignée" +# English: "Handle Side" # Source-Hash: b4a496a904640583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handleSide" msgstr "Côté de la poignée" +# English: "Hardware" # Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hardware" msgstr "Quincaillerie" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.height" msgstr "Hauteur" +# English: "Hinges Side" # Source-Hash: 7c8f079bbefe86c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinges Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hingesSide" msgstr "Côté des charnières" +# English: "Horizontal" # Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.horizontal" msgstr "Horizontal" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.individual" msgstr "Individuel" +# English: "Inset" # Source-Hash: 3db1dea825785a30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inset" msgstr "Encastré" +# English: "Inward" # Source-Hash: 1f4ad5ad3e1be222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inward" msgstr "Vers l’intérieur" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.left" msgstr "Gauche" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.move" msgstr "Déplacer" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.open" msgstr "Ouvrir" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.opening" msgstr "Ouverture" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.openingShape" msgstr "Forme de l’ouverture" +# English: "Outward" # Source-Hash: ccd8f06b8f8d27c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.outward" msgstr "Vers l’extérieur" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panel" msgstr "Panneau" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panels" msgstr "Panneaux" +# English: "Panic Bar" # Source-Hash: ad56a8ab82ff1a77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panic Bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panicBar" msgstr "Barre antipanique" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.pocket" msgstr "À galandage" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.position" msgstr "Position" +# English: "Rail" # Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rail" msgstr "Lisse" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rect" msgstr "Rect." +# English: "- Remove" # Source-Hash: c05172113af5a2b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "- Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.remove" msgstr "- Retirer" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.revealRadius" msgstr "Rayon de l’ébrasement" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.right" msgstr "Droite" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rounded" msgstr "Arrondi" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.segments" msgstr "Segments" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.sizeIsLimitedToTheWall" msgstr "La taille est limitée au mur, dégagement du cadre d’ouverture compris." +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.slide" msgstr "Coulisser" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.style" msgstr "Style" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.swing" msgstr "Battant" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.thickness" msgstr "Épaisseur" +# English: "Threshold" # Source-Hash: f165d6c05be2b651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.threshold" msgstr "Seuil" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.topShape" msgstr "Forme du haut" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.type" msgstr "Type" +# English: "Vertical" # Source-Hash: 8038a34ffebf4932 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.vertical" msgstr "Vertical" +# English: "wall" # Source-Hash: 63d6b766151965f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.wall" msgstr "mur" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.width" msgstr "Largeur" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.barn" msgstr "Coulissante de grange" +# English: "Craftsman" # Source-Hash: f747fbe71717772e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.craftsman" msgstr "Craftsman" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.french" msgstr "Porte-fenêtre" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.glass" msgstr "Vitrage" +# English: "Half-louvered" # Source-Hash: 6ab8b98b38eb2e65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.halfLouvered" msgstr "Mi-persiennée" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.modern" msgstr "Moderne" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.panel" msgstr "Panneau" +# English: "Paneled glass" # Source-Hash: ac42c328610a0edd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.paneledGlass" msgstr "Vitrée à panneaux" +# English: "Shaker" # Source-Hash: 5b68e435b7210876 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.shaker" msgstr "Shaker" +# English: "Six-panel" # Source-Hash: 9ad6ac42b4152b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.sixPanel" msgstr "Six panneaux" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.barn" msgstr "Coulissante de grange" +# English: "Double" # Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.double" msgstr "Double" +# English: "Folding" # Source-Hash: 29ff60e662b789fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.folding" msgstr "Pliante" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.french" msgstr "Porte-fenêtre" +# English: "Roll-up" # Source-Hash: c86309fefe89699b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roll-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageRollup" msgstr "Enroulable" +# English: "Sectional" # Source-Hash: fb6a868430547544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sectional" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageSectional" msgstr "Sectionnelle" +# English: "Tilt-up" # Source-Hash: 0b5eb64b569db837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageTiltup" msgstr "Basculante" +# English: "Hinged" # Source-Hash: e744be60baad6abc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.hinged" msgstr "Battante" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.pocket" msgstr "À galandage" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.sliding" msgstr "Coulissante" +# English: "House-shaped protrusion on a roof segment." # Source-Hash: 387e57242c8d110a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "House-shaped protrusion on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.description" msgstr "Saillie en forme de maison sur un pan de toit." +# English: "Dormer" # Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.label" msgstr "Lucarne" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: 6b6f8068bbc4c902 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dormer" msgstr "Lucarne" -# Review: required -# Source-Hash: 6949d667043bee0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dutch" +# Source-Hash: 6949d667043bee0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dutch" msgstr "Demi-croupe" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.flat" msgstr "Plat" -# Review: required -# Source-Hash: deaffa095d126dfe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gable" +# Source-Hash: deaffa095d126dfe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gable" msgstr "Deux pans" -# Review: required -# Source-Hash: 7f53d9ffca39984a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gambrel" msgstr "Brisé" -# Review: required -# Source-Hash: 9eda0ea8d447066d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hip" +# Source-Hash: 9eda0ea8d447066d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.hip" msgstr "Croupe" -# Review: required -# Source-Hash: 7f7bb0e875af3260 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.mansard" msgstr "Mansarde" -# Review: required -# Source-Hash: 53af9efc7eca6594 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Direction" +# Source-Hash: 53af9efc7eca6594 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchDirection" msgstr "Sens de la pente" -# Review: required -# Source-Hash: 6c39ef7125494c4a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Rise" +# Source-Hash: 6c39ef7125494c4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchRise" msgstr "Hauteur de pente" -# Review: required -# Source-Hash: 96336d4b0820b977 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Back" +# Source-Hash: 96336d4b0820b977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseBack" msgstr "Montée vers l'arrière" -# Review: required -# Source-Hash: ee3d45f8b756d029 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Front" +# Source-Hash: ee3d45f8b756d029 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseFront" msgstr "Montée vers l'avant" -# Review: required -# Source-Hash: 5e0622f3b09c9326 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Height" +# Source-Hash: 5e0622f3b09c9326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofHeight" msgstr "Hauteur de toiture" -# Review: required -# Source-Hash: 19ab9527ae7889f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Type" +# Source-Hash: 19ab9527ae7889f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofType" msgstr "Type de toit" -# Review: required -# Source-Hash: dba6a7b56cf7083f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Section" +# Source-Hash: dba6a7b56cf7083f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.section" msgstr "Section" -# Review: required -# Source-Hash: c8d0df27faa3feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shed" +# Source-Hash: c8d0df27faa3feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.shed" msgstr "Appentis" -# Review: required -# Source-Hash: e0f973edce3e4ca8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Height" +# Source-Hash: e0f973edce3e4ca8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.wallHeight" msgstr "Hauteur du mur" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: 0b1d1e9a36456a50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows" +# Source-Hash: 0b1d1e9a36456a50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.windows" msgstr "Fenêtres" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.actions" msgstr "Actions" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.position" msgstr "Position" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: 7b19d21b9ad868c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Window" +# Source-Hash: 7b19d21b9ad868c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.addWindow" msgstr "Ajouter une fenêtre" -# Review: required -# Source-Hash: 70d7cc5a6422feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit {name}" +# Source-Hash: 70d7cc5a6422feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editNamed" msgstr "Modifier {name}" -# Review: required -# Source-Hash: b6648f023e6c83dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit window" +# Source-Hash: b6648f023e6c83dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editWindow" msgstr "Modifier la fenêtre" -# Review: required -# Source-Hash: 4649baf9753aeb74 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase the dormer width to add another window." +# Source-Hash: 4649baf9753aeb74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd" msgstr "Élargissez la lucarne pour ajouter une autre fenêtre." -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.move" msgstr "Déplacer" -# Review: required -# Source-Hash: c24226954309d83e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move {name}" +# Source-Hash: c24226954309d83e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveNamed" msgstr "Déplacer {name}" -# Review: required -# Source-Hash: f07faaf0d5e43eef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move window" +# Source-Hash: f07faaf0d5e43eef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveWindow" msgstr "Déplacer la fenêtre" -# Review: required -# Source-Hash: d2816490ea93d363 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No windows" +# Source-Hash: d2816490ea93d363 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.noWindows" msgstr "Aucune fenêtre" -# Review: required -# Source-Hash: 5cbb24f8b1ce7fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Window {number}" +# Source-Hash: 5cbb24f8b1ce7fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowNumber" msgstr "Fenêtre {number}" -# Review: required -# Source-Hash: 9ca7d951240907cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows ({length})" +# Source-Hash: 9ca7d951240907cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowsLength" msgstr "Fenêtres ({length})" -# Review: required -# Source-Hash: f975c4759607806a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{face, select, back {Back} left {Left} right {Right} other {Front}} · {width} × {height} m" +# Source-Hash: f975c4759607806a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowSummary" msgstr "{face, select, back {Arrière} left {Gauche} right {Droite} other {Avant}} · {width} × {height} m" +# English: "Vertical drop pipe from a gutter outlet to the ground." # Source-Hash: b46392d2ea865760 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical drop pipe from a gutter outlet to the ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.description" msgstr "Tuyau vertical de la sortie de gouttière jusqu’au sol." -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/downspout/inspector-editors.tsx #, fuzzy msgid "node.downspout.inspectorEditors.position" msgstr "Position" +# English: "Downspout" # Source-Hash: 1a1ec08a311f1b35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.label" msgstr "Descente pluviale" +# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." # Source-Hash: 29eadb8c84ae9632 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.description" msgstr "Coude, té, réduction ou transition carré-rond reliant des gaines." -# Review: required -# Source-Hash: 7323a0fc9efb077f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap W/H" +# Source-Hash: 7323a0fc9efb077f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWH" msgstr "Inverser L/H" -# Review: required -# Source-Hash: 064b36c4fc5deb87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap width and height" +# Source-Hash: 064b36c4fc5deb87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWidthAndHeight" msgstr "Inverser largeur et hauteur" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.label" msgstr "Raccord de gaine" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis" msgstr "Axe" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis2" msgstr "⌥ axe" -# Review: required -# Source-Hash: 880b34f6fc0ae854 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hover a duct face that fits the door" +# Source-Hash: 880b34f6fc0ae854 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.hoverADuctFaceThatFits" msgstr "Survolez une face de gaine adaptée à la trappe" -# Review: required -# Source-Hash: 706f5b43a1d4a84a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Place access door" +# Source-Hash: 706f5b43a1d4a84a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.placeAccessDoor" msgstr "Placer une trappe de visite" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.rTRotate" msgstr "R/T pivoter" +# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." # Source-Hash: 09eb45a9b35d1a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.description" msgstr "Réseau de gaines CVC — polyligne de sections rondes, rectangulaires ou oblongues." +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.label" msgstr "Gaine" +# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." # Source-Hash: 33181d0f42421013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.description" msgstr "Terminal aéraulique — bouche de soufflage, diffuseur plafonnier ou grille de reprise. Les gaines se terminent sur son collet." +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.label" msgstr "Bouche" -# Review: required -# Source-Hash: 83c665eafcf43a68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{mount, select, floor {Mount Floor} ceiling {Mount Ceiling} wall {Mount Wall} other {Mount {label}}}" +# Source-Hash: 83c665eafcf43a68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mount" msgstr "{mount, select, floor {Pose au sol} ceiling {Pose au plafond} wall {Pose murale} other {Pose {label}}}" -# Review: required -# Source-Hash: f067dd52ea051385 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "M surface" +# Source-Hash: f067dd52ea051385 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Key hint: pressing M switches the mounting surface. #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mSurface" msgstr "M surface" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.rTRotate" msgstr "R/T pivoter" -# Review: required -# Source-Hash: 98446855908ee71d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Snapped to duct" +# Source-Hash: 98446855908ee71d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.snappedToDuct" msgstr "Accroché à la gaine" +# English: "A multi-level elevator shaft with configurable openings per level." # Source-Hash: d0afa386e65cd3a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A multi-level elevator shaft with configurable openings per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.description" msgstr "Gaine d’ascenseur sur plusieurs niveaux, ouvertures configurables par niveau." +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.label" msgstr "Ascenseur" -# Review: required -# Source-Hash: 7e6d5f138e315bdb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Access" +# Source-Hash: 7e6d5f138e315bdb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.access" msgstr "Accès" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: fdf7108a011bd94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab" +# Source-Hash: fdf7108a011bd94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cab" msgstr "Cabine" -# Review: required -# Source-Hash: 2d0ab44f3820f22c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab Height" +# Source-Hash: 2d0ab44f3820f22c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cabHeight" msgstr "Hauteur de cabine" -# Review: required -# Source-Hash: 65f410261609060b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center opening" +# Source-Hash: 65f410261609060b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.centerOpening" msgstr "Ouverture centrale" -# Review: required -# Source-Hash: c5433f4d3ea8a8a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Default Floor" +# Source-Hash: c5433f4d3ea8a8a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.defaultFloor" msgstr "Niveau par défaut" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 748877ae309c313c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Destination" +# Source-Hash: 748877ae309c313c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.destination" msgstr "Destination" -# Review: required -# Source-Hash: 3bff69bd74544339 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Disabled" +# Source-Hash: 3bff69bd74544339 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.disabled" msgstr "Désactivé" -# Review: required -# Source-Hash: d8baf402fa9c52f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Height" +# Source-Hash: d8baf402fa9c52f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorHeight" msgstr "Hauteur de porte" -# Review: required -# Source-Hash: 20f60b98de67849b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Doors" +# Source-Hash: 20f60b98de67849b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doors" msgstr "Portes" -# Review: required -# Source-Hash: eb0d4023a513b16c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Time" +# Source-Hash: eb0d4023a513b16c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorTime" msgstr "Durée de porte" -# Review: required -# Source-Hash: b3e001acdb1f869d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Type" +# Source-Hash: b3e001acdb1f869d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorType" msgstr "Type de porte" -# Review: required -# Source-Hash: c6203a69fdf44065 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Width" +# Source-Hash: c6203a69fdf44065 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorWidth" msgstr "Largeur de porte" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: eb3abe5340b26138 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dwell" +# Source-Hash: eb3abe5340b26138 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.dwell" msgstr "Temps d'arrêt" -# Review: required -# Source-Hash: ab5cbab7be160a1c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Elevator" +# Source-Hash: ab5cbab7be160a1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.elevator" msgstr "Ascenseur" -# Review: required -# Source-Hash: 702140636ff3977a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "From" +# Source-Hash: 702140636ff3977a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.from" msgstr "Depuis" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glass" msgstr "Vitrage" -# Review: required -# Source-Hash: 88810658a2b97bc6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glassFrame" msgstr "Cadre vitré" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.motion" msgstr "Mouvement" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 825ac8855353a4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Style" +# Source-Hash: 825ac8855353a4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.openingStyle" msgstr "Type d'ouverture" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: f9a95d8407fafda4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.segmentedPanel" msgstr "Panneau segmenté" -# Review: required -# Source-Hash: aa7b49b13f96057f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Service" +# Source-Hash: aa7b49b13f96057f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.service" msgstr "Desserte" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaft" msgstr "Gaine" -# Review: required -# Source-Hash: 6605ade39317cd42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Depth" +# Source-Hash: 6605ade39317cd42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftDepth" msgstr "Profondeur de gaine" -# Review: required -# Source-Hash: b28db5a3a48fe52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Style" +# Source-Hash: b28db5a3a48fe52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftStyle" msgstr "Style de gaine" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftWidth" msgstr "Largeur de gaine" -# Review: required -# Source-Hash: aee3b2bd78c87d0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single left" +# Source-Hash: aee3b2bd78c87d0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleLeft" msgstr "Simple gauche" -# Review: required -# Source-Hash: cc43ffbef4ac7ae0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleRight" msgstr "Simple droite" -# Review: required -# Source-Hash: ef3e765e692faf4c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solid" msgstr "Plein" -# Review: required -# Source-Hash: 8008b6f17089beaf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid panel" +# Source-Hash: 8008b6f17089beaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solidPanel" msgstr "Panneau plein" -# Review: required -# Source-Hash: 3b006be0b1f0493f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Speed" +# Source-Hash: 3b006be0b1f0493f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.speed" msgstr "Vitesse" -# Review: required -# Source-Hash: 6d37a1679fa3708e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop {stopOrder}" +# Source-Hash: 6d37a1679fa3708e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.stopStoporder" msgstr "Arrêt {stopOrder}" -# Review: required -# Source-Hash: b3214a20e45a7aed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "To" +# Source-Hash: b3214a20e45a7aed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.to" msgstr "Vers" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.wallThickness" msgstr "Épaisseur de paroi" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.yaw" msgstr "Lacet" +# English: "Low curved lens-shaped roof vent with a louvered front." # Source-Hash: 7b22c91cf520e161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low curved lens-shaped roof vent with a louvered front." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.description" msgstr "Aérateur de toit bas, courbe et lenticulaire avec façade à persiennes." +# English: "Eyebrow Vent" # Source-Hash: 654b29319185ac66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.label" msgstr "Chien-assis de ventilation" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: abc97377800a8333 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back height" +# Source-Hash: abc97377800a8333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.backHeight" msgstr "Hauteur arrière" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: 654b29319185ac66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Eyebrow Vent" +# Source-Hash: 654b29319185ac66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.eyebrowVent" msgstr "Chien-assis de ventilation" -# Review: required -# Source-Hash: a2d107a5deaa01f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Half-round" +# Source-Hash: a2d107a5deaa01f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.halfRound" msgstr "Demi-rond" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 318192f27a774fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Louvers" +# Source-Hash: 318192f27a774fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.louvers" msgstr "Persiennes" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: e16f85e4bebfefb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.scoop" msgstr "Écope" -# Review: required -# Source-Hash: ec53d40877e11b62 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slant-box" +# Source-Hash: ec53d40877e11b62 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.slantBox" msgstr "Boîte inclinée" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.style" msgstr "Style" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.width" msgstr "Largeur" +# English: "A straight or curved fence segment with configurable posts and infill." # Source-Hash: 20032fe9430f610d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved fence segment with configurable posts and infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.description" msgstr "Tronçon de clôture droit ou courbe, poteaux et remplissage configurables." -# Review: required -# Source-Hash: 6f84b85097bc1ffc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add point after" +# Source-Hash: 6f84b85097bc1ffc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.addPointAfter" msgstr "Ajouter un point après" -# Review: required -# Source-Hash: 1a15ab3101e2d166 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a slab or shaped surface on this level." +# Source-Hash: 1a15ab3101e2d166 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASlabOrShapedSurface" msgstr "Choisissez une dalle ou une surface modelée sur ce niveau." -# Review: required -# Source-Hash: 78a5625401384000 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a surface" +# Source-Hash: 78a5625401384000 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASurface" msgstr "Choisir une surface" -# Review: required -# Source-Hash: 2ee121dca87bb64a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner" +# Source-Hash: 2ee121dca87bb64a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.corner" msgstr "Angle" -# Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curve" +# Source-Hash: f08fbb462e9afc0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curve" msgstr "Courbe" -# Review: required -# Source-Hash: a028e08962c388e0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curved" +# Source-Hash: a028e08962c388e0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curved" msgstr "Courbe" -# Review: required -# Source-Hash: 81b75058b7843a0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{style, select, picket {Distribution controls pickets in each bay between posts.} horizontal {Distribution controls posts along the full fence.} other {Distribution controls infill pieces between the two end posts.}} Spacing is measured between centers. The available width limits the count so pieces do not overlap." +# Source-Hash: 81b75058b7843a0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.distributionInfo" msgstr "{style, select, picket {La répartition règle les lattes de chaque travée entre poteaux.} horizontal {La répartition règle les poteaux sur toute la clôture.} other {La répartition règle les éléments de remplissage entre les deux poteaux d'extrémité.}} L'espacement se mesure entre centres. La largeur disponible limite le nombre pour éviter les chevauchements." -# Review: required -# Source-Hash: fbc8f54b0e3fdeb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit path" +# Source-Hash: fbc8f54b0e3fdeb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.editPath" msgstr "Modifier le tracé" -# Review: required -# Source-Hash: 55686db44529c013 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extend path" +# Source-Hash: 55686db44529c013 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.extendPath" msgstr "Prolonger le tracé" -# Review: required -# Source-Hash: 0bf0f27e631aa98d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow highest surface" +# Source-Hash: 0bf0f27e631aa98d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followHighestSurface" msgstr "Suivre la surface la plus haute" -# Review: required -# Source-Hash: e0b381fcb05a65c4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow selected surface" +# Source-Hash: e0b381fcb05a65c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followSelectedSurface" msgstr "Suivre la surface sélectionnée" -# Review: required -# Source-Hash: c418d97f803af8dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hold starting height" +# Source-Hash: c418d97f803af8dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.holdStartingHeight" msgstr "Garder la hauteur de départ" -# Review: required -# Source-Hash: 295ca9336dc1935b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Join" +# Source-Hash: 295ca9336dc1935b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.join" msgstr "Joindre" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.length" msgstr "Longueur" -# Review: required -# Source-Hash: 1fedc8a2f2ea6887 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} point} other {{number} points}} · {length} m" +# Source-Hash: 1fedc8a2f2ea6887 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.pathSummary" msgstr "{count, plural, one {{number} point} many {{number} points} other {{number} points}} · {length} m" -# Review: required -# Source-Hash: c96d499e11181429 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket rail projection" +# Source-Hash: c96d499e11181429 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.picketRailProjection" msgstr "Saillie des lattes sur la lisse" -# Review: required -# Source-Hash: a1880fca8372d06f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Point" +# Source-Hash: a1880fca8372d06f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.point" msgstr "Point" -# Review: required -# Source-Hash: 39bc6254b5307f53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove point" +# Source-Hash: 39bc6254b5307f53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.removePoint" msgstr "Supprimer le point" -# Review: required -# Source-Hash: ea56af3a0d7a65e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Set each span to straight or curved, then drag its points and curve handles in either view." +# Source-Hash: ea56af3a0d7a65e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.setEachSpanToStraightOr" msgstr "Rendez chaque travée droite ou courbe, puis faites glisser ses points et poignées de courbure dans l’une ou l’autre vue." -# Review: required -# Source-Hash: 354c8bc5c8da5a6e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Smooth" +# Source-Hash: 354c8bc5c8da5a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.smooth" msgstr "Lisser" -# Review: required -# Source-Hash: 3c700cc1299a150f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Span to point {number}" +# Source-Hash: 3c700cc1299a150f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.spanToPointNumber" msgstr "Travée jusqu'au point {number}" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.straight" msgstr "Droit" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surface" msgstr "Surface" -# Review: required -# Source-Hash: ab2de0428c093018 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface behavior" +# Source-Hash: ab2de0428c093018 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surfaceBehavior" msgstr "Comportement sur la surface" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.label" msgstr "Clôture" -# Review: required -# Source-Hash: 3e07a9889c1bd7f9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Detach endpoint" +# Source-Hash: 3e07a9889c1bd7f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.detachEndpoint" msgstr "Détacher l'extrémité" -# Review: required -# Source-Hash: 7a3ce9fec6370e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drag endpoint" +# Source-Hash: 7a3ce9fec6370e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.dragEndpoint" msgstr "Faire glisser l'extrémité" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.close" msgstr "Fermer" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.open" msgstr "Ouvrir" -# Review: required -# Source-Hash: fc4cf2bc034fc547 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Board width" +# Source-Hash: fc4cf2bc034fc547 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.boardWidth" msgstr "Largeur des planches" -# Review: required -# Source-Hash: f3b11e5b2dd3b25f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bracing" +# Source-Hash: f3b11e5b2dd3b25f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.bracing" msgstr "Contreventement" -# Review: required -# Source-Hash: d09ec3d9565734c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Distance from start" +# Source-Hash: d09ec3d9565734c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.distanceFromStart" msgstr "Distance depuis le début" -# Review: required -# Source-Hash: dcd803eaf3796298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fence unavailable." +# Source-Hash: dcd803eaf3796298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.fenceUnavailable" msgstr "Clôture indisponible." -# Review: required -# Source-Hash: 3519d91b7bda18a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame width" +# Source-Hash: 3519d91b7bda18a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.frameWidth" msgstr "Largeur du cadre" -# Review: required -# Source-Hash: cb18554b2420e700 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate height" +# Source-Hash: cb18554b2420e700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateHeight" msgstr "Hauteur du portail" -# Review: required -# Source-Hash: 54098b5ee574737a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate style" +# Source-Hash: 54098b5ee574737a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateStyle" msgstr "Style de portail" -# Review: required -# Source-Hash: 4fc3e60be28f60d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.groundClearance" msgstr "Garde au sol" -# Review: required -# Source-Hash: 916f2e07bf082795 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinges and latch" +# Source-Hash: 916f2e07bf082795 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingesAndLatch" msgstr "Charnières et loquet" -# Review: required -# Source-Hash: 5bd2e597fbb2a2c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinge side" +# Source-Hash: 5bd2e597fbb2a2c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingeSide" msgstr "Côté des charnières" -# Review: required -# Source-Hash: 8fec7e969045a8e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Horizontal boards" +# Source-Hash: 8fec7e969045a8e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.horizontalBoards" msgstr "Lames horizontales" -# Review: required -# Source-Hash: f4addad87cc4223e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Infill spacing" +# Source-Hash: f4addad87cc4223e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.infillSpacing" msgstr "Espacement du remplissage" -# Review: required -# Source-Hash: cdec08b292742b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Jamb posts" +# Source-Hash: cdec08b292742b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.jambPosts" msgstr "Poteaux de jambage" -# Review: required -# Source-Hash: ef88410453ee7414 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Keep the opening inside the fence and clear of other gates or passages." +# Source-Hash: ef88410453ee7414 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.keepTheOpeningInsideTheFence" msgstr "Gardez l'ouverture dans la clôture et à l'écart des autres portails ou passages." -# Review: required -# Source-Hash: 67cd0171b226fedb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaf thickness" +# Source-Hash: 67cd0171b226fedb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leafThickness" msgstr "Épaisseur du vantail" -# Review: required -# Source-Hash: c390ba248e629b9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaves" +# Source-Hash: c390ba248e629b9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leaves" msgstr "Vantaux" -# Review: required -# Source-Hash: d7bb934589475512 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left leaf proportion" +# Source-Hash: d7bb934589475512 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leftLeafProportion" msgstr "Proportion du vantail gauche" -# Review: required -# Source-Hash: aa9fe0ebace8957d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence height" +# Source-Hash: aa9fe0ebace8957d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceHeight" msgstr "Hauteur de la clôture" -# Review: required -# Source-Hash: c15c1af69ec5c1eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence style" +# Source-Hash: c15c1af69ec5c1eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceStyle" msgstr "Assortir au style de clôture" -# Review: required -# Source-Hash: 51b7ab6a0a78d90a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingAngle" msgstr "Angle d’ouverture" -# Review: required -# Source-Hash: d2e20300c07aa722 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening width" +# Source-Hash: d2e20300c07aa722 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingWidth" msgstr "Largeur d'ouverture" -# Review: required -# Source-Hash: 6b108fb1d0eb8f9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open rails" +# Source-Hash: 6b108fb1d0eb8f9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openRails" msgstr "Lisses ajourées" -# Review: required -# Source-Hash: d500c4b7827d3390 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cross" +# Source-Hash: d500c4b7827d3390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.cross" msgstr "Croix" -# Review: required -# Source-Hash: 7420c0d2caee1c22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.diagonal" msgstr "Diagonale" -# Review: required -# Source-Hash: 7308869944020839 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double gate" +# Source-Hash: 7308869944020839 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.doubleGate" msgstr "Portail double" -# Review: required -# Source-Hash: 1f4ad5ad3e1be222 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inward" +# Source-Hash: 1f4ad5ad3e1be222 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.inward" msgstr "Vers l’intérieur" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.left" msgstr "Gauche" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.none" msgstr "Aucun" -# Review: required -# Source-Hash: ccd8f06b8f8d27c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Outward" +# Source-Hash: ccd8f06b8f8d27c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.outward" msgstr "Vers l’extérieur" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.right" msgstr "Droite" -# Review: required -# Source-Hash: d327097d9ac1099f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single gate" +# Source-Hash: d327097d9ac1099f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.singleGate" msgstr "Portail simple" -# Review: required -# Source-Hash: 9295ddc2805b21e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket" +# Source-Hash: 9295ddc2805b21e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.picket" msgstr "Piquets" -# Review: required -# Source-Hash: 8dd5999b235f60ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Post height" +# Source-Hash: 8dd5999b235f60ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.postHeight" msgstr "Hauteur des poteaux" -# Review: required -# Source-Hash: 90bcd7a8123ec343 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove gate" +# Source-Hash: 90bcd7a8123ec343 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeGate" msgstr "Supprimer le portail" -# Review: required -# Source-Hash: 7e3505710613543f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove opening" +# Source-Hash: 7e3505710613543f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeOpening" msgstr "Supprimer l'ouverture" -# Review: required -# Source-Hash: af6f8885fe0fa4e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid privacy" +# Source-Hash: af6f8885fe0fa4e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.solidPrivacy" msgstr "Occultant plein" -# Review: required -# Source-Hash: 1a7048255c3bdfcb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swing direction" +# Source-Hash: 1a7048255c3bdfcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.swingDirection" msgstr "Sens d'ouverture" -# Review: required -# Source-Hash: eba3df14a2de0812 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical slats" +# Source-Hash: eba3df14a2de0812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.verticalSlats" msgstr "Lames verticales" +# English: "A gate hosted by a fence." # Source-Hash: df61821ed00852d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A gate hosted by a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.description" msgstr "Portail porté par une clôture." +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.label" msgstr "Portail" +# English: "An intentional opening in a fence." # Source-Hash: 8f9c927d79796c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An intentional opening in a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.description" msgstr "Ouverture volontaire dans une clôture." +# English: "Open Passage" # Source-Hash: ab67bb47e647d594 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.label" msgstr "Passage" +# English: "Floor opening" # Source-Hash: dc3adf0c2d50dba6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.floorOpening.label" msgstr "Trémie" -# Source-Hash: 2f4fac2b1d39fdb1 +# English: "A concrete strip or pad footing poured under the lowest floor." +# Source-Hash: 0d2952c324c93b87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.description" +msgstr "Une semelle en béton, filante ou isolée, coulée sous le plancher le plus bas." + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.label" +msgstr "Semelle" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.actions" +msgstr "Actions" + +# English: "A grade beam on piles, as the building is." +# Source-Hash: 5cbf600af0f430c1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aGradeBeamOnPilesAs" +msgstr "Une longrine sur pieux, comme le bâtiment." + +# English: "A pier carries the floor." +# Source-Hash: 4de35a53f73e6d74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPierCarriesTheFloor" +msgstr "Un pilier porte le plancher." + +# English: "A pile cap on one pile, as the building is." +# Source-Hash: b07f0dafd314ce61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPileCapOnOnePile" +msgstr "Une semelle sur un seul pieu, comme le bâtiment." + +# English: "A stem wall carries the floor." +# Source-Hash: 9b596c6f41bdd6dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aStemWallCarriesTheFloor" +msgstr "Un mur de soubassement porte le plancher." + +# English: "A thickened slab under the floor slab." +# Source-Hash: edd463abf1ba203f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aThickenedSlabUnderTheFloor" +msgstr "Une surépaisseur sous la dalle du plancher." + +# English: "At least" +# Source-Hash: 7d978d555a1fba4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.atLeast" +msgstr "Au moins" + +# English: ", bottom" +# Source-Hash: a6b79a24d92094bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.bottom" +msgstr ", fond" + +# English: "Clear the depth" +# Source-Hash: f09c3e23c581e6e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.clearTheDepth" +msgstr "Effacer la profondeur" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.delete" +msgstr "Supprimer" + +# English: "Depth under grade" +# Source-Hash: 13c7635ae6ebd98b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGrade" +msgstr "Profondeur sous le sol" + +# English: "Depth under grade (building's)" +# Source-Hash: fb5c116d6c004e8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGradeBuildingS" +msgstr "Profondeur sous le sol (du bâtiment)" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.footing" +msgstr "Semelle" + +# English: "IRC R403.1 asks a footing at least 12 in wide and 6 in thick." +# Source-Hash: 742775b7550e4d10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.ircR4031AsksAFooting" +msgstr "L’IRC R403.1 exige une semelle d’au moins 12 in de large et 6 in d’épaisseur." + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.length" +msgstr "Longueur" + +# English: "Pad" +# Source-Hash: 862a4fa9c40e14b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.pad" +msgstr "Isolée" + +# English: "Strip" +# Source-Hash: 168d7c95aa1872fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.strip" +msgstr "Filante" + +# English: "the footing’s thickness under the floor reaches this far." +# Source-Hash: 2a4c3ad2bb841f1d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theFootingSThicknessUnderThe" +msgstr "l’épaisseur de la semelle sous le plancher descend jusque-là." + +# English: "the grade beam reaches this far." +# Source-Hash: b907c56408d326b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theGradeBeamReachesThisFar" +msgstr "la longrine descend jusque-là." + +# English: "the slab and the footing’s thickness reach this far." +# Source-Hash: 0ec23acc847b60c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theSlabAndTheFootingS" +msgstr "la dalle et l’épaisseur de la semelle descendent jusque-là." + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.thickness" +msgstr "Épaisseur" + +# English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.top" +msgstr "Dessus" + +# English: "Use the building's depth" +# Source-Hash: 78d9e3ca09235067 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.useTheBuildingSDepth" +msgstr "Utiliser la profondeur du bâtiment" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.width" +msgstr "Largeur" + +# English: "The outline the foundation is poured to: drawn, or traced from the walls and stretched." +# Source-Hash: 1e069ed68ad21990 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.description" +msgstr "Le contour selon lequel la fondation est coulée : dessiné, ou tracé d’après les murs puis étiré." + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.label" +msgstr "Fondation" + # English: "A measurement / reference annotation (linear, area, or arc)." +# Source-Hash: 2f4fac2b1d39fdb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.description" msgstr "Annotation de mesure ou de référence (linéaire, surface ou arc)." +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.label" msgstr "Guide" +# English: "Rain-water channel running along the eave of a roof segment." # Source-Hash: 5402d634f40b2f97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rain-water channel running along the eave of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.description" msgstr "Chéneau d’eau pluviale le long de l’égout d’un pan de toit." -# Review: required -# Source-Hash: fc7d4ecf973db135 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Downspout" +# Source-Hash: fc7d4ecf973db135 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.addDownspout" msgstr "Ajouter une descente" -# Review: required -# Source-Hash: 9b9929bac2c39bca -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspout {number}" +# Source-Hash: 9b9929bac2c39bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspoutNumber" msgstr "Descente {number}" -# Review: required -# Source-Hash: fc34d45725838141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspouts" +# Source-Hash: fc34d45725838141 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspouts" msgstr "Descentes d'eau" -# Review: required -# Source-Hash: ac329c74056f0616 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove downspout" +# Source-Hash: ac329c74056f0616 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.removeDownspout" msgstr "Supprimer la descente" +# English: "Gutter" # Source-Hash: 14ba981da3627ae2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.label" msgstr "Gouttière" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.hole.manual" msgstr "Manuel" +# English: "Hole {number} (Editing)" # Source-Hash: fffde6fa82e65ffc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number} (Editing)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeEditing" msgstr "Trémie {number} (en cours de modification)" +# English: "Hole {number}" # Source-Hash: 0fe446c72451e4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeNumber" msgstr "Trémie {number}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: fd26f8df9d087c6b +# English: "{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# Source-Hash: 8eeeb79ff4221939 # Review: required -# English: "{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeSummary" -msgstr "{area} m² · {count, plural, one {{points} pt} many {{points} pts} other {{points} pts}} · {source}" +msgstr "{area} {unit} · {count, plural, one {{points} pt} many {{points} pts} other {{points} pts}} · {source}" +# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." # Source-Hash: 50e9252d30f3fb4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.description" msgstr "Chaudière, centrale de traitement d’air ou condenseur — les gaines se raccordent à ses collets de soufflage et de reprise." +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.label" msgstr "Unité CVC" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/hvac-equipment/tool.tsx #, fuzzy msgid "node.hvacEquipment.tool.rTRotate" msgstr "R/T pivoter" +# English: "Geometry preserved from an imported model when no native Pascal shape exists." # Source-Hash: f24a1089d9256460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry preserved from an imported model when no native Pascal shape exists." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.description" msgstr "Géométrie conservée d’un modèle importé lorsqu’aucune forme Pascal native n’existe." +# English: "Imported Mesh" # Source-Hash: 9f9d11f287f722d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Imported Mesh" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.label" msgstr "Maillage importé" +# English: "Couldn't rebuild with these values." # Source-Hash: 9cce89f94c673821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Couldn't rebuild with these values." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.couldnTRebuildWithTheseValues" msgstr "Impossible de reconstruire avec ces valeurs." +# English: "Parameters" # Source-Hash: 373b929318436fe3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.parameters" msgstr "Paramètres" +# English: "Rebuilding…" # Source-Hash: 558cb886dbe0babe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuilding" msgstr "Reconstruction…" +# English: "Rebuilding in Blender…" # Source-Hash: a094c99dc8ad29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding in Blender…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuildingInBlender" msgstr "Reconstruction dans Blender…" +# English: "Reset {param}" # Source-Hash: 1778f93ee143cd95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset {param}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParam" msgstr "Réinitialiser {param}" +# English: "Reset parameters" # Source-Hash: 5a28cf5f2402dd7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParameters" msgstr "Réinitialiser les paramètres" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetToDefault" msgstr "Rétablir la valeur par défaut" +# English: "A catalog-backed item (furniture, fixtures, decorations)." # Source-Hash: 63f79b3c467177b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A catalog-backed item (furniture, fixtures, decorations)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.description" msgstr "Élément issu du catalogue (mobilier, équipements, décoration)." +# English: "Item" # Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.label" msgstr "Élément" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.actions" msgstr "Actions" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.delete" msgstr "Supprimer" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.dimensions" msgstr "Dimensions" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.duplicate" msgstr "Dupliquer" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.info" msgstr "Info" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.move" msgstr "Déplacer" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.position" msgstr "Position" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rot" msgstr "rot" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rotation" msgstr "Rotation" +# English: "Scale" # Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale" msgstr "Échelle" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale2" msgstr "échelle" +# English: "Uniform Scale" # Source-Hash: b0d264950558d50e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uniform Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.uniformScale" msgstr "Échelle uniforme" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.xyz" msgstr "XYZ" +# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." # Source-Hash: a9e2d7b215696e2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.description" msgstr "Auvent adossé à un pan ou autoportant (un pan, deux pans ou papillon) avec structure et évacuation gérées." +# English: "Canopy" # Source-Hash: 8503bedfbafde9a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.label" msgstr "Auvent" +# English: "A single floor of a building, holding walls / slabs / ceilings / items." # Source-Hash: 45673462f914d3df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single floor of a building, holding walls / slabs / ceilings / items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.description" msgstr "Un étage d’un bâtiment, avec murs, dalles, plafonds et éléments." +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.label" msgstr "Niveau" +# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." # Source-Hash: 156bdee8fee5bec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.description" msgstr "Liaison frigorifique — paire cuivre aspiration + liquide reliant le condenseur à la batterie intérieure." +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.label" msgstr "Liaison frigorifique" +# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." # Source-Hash: 2bd4a85bed158269 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.description" msgstr "Ligne liquide frigorifique autonome — fin tube cuivre nu ; le mode Suivre longe une liaison existante." +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.label" msgstr "Ligne liquide" -# Review: required -# Source-Hash: 601aaf23ce5a9279 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Click to trace this lineset run" +# Source-Hash: 601aaf23ce5a9279 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.clickToTraceThisLinesetRun" msgstr "Cliquez pour suivre ce tronçon de liaison frigorifique" -# Review: required -# Source-Hash: 77df017af9260468 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow: hover a lineset" +# Source-Hash: 77df017af9260468 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.followHoverALineset" msgstr "Suivre : survolez une liaison frigorifique" +# English: "A persistent distance, angle, area, perimeter, or volume annotation." # Source-Hash: ff4e3bb72267859e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A persistent distance, angle, area, perimeter, or volume annotation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.description" msgstr "Annotation persistante de distance, d’angle, de surface, de périmètre ou de volume." +# English: "Measurement" # Source-Hash: ec3567fd058bab7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.label" msgstr "Mesure" +# English: "{kind} {number}" # Source-Hash: d71b38e990539364 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind} {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.numberedName" msgstr "{kind} {number}" +# English: "Currently {value}" # Source-Hash: 09865d8781ef8699 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -24566,1951 +28873,2321 @@ msgstr "{kind} {number}" msgid "node.panel.currently" msgstr "Actuellement {value}" +# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." # Source-Hash: 7764b50334e9f71d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.description" msgstr "Raccord d’évacuation — coude, culotte à 45° ou té sanitaire." +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.label" msgstr "Raccord de tuyau" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis" msgstr "Axe" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis2" msgstr "⌥ axe" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.rTRotate" msgstr "R/T pivoter" +# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." # Source-Hash: a0bb4762aa66fa3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.description" msgstr "Canalisation d’évacuation et de ventilation — tracez à plat ou basculez une pente de ¼″ par pied avec S." +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.label" msgstr "Tuyau d’évacuation" +# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." # Source-Hash: 6d0cdd14d0675e05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.description" msgstr "Siphon en P — garde d’eau sur l’évacuation. Le bras du siphon rejoint la ventilation." +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.label" msgstr "Siphon" -# Review: required -# Source-Hash: 73078bc0f5d686fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" Trap" +# Source-Hash: 73078bc0f5d686fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.diameterTrap" msgstr "Siphon {diameter}\"" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.rTRotate" msgstr "R/T pivoter" +# English: "Experimental editable recipe" # Source-Hash: 0efe82436b915f6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Experimental editable recipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.description" msgstr "Recette modifiable expérimentale" +# English: "Procedural item" # Source-Hash: c8bbcc888cce7b8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Procedural item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.label" msgstr "Élément procédural" +# English: "Ventilation strip running along the ridge of a roof segment." # Source-Hash: 356aa0ed69cb8ec5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ventilation strip running along the ridge of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.description" msgstr "Bande de ventilation le long du faîtage d’un pan de toit." +# English: "Ridge Vent" # Source-Hash: 54b74080f33c8c4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.label" msgstr "Ventilation de faîtage" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: 18bf817325c1c537 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Caps" +# Source-Hash: 18bf817325c1c537 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.endCaps" msgstr "Embouts d'extrémité" -# Review: required -# Source-Hash: 466f817a0102d4a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flanged" +# Source-Hash: 466f817a0102d4a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.flanged" msgstr "À bride" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.length" msgstr "Longueur" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.open" msgstr "Ouvrir" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 54b74080f33c8c4e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ridge Vent" +# Source-Hash: 54b74080f33c8c4e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.ridgeVent" msgstr "Ventilation de faîtage" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: e9046e9823b400a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shingled" +# Source-Hash: e9046e9823b400a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.shingled" msgstr "En bardeaux" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.standard" msgstr "Standard" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.style" msgstr "Style" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.width" msgstr "Largeur" +# English: "Arch bays" # Source-Hash: c6d3976b4eb80586 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bays" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.archBays" msgstr "Travées en arc" +# English: "Barrel arch" # Source-Hash: 1cfdd5affedf153c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.barrelArch" msgstr "Voûte en berceau" +# English: "Flat panes follow a circular arch. Rise is measured from the eaves." # Source-Hash: b3e8929750d18f7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat panes follow a circular arch. Rise is measured from the eaves." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.flatPanesFollowACircularArch" msgstr "Les vitrages plats suivent un arc circulaire. La flèche se mesure depuis l’égout." +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.rise" msgstr "Hauteur" +# English: "A pitched / hip / gable roof composed of one or more segments." # Source-Hash: 6afeec8de7ed618d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A pitched / hip / gable roof composed of one or more segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.description" msgstr "Toit en pente, en croupe ou à deux pans composé d’un ou plusieurs pans." +# English: "Arch bay {number}" # Source-Hash: b5be04471fd3fb7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bay {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.archBay" msgstr "Travée en arc {number}" +# English: "Back slope" # Source-Hash: 93381b9024a75a99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.back" msgstr "Versant arrière" +# English: "Left end" # Source-Hash: 08b00107b635e11d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endLeft" msgstr "Extrémité gauche" +# English: "Right end" # Source-Hash: 821734ea5d17a693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endRight" msgstr "Extrémité droite" +# English: "Front slope" # Source-Hash: 40c9ef62055067e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.front" msgstr "Versant avant" +# English: "Shed slope" # Source-Hash: 1a88da71d5e5f0d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.slope" msgstr "Versant du monopente" +# English: "Alignment" # Source-Hash: 3bcc542295b4c61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.alignment" msgstr "Alignement" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.center" msgstr "Centre" +# English: "Choose a layout to edit the grid." # Source-Hash: 512e47607a37af46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a layout to edit the grid." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.chooseALayoutToEditThe" msgstr "Choisissez une disposition pour modifier la trame." +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.empty" msgstr "Vide" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.end" msgstr "Fin" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.fixedSpacing" msgstr "Espacement fixe" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.glass" msgstr "Vitrage" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.layout" msgstr "Disposition" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maximumSpacing" msgstr "Espacement maximal" +# English: "Max spacing" # Source-Hash: d10743fce478d9f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Max spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maxSpacing" msgstr "Espacement max." +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.mixed" msgstr "Mixte" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panelCount" msgstr "Nombre de panneaux" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panels" msgstr "Panneaux" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.solid" msgstr "Plein" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.spacing" msgstr "Espacement" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.start" msgstr "Début" +# English: "{title} alignment" # Source-Hash: 095237e9e2df4d9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{title} alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.titleAlignment" msgstr "Alignement {title}" +# English: "Changes apply to all {length} roof segments." # Source-Hash: c98a4e6b28f05e0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Changes apply to all {length} roof segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.changesApplyToAllLengthRoof" msgstr "Les modifications s’appliquent aux {length} segments de toit." +# English: "Choose one roof system to edit settings for all selected segments." # Source-Hash: c5d2ff7fda4787cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose one roof system to edit settings for all selected segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.chooseOneRoofSystemToEdit" msgstr "Choisissez un système de toiture pour modifier les réglages de tous les segments sélectionnés." +# English: "Close the roof ends" # Source-Hash: 80e07a119b63187c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close the roof ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.closeTheRoofEnds" msgstr "Fermer les extrémités du toit" +# English: "Default panel infill" # Source-Hash: e28dd27d54aaa7ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.defaultPanelInfill" msgstr "Remplissage par défaut des panneaux" +# English: "Divides each roof face into columns." # Source-Hash: 134d33e2465937e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides each roof face into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesEachRoofFaceIntoColumns" msgstr "Divise chaque versant en colonnes." +# English: "Divides the length of each arch bay into columns." # Source-Hash: 896a6e4da3357c39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides the length of each arch bay into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesTheLengthOfEachArch" msgstr "Divise la longueur de chaque travée en arc en colonnes." +# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." # Source-Hash: be9c810d28d1feb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.endInfillFillsTheSpaceAbove" msgstr "Le remplissage d’about comble l’espace au-dessus des murs. Modifiez séparément les murs et plafonds des pièces." +# English: "Frame color" # Source-Hash: 0b8b27dbc57cca44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameColor" msgstr "Couleur du cadre" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameDepth" msgstr "Profondeur du cadre" +# English: "Frame width" # Source-Hash: 3519d91b7bda18a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameWidth" msgstr "Largeur du cadre" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.framing" msgstr "Ossature" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glass" msgstr "Vitrage" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glassTint" msgstr "Teinte du vitrage" +# English: "Glazed" # Source-Hash: dd330a94b3f7a65a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazed" msgstr "Vitré" +# English: "Glazing is available for gable, straight shed, and barrel roofs." # Source-Hash: e6be52df9197243a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing is available for gable, straight shed, and barrel roofs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazingIsAvailableForGableStraight" msgstr "Le vitrage est disponible pour les toits à pignon, monopentes droits et voûtés." +# English: "Grid across the roof" # Source-Hash: a4e7ef2e31d7220b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid across the roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAcrossTheRoof" msgstr "Trame en travers du toit" +# English: "Grid along the length" # Source-Hash: 2f28a3b33fccf56b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheLength" msgstr "Trame dans la longueur" +# English: "Grid along the slope" # Source-Hash: 1f701893dbf9d72b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheSlope" msgstr "Trame dans la pente" +# English: "Grid within each arch bay" # Source-Hash: 20c8dfbadda20e3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid within each arch bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridWithinEachArchBay" msgstr "Trame dans chaque travée en arc" +# English: "Infill" # Source-Hash: 7c339a5fae55681d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.infill" msgstr "Remplissage" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.opacity" msgstr "Opacité" +# English: "Pane thickness" # Source-Hash: 4d412746658f2faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.paneThickness" msgstr "Épaisseur du carreau" +# English: "Roof system" # Source-Hash: 570136e723cb7d99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roofSystem" msgstr "Système de toiture" +# English: "Roughness" # Source-Hash: a6a7a36d982b4911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roughness" msgstr "Rugosité" +# English: "Spacing is measured on the slope, not its horizontal projection." # Source-Hash: 2fa6fbeda5d45949 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing is measured on the slope, not its horizontal projection." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.spacingIsMeasuredOnTheSlope" msgstr "L’espacement se mesure sur la pente, pas sur sa projection horizontale." +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.standard" msgstr "Standard" +# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." # Source-Hash: efbe1c4facf561f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.subdividesEachFlatArchBayArch" msgstr "Subdivise chaque travée plane en arc. Le nombre de travées règle les divisions le long de la courbe." +# English: "Choose a panel to override its default infill." # Source-Hash: 1497eac9e4f63016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a panel to override its default infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.chooseAPanelToOverrideIts" msgstr "Choisissez un panneau pour remplacer son remplissage par défaut." +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.column2" msgstr "Poteau" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.custom" msgstr "Personnalisé" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.default" msgstr "Par défaut" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" # Source-Hash: 9ef92ad8ae49ed72 # Review: required -# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.grid" msgstr "{columns, plural, one {{columnCount} colonne} many {{columnCount} colonnes} other {{columnCount} colonnes}} · {rows, plural, one {{rowCount} rangée} many {{rowCount} rangées} other {{rowCount} rangées}}" +# English: "Individual panels" # Source-Hash: 11624abbcfdd5506 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.individualPanels" msgstr "Panneaux individuels" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.panelInfill" msgstr "Remplissage du panneau" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.resetToDefault" msgstr "Rétablir la valeur par défaut" +# English: "Roof face" # Source-Hash: 6fba44f2bfd8af8e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.roofFace" msgstr "Versant" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.row2" msgstr "Rangée" +# English: "Selected panel infill" # Source-Hash: 6c2cc2d9cd778998 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.selectedPanelInfill" msgstr "Remplissage du panneau sélectionné" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.label" msgstr "Toit" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.actions" msgstr "Actions" +# English: "Add Chimney" # Source-Hash: d60471ce3cd45348 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addChimney" msgstr "Ajouter une cheminée" +# English: "Add Cupola" # Source-Hash: 1ca6a1cf9cc6e3ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addCupola" msgstr "Ajouter une coupole" +# English: "Add Dormer" # Source-Hash: 94dea147eeaac52b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addDormer" msgstr "Ajouter une lucarne" +# English: "Add Eyebrow Vent" # Source-Hash: 5679acdd64484726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addEyebrowVent" msgstr "Ajouter une chatière en œil-de-bœuf" +# English: "Add Gutter" # Source-Hash: 25a141dc0d7c4b44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addGutter" msgstr "Ajouter une gouttière" +# English: "Add Skylight" # Source-Hash: 7230bdc8a2252626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSkylight" msgstr "Ajouter un puits de lumière" +# English: "Add Solar Panel" # Source-Hash: 0f220e2602b7ff24 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSolarPanel" msgstr "Ajouter un panneau solaire" +# English: "Add Vent" # Source-Hash: 4d83a2e5a4d9e6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addVent" msgstr "Ajouter une ventilation" +# English: "Box" # Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.box" msgstr "Boîte" +# English: "box vent" # Source-Hash: 3bf078fe2560daca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "box vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.boxVent" msgstr "ventilation caisson" +# English: "chimney" # Source-Hash: 4986740251121471 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.chimney" msgstr "cheminée" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.custom" msgstr "Personnalisé" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.delete" msgstr "Supprimer" +# English: "dormer" # Source-Hash: 5169968a4b0c1840 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.dormer" msgstr "lucarne" +# English: "Draw Segment" # Source-Hash: 72e51c6e5e9c6c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.drawSegment" msgstr "Dessiner un segment" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.duplicate" msgstr "Dupliquer" -# Source-Hash: 7c4196b9e791c195 +# English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaveOverhang" +msgstr "Débord d’égout" + +# English: "Eaves" +# Source-Hash: 34756c5ea718ccb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaves" +msgstr "Avant-toits" + # English: "Elements" +# Source-Hash: 7c4196b9e791c195 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.elements" msgstr "Éléments" +# English: "Follows walls" # Source-Hash: 6b8ba49aa355fe87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.followsWalls" msgstr "Suit les murs" -# Source-Hash: 23b434461c990711 +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.gableOverhang" +msgstr "Débord de rive" + # English: "gutter" +# Source-Hash: 23b434461c990711 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.gutter" msgstr "gouttière" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.level" msgstr "Niveau" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.move" msgstr "Déplacer" +# English: "Move to {level}" # Source-Hash: 39661f4827b0f435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToLevel" msgstr "Déplacer vers {level}" +# English: "Move to the roof level" # Source-Hash: c534093ff560a9f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to the roof level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToTheRoofLevel" msgstr "Déplacer vers le niveau de toiture" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" # Source-Hash: 0939fad1cb1831f0 # Review: required -# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.onStoreyWalls" msgstr "Sur {storey} avec ses murs : il masque donc le plan de cet étage dans les vues isolée et éclatée.{roofs, plural, =0 {} =1 {} one {} many { Les {roofCount} toits de l’étage se déplacent ensemble.} other { Les {roofCount} toits de l’étage se déplacent ensemble.}}" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.position" msgstr "Position" +# English: "Ridge" # Source-Hash: e6ddc8a624465793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridge" msgstr "Faîtage" +# English: "ridge vent" # Source-Hash: 83112cbc205e9972 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridgeVent" msgstr "faîtière ventilée" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.roof" msgstr "Toit" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.rotation" msgstr "Rotation" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segments" msgstr "Segments" +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" # Source-Hash: 0c084c517dbc153b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segmentType" msgstr "{roofType, select, hip {Croupe} gable {Pignon} shed {Monopente} gambrel {Gambrel} dutch {Demi-croupe} mansard {Mansarde} flat {Plat} conical {Conique} barrel {Voûte} other {Toit}}" +# English: "skylight" # Source-Hash: 0c4c82caccb4775c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.skylight" msgstr "puits de lumière" +# English: "solar panel" # Source-Hash: b9f931508dd6ec70 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "solar panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.solarPanel" msgstr "panneau solaire" +# English: "Turbine" # Source-Hash: d2428b22f1757a4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbine" msgstr "Turbine" +# English: "turbine vent" # Source-Hash: 86d02a3d3a3a7fa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "turbine vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbineVent" msgstr "aérateur à turbine" -# Source-Hash: 99afd827dbe5d647 +# English: "Apply to all planes of this roof" +# Source-Hash: 6df330bcfcaac604 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.applyToAllPlanesOfThis" +msgstr "Appliquer à tous les pans de ce toit" + +# English: "Architectural shingle" +# Source-Hash: 7254a3eb47471b8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.architecturalShingle" +msgstr "Bardeau architectural" + +# English: "Asphalt 3-tab" +# Source-Hash: afd23a6d653689c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.asphalt3Tab" +msgstr "Bardeau d’asphalte 3 pattes" + +# English: "Clay tile" +# Source-Hash: 2650e7c436297ef2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.clayTile" +msgstr "Tuile terre cuite" + +# English: "Concrete tile" +# Source-Hash: 92af091c56892f25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.concreteTile" +msgstr "Tuile béton" + +# English: "Covering" +# Source-Hash: 75168c15633cbad3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.covering" +msgstr "Revêtement" + +# English: "Deck" +# Source-Hash: f3aa561772702108 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.deck" +msgstr "Support" + +# English: "Edit layers" +# Source-Hash: 14187d972ce9e1de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.editLayers" +msgstr "Modifier les couches" + +# English: "Finish name" +# Source-Hash: 5aff5f630e4b18c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.finishName" +msgstr "Nom de la finition" + +# English: "Ice and water shield" +# Source-Hash: b9ed3f21665672eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.iceAndWaterShield" +msgstr "Membrane autoadhésive" + +# English: "Insulation name" +# Source-Hash: 6d79431c0092abac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.insulationName" +msgstr "Nom de l’isolant" + +# English: "Interior finish" +# Source-Hash: e4e9305964238fda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.interiorFinish" +msgstr "Finition intérieure" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.layers" +msgstr "Couches" + +# English: "#15 felt" +# Source-Hash: 8f343a87f121347e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n15Felt" +msgstr "Feutre n° 15" + +# English: "#30 felt" +# Source-Hash: faa54521399dc5b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n30Felt" +msgstr "Feutre n° 30" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.none" +msgstr "Aucune" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.preset" +msgstr "Préréglage" + +# English: "Rafters" +# Source-Hash: a9cb232546779779 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.rafters" +msgstr "Chevrons" + +# English: "Roofing" +# Source-Hash: 5c0d95afa820e706 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofing" +msgstr "Couverture" + +# English: "Roofing name" +# Source-Hash: 75a1bff32810894e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofingName" +msgstr "Nom de la couverture" + +# English: "Sheathing" +# Source-Hash: 36a588e44085108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathing" +msgstr "Voligeage" + +# English: "Sheathing name" +# Source-Hash: abdad84e524e56e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathingName" +msgstr "Nom du voligeage" + +# English: "Slate" +# Source-Hash: a7b5921026935493 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.slate" +msgstr "Ardoise" + +# English: "Standing-seam metal" +# Source-Hash: c156b8bd7920b129 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.standingSeamMetal" +msgstr "Métal à joint debout" + +# English: "Synthetic" +# Source-Hash: f5defb7d8a17154e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.synthetic" +msgstr "Synthétique" + +# English: "Top chord" +# Source-Hash: 472f395a8b6319a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.topChord" +msgstr "Arbalétrier" + +# English: "Trusses" +# Source-Hash: 4dd558e7eeec7acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.trusses" +msgstr "Fermettes" + +# English: "Underlayment" +# Source-Hash: 3909955f8a920be1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlayment" +msgstr "Sous-couche" + +# English: "Underlayment name" +# Source-Hash: 0167cfd2ec25cf5d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlaymentName" +msgstr "Nom de la sous-couche" + +# English: "Wood shake" +# Source-Hash: 7095fa6e1c927c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.woodShake" +msgstr "Bardeau de fente" + # English: "A single pitched plane of a parent roof." +# Source-Hash: 99afd827dbe5d647 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.description" msgstr "Un pan incliné d’un toit parent." +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.label" msgstr "Pan de toit" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.actions" msgstr "Actions" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.angle" msgstr "Angle" +# English: "Arc" # Source-Hash: eb8f344a3c137280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.arc" msgstr "Arc" +# English: "Auto gutters" # Source-Hash: 1eb45f83eeaabfad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto gutters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoGutters" msgstr "Gouttières auto" +# English: "Auto ridge vent" # Source-Hash: 0986c4933d2b5808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoRidgeVent" msgstr "Faîtière ventilée auto" +# English: "Barrel" # Source-Hash: 344fe9d407a233a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.barrel" msgstr "Voûte" +# English: "Clipped version" # Source-Hash: 6ca418a2ecab5984 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clipped version" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.clippedVersion" msgstr "Version tronquée" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conical" msgstr "Conique" +# English: "Conical Shape" # Source-Hash: a37cfbc4db93cfd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conicalShape" msgstr "Forme conique" +# English: "Deck Thick." # Source-Hash: a10f29fcde60eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.deckThick" msgstr "Ép. terrasse" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.delete" msgstr "Supprimer" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.depth" msgstr "Profondeur" +# English: "Diameter" # Source-Hash: 016d8e70618916d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.diameter" msgstr "Diamètre" +# English: "Done editing" # Source-Hash: e132bf88b9e4d75e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.doneEditing" msgstr "Terminer la modification" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.drainage" msgstr "Évacuation" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.duplicate" msgstr "Dupliquer" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.dutch" msgstr "Demi-croupe" -# Source-Hash: 5c99a67253bfc02a +# English: "Eave cut" +# Source-Hash: 4c539865731d5d66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.eaveCut" +msgstr "Coupe d’about" + # English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.eaveOverhang" msgstr "Débord d’égout" +# English: "Edit footprint" # Source-Hash: dc90862aafc9d287 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.editFootprint" msgstr "Modifier l’emprise" +# English: "End overhang" # Source-Hash: 9532d5e73585ea62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.endOverhang" msgstr "Débord d’about" +# English: "Fascia" # Source-Hash: 74f4143cd02de4a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fascia" msgstr "Bandeau de rive" +# English: "Fascia height" # Source-Hash: 45baed9dc8b8047a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fasciaHeight" msgstr "Hauteur du bandeau" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.flat" msgstr "Plat" +# English: "Footprint" # Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.footprint" msgstr "Emprise" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gable" msgstr "Deux pans" +# English: "Gable overhang" # Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gableOverhang" msgstr "Débord de rive" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gambrel" msgstr "Brisé" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.hip" msgstr "Croupe" +# English: "Kink Depth" # Source-Hash: 7ae4179d0abd80f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkDepth" msgstr "Profondeur du coude" +# English: "Kink Height" # Source-Hash: e19b2bca70030855 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkHeight" msgstr "Hauteur du coude" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.length" msgstr "Longueur" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.mansard" msgstr "Mansarde" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.move" msgstr "Déplacer" +# English: "Pitch" # Source-Hash: 75e3449fc1776d09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.pitch" msgstr "Pente" -# Source-Hash: 9da0cbfad144d737 +# English: "Plumb" +# Source-Hash: 6ae4075a23bb4ec4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumb" +msgstr "Aplomb" + +# English: "Plumb + level" +# Source-Hash: 16d8ab5937d74bdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumbLevel" +msgstr "Aplomb + niveau" + # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.position" msgstr "Position" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.reset" msgstr "Réinitialiser" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofSegment" msgstr "Pan de toit" +# English: "Roof Type" # Source-Hash: 19ab9527ae7889f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofType" msgstr "Type de toit" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.rotation" msgstr "Rotation" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shape" msgstr "Forme" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shed" msgstr "Appentis" +# English: "Shingle Thick." # Source-Hash: 4d944a2393bbddb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shingleThick" msgstr "Ép. bardeau" +# English: "Span" # Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.span" msgstr "Portée" -# Source-Hash: e90aceedf4a6b183 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.square" +msgstr "Équerre" + # English: "Start Angle" +# Source-Hash: e90aceedf4a6b183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.startAngle" msgstr "Angle de départ" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.structure" msgstr "Structure" +# English: "Top Rake Length" # Source-Hash: a2db62c74fe2bcee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeLength" msgstr "Longueur de rive haute" +# English: "Top Rake Thick." # Source-Hash: 913d9552030ac86a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeThick" msgstr "Ép. rive haute" +# English: "Trim" # Source-Hash: 3beb5eb35f0c5c17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.trim" msgstr "Moulures" +# English: "Waist Height" # Source-Hash: e6c5009c0d31fca1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistHeight" msgstr "Hauteur de taille" +# English: "Waist Length" # Source-Hash: 4a8caffd24b6ee02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistLength" msgstr "Longueur de taille" +# English: "Waist Width" # Source-Hash: 71045af5c81a11e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistWidth" msgstr "Largeur de taille" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wall" msgstr "Mur" +# English: "Wall Height" # Source-Hash: e0f973edce3e4ca8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallHeight" msgstr "Hauteur du mur" +# English: "Wall Thick." # Source-Hash: d0db6dee5fe2edbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallThick" msgstr "Ép. mur" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.width" msgstr "Largeur" +# English: "A captured session with optional mesh, motion, media, and sensor data." # Source-Hash: 6600a850000b98ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A captured session with optional mesh, motion, media, and sensor data." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.description" msgstr "Session capturée avec maillage, mouvement, médias et données de capteurs facultatifs." +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.label" msgstr "Capture" +# English: "Segment {number}" # Source-Hash: 95b5307eefb187c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -26518,7007 +31195,7752 @@ msgstr "Capture" msgid "node.segmentNumber" msgstr "Segment {number}" +# English: "Separator" # Source-Hash: acb8cb92b3674b50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.separator.label" msgstr "Séparateur" -# Source-Hash: e7512c891d8da7d8 +# English: "Batt" +# Source-Hash: e34eb5b5bfbfce7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.batt" +msgstr "Matelas" + +# English: "Blown" +# Source-Hash: 430cef5fa9bd2787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.blown" +msgstr "Soufflé" + +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywall" +msgstr "Plaque de plâtre" + +# English: "Drywall thickness" +# Source-Hash: 4e8f119349fa2dd4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywallThickness" +msgstr "Épaisseur de plaque de plâtre" + +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulation" +msgstr "Isolant" + +# English: "Insulation thickness" +# Source-Hash: 4251fc1e9051a7db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulationThickness" +msgstr "Épaisseur d’isolant" + +# English: "{label} thickness" +# Source-Hash: f567133879529366 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.labelThickness" +msgstr "Épaisseur ({label})" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.none" +msgstr "Aucun" + +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.osb" +msgstr "OSB" + +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plaster" +msgstr "Plâtre" + +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plywood" +msgstr "Contreplaqué" + +# English: "Rigid board" +# Source-Hash: 3d153fa2037159f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.rigidBoard" +msgstr "Panneau rigide" + +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacing" +msgstr "Espacement" + +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. A framing spacing on centre (centre of one member to the next), e.g. 16" o.c. +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacingOC" +msgstr "{spacing} d’entraxe" + +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.woodPlank" +msgstr "Lambris" + # English: "Turn light off" +# Source-Hash: e7512c891d8da7d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOff" msgstr "Éteindre la lumière" +# English: "Turn light on" # Source-Hash: d47ec95e14909fe5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turn light on" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOn" msgstr "Allumer la lumière" +# English: "Casing" +# Source-Hash: 18bf48fbbe121ef0 # Review: required -# Source-Hash: ee1c8bc3e27fb550 -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.casing" +msgstr "Chambranle" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.width" +msgstr "Largeur" + # English: "Automatic support" +# Source-Hash: ee1c8bc3e27fb550 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.automaticSupport" msgstr "Support automatique" -# Review: required -# Source-Hash: 1353e40320c72a99 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} · segment {segment}" +# Source-Hash: 1353e40320c72a99 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.hangerOnSegment" msgstr "Suspente {number} · segment {segment}" -# Review: required -# Source-Hash: 737ced90d0454197 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Individual hangers" +# Source-Hash: 737ced90d0454197 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.individualHangers" msgstr "Suspentes individuelles" -# Review: required -# Source-Hash: e6b17b09ed91c8c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Missing support" +# Source-Hash: e6b17b09ed91c8c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.missingSupport" msgstr "Support manquant" -# Review: required -# Source-Hash: c895c3c603382ec9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No support within reach. Move the hanger or choose another host." +# Source-Hash: c895c3c603382ec9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.noSupportWithinReachMoveThe" msgstr "Aucun support à portée. Déplacez la suspente ou choisissez un autre support." -# Review: required -# Source-Hash: 6ce526cb7444be36 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} position in metres" +# Source-Hash: 6ce526cb7444be36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionLabel" msgstr "Position de la suspente {number} en mètres" -# Review: required -# Source-Hash: cbd36c4b82a55539 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position (m)" +# Source-Hash: cbd36c4b82a55539 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionM" msgstr "Position (m)" -# Review: required -# Source-Hash: b629c4f8388fd36f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Positions are measured along each path segment. Changing spacing regenerates the numbered slots." +# Source-Hash: b629c4f8388fd36f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath" msgstr "Les positions se mesurent le long de chaque segment du tracé. Modifier l'espacement régénère les emplacements numérotés." -# Review: required -# Source-Hash: c21cfbdb64e53388 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal support" +# Source-Hash: c21cfbdb64e53388 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.revealSupport" msgstr "Afficher le support" -# Review: required -# Source-Hash: 9d5e4440db18e477 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skip" +# Source-Hash: 9d5e4440db18e477 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.skip" msgstr "Ignorer" -# Review: required -# Source-Hash: 1b184f78bf989931 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} support" +# Source-Hash: 1b184f78bf989931 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.supportLabel" msgstr "Support de la suspente {number}" +# English: "About these checks" # Source-Hash: b2d703d283baf64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "About these checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.aboutTheseChecks" msgstr "À propos de ces vérifications" +# English: "All findings" # Source-Hash: 461cc26706956993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allFindings" msgstr "Tous les constats" +# English: "All systems" # Source-Hash: 7beeb4c4997f5edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All systems" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allSystems" msgstr "Tous les réseaux" +# English: "Connections" # Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.connections" msgstr "Raccordements" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.drainage" msgstr "Évacuation" +# English: "Error" # Source-Hash: fc4ac19649f0ee8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Error" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.error" msgstr "Erreur" +# English: "Errors" # Source-Hash: b92720ff88a1bcfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Errors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.errors" msgstr "Erreurs" +# English: "Filter system checks" # Source-Hash: d0e23bdbe1b380ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Filter system checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.filterSystemChecks" msgstr "Filtrer les vérifications" +# English: "Hangers" # Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.hangers" msgstr "Supports" +# English: "Intersections" # Source-Hash: 8cd416ca57d969f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Intersections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.intersections" msgstr "Croisements" +# English: "Locate {name} ({id})" # Source-Hash: 18d652275a9a525c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Locate {name} ({id})" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.locate" msgstr "Localiser {name} ({id})" +# English: "Missing item" # Source-Hash: 88546aa3a5987966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Missing item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.missingItem" msgstr "Élément manquant" +# English: "No findings" # Source-Hash: 2452d2922a7ecf4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindings" msgstr "Aucun constat" +# English: "No findings in this category" # Source-Hash: 995442fbfc786768 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings in this category" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindingsInThisCategory" msgstr "Aucun constat dans cette catégorie" +# English: "Open ends" # Source-Hash: b5c58159b3eef89c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEnds" msgstr "Extrémités ouvertes" +# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." # Source-Hash: 3003dacc858d36ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay" msgstr "Les extrémités ouvertes et les branches séparées peuvent être voulues. Les croisements sont testés par boîtes englobantes ; vérifiez ouvertures et dégagements." +# English: "Selected connected system" # Source-Hash: c40c64562b6a1a91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected connected system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedConnectedSystem" msgstr "Réseau connecté sélectionné" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" # Source-Hash: 68355a98439229e2 # Review: required -# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedRuns" msgstr "Sélection : {count, plural, one {{number} tronçon} many {{number} tronçons} other {{number} tronçons}} · {length} m" +# English: "Separate branches" # Source-Hash: e7a352627f8fcd45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate branches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.separateBranches" msgstr "Branches séparées" +# English: "Show all findings" # Source-Hash: 910a5ee73f2ef8a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show all findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.showAllFindings" msgstr "Afficher tous les constats" +# English: "System checks" # Source-Hash: dda8887833d07390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemChecks" msgstr "Vérifications du réseau" +# English: "System finding" # Source-Hash: 4dad0ed68e63d124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System finding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFinding" msgstr "Constat du réseau" +# English: "System findings" # Source-Hash: e37307a519466b38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFindings" msgstr "Constats du réseau" +# English: "Warning" # Source-Hash: 5128de8b5a618339 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warning" msgstr "Avertissement" +# English: "Warnings" # Source-Hash: 7e1647f9ce51509e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warnings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warnings" msgstr "Avertissements" +# English: "A configurable shelving unit. Items host on each row." # Source-Hash: 26aff27b7284fc84 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable shelving unit. Items host on each row." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.description" msgstr "Étagère configurable. Les éléments se posent sur chaque rangée." +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.label" msgstr "Étagère" +# English: "The top-level container holding buildings, zones, and the property boundary." # Source-Hash: c52492c73312a922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The top-level container holding buildings, zones, and the property boundary." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.description" msgstr "Conteneur de premier niveau avec les bâtiments, les zones et la limite de propriété." +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.label" msgstr "Terrain" +# English: "Address" # Source-Hash: 3b5a53b6d058a02a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.address" msgstr "Adresse" +# English: "APN" # Source-Hash: ea03c49930137a0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "APN" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.apn" msgstr "N° de parcelle" +# English: "Auto — most north-facing edge" # Source-Hash: 8519c9ddf5e6b371 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto — most north-facing edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.autoMostNorthFacingEdge" msgstr "Auto — bord le plus au nord" +# English: "Contour interval (site plan and 3D)" # Source-Hash: bc493d7655b3e3ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Contour interval (site plan and 3D)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.contourIntervalSitePlanAnd3d" msgstr "Équidistance des courbes (plan de masse et 3D)" +# English: "County" # Source-Hash: 99208d226da1315f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "County" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.county" msgstr "Comté" +# English: "e.g. RS-60" # Source-Hash: 0830cf0f95ad429d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. RS-60" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGRs60" msgstr "p. ex. RS-60" +# English: "e.g. Tampa LDC §27-156" # Source-Hash: b3e7e118266b5074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. Tampa LDC §27-156" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGTampaLdc27156" msgstr "p. ex. Tampa LDC §27-156" +# English: "every 12 in" # Source-Hash: 187e11889d08a2dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 12 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every12In" msgstr "tous les 12 in" +# English: "every 24 in" # Source-Hash: 17ee041f655085f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 24 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every24In" msgstr "tous les 24 in" +# English: "every 6 in" # Source-Hash: 40d7f9ddf5efcb7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 6 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every6In" msgstr "tous les 6 in" +# English: "Finding…" # Source-Hash: 442a3ce78ae6b462 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.finding" msgstr "Recherche…" +# English: "Find parcel" # Source-Hash: 734c1eac014adc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.findParcel" msgstr "Trouver la parcelle" +# English: "Front (street) edge" # Source-Hash: ccb70faaf91dbcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front (street) edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.frontStreetEdge" msgstr "Bord avant (rue)" +# English: "Looking up the parcel and the streets around it…" # Source-Hash: 4232218226616327 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Looking up the parcel and the streets around it…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lookingUpTheParcel" msgstr "Recherche de la parcelle et des rues alentour…" +# English: "Lot area" # Source-Hash: c39eb5933468a637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lot area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lotArea" msgstr "Surface du terrain" +# English: "1200 W Cass St, Tampa, FL" # Source-Hash: c8a874b339ff9696 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "1200 W Cass St, Tampa, FL" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.n1200WCassStTampaFl" msgstr "1200 W Cass St, Tampa, FL" +# English: "none" # Source-Hash: 30a0924a3360be68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "none" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.none" msgstr "aucun" +# English: "North rotation" # Source-Hash: dd4e835cb90c5736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "North rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.northRotation" msgstr "Rotation du nord" +# English: "No surveyed lines — contours are drawn from the sculpted ground." # Source-Hash: a4bfb2891c820e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No surveyed lines — contours are drawn from the sculpted ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.noSurveyedLinesContoursAreDrawn" msgstr "Aucune ligne relevée — les courbes sont tracées depuis le terrain modelé." +# English: "Orientation" # Source-Hash: 1f0d024c0adbf0e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orientation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.orientation" msgstr "Orientation" +# English: "Parcel" # Source-Hash: 9a3961409e34bf66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcel" msgstr "Parcelle" +# English: "Parcel lookup failed." # Source-Hash: 3aecc563cb9a2c68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel lookup failed." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcelLookupFailed" msgstr "La recherche de parcelle a échoué." +# English: "Project look" # Source-Hash: 36696c4a8bede421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.projectLook" msgstr "Apparence du projet" +# English: "Save current look" # Source-Hash: 5b50fc78ccdaf7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save current look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveCurrentLook" msgstr "Enregistrer l’apparence actuelle" +# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." # Source-Hash: f06b316f421963d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveTheCurrentLightingAndDisplay" msgstr "Enregistrer l’éclairage et l’affichage actuels comme valeurs par défaut du projet. Les réglages d’affichage personnels restent dans ce navigateur." +# English: "Setbacks" # Source-Hash: e87df48b61eda8ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Setbacks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.setbacks" msgstr "Reculs" +# English: "Show ground" # Source-Hash: 9ede31abea7ad545 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showGround" msgstr "Afficher le sol" +# English: "Show the contour lines in 3D" # Source-Hash: 1e16a47e5e10c83e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show the contour lines in 3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showTheContourLinesIn3d" msgstr "Afficher les courbes de niveau en 3D" +# English: "side" # Source-Hash: 464241679edcbdb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.side" msgstr "côté" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.site" msgstr "Terrain" +# English: "Source" # Source-Hash: 6165ee130f0082e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.source" msgstr "Source" +# English: "Source / citation" # Source-Hash: 5f3110d075c6a1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source / citation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.sourceCitation" msgstr "Source / référence" +# English: "Street address" # Source-Hash: 041cfc610fef830b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Street address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.streetAddress" msgstr "Adresse postale" +# English: "survey" # Source-Hash: bdca51c5e6437075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "survey" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.survey" msgstr "relevé" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" # Source-Hash: 2177f21713b01a24 # Review: required -# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.surveyedContours" msgstr "{count, plural, one {{lineCount} ligne relevée} many {{lineCount} lignes relevées} other {{lineCount} lignes relevées}} tous les {interval} ft ({datum}) — {source}" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.terrain" msgstr "Terrain" +# English: "Type an address first." # Source-Hash: 4c8601a0b7aeed3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type an address first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.typeAnAddressFirst" msgstr "Saisissez d’abord une adresse." +# English: "Use project look" # Source-Hash: dfe4cd35b3e0d5f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.useProjectLook" msgstr "Utiliser l’apparence du projet" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.zone" msgstr "Zone" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.front" msgstr "Avant" +# English: "Left (override)" # Source-Hash: ab1354f1aa520a54 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.leftOverride" msgstr "Gauche (remplacement)" +# English: "Rear" # Source-Hash: 1a7e8e8f8e9232d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rear" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rear" msgstr "Arrière" +# English: "Right (override)" # Source-Hash: 8370146b38c30102 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rightOverride" msgstr "Droite (remplacement)" +# English: "Side" # Source-Hash: feefe9c7cb273e79 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.side" msgstr "Côté" +# English: "Framed glass opening on a roof segment." # Source-Hash: 67d903d7fe0c69c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framed glass opening on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.description" msgstr "Ouverture vitrée encadrée sur un pan de toit." +# English: "Skylight" # Source-Hash: 358826fbdeaf72b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.label" msgstr "Fenêtre de toit" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: f1113261ad2797de -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along X" +# Source-Hash: f1113261ad2797de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongX" msgstr "Selon X" -# Review: required -# Source-Hash: 5b84b1c644493370 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along Z" +# Source-Hash: 5b84b1c644493370 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongZ" msgstr "Selon Z" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.bottom" msgstr "Bas" -# Review: required -# Source-Hash: b8ff053666ee326b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.curb" msgstr "Costière" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.cutoutOffset" msgstr "Décalage de la découpe" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.depth" msgstr "Profondeur" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: ce3a6c03765dbbd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.frame" msgstr "Cadre" -# Review: required -# Source-Hash: 911ea36eb835f3a6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass Thickness" +# Source-Hash: 911ea36eb835f3a6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.glassThickness" msgstr "Épaisseur du vitrage" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: d56303913961984f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Lantern Height" +# Source-Hash: d56303913961984f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.lanternHeight" msgstr "Hauteur du lanterneau" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.left" msgstr "Gauche" -# Review: required -# Source-Hash: f3e9604e3b85510f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor" +# Source-Hash: f3e9604e3b85510f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motor" msgstr "Moteur" -# Review: required -# Source-Hash: a025444e0f500725 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor Housing" +# Source-Hash: a025444e0f500725 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motorHousing" msgstr "Boîtier moteur" -# Review: required -# Source-Hash: b2cbc4e2e5545a6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No" +# Source-Hash: b2cbc4e2e5545a6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.no" msgstr "Non" -# Review: required -# Source-Hash: a8e0b9fe5cfb4e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Motor" +# Source-Hash: a8e0b9fe5cfb4e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.noMotor" msgstr "Sans moteur" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.open" msgstr "Ouvrir" -# Review: required -# Source-Hash: 9bfc55f879926fe6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Angle" +# Source-Hash: 9bfc55f879926fe6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.openingAngle" msgstr "Angle d'ouverture" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.position" msgstr "Position" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.right" msgstr "Droite" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: 358826fbdeaf72b9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skylight" +# Source-Hash: 358826fbdeaf72b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.skylight" msgstr "Fenêtre de toit" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.thickness" msgstr "Épaisseur" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.top" msgstr "Haut" -# Review: required -# Source-Hash: 75991d106eb93dae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Scale" +# Source-Hash: 75991d106eb93dae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.topScale" msgstr "Échelle du haut" -# Review: required -# Source-Hash: 3272a61417949895 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Track Width" +# Source-Hash: 3272a61417949895 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.trackWidth" msgstr "Largeur du rail" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.type" msgstr "Type" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: 184fda5c980b9d9c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yes" +# Source-Hash: 184fda5c980b9d9c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.yes" msgstr "Oui" -# Review: required -# Source-Hash: 445aacf307de133d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat roof skylight" +# Source-Hash: 445aacf307de133d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.flat" msgstr "Puits de lumière pour toit plat" -# Review: required -# Source-Hash: 446176b7c5d8b284 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof lantern" +# Source-Hash: 446176b7c5d8b284 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.lantern" msgstr "Lanterneau" -# Review: required -# Source-Hash: a6559387f108518f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening skylight" +# Source-Hash: a6559387f108518f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.opening" msgstr "Puits de lumière ouvrant" -# Review: required -# Source-Hash: 379436bb5a74867a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sliding skylight" +# Source-Hash: 379436bb5a74867a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.sliding" msgstr "Puits de lumière coulissant" -# Review: required -# Source-Hash: d26ac61f593fe1bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Walk-on rooflight" +# Source-Hash: d26ac61f593fe1bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.walkOn" msgstr "Verrière praticable" +# English: "A polygon-bounded floor surface that hosts items on top." # Source-Hash: ad8f8036d34f6cea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygon-bounded floor surface that hosts items on top." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.description" msgstr "Surface de sol délimitée par un polygone, sur laquelle se posent les éléments." +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.label" msgstr "Dalle" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.addHole" msgstr "Ajouter une trémie" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.area" msgstr "Surface" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.auto" msgstr "Auto" +# English: "Base" # Source-Hash: fadada809e27477c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.base" msgstr "Base" +# English: "Cut hatch" # Source-Hash: 12195951bba19804 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut hatch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutHatch" msgstr "Découper une trappe" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutOpening" msgstr "Découper une ouverture" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.depth" msgstr "Profondeur" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.done" msgstr "Terminé" +# English: "Elevation" # Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.elevation" msgstr "Altitude" +# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." # Source-Hash: 201b720b76d79762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.extendsThePerimeterDownToTerrain" msgstr "Prolonge le pourtour jusqu’au terrain. La surface plane, la base et l’épaisseur restent inchangées." +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.fixed" msgstr "Fixe" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.floor" msgstr "Niveau" +# English: "Follows terrain" # Source-Hash: 89b2cfd72a0a7e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.followsTerrain" msgstr "Suit le terrain" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.foundation" msgstr "Fondation" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.holes" msgstr "Trémies" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.info" msgstr "Info" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.move" msgstr "Déplacer" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.noHoles" msgstr "Aucune trémie" +# English: "Rim" # Source-Hash: c6a29e75bf215488 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.rim" msgstr "Rebord" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.slab" msgstr "Dalle" +# English: "Standard (2\")" # Source-Hash: fb23c960abf5de23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard2" msgstr "Standard (2\")" +# English: "Standard (5cm)" # Source-Hash: 5f865b052c9b40eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (5cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard5cm" msgstr "Standard (5 cm)" +# English: "Sunken (15cm)" # Source-Hash: cff0e2e72cfab157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken15cm" msgstr "Encaissé (15 cm)" +# English: "Sunken (6\")" # Source-Hash: 8abc2aec4be98fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken6" msgstr "Encaissé (6\")" +# English: "Surface" # Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.surface" msgstr "Surface" +# English: "Thick (15cm)" # Source-Hash: 516636d85be919f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick15cm" msgstr "Épais (15 cm)" +# English: "Thick (6\")" # Source-Hash: c84078f3560da233 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick6" msgstr "Épais (6\")" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thickness" msgstr "Épaisseur" +# English: "Thin (1\")" # Source-Hash: 61c191f6df40285b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (1\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin1" msgstr "Mince (1\")" +# English: "Thin (2cm)" # Source-Hash: d345b1f707506fe7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (2cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin2cm" msgstr "Mince (2 cm)" +# English: "Grid of photovoltaic panels mounted on a roof segment." # Source-Hash: 065ffaaf64ab479d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid of photovoltaic panels mounted on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.description" msgstr "Ensemble de panneaux photovoltaïques fixés sur un pan de toit." +# English: "Solar Panel" # Source-Hash: 4d18446ff8eaa680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.label" msgstr "Panneau solaire" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: 2184867946897298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Array" +# Source-Hash: 2184867946897298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.array" msgstr "Champ" -# Review: required -# Source-Hash: 6eaa0e2a78d44d59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto-fit to roof" +# Source-Hash: 6eaa0e2a78d44d59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.autoFitToRoof" msgstr "Ajuster au toit" -# Review: required -# Source-Hash: 30edd1c180aac9b4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.columns" msgstr "Colonnes" -# Review: required -# Source-Hash: d1c6dd6335337cc1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Custom — dimensions don't match any preset" +# Source-Hash: d1c6dd6335337cc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.customDimensionsDonTMatchAny" msgstr "Personnalisé — les dimensions ne correspondent à aucun préréglage" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: fe9356b1d0e0396b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip orientation" +# Source-Hash: fe9356b1d0e0396b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flipOrientation" msgstr "Inverser l'orientation" -# Review: required -# Source-Hash: eaaa20f3b603140f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flush" msgstr "Affleurant" -# Review: required -# Source-Hash: fd413477f1a9e5ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameDepth" msgstr "Profondeur du cadre" -# Review: required -# Source-Hash: 6aa2318fa815e5f2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameThickness" msgstr "Épaisseur du cadre" -# Review: required -# Source-Hash: c5657f721eae3dff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap X" +# Source-Hash: c5657f721eae3dff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapX" msgstr "Espacement X" -# Review: required -# Source-Hash: e6e3b393aa7c0d98 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Y" +# Source-Hash: e6e3b393aa7c0d98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapY" msgstr "Espacement Y" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.mounting" msgstr "Montage" -# Review: required -# Source-Hash: 482469f5a0873f73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel" +# Source-Hash: 482469f5a0873f73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.panel" msgstr "Panneau" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.preset" msgstr "Préréglage" -# Review: required -# Source-Hash: c913b494c3901a17 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rows" +# Source-Hash: c913b494c3901a17 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.rows" msgstr "Rangées" -# Review: required -# Source-Hash: a4dba2b2675dbba0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Setbacks too large to fit a panel." +# Source-Hash: a4dba2b2675dbba0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.setbacksTooLarge" msgstr "Retraits trop grands pour placer un panneau." -# Review: required -# Source-Hash: 4d18446ff8eaa680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solar Panel" +# Source-Hash: 4d18446ff8eaa680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.solarPanel" msgstr "Panneau solaire" -# Review: required -# Source-Hash: 0af6194a8b8d8be6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.standoff" msgstr "Entretoise" -# Review: required -# Source-Hash: 58cb60c14826f1d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tiltAngle" msgstr "Angle d’inclinaison" -# Review: required -# Source-Hash: 787005b26aec58a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilted" +# Source-Hash: 787005b26aec58a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tilted" msgstr "Incliné" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.width" msgstr "Largeur" -# Review: required -# Source-Hash: cd53f16290b67f7f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compact" +# Source-Hash: cd53f16290b67f7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.compact" msgstr "Compact" -# Review: required -# Source-Hash: 317ebe2aa996af70 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frameless" +# Source-Hash: 317ebe2aa996af70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.frameless" msgstr "Sans cadre" -# Review: required -# Source-Hash: 46660ad432e19312 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential" +# Source-Hash: 46660ad432e19312 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residential" msgstr "Résidentiel" -# Review: required -# Source-Hash: b33386b8fe3725fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential Large" +# Source-Hash: b33386b8fe3725fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residentialLarge" msgstr "Résidentiel grand format" +# English: "Player or camera origin within a level. One per level." # Source-Hash: 638f17c80f424dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Player or camera origin within a level. One per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.description" msgstr "Origine du joueur ou de la caméra dans un niveau. Une par niveau." +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.label" msgstr "Point d’apparition" +# English: "Body" # Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.body" msgstr "Corps" +# English: "Center stringer" # Source-Hash: 29b9fdb127a54435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center stringer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.centerStringer" msgstr "Limon central" +# English: "Closed risers" # Source-Hash: b2b2383577835a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closed risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.closedRisers" msgstr "Contremarches fermées" -# Source-Hash: 6f6b188bff3c0223 +# English: "From structure" +# Source-Hash: 49a25acbaf33ee3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/construction-controls.tsx +#, fuzzy +msgid "node.stair.constructionControls.fromStructure" +msgstr "Selon la structure" + # English: "Inherit" +# Source-Hash: 6f6b188bff3c0223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.inherit" msgstr "Hériter" +# English: "Nosing" # Source-Hash: ce25dc4a23f3ae10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nosing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.nosing" msgstr "Nez de marche" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.open" msgstr "Ouvrir" +# English: "Original" # Source-Hash: 7717e0464ad1e7df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.original" msgstr "Original" +# English: "Riser thickness" # Source-Hash: 8a6f6c6b0cca7460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.riserThickness" msgstr "Épaisseur de contremarche" +# English: "Side stringers" # Source-Hash: 2fe058966023b542 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side stringers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.sideStringers" msgstr "Limons latéraux" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.solid" msgstr "Plein" +# English: "Stringer depth" # Source-Hash: 4c1489839e306865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerDepth" msgstr "Hauteur du limon" +# English: "Stringer width" # Source-Hash: 17e1a09c422d3660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerWidth" msgstr "Largeur du limon" +# English: "Tread finish" # Source-Hash: 146f8e61ff310dc5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadFinish" msgstr "Finition des marches" +# English: "Tread thickness" # Source-Hash: 9089f91f3fbbf4cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadThickness" msgstr "Épaisseur des marches" +# English: "Waist" # Source-Hash: 44dda7136b93c8ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waist" msgstr "Paillasse" +# English: "Waist thickness" # Source-Hash: dfd6d2ff7a94f22e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waistThickness" msgstr "Épaisseur de paillasse" +# English: "A stair composed of one or more flights with configurable treads, risers, railings." # Source-Hash: 8535b75637105412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A stair composed of one or more flights with configurable treads, risers, railings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.description" msgstr "Escalier composé d’une ou plusieurs volées, avec marches, contremarches et garde-corps configurables." +# English: "The flight chain does not reach the declared destination." # Source-Hash: c24b098693e1006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The flight chain does not reach the declared destination." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.arrivalMismatch" msgstr "La suite de volées n’atteint pas la destination déclarée." +# English: "Two risers plus going is outside the 0.60–0.65 m design range." # Source-Hash: 124ad1cd6bb00013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Two risers plus going is outside the 0.60–0.65 m design range." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.comfortTarget" msgstr "Deux hauteurs plus un giron sortent de la plage de conception 0,60–0,65 m." +# English: "Going is below the design target." # Source-Hash: 7452fcd8370bef34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Going is below the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.goingTarget" msgstr "Le giron est inférieur à l’objectif de conception." +# English: "Overhead surfaces leave less than the target headroom." # Source-Hash: 449683eb727091d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhead surfaces leave less than the target headroom." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomTarget" msgstr "Les surfaces au-dessus laissent moins que l’échappée cible." +# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." # Source-Hash: 9e9357408f6a0d08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomUnresolved" msgstr "L’échappée détaillée n’a pas pu être évaluée : vérifiez le nombre de contremarches et le budget de requêtes de surface." +# English: "Flight dimensions and integer riser count must be positive." # Source-Hash: 40fd019d72ff3835 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight dimensions and integer riser count must be positive." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidFlight" msgstr "Les dimensions de volée et le nombre entier de contremarches doivent être positifs." +# English: "The destination must be above the stair base." # Source-Hash: 3d4b6f859bd8de50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The destination must be above the stair base." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidRise" msgstr "La destination doit être au-dessus de la base de l’escalier." +# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." # Source-Hash: 5f196b22e2c8296a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.landingOverlap" msgstr "La profondeur du palier dépasse un cercle complet sur la ligne de foulée ; le palier visible est limité à un cercle." +# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." # Source-Hash: 0122ec92b951a114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.narrowWinderEnd" msgstr "L’extrémité étroite d’une marche balancée est inférieure à 0,10 m ; vérifiez l’usage prévu de l’escalier." +# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." # Source-Hash: a664dc7f3b014af8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.nonuniformRisers" msgstr "Les hauteurs de contremarche de la volée diffèrent. N’utilisez la correction de contremarches régulières que pour un projet." +# English: "A landing has a stored rise; inspect its transition before repair." # Source-Hash: cd140b519d37b01e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A landing has a stored rise; inspect its transition before repair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.raisedLanding" msgstr "Un palier a une hauteur enregistrée ; vérifiez sa transition avant de corriger." +# English: "Riser height exceeds the design target." # Source-Hash: a94cfa7b2a21cf14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser height exceeds the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.riserTarget" msgstr "La hauteur de contremarche dépasse l’objectif de conception." +# English: "Stair" # Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.label" msgstr "Escalier" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.straight" msgstr "Droit" +# English: "{layout} turn" # Source-Hash: 1cda16cb49e6fb93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{layout} turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.turn" msgstr "Retour {layout}" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.actions" msgstr "Actions" +# English: "Add flight" # Source-Hash: 15eaf25f306541f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addFlight" msgstr "Ajouter une volée" +# English: "Add landing" # Source-Hash: 1fa37c346bffb8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addLanding" msgstr "Ajouter un palier" +# English: "Apply {label} layout" # Source-Hash: 9ccbd6c1785f1e73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply {label} layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.applyLabelLayout" msgstr "Appliquer la disposition {label}" +# English: "Auto Cutout" # Source-Hash: 7eb42ea0fc442b8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto Cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.autoCutout" msgstr "Découpe auto" +# English: "Balusters" # Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.balusters" msgstr "Balustres" +# English: "Boards" # Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.boards" msgstr "Planches" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.both" msgstr "Les deux" +# English: "Cable" # Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.cable" msgstr "Câble" +# English: "Center Column" # Source-Hash: 11d6eec5d485488d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.centerColumn" msgstr "Colonne centrale" +# English: "Clockwise" # Source-Hash: 5fbbf4ad24befa7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.clockwise" msgstr "Sens horaire" +# English: "Connected to {surface}. Move the stair to connect to another edge." # Source-Hash: 31028ca51b49018b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to {surface}. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedTo" msgstr "Raccordé à {surface}. Déplacez l’escalier pour le raccorder à un autre bord." +# English: "Connected to a surface. Move the stair to connect to another edge." # Source-Hash: 33133921e270509f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to a surface. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedToSurface" msgstr "Raccordé à une surface. Déplacez l’escalier pour le raccorder à un autre bord." +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.construction" msgstr "Construction" +# English: "Counterclockwise" # Source-Hash: 5e3e3ab7d91c2c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Counterclockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.counterclockwise" msgstr "Sens antihoraire" +# English: "Curved" # Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.curved" msgstr "Courbe" +# English: "Custom rise" # Source-Hash: 0bd4232a56d25366 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.customRise" msgstr "Hauteur de marche personnalisée" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.deck" msgstr "Terrasse" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.delete" msgstr "Supprimer" +# English: "Design targets" # Source-Hash: 1c34e5cc37add163 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Design targets" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.designTargets" msgstr "Objectifs de conception" +# English: "Destination" # Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.destination" msgstr "Destination" +# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." # Source-Hash: 1fa48e82752d1505 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.dragNearADeckPatioConcrete" msgstr "Faites glisser près d’une terrasse, d’un patio, d’une dalle ou d’un bord de palier pour ajuster l’escalier." +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.duplicate" msgstr "Dupliquer" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalAngle" msgstr "Angle égal" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalGoing" msgstr "Giron égal" +# English: "Fit risers and going" # Source-Hash: 6ff728d5058c7e31 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit risers and going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitRisersAndGoing" msgstr "Ajuster contremarches et giron" +# English: "Fit To Floor" # Source-Hash: d60a4c7995bd2184 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit To Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitToFloor" msgstr "Ajuster au plancher" +# English: "Fit uniform risers" # Source-Hash: 0638614580661eb0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit uniform risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitUniformRisers" msgstr "Ajuster des contremarches régulières" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" # Source-Hash: 4d365dbc1fb92b90 # Review: required -# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.flightMeasurements" msgstr "Volée {number} : {count, plural, one {{riserCount} contremarche} many {{riserCount} contremarches} other {{riserCount} contremarches}} · H {riser} · G {going} · {slope}" +# English: "Floors, ceilings and stairs only." # Source-Hash: 6e946bd1acd94637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floors, ceilings and stairs only." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.floorsCeilingsAndStairsOnly" msgstr "Planchers, plafonds et escaliers uniquement." +# English: "Follows deck" # Source-Hash: fa10ea42944107a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsDeck" msgstr "Suit la terrasse" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsLevel" msgstr "Suit le niveau" +# English: "From Level" # Source-Hash: b7fc627a965bc016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fromLevel" msgstr "Depuis le niveau" +# English: "Geometry" # Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.geometry" msgstr "Géométrie" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.glass" msgstr "Vitrage" +# English: "Headroom:" # Source-Hash: 55adba80c39500f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Headroom:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.headroom" msgstr "Échappée :" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.height" msgstr "Hauteur" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerGap" msgstr "Écart intérieur" +# English: "Inner Radius" # Source-Hash: 8b54356df7ae40bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerRadius" msgstr "Rayon intérieur" +# English: "Integrated" # Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.integrated" msgstr "Intégré" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.landing" msgstr "Palier" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.layout" msgstr "Disposition" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.left" msgstr "Gauche" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.leftTurn" msgstr "Retour à gauche" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.measurements" msgstr "Mesures" +# English: "Metal" # Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.metal" msgstr "Métal" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.move" msgstr "Déplacer" +# English: "Moving this stair keeps its current rise, steps, and run." # Source-Hash: a4f1cc34aae4db27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Moving this stair keeps its current rise, steps, and run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.movingThisStairKeepsItsCurrent" msgstr "Déplacer cet escalier conserve sa hauteur, ses marches et son reculement actuels." +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.none" msgstr "Aucun" +# English: "No overhead surface" # Source-Hash: 47c858b3022b3ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No overhead surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.noOverheadSurface" msgstr "Aucune surface au-dessus" +# English: "Not evaluated" # Source-Hash: 17f1933d93cf655c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not evaluated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.notEvaluated" msgstr "Non évalué" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.opening" msgstr "Ouverture" +# English: "Opening Offset" # Source-Hash: 599220556fc4bbfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.openingOffset" msgstr "Décalage de l’ouverture" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.position" msgstr "Position" +# English: "Post & rail" # Source-Hash: f522323372a7a59e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post & rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.postRail" msgstr "Poteaux et lisses" +# English: "Railing" # Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.railing" msgstr "Garde-corps" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.right" msgstr "Droite" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rightTurn" msgstr "Retour à droite" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rise" msgstr "Hauteur" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rotation" msgstr "Rotation" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segments" msgstr "Segments" +# English: "{type, select, landing {Landing} other {Stair}}" # Source-Hash: 0b9a0bef102b75bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{type, select, landing {Landing} other {Stair}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segmentType" msgstr "{type, select, landing {Palier} other {Volée}}" +# English: "Sizing" # Source-Hash: 87b926bbf34a78ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sizing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sizing" msgstr "Dimensionnement" +# English: "Snap to nearby surfaces" # Source-Hash: 8a0f4562198c266f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap to nearby surfaces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.snapToNearbySurfaces" msgstr "Aimanter aux surfaces proches" +# English: "Spiral" # Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.spiral" msgstr "Hélicoïdal" +# English: "Staircase" # Source-Hash: f8d4f66145e1eba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.staircase" msgstr "Escalier" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.steps" msgstr "Marches" +# English: "Step Supports" # Source-Hash: 8846dc96332c5181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step Supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.stepSupports" msgstr "Supports de marche" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.straight" msgstr "Droit" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.style" msgstr "Style" +# English: "Surface connection" # Source-Hash: cde3878287069df2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.surfaceConnection" msgstr "Raccordement à la surface" +# English: "Sweep" # Source-Hash: 4e0f8b363503efff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sweep" msgstr "Balayage" +# English: "Target going" # Source-Hash: 4c294d88d712855a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetGoing" msgstr "Giron cible" +# English: "Target headroom" # Source-Hash: 74cfba22cf2e6eb8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetHeadroom" msgstr "Échappée cible" +# English: "Target max riser" # Source-Hash: 9d05958a265a6726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target max riser" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMaxRiser" msgstr "Hauteur de marche max. cible" +# English: "Target minimum going" # Source-Hash: d10322b16178ce37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target minimum going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMinimumGoing" msgstr "Giron minimal cible" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.thickness" msgstr "Épaisseur" +# English: "To" # Source-Hash: b3214a20e45a7aed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.to" msgstr "Vers" +# English: "Top Landing" # Source-Hash: 846fc58043454194 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.topLanding" msgstr "Palier d’arrivée" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.type" msgstr "Type" +# English: "Unavailable" # Source-Hash: b4c2f503b19443c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.unavailable" msgstr "Indisponible" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.walkingLineOffset" msgstr "Décalage de la ligne de foulée" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.width" msgstr "Largeur" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.winder" msgstr "Balancement" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.both" msgstr "Les deux" +# English: "Bottom sloped extension" # Source-Hash: fe633d20f198d7e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom sloped extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.bottomExtension" msgstr "Prolongement incliné en bas" +# English: "Continuous" # Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.continuous" msgstr "Continu" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.floor" msgstr "Niveau" +# English: "Handrail diameter" # Source-Hash: 644d2d9e9c3dd31f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailDiameter" msgstr "Diamètre de la main courante" +# English: "Handrail height" # Source-Hash: 5f3f2be1e572a066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailHeight" msgstr "Hauteur de la main courante" +# English: "Handrail inset" # Source-Hash: cb3239847f068a1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailInset" msgstr "Retrait de la main courante" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.left" msgstr "Gauche" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.none" msgstr "Aucun" +# English: "No return" # Source-Hash: e6ac8d7c9639c351 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.noReturn" msgstr "Sans retour" +# English: "Original layout" # Source-Hash: 5688b2003002d03a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.originalLayout" msgstr "Disposition d’origine" +# English: "Post-facing" # Source-Hash: d0d904fbd052d7b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post-facing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.postFacing" msgstr "Vers le poteau" +# English: "Return length" # Source-Hash: fc4d8c6f156b0430 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.returnLength" msgstr "Longueur du retour" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.right" msgstr "Droite" +# English: "Separate handrail" # Source-Hash: e1f8354a95b5060a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate handrail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.separateHandrail" msgstr "Main courante séparée" +# English: "Set the length to reach the wall or post." # Source-Hash: 005f0197ec3bc95a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set the length to reach the wall or post." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.setTheLengthToReachThe" msgstr "Réglez la longueur pour atteindre le mur ou le poteau." +# English: "Top horizontal extension" # Source-Hash: bef713f3ec96f758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top horizontal extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/railing-controls.tsx +#, fuzzy +msgid "node.stair.railingControls.topExtension" +msgstr "Prolongement horizontal en haut" + +# English: "Wall-facing" +# Source-Hash: bdad79929de6b142 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy -msgid "node.stair.railingControls.topExtension" -msgstr "Prolongement horizontal en haut" +msgid "node.stair.railingControls.wallFacing" +msgstr "Vers le mur" + +# English: "A solid poured flight on a footing; risers closed, no stringers." +# Source-Hash: 1709ce71eba750ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.aSolidPouredFlightOnA" +msgstr "Une volée pleine coulée sur une semelle ; contremarches fermées, sans limons." + +# English: "Auto ({autoCount})" +# Source-Hash: 56be143b76f65392 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoAutocount" +msgstr "Auto ({autoCount})" + +# English: "Auto (closed)" +# Source-Hash: d7820aaac08e4fcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoClosed" +msgstr "Auto (fermées)" + +# English: "Auto (open)" +# Source-Hash: c7cbd493df719e71 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoOpen" +msgstr "Auto (ouvertes)" + +# English: "Auto (pressure-treated)" +# Source-Hash: be21258d52930d5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoPressureTreated" +msgstr "Auto (traité autoclave)" + +# English: "Auto ({treads})" +# Source-Hash: 1828540d95a5b5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoTreads" +msgstr "Auto ({treads})" + +# English: "Auto (untreated)" +# Source-Hash: 43c1949fa53685b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoUntreated" +msgstr "Auto (non traité)" + +# English: "Closed (1x8 boards)" +# Source-Hash: d0a3ca3ce29ae833 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.closed1x8Boards" +msgstr "Fermées (planches 1x8)" + +# English: "Concrete" +# Source-Hash: 824471a3691b51db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.concrete" +msgstr "Béton" + +# English: "{count, plural, one {# steel channel} other {# steel channels}}" +# Source-Hash: dcb2dd54c3301068 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannels" +msgstr "{count, plural, one {# profilé acier en U} many {# profilés acier en U} other {# profilés acier en U}}" + +# English: "{count, plural, one {# steel channel at {spacing} o.c.} other {# steel channels at {spacing} o.c.}}" +# Source-Hash: fbf12088b828a103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannelsAtSpacing" +msgstr "{count, plural, one {# profilé acier en U à {spacing} d’entraxe} many {# profilés acier en U à {spacing} d’entraxe} other {# profilés acier en U à {spacing} d’entraxe}}" + +# English: "{count, plural, one {# stringer} other {# stringers}}" +# Source-Hash: 6f0f06f0531c7e96 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringers" +msgstr "{count, plural, one {# limon} many {# limons} other {# limons}}" + +# English: "{count, plural, one {# stringer at {spacing} o.c.} other {# stringers at {spacing} o.c.}}" +# Source-Hash: cfdb3f3fc95ea940 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringersAtSpacing" +msgstr "{count, plural, one {# limon à {spacing} d’entraxe} many {# limons à {spacing} d’entraxe} other {# limons à {spacing} d’entraxe}}" + +# English: "Cut" +# Source-Hash: fa4b97d3aa600bcd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.cut" +msgstr "À crémaillère" + +# English: "Flight {number}: {layout}" +# Source-Hash: ba4f3c1346b3e7af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.flightLayout" +msgstr "Volée {number} : {layout}" + +# English: "Housed sides" +# Source-Hash: 4cbb34fdf4c1e107 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.housedSides" +msgstr "À la française" + +# English: "{inches} in o.c." +# Source-Hash: e3ddff241f642295 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.inchesInOC" +msgstr "{inches} in d’entraxe" + +# English: "{layout} (auto)" +# Source-Hash: 542ca0bee99a1533 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. The stringer count was chosen automatically, not set by hand. +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.layoutAuto" +msgstr "{layout} (auto)" + +# English: "Lumber" +# Source-Hash: 73dcfae611575500 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.lumber" +msgstr "Bois" + +# English: "Max spacing" +# Source-Hash: d10743fce478d9f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.maxSpacing" +msgstr "Espacement max." + +# English: "Nosing" +# Source-Hash: ce25dc4a23f3ae10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.nosing" +msgstr "Nez de marche" + +# English: "One {stock}" +# Source-Hash: ba534b07476bc06d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.oneStock" +msgstr "Un {stock}" + +# English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.open" +msgstr "Ouvertes" + +# English: "Original body" +# Source-Hash: 20f023962f848e14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.originalBody" +msgstr "Corps d’origine" + +# English: "Plywood + finish" +# Source-Hash: ac658d76a942652f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.plywoodFinish" +msgstr "Contreplaqué + finition" + +# English: "Pressure-treated" +# Source-Hash: 86fda85cdc89fd27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.pressureTreated" +msgstr "Traité autoclave" + +# English: "Risers" +# Source-Hash: d903bba423ddb755 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.risers" +msgstr "Contremarches" + +# English: "Steel stringers" +# Source-Hash: ff0400a550f24adf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.steelStringers" +msgstr "Limons acier" + +# English: "Stringer count" +# Source-Hash: 50d4cac7f3002cd2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerCount" +msgstr "Nombre de limons" + +# English: "Stringers" +# Source-Hash: 71733e5b479af2a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringers" +msgstr "Limons" + +# English: "Stringer type" +# Source-Hash: 191196b94d1c86f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerType" +msgstr "Type de limon" + +# English: "Structure" +# Source-Hash: a20c89205a4448d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.structure" +msgstr "Structure" + +# English: "Treads" +# Source-Hash: 869e3785611a0858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.treads" +msgstr "Marches" + +# English: "Two {stock}" +# Source-Hash: e1e6555b827b4620 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.twoStock" +msgstr "Deux {stock}" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.untreated" +msgstr "Non traité" -# Source-Hash: bdad79929de6b142 +# English: "Wood stringers" +# Source-Hash: 401bae5ffee978fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall-facing" -#: packages/nodes/src/stair/railing-controls.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx #, fuzzy -msgid "node.stair.railingControls.wallFacing" -msgstr "Vers le mur" +msgid "node.stair.structureControls.woodStringers" +msgstr "Limons bois" +# English: "A single flight of a parent stair." # Source-Hash: ce0b6361eb77e93c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single flight of a parent stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.description" msgstr "Une volée d’un escalier parent." +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.label" msgstr "Volée" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.actions" msgstr "Actions" +# English: "Attachment" # Source-Hash: 551b04c03b07595e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.attachment" msgstr "Raccordement" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.delete" msgstr "Supprimer" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.dimensions" msgstr "Dimensions" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.duplicate" msgstr "Dupliquer" +# English: "Editing switches the stair to Custom rise" # Source-Hash: ad063fb27f370c28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Editing switches the stair to Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.editingSwitchesTheStairToCustom" msgstr "La modification passe l’escalier en hauteur de marche personnalisée" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalAngle" msgstr "Angle égal" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalGoing" msgstr "Giron égal" +# English: "Fill to floor" # Source-Hash: 66f1018b50c2e426 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.fillToFloor" msgstr "Remplir jusqu’au sol" +# English: "Flight" # Source-Hash: deff4254bb163b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flight" msgstr "Volée" +# English: "Flight shape" # Source-Hash: d6da828cd0c5e8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flightShape" msgstr "Forme de la volée" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.front" msgstr "Avant" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.height" msgstr "Hauteur" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.innerGap" msgstr "Écart intérieur" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.landing" msgstr "Palier" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.left" msgstr "Gauche" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.leftTurn" msgstr "Retour à gauche" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.length" msgstr "Longueur" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.move" msgstr "Déplacer" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.position" msgstr "Position" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.right" msgstr "Droite" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rightTurn" msgstr "Retour à droite" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rotation" msgstr "Rotation" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.stairSegment" msgstr "Volée" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.steps" msgstr "Marches" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.straight" msgstr "Droit" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.structure" msgstr "Structure" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.thickness" msgstr "Épaisseur" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.type" msgstr "Type" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.walkingLineOffset" msgstr "Décalage de la ligne de foulée" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.width" msgstr "Largeur" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.winder" msgstr "Balancement" +# English: "Persistent construction grid axis with identification bubbles." # Source-Hash: 5434a3189441c913 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Persistent construction grid axis with identification bubbles." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.description" msgstr "Axe de trame de construction persistant avec bulles d’identification." +# English: "Structural Grid" # Source-Hash: 0cfe636f7fdd9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.label" msgstr "Trame structurelle" +# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." # Source-Hash: 36b714210f25c284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.description" msgstr "Extracteur éolien rotatif pour versant de toit." +# English: "Turbine Vent" # Source-Hash: 4a855abb962f5e7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.label" msgstr "Extracteur éolien" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.actions" msgstr "Actions" -# Review: required -# Source-Hash: c65f02bcdc3231d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.cylinder" msgstr "Cylindre" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.delete" msgstr "Supprimer" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.diameter" msgstr "Diamètre" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.dimensions" msgstr "Dimensions" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.duplicate" msgstr "Dupliquer" -# Review: required -# Source-Hash: 2d4223f80dc3945c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Globe" +# Source-Hash: 2d4223f80dc3945c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.globe" msgstr "Globe" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.height" msgstr "Hauteur" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.motion" msgstr "Mouvement" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.move" msgstr "Déplacer" -# Review: required -# Source-Hash: 6faf9532dcbfe4bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Height" +# Source-Hash: 6faf9532dcbfe4bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.neckHeight" msgstr "Hauteur du col" -# Review: required -# Source-Hash: 1550d72740e66f77 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pause" +# Source-Hash: 1550d72740e66f77 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.pause" msgstr "Pause" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.play" msgstr "Lancer" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.position" msgstr "Position" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.rotation" msgstr "Rotation" -# Review: required -# Source-Hash: bf553d40ce68834d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spin Speed" +# Source-Hash: bf553d40ce68834d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.spinSpeed" msgstr "Vitesse de rotation" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.style" msgstr "Style" -# Review: required -# Source-Hash: 4a855abb962f5e7a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Turbine Vent" +# Source-Hash: 4a855abb962f5e7a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.turbineVent" msgstr "Extracteur éolien" -# Review: required -# Source-Hash: 80cef0dce71e121f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vanes" +# Source-Hash: 80cef0dce71e121f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.vanes" msgstr "Ailettes" +# English: "A named group of zones forming one apartment, hotel room or commercial lot." # Source-Hash: e3d2aaaf5c72bc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A named group of zones forming one apartment, hotel room or commercial lot." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.description" msgstr "Groupe nommé de zones formant un appartement, une chambre d’hôtel ou un lot commercial." +# English: "Apartment" # Source-Hash: 40e5a51d0d205fcf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apartment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.apartment" msgstr "Appartement" +# English: "Commercial" # Source-Hash: ad041d9634dd55ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Commercial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.commercial" msgstr "Commercial" +# English: "Common" # Source-Hash: 862f30cb2d49f812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Common" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.common" msgstr "Commun" +# English: "Hotel room" # Source-Hash: cd7e1bc42d47a105 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hotel room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.hotelRoom" msgstr "Chambre d’hôtel" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.label" msgstr "Lot" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberRoom" msgstr "Pièce" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberZone" msgstr "Zone" +# English: "Members sit on levels that are not adjacent." # Source-Hash: e25d5b5630b9d8df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members sit on levels that are not adjacent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.nonAdjacentLevels" msgstr "Les membres sont sur des niveaux non adjacents." +# English: "No zones assigned yet." # Source-Hash: 5ebeb440e0e64d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones assigned yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.noZonesAssigned" msgstr "Aucune zone attribuée pour l’instant." +# English: "Remove {name} from unit" # Source-Hash: 07731fa851c75d8c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove {name} from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeMember" msgstr "Retirer {name} du logement" +# English: "Remove zone from unit" # Source-Hash: 389839734c8350b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove zone from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeZone" msgstr "Retirer la zone du logement" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.actions" msgstr "Actions" +# English: "Color" # Source-Hash: fe18958cedd627d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.color" msgstr "Couleur" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.delete" msgstr "Supprimer" +# English: "Focus" # Source-Hash: b80dc93401f953a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focus" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.focus" msgstr "Cibler" +# English: "Gross area" # Source-Hash: 1218205b3bb34f1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gross area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.grossArea" msgstr "Surface brute" +# English: "Kind" # Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kind" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.kind" msgstr "Type" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.levels" msgstr "Niveaux" +# English: "Members" # Source-Hash: 6bbc51529519435c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.members" msgstr "Membres" +# English: "Name" # Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.name" msgstr "Nom" +# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." # Source-Hash: 54b25f70c7b63446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.noRoomsYetPickThisUnit" msgstr "Aucune pièce pour l’instant. Choisissez ce logement depuis le panneau d’une pièce, ou dessinez des zones pendant qu’il est ciblé." +# English: "Report" # Source-Hash: 85d640066187108d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Report" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.report" msgstr "Rapport" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.unit" msgstr "Lot" -# Source-Hash: d53242481ff46157 +# English: "Dimensions to the exterior faces (outside size). Click for both" +# Source-Hash: e04a35319a83ad7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheExteriorFacesOutside" +msgstr "Cotes aux faces extérieures (dimensions hors tout). Cliquez pour les deux" + +# English: "Dimensions to the interior and exterior faces. Click for interior only" +# Source-Hash: 41c6c329ecb15086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorAndExterior" +msgstr "Cotes aux faces intérieures et extérieures. Cliquez pour l’intérieur seul" + +# English: "Dimensions to the interior faces (room size). Click for exterior only" +# Source-Hash: 57524fee08ddde73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorFacesRoom" +msgstr "Cotes aux faces intérieures (dimensions de la pièce). Cliquez pour l’extérieur seul" + +# English: "ID" +# Source-Hash: 81ef3a25ccb6eaf7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: interior dimension, measured to the interior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.id" +msgstr "Int." + +# English: "ID+OD" +# Source-Hash: e846b1fcc616fb6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: ID is the interior dimension, OD the outside dimension. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.idOd" +msgstr "Int.+Ext." + # English: "Merge walls" +# Source-Hash: d53242481ff46157 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.mergeWalls" msgstr "Fusionner les murs" -# Source-Hash: d7ead4708d50965f +# English: "OD" +# Source-Hash: fdaa3b3faa1844fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: outside dimension, measured to the exterior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.od" +msgstr "Ext." + # English: "Split wall" +# Source-Hash: d7ead4708d50965f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.splitWall" msgstr "Scinder le mur" -# Source-Hash: 96a6f084d095ea36 +# English: "These walls cannot be merged." +# Source-Hash: 2f8c3189a0b3972b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.theseWallsCannotBeMerged" +msgstr "Ces murs ne peuvent pas être fusionnés." + # English: "Column {column}, row {row}: {type, select, glass {glass} solid {solid} other {empty}}" +# Source-Hash: 96a6f084d095ea36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.cell" msgstr "Colonne {column}, rangée {row} : {type, select, glass {vitrage} solid {plein} other {vide}}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" # Source-Hash: 904e9b269b68c670 # Review: required -# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.columnSpacing" msgstr "{count, plural, one {{number} colonne} many {{number} colonnes} other {{number} colonnes}} · trame de {spacing}" +# English: "No panels" # Source-Hash: 8b2b2f11fca9f077 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.noPanels" msgstr "Aucun panneau" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" # Source-Hash: 9f5d9775377b666e # Review: required -# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.rowSpacing" msgstr "{count, plural, one {{number} rangée} many {{number} rangées} other {{number} rangées}} · trame de {spacing}" +# English: "Apply tint" # Source-Hash: 90638d5a19a4f0a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.applyTint" msgstr "Appliquer la teinte" +# English: "Border / opening frame" # Source-Hash: a7d624ec0f879829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Border / opening frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.borderOpeningFrame" msgstr "Bordure / cadre d’ouverture" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottom" msgstr "Bas" +# English: "Bottom row" # Source-Hash: 5503fb89c40cefeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottomRow" msgstr "Rangée du bas" +# English: "Cancel tint" # Source-Hash: 1928d9e7a16360d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.cancelTint" msgstr "Annuler la teinte" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.center" msgstr "Centre" +# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." # Source-Hash: 9f4c033fa80119db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.chooseACellOrEnterIts" msgstr "Choisissez une cellule ou saisissez sa colonne et sa rangée. Utilisez les flèches pour passer d’une cellule à l’autre. Les colonnes partent du début du mur ; les rangées se comptent vers le haut." +# English: "Clear hidden overrides" # Source-Hash: bbeadcebc59c923f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear hidden overrides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.clearHiddenOverrides" msgstr "Effacer les remplacements masqués" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.column" msgstr "Poteau" +# English: "Column layout" # Source-Hash: 711a6426e336877a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnLayout" msgstr "Disposition des colonnes" +# English: "Column {column}, row {row}" # Source-Hash: 59b2ab5fdf54c2e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column {column}, row {row}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnRow" msgstr "Colonne {column}, rangée {row}" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.construction" msgstr "Construction" +# English: "Continuous mullions and transoms assembled along the wall." # Source-Hash: 59d2bec8b9ae1025 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous mullions and transoms assembled along the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong" msgstr "Meneaux et traverses continus assemblés le long du mur." +# English: "Curtain wall panel selection" # Source-Hash: bbc5e5f3f3b99232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall panel selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.curtainWallPanelSelection" msgstr "Sélection des panneaux du mur-rideau" +# English: "Custom infill" # Source-Hash: 9fd79cf417eab9ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.customInfill" msgstr "Remplissage personnalisé" +# English: "Default panel" # Source-Hash: 097ed751fa47e5fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.defaultPanel" msgstr "Panneau par défaut" +# English: "Edit individual panels" # Source-Hash: 0a0c98ae42f519ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.editIndividualPanels" msgstr "Modifier les panneaux individuellement" +# English: "Effective thickness: {thickness}, limited by frame depth." # Source-Hash: 8690f82f64806e4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Effective thickness: {thickness}, limited by frame depth." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.effectiveThicknessLimited" msgstr "Épaisseur effective : {thickness}, limitée par la profondeur du cadre." +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.empty" msgstr "Vide" +# English: "Enable panel editing" # Source-Hash: c6e7792839d09c69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable panel editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.enablePanelEditing" msgstr "Activer la modification des panneaux" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.end" msgstr "Fin" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fixedSpacing" msgstr "Espacement fixe" +# English: "Following wall defaults" # Source-Hash: 1c851bde836b3b57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Following wall defaults" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.followingWallDefaults" msgstr "Valeurs par défaut du mur" +# English: "Frame dimensions" # Source-Hash: 49be990b89741501 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.frameDimensions" msgstr "Dimensions du cadre" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.framing" msgstr "Ossature" +# English: "Fully capped" # Source-Hash: 221f418784836f11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fully capped" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fullyCapped" msgstr "Entièrement coiffé" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glass" msgstr "Vitrage" +# English: "Glass appearance" # Source-Hash: a17c63662aa4404d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearance" msgstr "Aspect du vitrage" +# English: "Glass appearance is controlled by the paint tool." # Source-Hash: f59c4938a2116a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance is controlled by the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearanceIsControlledByThe" msgstr "L’aspect du vitrage se règle avec l’outil Peinture." +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassTint" msgstr "Teinte du vitrage" +# English: "Glazing gap" # Source-Hash: 7660a6aa7e7cd365 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glazingGap" msgstr "Jeu de vitrage" +# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." # Source-Hash: 6e739c8a698efaab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.gridLimitedTo32PanelsPer" msgstr "Trame limitée à 32 panneaux par axe. L’espacement s’élargit pour ce mur." +# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" # Source-Hash: 1c63255b675bbcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.hiddenCustomPanelCount" msgstr "{length, plural, one {{number} panneau personnalisé est hors de cette grille. Il réapparaît si la grille s’agrandit.} many {{number} panneaux personnalisés sont hors de cette grille. Ils réapparaissent si la grille s’agrandit.} other {{number} panneaux personnalisés sont hors de cette grille. Ils réapparaissent si la grille s’agrandit.}}" +# English: "Horizontal caps" # Source-Hash: 84af317e818c10cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalCaps" msgstr "Couvre-joints horizontaux" +# English: "Horizontal frame width" # Source-Hash: 7a71f6b25c5dc718 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalFrameWidth" msgstr "Largeur du cadre horizontal" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.layout" msgstr "Disposition" +# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." # Source-Hash: fc37833ef19c8feb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness" msgstr "Une rugosité faible donne une surface lisse ; une rugosité élevée donne un rendu mat." +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.maximumSpacing" msgstr "Espacement maximal" +# English: "Module joint width" # Source-Hash: 1452f51ec5dfc6ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Module joint width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.moduleJointWidth" msgstr "Largeur de joint entre modules" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.none" msgstr "Aucun" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opacity" msgstr "Opacité" +# English: "Opaque band" # Source-Hash: 0f9ba67daa6b4625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque band" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBand" msgstr "Bandeau opaque" +# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." # Source-Hash: fdf533d6592eff60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow" msgstr "Les bandeaux opaques remplacent une rangée entière par des panneaux pleins. Appliquez leur finition avec l’outil Peinture." +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelCount" msgstr "Nombre de panneaux" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelInfill" msgstr "Remplissage du panneau" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panels" msgstr "Panneaux" +# English: "Panel thickness" # Source-Hash: d24fe036670f54a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelThickness" msgstr "Épaisseur du panneau" +# English: "Reset this panel" # Source-Hash: a780f13a732bab88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset this panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.resetThisPanel" msgstr "Réinitialiser ce panneau" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.row2" msgstr "Rangée" +# English: "Row layout" # Source-Hash: 0e18ec19d0e4ea93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.rowLayout" msgstr "Disposition des rangées" +# English: "Separate framed modules with joints between units." # Source-Hash: dfb58bbd8932811d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate framed modules with joints between units." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween" msgstr "Modules encadrés séparés avec des joints entre unités." +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.solid" msgstr "Plein" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.spacing" msgstr "Espacement" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.start" msgstr "Début" +# English: "Stick-built" # Source-Hash: d3d738ccbc7103ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stick-built" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.stickBuilt" msgstr "Construit sur place" +# English: "Structural glazing" # Source-Hash: fbdb9260ee302d3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural glazing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.structuralGlazing" msgstr "Vitrage structurel" +# English: "Surface roughness" # Source-Hash: 393d5c5ec4e7014c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.surfaceRoughness" msgstr "Rugosité de surface" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.top" msgstr "Haut" +# English: "Top row" # Source-Hash: 51aa1c35a1761178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.topRow" msgstr "Rangée du haut" +# English: "Uncapped members sit behind the glazing." # Source-Hash: 1181faa315f26c83 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncapped members sit behind the glazing." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing" msgstr "Les profilés sans capot sont derrière le vitrage." +# English: "Unitized" # Source-Hash: e68eeb4033268842 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unitized" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.unitized" msgstr "Modulaire" +# English: "Use glass settings instead" # Source-Hash: 355b469b8a9c42f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use glass settings instead" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useGlassSettingsInstead" msgstr "Utiliser plutôt les réglages du vitrage" +# English: "Use the Door or Window tool to add an opening through the wall." # Source-Hash: 9d75fad413e547e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use the Door or Window tool to add an opening through the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useTheDoorOrWindowTool" msgstr "Utilisez l’outil Porte ou Fenêtre pour ajouter une ouverture dans le mur." +# English: "Vertical caps" # Source-Hash: c1fa8c24203a5dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalCaps" msgstr "Couvre-joints verticaux" +# English: "Vertical frame width" # Source-Hash: 0560516882b93f0d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalFrameWidth" msgstr "Largeur du cadre vertical" +# English: "Wall system" # Source-Hash: 4b903041c38cdd72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.wallSystem" msgstr "Système de mur" +# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." # Source-Hash: 6c753f6234766116 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.description" msgstr "Tronçon de mur droit ou courbe. Accueille portes, fenêtres, appentis et éléments muraux." +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.label" msgstr "Mur" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.actions" msgstr "Actions" +# English: "Assembly" # Source-Hash: 67d2ec01cdce2bdc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.assembly" msgstr "Composition" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.auto" msgstr "Auto" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.both" msgstr "Les deux" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.bottom" msgstr "Bas" +# English: "Brick veneer" # Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brick veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.brickVeneer" msgstr "Parement brique" +# English: "Chair rail" # Source-Hash: 32d0bfee628c42a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chair rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.chairRail" msgstr "Cimaise" +# English: "CMU" # Source-Hash: a85f39db8f92f8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "CMU" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cmu" msgstr "Parpaing" +# English: "Colonial" # Source-Hash: f76e125a47ed0612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Colonial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.colonial" msgstr "Colonial" +# English: "Cove" # Source-Hash: a4962c0f39d629ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cove" msgstr "Gorge" +# English: "Craft" # Source-Hash: e5d425095ade6f06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.craft" msgstr "Artisanal" +# English: "Crown molding" # Source-Hash: fa8a6b7f2f4eee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crown molding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.crownMolding" msgstr "Corniche" +# English: "Curtain wall" # Source-Hash: 9736e0a78294e68d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curtainWall" msgstr "Mur-rideau" +# English: "Curve" # Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curve" msgstr "Courbe" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.custom" msgstr "Personnalisé" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.customHeight" msgstr "Hauteur personnalisée" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.depth" msgstr "Profondeur" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.dimensions" msgstr "Dimensions" +# English: "Drywall" # Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drywall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.drywall" msgstr "Plaque de plâtre" +# English: "Extends downward to meet the terrain. Height and top stay unchanged." # Source-Hash: d6ba089e1ca40cdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends downward to meet the terrain. Height and top stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.extendsDownwardToMeetTheTerrain" msgstr "Se prolonge vers le bas jusqu’au terrain. La hauteur et le dessus restent inchangés." +# English: "Exterior" # Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exterior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.exterior" msgstr "Extérieur" +# English: "Fiber cement" # Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fiber cement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fiberCement" msgstr "Fibrociment" +# English: "Fill to terrain" # Source-Hash: a6be391d580de5cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fillToTerrain" msgstr "Remplir jusqu’au terrain" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.flat" msgstr "Plat" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.followsLevel" msgstr "Suit le niveau" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.frameDepth" msgstr "Profondeur du cadre" -# Source-Hash: c7e72beff9fe4b37 +# English: "Framer's default" +# Source-Hash: cb22c8e4b0d92234 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.framersDefault" +msgstr "Par défaut du charpentier" + # English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.framing" msgstr "Ossature" +# English: "Gypsum" # Source-Hash: eda78e8a76f068d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gypsum" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.gypsum" msgstr "Plâtre" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.height" msgstr "Hauteur" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.hideCode" msgstr "Masquer le code" +# English: "ICF" # Source-Hash: 0c6dcbbbda6b4dbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ICF" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.icf" msgstr "Coffrage isolant" +# English: "Interior" # Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Interior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.interior" msgstr "Intérieur" +# English: "Lap siding" # Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lap siding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.lapSiding" msgstr "Bardage à clin" +# English: "Layered" # Source-Hash: 297b21e90e8c25f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layered" msgstr "Superposé" +# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." # Source-Hash: bf279d0f8f1e3b78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layerThicknessesFollowThe2021Irc" msgstr "Les épaisseurs des couches suivent les données de composition de l’IRC 2021. Aide au dessin, pas de l’ingénierie — vérifiez auprès de l’autorité compétente." +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.length" msgstr "Longueur" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.loading" msgstr "Chargement…" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.modern" msgstr "Moderne" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.none" msgstr "Aucun" +# English: "None (single layer)" # Source-Hash: c7300ca33851fba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None (single layer)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.noneSingleLayer" msgstr "Aucun (couche unique)" +# English: "Offset" # Source-Hash: 0f0e13abf9b2bb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.offset" msgstr "Décalage" +# English: "Ogee" # Source-Hash: 04b3006bd4e6e207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ogee" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.ogee" msgstr "Doucine" +# English: "OSB" # Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "OSB" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.osb" msgstr "OSB" +# English: "Partition: the interior finish is applied to both faces." # Source-Hash: 1243161deca7d470 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partition: the interior finish is applied to both faces." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.partitionTheInteriorFinishIsApplied" msgstr "Cloison : la finition intérieure s’applique aux deux faces." +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.picture" msgstr "Cadre" +# English: "Plaster" # Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plaster" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plaster" msgstr "Enduit" +# English: "Plywood" # Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plywood" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plywood" msgstr "Contreplaqué" +# English: "Preset" # Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.preset" msgstr "Préréglage" -# Source-Hash: 62bf2677b9ec30fc +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.railing" +msgstr "Garde-corps" + # English: "Reference" +# Source-Hash: 62bf2677b9ec30fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.reference" msgstr "Référence" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.remove" msgstr "Retirer" +# English: "Round" # Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.round" msgstr "Rond" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.scriptedFinish" msgstr "Finition scriptée" +# English: "Sheathing" # Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheathing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sheathing" msgstr "Voile de contreventement" +# English: "Shoe" # Source-Hash: 6ae9aa7061d8c617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.shoe" msgstr "Quart-de-rond" +# English: "Side A" # Source-Hash: 136762ac1bc96e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side A" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideA" msgstr "Face A" +# English: "Side B" # Source-Hash: 8b9cb16850549c35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side B" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideB" msgstr "Face B" +# English: "Side {face}" # Source-Hash: e6a27f6f8cd489b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side {face}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideFace" msgstr "Face {face}" +# English: "Skirting" # Source-Hash: d9d55c5883a53789 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skirting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.skirting" msgstr "Plinthe" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.standard" msgstr "Standard" +# English: "Steel studs" # Source-Hash: da3e3dd077163ba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steel studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.steelStuds" msgstr "Montants acier" +# English: "Step" # Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.step" msgstr "Gradin" +# English: "Stone veneer" # Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stone veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stoneVeneer" msgstr "Parement pierre" +# English: "Stucco" # Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stucco" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stucco" msgstr "Enduit" -# Source-Hash: fdbb503f0941bd56 +# English: "Studs" +# Source-Hash: 4fce02ee3f69bf99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.studs" +msgstr "Montants" + # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thickness" msgstr "Épaisseur" +# English: "{thickness} · from assembly" # Source-Hash: 00cb7b58f3a10140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{thickness} · from assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thicknessFromAssembly" msgstr "{thickness} · selon la composition" -# Source-Hash: 27644f0497fd9849 +# English: "This project" +# Source-Hash: 9eef4c34dfbc970b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.thisProject" +msgstr "Ce projet" + # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.top" msgstr "Haut" +# English: "Total thickness" # Source-Hash: 3ee03b0e9ae1be4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Total thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.totalThickness" msgstr "Épaisseur totale" +# English: "Unverified: {presetNote}" # Source-Hash: ea7e8bbd87ce4152 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unverified: {presetNote}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.unverifiedPresetnote" msgstr "Non vérifié : {presetNote}" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.viewCode" msgstr "Voir le code" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wall" msgstr "Mur" +# English: "Wall type" # Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wallType" msgstr "Type de mur" +# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." # Source-Hash: e97a456d667465f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.whichFaceIsOutsideIsUndetermined" msgstr "Impossible de savoir quelle face est extérieure (aucune pièce détectée d’un côté ni de l’autre) — les couches extérieures sont dessinées face B." +# English: "Wood studs" # Source-Hash: 31e404a4a4870faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wood studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.woodStuds" msgstr "Montants bois" -# Source-Hash: 5211341ed18f33f6 +# English: "A guard's openings must not pass a 4 in sphere (IRC R312.1.3)." +# Source-Hash: ece946252401f4ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.aGuardSOpeningsMustNot" +msgstr "Les ouvertures d’un garde-corps ne doivent pas laisser passer une sphère de 4 in (IRC R312.1.3)." + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.height" +msgstr "Hauteur" + +# English: "Infill" +# Source-Hash: 7c339a5fae55681d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.infill" +msgstr "Remplissage" + +# English: "2x6 cap" +# Source-Hash: d814a47c4eccee3f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n2x6Cap" +msgstr "Chapeau 2x6" + +# English: "36 in" +# Source-Hash: 137b45940b723a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n36In" +msgstr "36 in" + +# English: "42 in" +# Source-Hash: ebf854ac4dd4daaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n42In" +msgstr "42 in" + +# English: "Posts" +# Source-Hash: ede386e1d510ebfb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.posts" +msgstr "Poteaux" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.railing" +msgstr "Garde-corps" + +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.style" +msgstr "Style" + # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.center" msgstr "Centre" +# English: "Inside face" # Source-Hash: 09576259041494f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.inside" msgstr "Face intérieure" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.left" msgstr "Gauche" +# English: "Outside face" # Source-Hash: 697001376b872e74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.outside" msgstr "Face extérieure" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.right" msgstr "Droite" +# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" # Source-Hash: eac63d14f275db3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.hide" msgstr "{trim, select, skirting {Masquer la plinthe} crown {Masquer la corniche} other {Masquer la cimaise}}" +# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" # Source-Hash: 8db070a4247db221 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.show" msgstr "{trim, select, skirting {Afficher la plinthe} crown {Afficher la corniche} other {Afficher la cimaise}}" +# English: "A window cut into a wall. Animated open/close for opening windows." # Source-Hash: ea6a51e66eb0c0dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A window cut into a wall. Animated open/close for opening windows." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.description" msgstr "Fenêtre découpée dans un mur, avec ouverture animée pour les ouvrants." +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.label" msgstr "Fenêtre" +# English: "Raise" # Source-Hash: 011e4de244050a78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.raise" msgstr "Guillotine" +# English: "Slats" # Source-Hash: 19ca2434d4f7eb87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slats" msgstr "Lames" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slide" msgstr "Coulisser" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.swing" msgstr "Battant" +# English: "Tilt" # Source-Hash: 1615637fe68e188a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.tilt" msgstr "Basculer" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.actions" msgstr "Actions" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.all" msgstr "Tout" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.arch" msgstr "Arc" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.archHeight" msgstr "Hauteur de l’arc" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.auto" msgstr "Auto" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.autoFollowsTheWall" msgstr "Auto suit le mur" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.columns" msgstr "Colonnes" +# English: "Col Widths" # Source-Hash: 2f417371c399ad67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Col Widths" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.colWidths" msgstr "Largeurs de colonne" +# English: "Corner" # Source-Hash: 2ee121dca87bb64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.corner" msgstr "Angle" +# English: "Corner post" # Source-Hash: 04e3425c5ea29359 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerPost" msgstr "Poteau d’angle" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerRadius" msgstr "Rayon d’angle" +# English: "Custom: the panes match no style." # Source-Hash: 67d10b97eff316c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the panes match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.customThePanesMatchNoStyle" msgstr "Personnalisé : les carreaux ne correspondent à aucun style." +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.delete" msgstr "Supprimer" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.depth" msgstr "Profondeur" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.dimensions" msgstr "Dimensions" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.divider" msgstr "Séparateur" +# English: "Down" # Source-Hash: 72c65752a1d1bbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.down" msgstr "Bas" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.duplicate" msgstr "Dupliquer" +# English: "Enable Sill" # Source-Hash: 8497c4373d5d18ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.enableSill" msgstr "Activer l’appui" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.flipSide" msgstr "Inverser le côté" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frame" msgstr "Cadre" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frameDepthAuto" msgstr "Profondeur (auto)" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.french" msgstr "Porte-fenêtre" +# English: "Fused glass" # Source-Hash: a6362b937fffe999 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fused glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.fusedGlass" msgstr "Verre fusionné" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.grid" msgstr "Grille" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.height" msgstr "Hauteur" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.individual" msgstr "Individuel" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.left" msgstr "Gauche" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.move" msgstr "Déplacer" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.opening" msgstr "Ouverture" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.openingShape" msgstr "Forme de l’ouverture" +# English: "Other side" # Source-Hash: cf40395b71bedcca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.otherSide" msgstr "Autre côté" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.pos" msgstr "pos" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.position" msgstr "Position" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rect" msgstr "Rect." +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.revealRadius" msgstr "Rayon de l’ébrasement" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.right" msgstr "Droite" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rounded" msgstr "Arrondi" +# English: "Row Heights" # Source-Hash: fb811056b862b6d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row Heights" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rowHeights" msgstr "Hauteurs de rangée" +# English: "Rows" # Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rows" msgstr "Rangées" +# English: "Sill" # Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sill" msgstr "Appui" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.single" msgstr "Simple" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sizeIsLimitedToTheWall" msgstr "La taille est limitée au mur, dégagement du cadre d’ouverture compris." +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.style" msgstr "Style" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thickness" msgstr "Épaisseur" +# English: "This side" # Source-Hash: 79ff1ae7250c1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thisSide" msgstr "Ce côté" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.topShape" msgstr "Forme du haut" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.type" msgstr "Type" +# English: "Unwrap" # Source-Hash: c29767f86bd9df1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.unwrap" msgstr "Détacher" +# English: "Up" # Source-Hash: 1d732503a54a532f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.up" msgstr "Haut" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.width" msgstr "Largeur" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.window" msgstr "Fenêtre" +# English: "Window Type" # Source-Hash: f59cd625e6082651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.windowType" msgstr "Type de fenêtre" +# English: "Wraps the corner with {partner}" # Source-Hash: 7bf8c4e2b90b3021 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with {partner}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithNamed" msgstr "Fait l’angle avec {partner}" +# English: "Wraps the corner with its partner" # Source-Hash: 8f1f8eff5fbe90e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with its partner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithPartner" msgstr "Fait l’angle avec sa jumelle" +# English: "Wrap the corner" # Source-Hash: cccf75b8c8dc5ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapTheCorner" msgstr "Faire l’angle" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.casement" msgstr "Battant" +# English: "Double-hung" # Source-Hash: e06fc5a432b74a5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.doubleHung" msgstr "Double guillotine" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.grid" msgstr "Grille" +# English: "Horizontal bands" # Source-Hash: fbb293fe8aee84b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.horizontalBands" msgstr "Bandes horizontales" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.picture" msgstr "Cadre" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.single" msgstr "Simple" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.sliding" msgstr "Coulissante" +# English: "Tall grid" # Source-Hash: 0c88aea2bd130193 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tallGrid" msgstr "Grille haute" +# English: "Transom" # Source-Hash: 9628d0cc155e29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.transom" msgstr "Imposte" +# English: "Triple-hung" # Source-Hash: 305f3aba543b1962 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tripleHung" msgstr "Triple guillotine" +# English: "Wide grid" # Source-Hash: 8af77effb76bc6e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.wideGrid" msgstr "Grille large" +# English: "Awning" # Source-Hash: 50393830365aaf2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.awning" msgstr "À l’italienne" +# English: "Bay" # Source-Hash: 8447ee52258e16e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bay" msgstr "Fenêtre en baie" +# English: "Bow" # Source-Hash: e828c89bd0ba1ea4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bow" msgstr "Fenêtre en arc" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.casement" msgstr "Fenêtre battante" +# English: "Double Hung" # Source-Hash: b0b737fc5cb1d32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.doubleHung" msgstr "Guillotine double" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.fixed" msgstr "Fixe" +# English: "Hopper" # Source-Hash: 04a40f27911c3192 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hopper" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.hopper" msgstr "Soufflet" +# English: "Louvered" # Source-Hash: 5746be19ba2a53be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.louvered" msgstr "À lames" +# English: "Single Hung" # Source-Hash: de6ffb1cf676f270 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.singleHung" msgstr "Guillotine simple" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.sliding" msgstr "Coulissante" +# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." # Source-Hash: 4a4d97a4101793b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.description" msgstr "Zone de terrain polygonale (pelouse, eau, revêtement) avec un matériau dégradé TSL." +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.label" msgstr "Zone" +# English: "Enclosed room" # Source-Hash: e9a6d2e71b97e757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enclosed room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.enclosedRoom" msgstr "Pièce fermée" +# English: "Floor surface" # Source-Hash: 3660922f0a8f1d3c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.floorSurface" msgstr "Surface du plancher" +# English: "Footprint only" # Source-Hash: 88c8703c4ff21ee1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint only" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.footprintOnly" msgstr "Emprise seule" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.none" msgstr "Aucun" +# English: "Not proven" # Source-Hash: a9756a3da570c3dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not proven" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.notProven" msgstr "Non prouvé" +# English: "Room quantities" # Source-Hash: ff97d005a536cf58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.roomQuantities" msgstr "Quantités de la pièce" +# English: "Top view with zone edge dimensions" # Source-Hash: f520e6a06fbeb803 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top view with zone edge dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions" msgstr "Vue de dessus avec les cotes des bords de la zone" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.unit" msgstr "Lot" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.volume" msgstr "Volume" +# English: "Wall surface" # Source-Hash: a7941c60a3b6998a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.wallSurface" msgstr "Surface murale" +# English: "Zone boundary unavailable" # Source-Hash: b8b0dbdff53a83ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone boundary unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneBoundaryUnavailable" msgstr "Limite de zone indisponible" +# English: "Zone quantities" # Source-Hash: 6bfb4c6f4d7b0ff2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneQuantities" msgstr "Quantités de la zone" +# English: "Orbit left" # Source-Hash: 8bf25afa3cb8baea # Review: human -# English: "Orbit left" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitLeft" msgstr "Pivoter à gauche" +# English: "Orbit right" # Source-Hash: b9cc64c1d1bb0d46 # Review: human -# English: "Orbit right" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitRight" msgstr "Pivoter à droite" +# English: "Top view" # Source-Hash: 1ab1eb3a14f4b0e0 # Review: human -# English: "Top view" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.topView" msgstr "Vue de dessus" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.off" msgstr "Désactivé" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: human -# English: "On" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.on" msgstr "Activé" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: human -# English: "Camera" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.camera" msgstr "Caméra" +# English: "Colored" # Source-Hash: d3d478fa6c6d7ddc # Review: human -# English: "Colored" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colored" msgstr "En couleur" +# English: "Colors" # Source-Hash: 82b74c4f5ef43336 # Review: human -# English: "Colors" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colors" msgstr "Couleurs" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.off" msgstr "Désactivées" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: human -# English: "No edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.offDetail" msgstr "Aucune ligne d’arête" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: human -# English: "Soft" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.soft" msgstr "Douces" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: human -# English: "Faint outline of major creases" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.softDetail" msgstr "Contour léger des plis principaux" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: human -# English: "Strong" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strong" msgstr "Marquées" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: human -# English: "Crisp, opaque edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strongDetail" msgstr "Lignes d’arête nettes et opaques" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: human -# English: "Edges" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.title" msgstr "Arêtes" +# English: "Monochrome" # Source-Hash: ecde2f7c26914be3 # Review: human -# English: "Monochrome" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.monochrome" msgstr "Monochrome" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: human -# English: "Orthographic" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.orthographic" msgstr "Orthographique" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: human -# English: "Perspective" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.perspective" msgstr "Perspective" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: human -# English: "Rendered" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.rendered" msgstr "Réaliste" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: human -# English: "Full ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.renderedDetail" msgstr "Occlusion ambiante complète" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: human -# English: "Solid" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solid" msgstr "Solide" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: human -# English: "Flat and fast — no ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solidDetail" msgstr "Rapide et uniforme, sans occlusion ambiante" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: human -# English: "Render" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.title" msgstr "Rendu" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: human -# English: "Shadows" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.shadows" msgstr "Ombres" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: human -# English: "Display settings" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.title" msgstr "Paramètres d’affichage" +# English: "Align view to north" # Source-Hash: 2bdf83801d4a40af # Review: human -# English: "Align view to north" #: packages/editor/src/components/viewer/floorplan-compass-button.tsx msgid "viewer.floorplan.alignNorth" msgstr "Orienter la vue vers le nord" +# English: "No floor plans are available for this scene." # Source-Hash: 286a757d8b52bb02 # Review: human -# English: "No floor plans are available for this scene." #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.empty" msgstr "Aucun plan d’étage n’est disponible pour cette scène." +# English: "Fit floor plan" # Source-Hash: bf63c679ab94dbcd # Review: human -# English: "Fit floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.fit" msgstr "Ajuster le plan" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: human -# English: "Floor" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floor" msgstr "Niveau" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: human -# English: "Floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floorPlan" msgstr "Plan d’étage" +# English: "{floor} 2D view" # Source-Hash: 28b255b7bf5f1084 # Review: human -# English: "{floor} 2D view" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.viewLabel" msgstr "Vue 2D — {floor}" +# English: "Zoom in" # Source-Hash: d17804412f8dd081 # Review: human -# English: "Zoom in" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomIn" msgstr "Zoom avant" +# English: "Zoom out" # Source-Hash: 23b012a283b2c14a # Review: human -# English: "Zoom out" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomOut" msgstr "Zoom arrière" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: human -# English: "Back" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.back" msgstr "Retour" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: human -# English: "Levels" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.levels" msgstr "Niveaux" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.rooms" msgstr "Pièces" +# English: "Selection" # Source-Hash: 170b2049ef0f9131 # Review: human -# English: "Selection" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.selection" msgstr "Sélection" +# English: "Units" # Source-Hash: b404dded35241274 # Review: human -# English: "Units" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.units" msgstr "Unités" +# English: "Untitled" # Source-Hash: 6547a4c0c368042a # Review: human -# English: "Untitled" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.untitled" msgstr "Sans titre" +# English: "Zones" # Source-Hash: 665db49a26f81425 # Review: human -# English: "Zones" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.zones" msgstr "Zones" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: human -# English: "Exploded" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.exploded" msgstr "Éclatés" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: human -# English: "Levels: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.label" msgstr "Niveaux : {mode}" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: human -# English: "Manual" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.manual" msgstr "Manuel" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: human -# English: "Solo" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.solo" msgstr "Isolé" +# English: "Stacked" # Source-Hash: c203c84eee18c0f6 # Review: human -# English: "Stacked" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.stacked" msgstr "Superposés" +# English: "Viewer layout" # Source-Hash: 9dd21ca47cffa3b8 # Review: human -# English: "Viewer layout" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.layout" msgstr "Disposition des vues" +# English: "Split" # Source-Hash: e4cd64b1680ac0d2 # Review: human -# English: "Split" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.split" msgstr "Partagé" +# English: "Blueprint" # Source-Hash: f14b89d713d2528a # Review: human -# English: "Blueprint" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.blueprint" msgstr "Plan" +# English: "Mediterranean" # Source-Hash: 142bcb2841f423c5 # Review: human -# English: "Mediterranean" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.mediterranean" msgstr "Méditerranéen" +# English: "Night" # Source-Hash: b399942ec5d7e1ba # Review: human -# English: "Night" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.night" msgstr "Nuit" +# English: "Overcast" # Source-Hash: f512d1b947b8ae5a # Review: human -# English: "Overcast" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.overcast" msgstr "Ciel couvert" +# English: "Paper" # Source-Hash: 583542b3bc837192 # Review: human -# English: "Paper" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.paper" msgstr "Papier" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: human -# English: "Studio" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.studio" msgstr "Studio" +# English: "Sunset" # Source-Hash: efe94d3ec8d8c44e # Review: human -# English: "Sunset" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.sunset" msgstr "Coucher de soleil" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: human -# English: "Scene theme" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.theme.title" msgstr "Ambiance" +# English: "Twilight" # Source-Hash: 4c10ea0aa0e2ea41 # Review: human -# English: "Twilight" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.twilight" msgstr "Crépuscule" +# English: "Verdant" # Source-Hash: f9592fb0672740c0 # Review: human -# English: "Verdant" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.verdant" msgstr "Verdoyant" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: human -# English: "Guides" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.guides" msgstr "Repères" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: human -# English: "Scans" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.scans" msgstr "Relevés 3D" +# English: "Visibility" # Source-Hash: 2cefbb554eadbb6e # Review: human -# English: "Visibility" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.title" msgstr "Visibilité" +# English: "Walkthrough: {state}" # Source-Hash: 31afd9dee47ba943 # Review: human -# English: "Walkthrough: {state}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walkthrough.label" msgstr "Visite : {state}" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: human -# English: "Cutaway" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.cutaway" msgstr "Vue en coupe" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: human -# English: "Full height" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.fullHeight" msgstr "Pleine hauteur" +# English: "Walls: {mode}" # Source-Hash: 32f3d058cf2526d2 # Review: human -# English: "Walls: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.label" msgstr "Murs : {mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: human -# English: "Low" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.low" msgstr "Bas" diff --git a/packages/editor/src/i18n/locales/fr/messages.ts b/packages/editor/src/i18n/locales/fr/messages.ts index 42848704ea..27712d5a95 100644 --- a/packages/editor/src/i18n/locales/fr/messages.ts +++ b/packages/editor/src/i18n/locales/fr/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"Sous-sol\"],\"core.level.basementNumber\":[\"Sous-sol \",[\"number\"]],\"core.level.floorNumber\":[\"Étage \",[\"number\"]],\"core.level.groundFloor\":[\"Rez-de-chaussée\"],\"editor.buildPanel.advanced\":[\"Avancé\"],\"editor.buildPanel.rooms.hint\":[\"murs · sol · plafond\"],\"editor.buildPanel.rooms.idle\":[\"Choisissez une forme pour commencer. Chaque pièce crée ses murs, son sol et son plafond.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" puis de nouveau sur le premier pour fermer. Pour les pièces en L et les angles obliques.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polygone\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Cliquez sur chaque angle,\"],\"editor.buildPanel.rooms.polygon.title\":[\"Pièce polygonale\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" sur la grille. Les murs, le sol et le plafond sont créés ensemble.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectangle\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Cliquez sur deux angles\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Pièce rectangulaire\"],\"editor.buildPanel.rooms.title\":[\"Pièces\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\" Fermez la boucle pour créer une pièce, ou laissez-la ouverte pour une cloison.\"],\"editor.buildPanel.rooms.walls.label\":[\"Murs\"],\"editor.buildPanel.rooms.walls.lead\":[\"Tracez un mur à la fois.\"],\"editor.buildPanel.rooms.walls.title\":[\"Murs\"],\"viewer.camera.orbitLeft\":[\"Pivoter à gauche\"],\"viewer.camera.orbitRight\":[\"Pivoter à droite\"],\"viewer.camera.topView\":[\"Vue de dessus\"],\"viewer.common.off\":[\"Désactivé\"],\"viewer.common.on\":[\"Activé\"],\"viewer.display.camera\":[\"Caméra\"],\"viewer.display.colored\":[\"En couleur\"],\"viewer.display.colors\":[\"Couleurs\"],\"viewer.display.edges.off\":[\"Désactivées\"],\"viewer.display.edges.offDetail\":[\"Aucune ligne d’arête\"],\"viewer.display.edges.soft\":[\"Douces\"],\"viewer.display.edges.softDetail\":[\"Contour léger des plis principaux\"],\"viewer.display.edges.strong\":[\"Marquées\"],\"viewer.display.edges.strongDetail\":[\"Lignes d’arête nettes et opaques\"],\"viewer.display.edges.title\":[\"Arêtes\"],\"viewer.display.monochrome\":[\"Monochrome\"],\"viewer.display.orthographic\":[\"Orthographique\"],\"viewer.display.perspective\":[\"Perspective\"],\"viewer.display.render.rendered\":[\"Réaliste\"],\"viewer.display.render.renderedDetail\":[\"Occlusion ambiante complète\"],\"viewer.display.render.solid\":[\"Solide\"],\"viewer.display.render.solidDetail\":[\"Rapide et uniforme, sans occlusion ambiante\"],\"viewer.display.render.title\":[\"Rendu\"],\"viewer.display.shadows\":[\"Ombres\"],\"viewer.display.title\":[\"Paramètres d’affichage\"],\"viewer.floorplan.alignNorth\":[\"Orienter la vue vers le nord\"],\"viewer.floorplan.empty\":[\"Aucun plan d’étage n’est disponible pour cette scène.\"],\"viewer.floorplan.fit\":[\"Ajuster le plan\"],\"viewer.floorplan.floor\":[\"Niveau\"],\"viewer.floorplan.floorPlan\":[\"Plan d’étage\"],\"viewer.floorplan.viewLabel\":[\"Vue 2D — \",[\"floor\"]],\"viewer.floorplan.zoomIn\":[\"Zoom avant\"],\"viewer.floorplan.zoomOut\":[\"Zoom arrière\"],\"viewer.header.back\":[\"Retour\"],\"viewer.header.levels\":[\"Niveaux\"],\"viewer.header.rooms\":[\"Pièces\"],\"viewer.header.selection\":[\"Sélection\"],\"viewer.header.units\":[\"Unités\"],\"viewer.header.untitled\":[\"Sans titre\"],\"viewer.header.zones\":[\"Zones\"],\"viewer.levels.exploded\":[\"Éclatés\"],\"viewer.levels.label\":[\"Niveaux : \",[\"mode\"]],\"viewer.levels.manual\":[\"Manuel\"],\"viewer.levels.solo\":[\"Isolé\"],\"viewer.levels.stacked\":[\"Superposés\"],\"viewer.stage.layout\":[\"Disposition des vues\"],\"viewer.stage.split\":[\"Partagé\"],\"viewer.theme.blueprint\":[\"Plan\"],\"viewer.theme.mediterranean\":[\"Méditerranéen\"],\"viewer.theme.night\":[\"Nuit\"],\"viewer.theme.overcast\":[\"Ciel couvert\"],\"viewer.theme.paper\":[\"Papier\"],\"viewer.theme.studio\":[\"Studio\"],\"viewer.theme.sunset\":[\"Coucher de soleil\"],\"viewer.theme.title\":[\"Ambiance\"],\"viewer.theme.twilight\":[\"Crépuscule\"],\"viewer.theme.verdant\":[\"Verdoyant\"],\"viewer.visibility.guides\":[\"Repères\"],\"viewer.visibility.scans\":[\"Relevés 3D\"],\"viewer.visibility.title\":[\"Visibilité\"],\"viewer.walkthrough.label\":[\"Visite : \",[\"state\"]],\"viewer.walls.cutaway\":[\"Vue en coupe\"],\"viewer.walls.fullHeight\":[\"Pleine hauteur\"],\"viewer.walls.label\":[\"Murs : \",[\"mode\"]],\"viewer.walls.low\":[\"Bas\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"Sous-sol\"],\"core.level.basementNumber\":[\"Sous-sol \",[\"number\"]],\"core.level.floorNumber\":[\"Étage \",[\"number\"]],\"core.level.groundFloor\":[\"Rez-de-chaussée\"],\"editor.buildPanel.advanced\":[\"Avancé\"],\"editor.buildPanel.rooms.hint\":[\"murs · sol · plafond\"],\"editor.buildPanel.rooms.idle\":[\"Choisissez une forme pour commencer. Chaque pièce crée ses murs, son sol et son plafond.\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\" puis de nouveau sur le premier pour fermer. Pour les pièces en L et les angles obliques.\"],\"editor.buildPanel.rooms.polygon.label\":[\"Polygone\"],\"editor.buildPanel.rooms.polygon.lead\":[\"Cliquez sur chaque angle,\"],\"editor.buildPanel.rooms.polygon.title\":[\"Pièce polygonale\"],\"editor.buildPanel.rooms.rectangle.caption\":[[\"lead\"],\" sur la grille. Les murs, le sol et le plafond sont créés ensemble.\"],\"editor.buildPanel.rooms.rectangle.label\":[\"Rectangle\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"Cliquez sur deux angles\"],\"editor.buildPanel.rooms.rectangle.title\":[\"Pièce rectangulaire\"],\"editor.buildPanel.rooms.title\":[\"Pièces\"],\"editor.buildPanel.rooms.walls.label\":[\"Murs\"],\"editor.buildPanel.rooms.walls.title\":[\"Murs\"],\"viewer.camera.orbitLeft\":[\"Pivoter à gauche\"],\"viewer.camera.orbitRight\":[\"Pivoter à droite\"],\"viewer.camera.topView\":[\"Vue de dessus\"],\"viewer.common.off\":[\"Désactivé\"],\"viewer.common.on\":[\"Activé\"],\"viewer.display.camera\":[\"Caméra\"],\"viewer.display.colored\":[\"En couleur\"],\"viewer.display.colors\":[\"Couleurs\"],\"viewer.display.edges.off\":[\"Désactivées\"],\"viewer.display.edges.offDetail\":[\"Aucune ligne d’arête\"],\"viewer.display.edges.soft\":[\"Douces\"],\"viewer.display.edges.softDetail\":[\"Contour léger des plis principaux\"],\"viewer.display.edges.strong\":[\"Marquées\"],\"viewer.display.edges.strongDetail\":[\"Lignes d’arête nettes et opaques\"],\"viewer.display.edges.title\":[\"Arêtes\"],\"viewer.display.monochrome\":[\"Monochrome\"],\"viewer.display.orthographic\":[\"Orthographique\"],\"viewer.display.perspective\":[\"Perspective\"],\"viewer.display.render.rendered\":[\"Réaliste\"],\"viewer.display.render.renderedDetail\":[\"Occlusion ambiante complète\"],\"viewer.display.render.solid\":[\"Solide\"],\"viewer.display.render.solidDetail\":[\"Rapide et uniforme, sans occlusion ambiante\"],\"viewer.display.render.title\":[\"Rendu\"],\"viewer.display.shadows\":[\"Ombres\"],\"viewer.display.title\":[\"Paramètres d’affichage\"],\"viewer.floorplan.alignNorth\":[\"Orienter la vue vers le nord\"],\"viewer.floorplan.empty\":[\"Aucun plan d’étage n’est disponible pour cette scène.\"],\"viewer.floorplan.fit\":[\"Ajuster le plan\"],\"viewer.floorplan.floor\":[\"Niveau\"],\"viewer.floorplan.floorPlan\":[\"Plan d’étage\"],\"viewer.floorplan.viewLabel\":[\"Vue 2D — \",[\"floor\"]],\"viewer.floorplan.zoomIn\":[\"Zoom avant\"],\"viewer.floorplan.zoomOut\":[\"Zoom arrière\"],\"viewer.header.back\":[\"Retour\"],\"viewer.header.levels\":[\"Niveaux\"],\"viewer.header.rooms\":[\"Pièces\"],\"viewer.header.selection\":[\"Sélection\"],\"viewer.header.units\":[\"Unités\"],\"viewer.header.untitled\":[\"Sans titre\"],\"viewer.header.zones\":[\"Zones\"],\"viewer.levels.exploded\":[\"Éclatés\"],\"viewer.levels.label\":[\"Niveaux : \",[\"mode\"]],\"viewer.levels.manual\":[\"Manuel\"],\"viewer.levels.solo\":[\"Isolé\"],\"viewer.levels.stacked\":[\"Superposés\"],\"viewer.stage.layout\":[\"Disposition des vues\"],\"viewer.stage.split\":[\"Partagé\"],\"viewer.theme.blueprint\":[\"Plan\"],\"viewer.theme.mediterranean\":[\"Méditerranéen\"],\"viewer.theme.night\":[\"Nuit\"],\"viewer.theme.overcast\":[\"Ciel couvert\"],\"viewer.theme.paper\":[\"Papier\"],\"viewer.theme.studio\":[\"Studio\"],\"viewer.theme.sunset\":[\"Coucher de soleil\"],\"viewer.theme.title\":[\"Ambiance\"],\"viewer.theme.twilight\":[\"Crépuscule\"],\"viewer.theme.verdant\":[\"Verdoyant\"],\"viewer.visibility.guides\":[\"Repères\"],\"viewer.visibility.scans\":[\"Relevés 3D\"],\"viewer.visibility.title\":[\"Visibilité\"],\"viewer.walkthrough.label\":[\"Visite : \",[\"state\"]],\"viewer.walls.cutaway\":[\"Vue en coupe\"],\"viewer.walls.fullHeight\":[\"Pleine hauteur\"],\"viewer.walls.label\":[\"Murs : \",[\"mode\"]],\"viewer.walls.low\":[\"Bas\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/i18n/locales/ja/messages.po b/packages/editor/src/i18n/locales/ja/messages.po index 3897362781..d1a7276fe6 100644 --- a/packages/editor/src/i18n/locales/ja/messages.po +++ b/packages/editor/src/i18n/locales/ja/messages.po @@ -13,24551 +13,28858 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" +# English: "Pascal home" # Source-Hash: 8ffb1c5066e53a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pascal home" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/app-home-link.tsx #, fuzzy msgid "app.appShell.pascalHome" msgstr "Pascal ホーム" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addGate" msgstr "門を追加" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addOpenPassage" msgstr "通路を追加" +# English: "Add to rooms" # Source-Hash: 9a4d56454dc89966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addToRooms" msgstr "部屋に追加" +# English: "← All build tools" # Source-Hash: a17210912addc85f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "← All build tools" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.allBuildTools" msgstr "← すべての建築ツール" +# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." # Source-Hash: 6b63404967e1d5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.chooseGateOrOpenPassageThen" msgstr "門または通路を選び、フェンス上の位置をクリックします。配置した門と開口はシーングラフで各フェンスの下にあります。" +# English: "Click a lineset to lay the line beside it." # Source-Hash: 8fd051e1e1a6c1a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a lineset to lay the line beside it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.clickALinesetToLayThe" msgstr "冷媒配管をクリックして、その横に配管します。" +# English: "Double gate" # Source-Hash: 7308869944020839 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.doubleGate" msgstr "両開き門" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.featuresExtensions" msgstr "部材と増築" +# English: "Fence features" # Source-Hash: b18788f1d889c1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence features" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.fenceFeatures" msgstr "フェンスの部材" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.followLineset" msgstr "冷媒配管に沿う" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gate" msgstr "門" +# English: "Gate style" # Source-Hash: 54098b5ee574737a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gateStyle" msgstr "門のスタイル" +# English: "Horizontal boards" # Source-Hash: 8fec7e969045a8e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.horizontalBoards" msgstr "横板" +# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." # Source-Hash: 09197c4fea516277 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.hoverAFenceToPreviewClick" msgstr "フェンスにホバーしてプレビュー。クリックで配置、Esc でキャンセル。開口の間は空けてください。" +# English: "Leaves" # Source-Hash: c390ba248e629b9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leaves" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.leaves" msgstr "扉" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.liquidLine" msgstr "液管" +# English: "Match fence style" # Source-Hash: c15c1af69ec5c1eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match fence style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.matchFenceStyle" msgstr "フェンスのスタイルに合わせる" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.mep" msgstr "設備" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.off" msgstr "オフ" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.on" msgstr "オン" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.opening" msgstr "開口" +# English: "Opening width (m)" # Source-Hash: 9514d3c022e68808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening width (m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openingWidthM" msgstr "開口幅(m)" +# English: "Open rails" # Source-Hash: 6b108fb1d0eb8f9d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rails" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openRails" msgstr "横桟" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.outdoor" msgstr "屋外" +# English: "Picket" # Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.picket" msgstr "ピケット" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roofType" msgstr "屋根の種類" +# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." # Source-Hash: 0f9daa442776a317 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roomsAlreadyCreateTheirFloorAnd" msgstr "部屋は床と天井を自動で作ります。これらは台やその他の単独の構造に使います。" +# English: "Search build tools and kitchen items" # Source-Hash: fc79fd0c7730de76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search build tools and kitchen items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchBuildToolsAndKitchenItems" msgstr "建築ツールとキッチンアイテムを検索" +# English: "Search tools, fridge, sink…" # Source-Hash: dc1901f276edca6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search tools, fridge, sink…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchToolsFridgeSink" msgstr "ツール、冷蔵庫、シンクを検索…" +# English: "Select a curved wall to match its radius and arc." # Source-Hash: 9558db89023d4f05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a curved wall to match its radius and arc." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.selectACurvedWallToMatch" msgstr "曲面の壁を選ぶと、その半径と円弧に合わせます。" +# English: "Single gate" # Source-Hash: d327097d9ac1099f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.singleGate" msgstr "片開き門" +# English: "Slab, ceiling, column…" # Source-Hash: 99acd5128c51c8a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab, ceiling, column…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.slabCeilingColumn" msgstr "スラブ、天井、柱…" +# English: "Solid privacy" # Source-Hash: af6f8885fe0fa4e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid privacy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.solidPrivacy" msgstr "目隠し" +# English: "Trace a line alongside an existing lineset (F)." # Source-Hash: e2b1a3bb9630b33b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace a line alongside an existing lineset (F)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.traceALineAlongsideAnExisting" msgstr "既存の冷媒配管に沿って配管 (F)。" +# English: "Vertical slats" # Source-Hash: eba3df14a2de0812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.verticalSlats" msgstr "縦板" +# English: "All projects" # Source-Hash: 2ee62be78ba76f53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All projects" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.allProjects" msgstr "すべてのプロジェクト" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.preview" msgstr "プレビュー" +# English: "Project name" # Source-Hash: 9f2cbc9214c8f7b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.projectName" msgstr "プロジェクト名" +# English: "Rename project" # Source-Hash: 150e143a84240eee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.renameProject" msgstr "プロジェクト名を変更" +# English: "Share" # Source-Hash: 331e1584943b09b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.share" msgstr "共有" +# English: "Add {label}" # Source-Hash: 4b2f676fb4c9ec63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addLabel" msgstr "{label}を追加" +# English: "Add to kitchen" # Source-Hash: 53f98cc505aacbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addToKitchen" msgstr "キッチンに追加" +# English: "Choose an item, then click to place it. Appliances include their cabinetry." # Source-Hash: 923a2e81dcc77852 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose an item, then click to place it. Appliances include their cabinetry." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.chooseAnItemThenClickTo" msgstr "アイテムを選び、クリックして配置します。家電には収納も含まれます。" +# English: "Find in this project · {length}" # Source-Hash: 44156a2c888e8a4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in this project · {length}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findInThisProjectLength" msgstr "このプロジェクト内を検索 · {length}" +# English: "Find {name}" # Source-Hash: aeda65db67edac21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find {name}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findName" msgstr "{name}を探す" +# English: "Kitchen · Add" # Source-Hash: c1d847411ba09cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · Add" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupAdd" msgstr "キッチン · 追加" +# English: "Kitchen · In this project" # Source-Hash: bb3f4f8d3407c409 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · In this project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupInThisProject" msgstr "キッチン · このプロジェクト内" +# English: "Kitchen cabinet" # Source-Hash: 25ffad31086309f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.kitchenCabinet" msgstr "キッチンキャビネット" +# English: "No matching kitchen items placed yet." # Source-Hash: a5af7817a7e8947d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No matching kitchen items placed yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.noMatchingKitchenItemsPlacedYet" msgstr "該当するキッチンアイテムはまだ配置されていません。" +# English: "Select and show in scene" # Source-Hash: 640155f99c25773b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select and show in scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.selectAndShowInScene" msgstr "選択してシーンに表示" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.ceiling" msgstr "天井" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.column" msgstr "柱" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.door" msgstr "ドア" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.duct" msgstr "ダクト" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.dwvPipe" msgstr "排水管" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.elevator" msgstr "エレベーター" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.fence" msgstr "フェンス" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.hvacUnit" msgstr "空調機" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.kitchen" msgstr "キッチン" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.lineset" msgstr "冷媒配管" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.liquidLine" msgstr "液管" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.mep" msgstr "設備" -# Source-Hash: 161e5386b939e1bc +# English: "Post" +# Source-Hash: 5cc981e79d737694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/lib/build-palette.ts +#, fuzzy +msgid "app.lib.buildPalette.post" +msgstr "支柱" + # English: "Register" +# Source-Hash: 161e5386b939e1bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.register" msgstr "吹出口" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.roof" msgstr "屋根" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.shelf" msgstr "棚" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.slab" msgstr "スラブ" +# English: "Spawn point" # Source-Hash: 95e8af5d2b3ca73d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.spawnPoint" msgstr "スポーン地点" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.stairs" msgstr "階段" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrace" msgstr "テラス" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrain" msgstr "地形" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.wall" msgstr "壁" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.window" msgstr "窓" +# English: "Add Fitting" # Source-Hash: d40c50013043284f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addFitting" msgstr "継手を追加" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addGate" msgstr "門を追加" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addOpenPassage" msgstr "通路を追加" +# English: "Add Trap" # Source-Hash: 7136b5836de6e69f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addTrap" msgstr "トラップを追加" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.build" msgstr "建築" +# English: "Create from" # Source-Hash: a23831beeb35ad3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Create from" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.createFrom" msgstr "作成元" +# English: "Draw the roof footprint with two corner clicks." # Source-Hash: 8146c309bd8326f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the roof footprint with two corner clicks." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.drawTheRoofFootprintWithTwo" msgstr "2つの角をクリックして屋根の外形を描きます。" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.duct" msgstr "ダクト" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.dwvPipe" msgstr "排水管" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.featuresExtensions" msgstr "部材と増築" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.fence" msgstr "フェンス" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.followLineset" msgstr "冷媒配管に沿う" +# English: "Gates & openings" # Source-Hash: c80972ec078ac551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gates & openings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.gatesOpenings" msgstr "門と開口" +# English: "Hover a room to preview its boundary, then click to place." # Source-Hash: 3720347aa16ee777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a room to preview its boundary, then click to place." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.hoverARoomToPreviewIts" msgstr "部屋にホバーして境界をプレビューし、クリックで配置します。" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.kitchen" msgstr "キッチン" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.mep" msgstr "設備" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.modularCabinet" msgstr "モジュール式キャビネット" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.off" msgstr "オフ" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.on" msgstr "オン" +# English: "Painting" # Source-Hash: 3fafdc69e7756f19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Painting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.painting" msgstr "塗装" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roof" msgstr "屋根" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roofType" msgstr "屋根の種類" +# English: "{count, plural, one {# tool} other {# tools}}" # Source-Hash: 0c35bee851cc57a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# tool} other {# tools}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.toolCount" msgstr "{count, plural, other {ツール # 個}}" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.conical" msgstr "円錐" +# English: "Draw" # Source-Hash: b5c56dd8c68de309 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.draw" msgstr "描画" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.dutch" msgstr "入母屋" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.flat" msgstr "陸屋根" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gable" msgstr "切妻" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gambrel" msgstr "腰折れ" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.hip" msgstr "寄棟" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.mansard" msgstr "マンサード" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.room" msgstr "部屋" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.shed" msgstr "片流れ" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.wall" msgstr "壁" +# English: "Another session saved first — refresh?" # Source-Hash: 8d17e7556ce142da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Another session saved first — refresh?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.anotherSessionSavedFirstRefresh" msgstr "別のセッションが先に保存しました。更新しますか?" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.build" msgstr "建築" +# English: "Dismiss" # Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.dismiss" msgstr "閉じる" +# English: "Items" # Source-Hash: daebc0755d239c99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.items" msgstr "アイテム" +# English: "Paint" # Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.paint" msgstr "塗装" +# English: "Reload" # Source-Hash: 854555777b69a4b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.reload" msgstr "再読み込み" +# English: "Scene" # Source-Hash: 8dc78e215a97d9fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.scene" msgstr "シーン" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.settings" msgstr "設定" +# English: "Your changes haven't been saved. Reload to pick up the latest version." # Source-Hash: ee255fa26d076dcc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Your changes haven't been saved. Reload to pick up the latest version." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.yourChangesHavenTBeenSaved" msgstr "変更は保存されていません。再読み込みして最新バージョンを取得してください。" +# English: "Anyone with the link can view this project. They cannot edit it." # Source-Hash: 703e1b4b9b715d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Anyone with the link can view this project. They cannot edit it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.anyoneWithTheLinkCanView" msgstr "リンクを知っている人は誰でもこのプロジェクトを閲覧できます。編集はできません。" +# English: "Copied" # Source-Hash: 07e04484867c17d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copied" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copied" msgstr "コピーしました" +# English: "Copy" # Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copy" msgstr "コピー" +# English: "Could not read the sharing state." # Source-Hash: 8ff50e1a9962a4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not read the sharing state." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotReadTheSharingState" msgstr "共有状態を読み込めませんでした。" +# English: "Could not turn off the public link." # Source-Hash: 791e281338a51483 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn off the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOffThePublic" msgstr "公開リンクをオフにできませんでした。" +# English: "Could not turn on the public link." # Source-Hash: 2c5ddecfd652ba63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn on the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOnThePublic" msgstr "公開リンクをオンにできませんでした。" +# English: "Embed code" # Source-Hash: 15e49d4da8765318 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedCode" msgstr "埋め込みコード" +# English: "Embed link" # Source-Hash: d4523c2d303f99b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedLink" msgstr "埋め込みリンク" +# English: "Hide controls" # Source-Hash: 252f2cbf5e1609a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.hideControls" msgstr "コントロールを非表示" +# English: "Link" # Source-Hash: ba77c0610a63bb2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.link" msgstr "リンク" +# English: "Only the 3D view, no header or toolbar." # Source-Hash: a2560430aaec31d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Only the 3D view, no header or toolbar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.onlyThe3dViewNoHeader" msgstr "3D ビューのみ。ヘッダーとツールバーは表示しません。" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.open" msgstr "開く" +# English: "Public link" # Source-Hash: b34faaa5378056c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Public link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.publicLink" msgstr "公開リンク" +# English: "Share project" # Source-Hash: 7525e8007fcab09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.shareProject" msgstr "プロジェクトを共有" +# English: "Automatic dimensions" # Source-Hash: 3d3f438d5a49eba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.automaticDimensions" msgstr "自動寸法" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.camera" msgstr "カメラ" -# Source-Hash: e6bce525c47ba3ac +# English: "Ceilings" +# Source-Hash: a03ae72f209e32db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/components/viewer-toolbar.tsx +#, fuzzy +msgid "app.viewerToolbar.ceilings" +msgstr "天井" + # English: "Clean plan; dimensions appear with selection" +# Source-Hash: e6bce525c47ba3ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cleanPlanDimensionsAppearWithSelection" msgstr "すっきりした図面。寸法は選択時に表示" +# English: "Collapse sidebar" # Source-Hash: 61ce356577cfb04a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.collapseSidebar" msgstr "サイドバーを折りたたむ" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crisp, opaque edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.crispOpaqueEdgeLines" msgstr "くっきりした不透明なエッジ線" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cutaway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cutaway" msgstr "カットアウェイ" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.default" msgstr "標準" +# English: "Display" # Source-Hash: a313661ef9dd0a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.display" msgstr "表示" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.displaySettings" msgstr "表示設定" +# English: "Door/window marks" # Source-Hash: 0286962ab8e9cfcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door/window marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.doorWindowMarks" msgstr "建具記号" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edges" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.edges" msgstr "エッジ" +# English: "Enter VR" # Source-Hash: d86126c5d16c6238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.enterVr" msgstr "VR を開始" +# English: "Exit VR" # Source-Hash: eb99fb0b382e58cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exitVr" msgstr "VR を終了" +# English: "Expand sidebar" # Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expandSidebar" msgstr "サイドバーを展開" +# English: "Expert" # Source-Hash: 08c178ee733ff009 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expert" msgstr "エキスパート" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exploded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exploded" msgstr "分解" +# English: "Face of stud" # Source-Hash: c6f865a366ef5755 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face of stud" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faceOfStud" msgstr "下地面" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Faint outline of major creases" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faintOutlineOfMajorCreases" msgstr "主な折れ目の淡い輪郭" +# English: "Feet & inches" # Source-Hash: 08ecd5878b08139d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Feet & inches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.feetInches" msgstr "フィート・インチ" +# English: "Finished faces" # Source-Hash: 43b8017fa821c46c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finished faces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.finishedFaces" msgstr "仕上げ面" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat and fast — no ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.flatAndFastNoAmbientOcclusion" msgstr "フラットで高速 — アンビエントオクルージョンなし" +# English: "Floor plan annotations" # Source-Hash: 060a718cdcff61b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanAnnotations" msgstr "平面図の注記" +# English: "Floor plan mode" # Source-Hash: 483171f79dc32ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanMode" msgstr "平面図モード" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullAmbientOcclusion" msgstr "フルアンビエントオクルージョン" +# English: "Full documentation and annotation controls" # Source-Hash: 91fd3544d9ce16bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full documentation and annotation controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullDocumentationAndAnnotationControls" msgstr "図面化と注記の全コントロール" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullHeight" msgstr "全高" +# English: "Full wall thickness" # Source-Hash: c9838bc98cb35125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullWallThickness" msgstr "壁の全厚" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.grid" msgstr "グリッド" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels: {mode}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.levelsMode" msgstr "階層: {mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.low" msgstr "低" +# English: "Magnetic snap" # Source-Hash: 32f9a404c0bdce10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Magnetic snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.magneticSnap" msgstr "マグネットスナップ" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manual" msgstr "手動" +# English: "Manual dimensions" # Source-Hash: 2fb69122dd996c0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manualDimensions" msgstr "手動寸法" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.measurements" msgstr "計測" +# English: "Meters" # Source-Hash: 0a39470be3b84536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Meters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.meters" msgstr "メートル" +# English: "Millimeters" # Source-Hash: 83cce6c521e71d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Millimeters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.millimeters" msgstr "ミリメートル" +# English: "3D measurements" # Source-Hash: b784527bb90b9c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.n3dMeasurements" msgstr "3D 計測" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.noEdgeLines" msgstr "エッジ線なし" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.off" msgstr "オフ" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.on" msgstr "オン" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orthographic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.orthographic" msgstr "平行投影" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perspective" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.perspective" msgstr "透視投影" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.preview" msgstr "プレビュー" +# English: "Preview mode" # Source-Hash: 34c20b72a42ca618 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.previewMode" msgstr "プレビューモード" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.render" msgstr "レンダリング" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rendered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.rendered" msgstr "レンダリング" +# English: "Room labels" # Source-Hash: 4959d5538efc595c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.roomLabels" msgstr "部屋ラベル" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene theme" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.sceneTheme" msgstr "シーンテーマ" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shadows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.shadows" msgstr "影" +# English: "Single wall axis" # Source-Hash: 59dc62417b44931d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single wall axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.singleWallAxis" msgstr "壁の中心線" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Soft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.soft" msgstr "ソフト" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solid" msgstr "ソリッド" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solo" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solo" msgstr "単独" +# English: "Stack" # Source-Hash: 481b7608ce12139b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stack" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stack" msgstr "積み重ね" +# English: "Stair annotations" # Source-Hash: 61ad94ca68b0458e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stairAnnotations" msgstr "階段の注記" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strong" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.strong" msgstr "強" +# English: "Structural core face" # Source-Hash: e1be031e67d95382 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural core face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralCoreFace" msgstr "構造躯体面" +# English: "Structural grids & column centers" # Source-Hash: efc50964d7b0ec47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural grids & column centers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralGridsColumnCenters" msgstr "構造通り芯と柱芯" +# English: "Translucent" # Source-Hash: 5ce4f6b80cd30f86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Translucent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.translucent" msgstr "半透明" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.units" msgstr "単位" +# English: "Walkthrough" # Source-Hash: faf51e28b748e0d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walkthrough" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.walkthrough" msgstr "ウォークスルー" +# English: "Wall centerline" # Source-Hash: 62281975e51da7e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall centerline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallCenterline" msgstr "壁芯" +# English: "Wall dimensions" # Source-Hash: ff145d7003118ec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallDimensions" msgstr "壁の寸法" +# English: "Walls: {label}" # Source-Hash: 13969d1d6e310aa4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls: {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallsLabel" msgstr "壁: {label}" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: human -# English: "Basement" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basement" msgstr "地下1階" +# English: "Basement {number}" # Source-Hash: 14927085ee91ba05 # Review: human -# English: "Basement {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basementNumber" msgstr "地下{number}階" +# English: "Floor {number}" # Source-Hash: e59074a7b9b3f56e # Review: human -# English: "Floor {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.floorNumber" msgstr "{number}階" +# English: "Ground floor" # Source-Hash: cde3efc9b7ddfa41 # Review: human -# English: "Ground floor" #: packages/editor/src/i18n/level-name.ts msgid "core.level.groundFloor" msgstr "1階" +# English: "Orbit Left" # Source-Hash: fdd8b3366021a1cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitLeft" msgstr "左に回る" +# English: "Orbit Right" # Source-Hash: 4a56359f284a3b17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitRight" msgstr "右に回る" +# English: "Top View" # Source-Hash: f5c12d738240f889 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top View" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.topView" msgstr "上面図" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.delete" msgstr "削除" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.select" msgstr "選択" +# English: "Resume following" # Source-Hash: d9a257653600d5a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resume following" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-pill.tsx #, fuzzy msgid "editor.actionMenu.followPascalPill.resumeFollowing" msgstr "追従を再開" +# English: "Follow Pascal: Off" # Source-Hash: 4f073f8051cca3ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.off" msgstr "Pascal を追従: オフ" +# English: "Follow Pascal: On" # Source-Hash: 328ea04bb8cd12f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.on" msgstr "Pascal を追従: オン" +# English: "Appliance" # Source-Hash: 7f4dcb90defe1ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appliance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.appliance" msgstr "家電" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.bathroom" msgstr "浴室" +# English: "Furniture" # Source-Hash: ed68e72f5353712b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furniture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.furniture" msgstr "家具" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.kitchen" msgstr "キッチン" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.outdoor" msgstr "屋外" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angle" msgstr "角度" +# English: "Angular dimension" # Source-Hash: 622f566fe1e3d8e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angular dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angularDimension" msgstr "角度寸法" +# English: "Arc length" # Source-Hash: 7e3b0d541355305e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.arcLength" msgstr "弧長" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.area" msgstr "面積" +# English: "Center mark" # Source-Hash: 88c7750ed51d47a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center mark" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.centerMark" msgstr "中心マーク" +# English: "Chord dimension" # Source-Hash: 634912dabb092872 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chord dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.chordDimension" msgstr "弦の寸法" +# English: "Continuous dimension" # Source-Hash: 3cb090120253f68f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.continuousDimension" msgstr "連続寸法" +# English: "Coordinate dimensions" # Source-Hash: fb91b1d43b8362d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coordinate dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.coordinateDimensions" msgstr "座標寸法" +# English: "Diameter dimension" # Source-Hash: 530cb7ea461e8298 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.diameterDimension" msgstr "直径寸法" +# English: "Distance" # Source-Hash: fe7ddb094aa8ff4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Distance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.distance" msgstr "距離" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.floorPlan" msgstr "平面図" +# English: "Linear dimension" # Source-Hash: a73539fc472e2e12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Linear dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.linearDimension" msgstr "直線寸法" +# English: "Measurement options" # Source-Hash: 6bba239f244c3d28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementOptions" msgstr "計測オプション" +# English: "Measurement type" # Source-Hash: 2cf5ca9b834ce8d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementType" msgstr "計測の種類" +# English: "Measure: {selectedLabel}" # Source-Hash: 0431574a0e6d69d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measure: {selectedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measureSelectedlabel" msgstr "計測: {selectedLabel}" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.perimeter" msgstr "周長" +# English: "Radius dimension" # Source-Hash: 6c9acc0589bdd4d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Radius dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.radiusDimension" msgstr "半径寸法" +# English: "Smart" # Source-Hash: 01b0015e4e176124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.smart" msgstr "スマート" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.volume" msgstr "体積" +# English: "Point and ask" # Source-Hash: 64f3eb5e1936906d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAsk" msgstr "指して質問" +# English: "Point and ask (C)" # Source-Hash: d86d31e83f6f0888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask (C)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskC" msgstr "指して質問 (C)" +# English: "Point and ask works in the 3D view" # Source-Hash: 65622d2ef53c1f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask works in the 3D view" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskWorksInThe" msgstr "指して質問は 3D ビューで使えます" -# Source-Hash: 4275c05172abd7fb +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.beam" +msgstr "梁" + # English: "Ceiling" +# Source-Hash: 4275c05172abd7fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ceiling" msgstr "天井" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.column" msgstr "柱" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.door" msgstr "ドア" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.duct" msgstr "ダクト" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ductFitting" msgstr "ダクト継手" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.dwvPipe" msgstr "排水管" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.elevator" msgstr "エレベーター" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.fence" msgstr "フェンス" -# Source-Hash: 7f4d93d5f2083844 +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.footing" +msgstr "フーチング" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.foundation" +msgstr "基礎" + # English: "Gable Roof" +# Source-Hash: 7f4d93d5f2083844 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.gableRoof" msgstr "切妻屋根" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.hvacUnit" msgstr "空調機" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.lineset" msgstr "冷媒配管" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.liquidLine" msgstr "液管" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.pipeFitting" msgstr "配管継手" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.register" msgstr "吹出口" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.shelf" msgstr "棚" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.slab" msgstr "スラブ" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.spawnPoint" msgstr "スポーン地点" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.stairs" msgstr "階段" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.trap" msgstr "トラップ" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.wall" msgstr "壁" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.window" msgstr "窓" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.zone" msgstr "ゾーン" -# Source-Hash: cdf3f12939ba108c +# English: "Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it." +# Source-Hash: a309ae5ee641f072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels" +msgstr "隣接する階層の壁を破線で表示します。壁の位置をそろえたり、屋根を下の壁と照らし合わせたりするのに使います。" + # English: "Delete guide image" +# Source-Hash: cdf3f12939ba108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteGuideImage" msgstr "ガイド画像を削除" +# English: "Delete scan" # Source-Hash: 6add06abbafa8289 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete scan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteScan" msgstr "3Dスキャンを削除" +# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" # Source-Hash: 6f5e541cb5fb502f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideCount" msgstr "{count, plural, other {この階層のガイド画像 # 件}}" +# English: "Guide image {number}" # Source-Hash: 8a832a2f33757bf8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageNumber" msgstr "ガイド画像 {number}" +# English: "Guide images" # Source-Hash: 7f3aaccfe8d1af1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImages" msgstr "ガイド画像" +# English: "Guide image settings" # Source-Hash: 9b1de02505434d74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageSettings" msgstr "ガイド画像の設定" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guides" msgstr "ガイド" +# English: "Guides: Hidden" # Source-Hash: ccfb05c78d47d6f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesHidden" msgstr "ガイド: 非表示" +# English: "Guides: Visible" # Source-Hash: 6d7ca85c4f389991 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesVisible" msgstr "ガイド: 表示" +# English: "Hide guide images" # Source-Hash: 5f6264ac469f57c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideGuideImages" msgstr "ガイド画像を非表示" +# English: "Hide reference floor" # Source-Hash: cae234c23229c80b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideReferenceFloor" msgstr "参照階を非表示" +# English: "Hide scans" # Source-Hash: 469a8416de876e0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideScans" msgstr "3Dスキャンを非表示" +# English: "{count} below" # Source-Hash: 873620c284a5651f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count} below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.levelsBelow" msgstr "{count} 階下" -# Source-Hash: 22a59ae72d8c9504 +# English: "No level above" +# Source-Hash: 6661c6766fdd1a7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelAbove" +msgstr "上の階層なし" + +# English: "No level below" +# Source-Hash: e34d9ec372c823ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelBelow" +msgstr "下の階層なし" + # English: "No lower floor available." +# Source-Hash: 22a59ae72d8c9504 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.noLowerFloorAvailable" msgstr "下の階はありません。" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.opacity" msgstr "不透明度" +# English: "Reference floor" # Source-Hash: 8bbd9bc90ac70a4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloor" msgstr "参照階" +# English: "Reference floor: {selectedLevelName}" # Source-Hash: 45493691a23c7690 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor: {selectedLevelName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSelectedlevelname" msgstr "参照階: {selectedLevelName}" +# English: "Reference floor settings" # Source-Hash: 7178c7b5a4581367 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSettings" msgstr "参照階の設定" +# English: "References" # Source-Hash: a52848021f822f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.references" msgstr "参照" +# English: "Upload GLB meshes as scan references or blueprint images as guide references." # Source-Hash: e59c9eec7dbd6ba1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload GLB meshes as scan references or blueprint images as guide references." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesEmpty" msgstr "GLB メッシュを3Dスキャン参照として、図面画像をガイド参照としてアップロードします。" +# English: "Reference settings" # Source-Hash: 3e73d06c3e96efba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceSettings" msgstr "参照の設定" +# English: "References: Hidden" # Source-Hash: aa0df2c2f00672e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesHidden" msgstr "参照: 非表示" +# English: "References: Visible" # Source-Hash: 9976708fc92bfa91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesVisible" msgstr "参照: 表示" +# English: "Riser diagram" # Source-Hash: 320a5f1ec012900b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser diagram" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.riserDiagram" msgstr "立て管系統図" +# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" # Source-Hash: 357b3d3eaf878a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanCount" msgstr "{count, plural, other {この階層の3Dスキャン # 件}}" +# English: "Scan {number}" # Source-Hash: f09f12547484c888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanNumber" msgstr "3Dスキャン {number}" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scans" msgstr "3Dスキャン" +# English: "Scan settings" # Source-Hash: c9c52015095997e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanSettings" msgstr "3Dスキャンの設定" +# English: "Scans: Hidden" # Source-Hash: 60abc9f1a8aec65c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansHidden" msgstr "3Dスキャン: 非表示" +# English: "Scans: Visible" # Source-Hash: 2d2ff119a7dd7911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansVisible" msgstr "3Dスキャン: 表示" +# English: "Show guide images" # Source-Hash: 8211234b205fd9a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showGuideImages" msgstr "ガイド画像を表示" +# English: "Show reference floor" # Source-Hash: 7786cae6acb04cfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showReferenceFloor" msgstr "参照階を表示" +# English: "Show scans" # Source-Hash: 6066f0b6cff8a910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showScans" msgstr "3Dスキャンを表示" -# Source-Hash: def6dff836767bda +# English: "Show walls below and above" +# Source-Hash: c100768fc929ccf4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.showWallsBelowAndAbove" +msgstr "上下の階層の壁を表示" + # English: "Upload scan or guide image" +# Source-Hash: def6dff836767bda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.uploadScanOrGuideImage" msgstr "3Dスキャンまたはガイド画像をアップロード" -# Source-Hash: 6aee0962fc18c0f1 +# English: "Walls above" +# Source-Hash: e9ac75feeb58a5be +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsAbove" +msgstr "上の階層の壁" + +# English: "Walls below" +# Source-Hash: 790a0b5c6dbf0e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelow" +msgstr "下の階層の壁" + +# English: "Walls below and above" +# Source-Hash: e74e28937511bce9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAbove" +msgstr "上下の階層の壁" + +# English: "Walls below and above settings" +# Source-Hash: 7c7bd2a996dfd5ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveSettings" +msgstr "上下の階層の壁の設定" + +# English: "Walls below and above: shown" +# Source-Hash: 43bc80f62aeab442 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveShown" +msgstr "上下の階層の壁: 表示" + # English: "Back to home" +# Source-Hash: 6aee0962fc18c0f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.backToHome" msgstr "ホームに戻る" +# English: "Advanced" # Source-Hash: f571cb2d06ae01ae # Review: human -# English: "Advanced" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.advanced" msgstr "詳細" +# English: "{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: 7afac05841af7b0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.caption" +msgstr "{lead}どこに描いても、各壁が外壁の種類になります。Esc またはダブルクリックで連続描画を終了します。" + +# English: "Exterior wall" +# Source-Hash: 0a7c3607e2011f54 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.label" +msgstr "外壁" + +# English: "Exterior walls: every wall drawn takes the exterior type and cladding" +# Source-Hash: 76fd5dab80b9d731 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.title" +msgstr "外壁:描いた壁はすべて外壁の種類と外装材になります" + +# English: "walls · floor · ceiling" # Source-Hash: fb40866c2d6db5fb # Review: human -# English: "walls · floor · ceiling" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.hint" msgstr "壁 · 床 · 天井" +# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." # Source-Hash: 271039bd919dc4c1 # Review: human -# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.idle" msgstr "形状を選んで開始します。部屋ごとに壁・床・天井が作成されます。" +# English: "{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: f1fddcf6d6b5c32b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.caption" +msgstr "{lead}どこに描いても、各壁が内壁の種類になります。Esc またはダブルクリックで連続描画を終了します。" + +# English: "Interior wall" +# Source-Hash: b9e25ba9781c9848 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.label" +msgstr "内壁" + +# English: "Interior walls: every wall drawn takes the interior type and paint" +# Source-Hash: 9e4a942fd52a06c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.title" +msgstr "内壁:描いた壁はすべて内壁の種類と塗装になります" + +# English: "{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run." +# Source-Hash: 3f519112d88b3ba6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.caption" +msgstr "{lead}各線が見えない壁になります。部屋の境界になりますが、何も建てられません。住宅の外側を区切ると、その部屋はポーチになります。Esc またはダブルクリックで連続描画を終了します。" + +# English: "Invisible wall" +# Source-Hash: 5b0317bd807d560e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.label" +msgstr "見えない壁" + +# English: "Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall." +# Source-Hash: 72fbf62dac3603b5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.title" +msgstr "見えない壁:壁を建てずに部屋を分割したり、ポーチを区切ったりします。部屋の境界になりますが、壁を分断することはありません。" + +# English: "{lead} then the first one again to close. For L-shapes and angled rooms." # Source-Hash: ae2800b6c9e2b6ed # Review: human -# English: "{lead} then the first one again to close. For L-shapes and angled rooms." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.caption" msgstr "{lead}最後に始点をクリックして閉じます。L字型や斜めの部屋に使用します。" +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: human -# English: "Polygon" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.label" msgstr "多角形" +# English: "Click each corner," # Source-Hash: 345e3aca3879b5c5 # Review: human -# English: "Click each corner," #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.lead" msgstr "各頂点をクリックし、" +# English: "Polygon room" # Source-Hash: 8ec6152e139b0e3f # Review: human -# English: "Polygon room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.title" msgstr "多角形の部屋" +# English: "{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run." +# Source-Hash: 18773160bb84f069 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.caption" +msgstr "{lead}各線が手すりになります。部屋の境界となる転落防止柵で、スタッド・サイディング・フーチングはありません。開口を設けるにはドアを配置します。Esc またはダブルクリックで連続描画を終了します。" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.label" +msgstr "手すり" + +# English: "Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel." +# Source-Hash: 161fbdfe5f09b81a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.title" +msgstr "手すり:壁と同じように描く高さ 36 in の転落防止柵で、支柱の間隔は最大 6 ft です。部屋の境界になり、開口も設けられます。スタイルと高さは壁パネルで設定します。" + +# English: "{lead} on the grid. Walls, floor and ceiling are created together." # Source-Hash: 9b46295eedcbedae # Review: human -# English: "{lead} on the grid. Walls, floor and ceiling are created together." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.caption" msgstr "グリッド上で{lead}します。壁・床・天井をまとめて作成します。" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: human -# English: "Rectangle" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.label" msgstr "長方形" +# English: "Click two corners" # Source-Hash: 060faa48e299d7c9 # Review: human -# English: "Click two corners" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.lead" msgstr "対角の2点をクリック" +# English: "Rectangle room" # Source-Hash: 83a45603ac7f41e8 # Review: human -# English: "Rectangle room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.title" msgstr "長方形の部屋" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.title" msgstr "部屋" -# Source-Hash: 8a21a03c4acf396b -# Review: human -# English: "{lead} Close a loop and it becomes a room; leave it open for partitions." +# English: "{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room." +# Source-Hash: ee04555fa3ead992 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.caption" -msgstr "{lead}輪郭を閉じると部屋になり、開いたままにすると間仕切りになります。" +msgstr "{lead}各壁は前の壁の終点から始まります。Esc またはダブルクリックで連続描画を終了し、ループを閉じると部屋になります。" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.label" msgstr "壁" -# Source-Hash: 9843edd68426fa2d -# Review: human -# English: "Draw one wall at a time." +# English: "Click each corner;" +# Source-Hash: ab5c2c822d968ca2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.lead" -msgstr "壁を1面ずつ描画します。" +msgstr "各頂点をクリックします。" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.title" msgstr "壁" -# Source-Hash: 1f3650da23603008 +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraControlsHint" -msgstr "カメラ操作のヒント" +msgid "editor.buildPanel.wallTypeControls.default" +msgstr "標準" -# Source-Hash: 101b6a54c377049a +# English: "Delete from this project" +# Source-Hash: 1dba2aacc55e0bb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pan" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.pan" -msgstr "パン" +msgid "editor.buildPanel.wallTypeControls.deleteFromThisProject" +msgstr "このプロジェクトから削除" -# Source-Hash: 306d409f8a1cf480 +# English: "Duplicate as a new type…" +# Source-Hash: 58a1b780e877ddc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.rotate" -msgstr "回転" +msgid "editor.buildPanel.wallTypeControls.duplicateAsANewType" +msgstr "新しい種類として複製…" -# Source-Hash: 663cecc316eb91b8 +# English: "Edit layers…" +# Source-Hash: 43e7aad9a5f75b6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zoom" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.zoom" -msgstr "ズーム" +msgid "editor.buildPanel.wallTypeControls.editLayers" +msgstr "層を編集…" -# Source-Hash: 34b7ca7ff46c44a9 +# English: "Edit or duplicate {label}" +# Source-Hash: 640331cc307a8421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label}: {finish}" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.ariaLabel" -msgstr "{label}:{finish}" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateLabel" +msgstr "{label}を編集または複製" -# Source-Hash: 4ca0e0e73229540c +# English: "Edit or duplicate this wall type" +# Source-Hash: 8f1ea5a4c16ba810 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.default" -msgstr "標準" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType" +msgstr "この壁の種類を編集または複製" -# Source-Hash: a2bd204e93e90598 +# English: "New wall type…" +# Source-Hash: 273fcb71b4f9272f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismiss" -msgstr "閉じる" +msgid "editor.buildPanel.wallTypeControls.newWallType" +msgstr "新しい壁の種類…" -# Source-Hash: 9222f970dce6cc60 +# English: "Presets stay as cited. Duplicate one to change its layers." +# Source-Hash: 079e4005e0093122 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismissCameraControlsHint" -msgstr "カメラ操作のヒントを閉じる" +msgid "editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne" +msgstr "プリセットは出典どおりのままです。層を変えるには複製してください。" -# Source-Hash: aeac665749d3101d +# English: "Add" +# Source-Hash: b071c943d8da1500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.expandSidebar" -msgstr "サイドバーを展開" +msgid "editor.buildPanel.wallTypeEditorDialog.add" +msgstr "追加" -# Source-Hash: dd86efcab72fc5c7 +# English: "Air space" +# Source-Hash: fcc69acc0fd92288 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis" -msgstr "軸" +msgid "editor.buildPanel.wallTypeEditorDialog.airSpace" +msgstr "空気層" -# Source-Hash: 5232a826549f915e +# English: "A wall type holds at most {limit, plural, one {# layer} other {# layers}}." +# Source-Hash: 46fb2832f979d656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⌥ axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis2" -msgstr "⌥ 軸" +msgid "editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost" +msgstr "壁の種類に含められる層は最大 {limit, plural, other {# 層}}です。" -# Source-Hash: 7001600001844c92 +# English: "Brick veneer" +# Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⚠ no equipment" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.noEquipment" -msgstr "⚠ 機器なし" +msgid "editor.buildPanel.wallTypeEditorDialog.brickVeneer" +msgstr "レンガ張り" -# Source-Hash: eae0194029f96edb +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# register} other {# registers}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.registerCount" -msgstr "{count, plural, other {吹出口 # 個}}" +msgid "editor.buildPanel.wallTypeEditorDialog.cancel" +msgstr "キャンセル" -# Source-Hash: 8fb180251a321cef +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "R/T rotate" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.rTRotate" -msgstr "R/T で回転" +msgid "editor.buildPanel.wallTypeEditorDialog.drywall" +msgstr "石膏ボード" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 6fb660d4eabe8d6a +# English: "Duplicate wall type" +# Source-Hash: 1f2e15ce909adf0c # Review: required -# English: "{count, plural, one {{number} run} other {{number} runs}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.runCount" -msgstr "{count, plural, other {{number} 系統}}" +msgid "editor.buildPanel.wallTypeEditorDialog.duplicateWallType" +msgstr "壁の種類を複製" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Edit wall type" +# Source-Hash: 832628bc67f36778 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.system" -msgstr "システム" +msgid "editor.buildPanel.wallTypeEditorDialog.editWallType" +msgstr "壁の種類を編集" -# Source-Hash: f0e76aea883d4e48 +# English: "Every wall type has one framing core" +# Source-Hash: 7d3d13d418b64f1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a floor under the roof" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" -msgstr "屋根の下に階層を追加" +msgid "editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming" +msgstr "壁の種類には必ず下地コアが 1 つあります" -# Source-Hash: a982ff9b075af829 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level above" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelAbove" -msgstr "上に階層を追加" +msgid "editor.buildPanel.wallTypeEditorDialog.fiberCement" +msgstr "繊維セメント" -# Source-Hash: 0db7d221df08fa6b +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level below" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelBelow" -msgstr "下に階層を追加" +msgid "editor.buildPanel.wallTypeEditorDialog.finish" +msgstr "仕上げ" -# Source-Hash: c0774c185060fc13 +# English: "Framing core" +# Source-Hash: 23306d290f066b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.cancel" -msgstr "キャンセル" +msgid "editor.buildPanel.wallTypeEditorDialog.framingCore" +msgstr "下地コア" -# Source-Hash: b26de372b4b0eaac +# English: "{fullReason} Remove one to add another." +# Source-Hash: b50fa488c2bfb502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.delete" -msgstr "削除" +msgid "editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother" +msgstr "{fullReason}追加するには 1 つ削除してください。" -# Source-Hash: 8e29375893d63408 +# English: "Gypsum sheathing" +# Source-Hash: e3e25fbbf14e97fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevel" -msgstr "階層を削除" +msgid "editor.buildPanel.wallTypeEditorDialog.gypsumSheathing" +msgstr "石膏ボード下張り" -# Source-Hash: c3bc69ec52022250 +# English: "Inside" +# Source-Hash: 8a7ff5011275abbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevelConfirm" -msgstr "{levelName}を削除してもよろしいですか?この階層のすべての壁、床、オブジェクトが完全に削除されます。" +msgid "editor.buildPanel.wallTypeEditorDialog.inside" +msgstr "内側" -# Source-Hash: 5b2826ddac95cd0c +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to reorder" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.dragToReorder" -msgstr "ドラッグして並べ替え" +msgid "editor.buildPanel.wallTypeEditorDialog.insulation" +msgstr "断熱材" -# Source-Hash: 933177338f5a03ed +# English: "Lap siding" +# Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateLevel" -msgstr "階層を複製" +msgid "editor.buildPanel.wallTypeEditorDialog.lapSiding" +msgstr "下見板張り" -# Source-Hash: 9614c6825a7c67bf +# English: "Layer" +# Source-Hash: d7ee1cefa20d0e32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateWithOptions" -msgstr "オプションを指定して複製..." +msgid "editor.buildPanel.wallTypeEditorDialog.layer" +msgstr "層" -# Source-Hash: 5ec91bb339806413 +# English: "Layer role" +# Source-Hash: bd00fff893ace820 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit unit focus" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.exitUnitFocus" -msgstr "ユニットのフォーカスを終了" +msgid "editor.buildPanel.wallTypeEditorDialog.layerRole" +msgstr "層の役割" -# Source-Hash: a88c3f8bed58b4fa +# English: "Lining" +# Source-Hash: e5536deeaabfacec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused unit has zones here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" -msgstr "フォーカス中のユニットにはここにゾーンがあります" +msgid "editor.buildPanel.wallTypeEditorDialog.lining" +msgstr "内張り" -# Source-Hash: 655055a310fb5d13 +# English: "Lists under Exterior: it has a cladding side." +# Source-Hash: d4e37e79879efa23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorOverGrade" -msgstr "1階は地盤面より {distance} 高い位置にあります。1階を地盤面から 8 in 上に据える" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA" +msgstr "外装側があるため「外壁」に表示されます。" -# Source-Hash: 77548faae4869eec +# English: "Lists under Interior: both faces match." +# Source-Hash: d455f88eb0e9e68e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorUnderGrade" -msgstr "1階は地盤面より {distance} 低い位置にあります。1階を地盤面から 8 in 上に据える" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch" +msgstr "両面が同じため「内壁」に表示されます。" -# Source-Hash: e3763ae2dc62c03f +# English: "List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type." +# Source-Hash: 5454746105949d05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground line: the levels under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" -msgstr "地盤線:その下の階層は地盤面より下です" +msgid "editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside" +msgstr "1 つの下地コアを挟んで、外側から内側の順に層を並べます。描画済みの壁は今の層のままで、今後描く壁には保存した種類が使われます。" -# Source-Hash: e12035eb47cc61cc +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground · {offset}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundOffset" -msgstr "地盤 · {offset}" +msgid "editor.buildPanel.wallTypeEditorDialog.material" +msgstr "材料" -# Source-Hash: 5661c11383217661 +# English: "Mineral wool board" +# Source-Hash: 75d79fb9a2e8fcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insert level here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.insertLevelHere" -msgstr "ここに階層を挿入" +msgid "editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard" +msgstr "ロックウールボード" -# Source-Hash: 0215b36d5a08597a +# English: "Move in" +# Source-Hash: 78acc1bad880ce0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level height" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.levelHeight" -msgstr "階層の高さ" +msgid "editor.buildPanel.wallTypeEditorDialog.moveIn" +msgstr "内側へ移動" -# Source-Hash: 91af436e81bb0875 +# English: "Move out" +# Source-Hash: 2de6ee30b4e608bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paste copied selection" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.pasteCopiedSelection" -msgstr "コピーした選択を貼り付け" +msgid "editor.buildPanel.wallTypeEditorDialog.moveOut" +msgstr "外側へ移動" -# Source-Hash: 68bc52f53240cbec +# English: "Name" +# Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reorder {levelName}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.reorderLevelname" -msgstr "{levelName}を並べ替え" +msgid "editor.buildPanel.wallTypeEditorDialog.name" +msgstr "名前" -# Source-Hash: 625d53de72ebc2ef +# English: "New wall type" +# Source-Hash: 4b3edeb00b32607e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seat the building so this floor stands at grade; the floors under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" -msgstr "この階が地盤面に来るよう建物を据えます。その下の階は地盤面より下になります" +msgid "editor.buildPanel.wallTypeEditorDialog.newWallType" +msgstr "新しい壁の種類" -# Source-Hash: 7ac9c00895c30d76 +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set as ground floor" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.setAsGroundFloor" -msgstr "1階に設定" +msgid "editor.buildPanel.wallTypeEditorDialog.osb" +msgstr "OSB" -# Source-Hash: d6b381247066a7aa +# English: "Outside" +# Source-Hash: dc4fe6cb415ed882 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" -msgstr "1階の階層は削除できません" +msgid "editor.buildPanel.wallTypeEditorDialog.outside" +msgstr "外側" -# Source-Hash: b1122051dce0d7a1 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor stands at grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" -msgstr "この階は地盤面にあります" +msgid "editor.buildPanel.wallTypeEditorDialog.plaster" +msgstr "漆喰" -# Source-Hash: 062cc0f1afef0c52 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.unit" -msgstr "住戸" +msgid "editor.buildPanel.wallTypeEditorDialog.plywood" +msgstr "合板" -# Source-Hash: 5871b7c20478f4a1 +# English: "Remove layer" +# Source-Hash: ca36e9fd7fba1fce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor area" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.area" -msgstr "床面積" +msgid "editor.buildPanel.wallTypeEditorDialog.removeLayer" +msgstr "層を削除" -# Source-Hash: 8b2e1435272aa86a +# English: "Rigid foam" +# Source-Hash: 7d8f351c35f89b94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{room} floor" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.room" -msgstr "{room}の床" +msgid "editor.buildPanel.wallTypeEditorDialog.rigidFoam" +msgstr "硬質フォーム" -# Source-Hash: 4731183fec06cbdf +# English: "Save wall type" +# Source-Hash: e8bc984965d5753d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shared floor" -#: packages/editor/src/components/ui/panels/room-construction-rows.tsx -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.shared" -msgstr "共有の床" +msgid "editor.buildPanel.wallTypeEditorDialog.saveWallType" +msgstr "壁の種類を保存" -# Source-Hash: c0774c185060fc13 +# English: "Sheathing" +# Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.cancel" -msgstr "キャンセル" +msgid "editor.buildPanel.wallTypeEditorDialog.sheathing" +msgstr "下張り" -# Source-Hash: 4c6ca3441c74da9e +# English: "Stone veneer" +# Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place building" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.placeBuilding" -msgstr "建物を配置" +msgid "editor.buildPanel.wallTypeEditorDialog.stoneVeneer" +msgstr "石張り" -# Source-Hash: 306d409f8a1cf480 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.rotate" -msgstr "回転" +msgid "editor.buildPanel.wallTypeEditorDialog.stucco" +msgstr "スタッコ" -# Source-Hash: 832470d3aeee82fd +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel placement" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.cancelPlacement" -msgstr "配置をキャンセル" +msgid "editor.buildPanel.wallTypeEditorDialog.thickness" +msgstr "厚さ" -# Source-Hash: e374fcaee33c3e2c +# English: "Total {thickness}" +# Source-Hash: 72e31eecaeb87447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation — click or press C to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" -msgstr "連続配置 — クリックまたは C で切り替え" +msgid "editor.buildPanel.wallTypeEditorDialog.totalThickness" +msgstr "総厚 {thickness}" -# Source-Hash: b108c7dc2ec2b9de +# English: "Wall type" +# Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation: {label}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationLabel" -msgstr "連続配置: {label}" +msgid "editor.buildPanel.wallTypeEditorDialog.wallType" +msgstr "壁の種類" -# Source-Hash: 729d87891cb7dbb1 +# English: "Brick" +# Source-Hash: abff67400a76e5eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to draw fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" -msgstr "ドラッグしてフェンスを描く" +msgid "editor.buildPanel.wallTypes.brick" +msgstr "レンガ" -# Source-Hash: b52e874718af1916 +# English: "Cladding" +# Source-Hash: ecca209317a2d1cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScope" -msgstr "消去: {scope}" +msgid "editor.buildPanel.wallTypes.cladding" +msgstr "外装材" -# Source-Hash: 61f7494abc319e66 +# English: "Drawing: Exterior walls." +# Source-Hash: 5c53959decce5dae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" -msgstr "消去の範囲 — クリックまたは Shift で切り替え" +msgid "editor.buildPanel.wallTypes.drawingExterior" +msgstr "描画中:外壁。" -# Source-Hash: 59993c163d68cc53 +# English: "Drawing: Interior walls." +# Source-Hash: 5206b8695c329b06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" -msgstr "消去の範囲: {scope}" +msgid "editor.buildPanel.wallTypes.drawingInterior" +msgstr "描画中:内壁。" -# Source-Hash: a4bdcbc6cab772bf +# English: "Drywall paint" +# Source-Hash: 8a542e796ac8edf0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence type — click or press T to switch between straight, curved and freehand" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" -msgstr "フェンスの種類 — クリックまたは T で直線・曲線・フリーハンドを切り替え" +msgid "editor.buildPanel.wallTypes.drywallPaint" +msgstr "石膏ボードの塗装" -# Source-Hash: 2a9ed258727078ac +# English: "Exterior" +# Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fewer shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" -msgstr "ショートカットを減らす" +msgid "editor.buildPanel.wallTypes.exterior" +msgstr "外壁" -# Source-Hash: 698fa6e78af4d4b7 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish curve (or double-click)" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" -msgstr "曲線を完了(またはダブルクリック)" +msgid "editor.buildPanel.wallTypes.fiberCement" +msgstr "繊維セメント" -# Source-Hash: 09f6d79edf5cfd16 +# English: "{drawing} Every wall takes the exterior type until you pick a shape or leave the tool." +# Source-Hash: 5badd399f078d485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridLength" -msgstr "グリッド: {length}" +msgid "editor.buildPanel.wallTypes.forcedExterior" +msgstr "{drawing}形状を選ぶかツールを終了するまで、すべての壁が外壁の種類になります。" -# Source-Hash: c493b562040c4328 +# English: "{drawing} Every wall takes the interior type until you pick a shape or leave the tool." +# Source-Hash: de2c329c8d03a385 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step — click or tap Ctrl to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" -msgstr "グリッド間隔 — クリックまたは Ctrl で切り替え" +msgid "editor.buildPanel.wallTypes.forcedInterior" +msgstr "{drawing}形状を選ぶかツールを終了するまで、すべての壁が内壁の種類になります。" -# Source-Hash: d36a8c93d81b3a36 +# English: "New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint." +# Source-Hash: b70a5d0948632955 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepLength" -msgstr "グリッド間隔: {length}" +msgid "editor.buildPanel.wallTypes.hint" +msgstr "新しい壁には外壁の種類と外装材が、部屋の内側に描いた壁には内壁の種類が使われます。石膏ボードの面にはすべてこの塗装が適用されます。" -# Source-Hash: abea993ede7daa7e +# English: "Interior" +# Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a painted surface to erase" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" -msgstr "塗装面にホバーして消去" +msgid "editor.buildPanel.wallTypes.interior" +msgstr "内壁" -# Source-Hash: fc1ed0ddc0cfcef5 +# English: "Plain wall" +# Source-Hash: 3744d42a0e4ee161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" -msgstr "面にホバーして塗装" +msgid "editor.buildPanel.wallTypes.plainWall" +msgstr "単層の壁" -# Source-Hash: 63dec5de4a337015 +# English: "Siding" +# Source-Hash: 8884a1d20caae02f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to pick its material" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" -msgstr "面にホバーして素材を取得" +msgid "editor.buildPanel.wallTypes.siding" +msgstr "サイディング" -# Source-Hash: 7ad6dc1b1f03f970 +# English: "Stone" +# Source-Hash: 44c6466e6907a0b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.moreShortcuts" -msgstr "その他のショートカット" +msgid "editor.buildPanel.wallTypes.stone" +msgstr "石" -# Source-Hash: 476b8f5f9ee542e6 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScope" -msgstr "塗装: {scope}" +msgid "editor.buildPanel.wallTypes.stucco" +msgstr "スタッコ" -# Source-Hash: 9c09ce4c34122277 +# English: "Camera controls hint" +# Source-Hash: 1f3650da23603008 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" -msgstr "塗装の範囲 — クリックまたは Shift で切り替え" +msgid "editor.cameraControlsHint" +msgstr "カメラ操作のヒント" -# Source-Hash: 7890aa81687f6654 +# English: "Pan" +# Source-Hash: 101b6a54c377049a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" -msgstr "塗装の範囲: {scope}" +msgid "editor.cameraHint.pan" +msgstr "パン" -# Source-Hash: a75b3abc0784b12b +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.pickScope" -msgstr "取得: {scope}" - +msgid "editor.cameraHint.rotate" +msgstr "回転" + +# English: "Zoom" +# Source-Hash: 663cecc316eb91b8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.cameraHint.zoom" +msgstr "ズーム" + +# English: "{label}: {finish}" +# Source-Hash: 34b7ca7ff46c44a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx +#, fuzzy +msgid "editor.controls.finishSwatch.ariaLabel" +msgstr "{label}:{finish}" + +# English: "Default" +# Source-Hash: 4ca0e0e73229540c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx +#, fuzzy +msgid "editor.controls.finishSwatch.default" +msgstr "標準" + +# English: "Dismiss" +# Source-Hash: a2bd204e93e90598 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.dismiss" +msgstr "閉じる" + +# English: "Dismiss camera controls hint" +# Source-Hash: 9222f970dce6cc60 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.dismissCameraControlsHint" +msgstr "カメラ操作のヒントを閉じる" + +# English: "Click to edit the foundation’s outline" +# Source-Hash: e314809b6907d9ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx +#, fuzzy +msgid "editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS" +msgstr "クリックして基礎の輪郭を編集" + +# English: "Expand sidebar" +# Source-Hash: aeac665749d3101d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.expandSidebar" +msgstr "サイドバーを展開" + +# English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.axis" +msgstr "軸" + +# English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.axis2" +msgstr "⌥ 軸" + +# English: "⚠ no equipment" +# Source-Hash: 7001600001844c92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.noEquipment" +msgstr "⚠ 機器なし" + +# English: "{count, plural, one {# register} other {# registers}}" +# Source-Hash: eae0194029f96edb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.registerCount" +msgstr "{count, plural, other {吹出口 # 個}}" + +# English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.rTRotate" +msgstr "R/T で回転" + +# English: "{count, plural, one {{number} run} other {{number} runs}}" +# Source-Hash: 6fb660d4eabe8d6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.runCount" +msgstr "{count, plural, other {{number} 系統}}" + +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx +#, fuzzy +msgid "editor.floatingActionMenu.system" +msgstr "システム" + +# English: "Add a floor under the roof" +# Source-Hash: f0e76aea883d4e48 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" +msgstr "屋根の下に階層を追加" + +# English: "Add level above" +# Source-Hash: a982ff9b075af829 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.addLevelAbove" +msgstr "上に階層を追加" + +# English: "Add level below" +# Source-Hash: 0db7d221df08fa6b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.addLevelBelow" +msgstr "下に階層を追加" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.cancel" +msgstr "キャンセル" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.delete" +msgstr "削除" + +# English: "Delete level" +# Source-Hash: 8e29375893d63408 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.deleteLevel" +msgstr "階層を削除" + +# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." +# Source-Hash: c3bc69ec52022250 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.deleteLevelConfirm" +msgstr "{levelName}を削除してもよろしいですか?この階層のすべての壁、床、オブジェクトが完全に削除されます。" + +# English: "Drag to reorder" +# Source-Hash: 5b2826ddac95cd0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.dragToReorder" +msgstr "ドラッグして並べ替え" + +# English: "Duplicate level" +# Source-Hash: 933177338f5a03ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.duplicateLevel" +msgstr "階層を複製" + +# English: "Duplicate with options..." +# Source-Hash: 9614c6825a7c67bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.duplicateWithOptions" +msgstr "オプションを指定して複製..." + +# English: "Exit unit focus" +# Source-Hash: 5ec91bb339806413 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.exitUnitFocus" +msgstr "ユニットのフォーカスを終了" + +# English: "Focused unit has zones here" +# Source-Hash: a88c3f8bed58b4fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" +msgstr "フォーカス中のユニットにはここにゾーンがあります" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundation" +msgstr "基礎" + +# English: "Basement" +# Source-Hash: 8caa959c0ae800c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeBasement" +msgstr "地下室" + +# English: "Basement walls cut into the ground" +# Source-Hash: 96878e51aa1f4aac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeBasementTitle" +msgstr "地盤に掘り込んだ地下壁" + +# English: "Off" +# Source-Hash: 50accd740fa2e736 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeOff" +msgstr "オフ" + +# English: "No foundation drawn: click to set one up" +# Source-Hash: 44761dadfc126cbb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeOffTitle" +msgstr "基礎は描画されていません:クリックして設定" + +# English: "Piles" +# Source-Hash: f4031d60a7899a2e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgePiles" +msgstr "杭" + +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgePilesTitle" +msgstr "杭上の地中梁" + +# English: "Raised" +# Source-Hash: bd50bd18e682db41 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeRaised" +msgstr "高床" + +# English: "Raised floor on a stem wall" +# Source-Hash: 69980d5b1f49e272 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeRaisedTitle" +msgstr "立ち上がりの上の高床" + +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeSlab" +msgstr "スラブ" + +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.foundationBadgeSlabTitle" +msgstr "土間スラブ" + +# English: "The ground floor stands {distance} over grade. {action}" +# Source-Hash: 6a00e82fda812178 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.groundFloorOverGrade" +msgstr "1階は地盤面より {distance} 高い位置にあります。{action}" + +# English: "The ground floor stands {distance} under grade. {action}" +# Source-Hash: 61f400e77494df9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.groundFloorUnderGrade" +msgstr "1階は地盤面より {distance} 低い位置にあります。{action}" + +# English: "Ground line: the levels under it are below grade" +# Source-Hash: e3763ae2dc62c03f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" +msgstr "地盤線:その下の階層は地盤面より下です" + +# English: "Ground · {offset}" +# Source-Hash: e12035eb47cc61cc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.groundOffset" +msgstr "地盤 · {offset}" + +# English: "Insert level here" +# Source-Hash: 5661c11383217661 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.insertLevelHere" +msgstr "ここに階層を挿入" + +# English: "Level height" +# Source-Hash: 0215b36d5a08597a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.levelHeight" +msgstr "階層の高さ" + +# English: "Paste copied selection" +# Source-Hash: 91af436e81bb0875 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.pasteCopiedSelection" +msgstr "コピーした選択を貼り付け" + +# English: "Reorder {levelName}" +# Source-Hash: 68bc52f53240cbec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.reorderLevelname" +msgstr "{levelName}を並べ替え" + +# English: "Seat the building so this floor stands at grade; the floors under it are below grade" +# Source-Hash: 625d53de72ebc2ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" +msgstr "この階が地盤面に来るよう建物を据えます。その下の階は地盤面より下になります" + +# English: "Set as ground floor" +# Source-Hash: 7ac9c00895c30d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.setAsGroundFloor" +msgstr "1階に設定" + +# English: "Show the foundation" +# Source-Hash: 46b0734342ab499f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.showTheFoundation" +msgstr "基礎を表示" + +# English: "The ground level cannot be deleted" +# Source-Hash: d6b381247066a7aa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" +msgstr "1階の階層は削除できません" + +# English: "This floor stands at grade" +# Source-Hash: b1122051dce0d7a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" +msgstr "この階は地盤面にあります" + +# English: "Unit" +# Source-Hash: 062cc0f1afef0c52 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#, fuzzy +msgid "editor.floatingLevelSelector.unit" +msgstr "住戸" + +# English: "Floor area" +# Source-Hash: 5871b7c20478f4a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.area" +msgstr "床面積" + +# English: "{room} floor" +# Source-Hash: 8b2e1435272aa86a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.room" +msgstr "{room}の床" + +# English: "Shared floor" +# Source-Hash: 4731183fec06cbdf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.shared" +msgstr "共有の床" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.cancel" +msgstr "キャンセル" + +# English: "Place building" +# Source-Hash: 4c6ca3441c74da9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.placeBuilding" +msgstr "建物を配置" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.rotate" +msgstr "回転" + +# English: "Cancel placement" +# Source-Hash: 832470d3aeee82fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.cancelPlacement" +msgstr "配置をキャンセル" + +# English: "Continuation — click or press C to cycle" +# Source-Hash: e374fcaee33c3e2c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" +msgstr "連続配置 — クリックまたは C で切り替え" + +# English: "Continuation: {label}" +# Source-Hash: b108c7dc2ec2b9de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationLabel" +msgstr "連続配置: {label}" + +# English: "Drag to draw fence" +# Source-Hash: 729d87891cb7dbb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" +msgstr "ドラッグしてフェンスを描く" + +# English: "Erase: {scope}" +# Source-Hash: b52e874718af1916 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScope" +msgstr "消去: {scope}" + +# English: "Erase scope — click or press Shift to cycle" +# Source-Hash: 61f7494abc319e66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" +msgstr "消去の範囲 — クリックまたは Shift で切り替え" + +# English: "Erase scope: {scope}" +# Source-Hash: 59993c163d68cc53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" +msgstr "消去の範囲: {scope}" + +# English: "Fence type — click or press T to switch between straight, curved and freehand" +# Source-Hash: a4bdcbc6cab772bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" +msgstr "フェンスの種類 — クリックまたは T で直線・曲線・フリーハンドを切り替え" + +# English: "Fewer shortcuts" +# Source-Hash: 2a9ed258727078ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" +msgstr "ショートカットを減らす" + +# English: "Finish curve (or double-click)" +# Source-Hash: 698fa6e78af4d4b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" +msgstr "曲線を完了(またはダブルクリック)" + +# English: "Grid: {length}" +# Source-Hash: 09f6d79edf5cfd16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridLength" +msgstr "グリッド: {length}" + +# English: "Grid step — click or tap Ctrl to cycle" +# Source-Hash: c493b562040c4328 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" +msgstr "グリッド間隔 — クリックまたは Ctrl で切り替え" + +# English: "Grid step: {length}" +# Source-Hash: d36a8c93d81b3a36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepLength" +msgstr "グリッド間隔: {length}" + +# English: "Hover a painted surface to erase" +# Source-Hash: abea993ede7daa7e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" +msgstr "塗装面にホバーして消去" + +# English: "Hover a surface to paint" +# Source-Hash: fc1ed0ddc0cfcef5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" +msgstr "面にホバーして塗装" + +# English: "Hover a surface to pick its material" +# Source-Hash: 63dec5de4a337015 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" +msgstr "面にホバーして素材を取得" + +# English: "More shortcuts" +# Source-Hash: 7ad6dc1b1f03f970 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.moreShortcuts" +msgstr "その他のショートカット" + +# English: "Paint: {scope}" +# Source-Hash: 476b8f5f9ee542e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScope" +msgstr "塗装: {scope}" + +# English: "Paint scope — click or press Shift to cycle" +# Source-Hash: 9c09ce4c34122277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" +msgstr "塗装の範囲 — クリックまたは Shift で切り替え" + +# English: "Paint scope: {scope}" +# Source-Hash: 7890aa81687f6654 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" +msgstr "塗装の範囲: {scope}" + +# English: "Pick: {scope}" +# Source-Hash: a75b3abc0784b12b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.pickScope" +msgstr "取得: {scope}" + +# English: "Place gate on a fence" # Source-Hash: 69c1044e6ea87ccf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gate on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" +msgstr "フェンスに門を配置" + +# English: "Place passage on a fence" +# Source-Hash: 3754255b8affc7a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" +msgstr "フェンスに通路を配置" + +# English: "Select a material to paint" +# Source-Hash: a8328c8964e947fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" +msgstr "塗る素材を選択" + +# English: "Snapping: Angles" +# Source-Hash: 6f95d7116aea5738 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingAngles" +msgstr "スナップ: 角度" + +# English: "Snapping: Grid" +# Source-Hash: 2d6d8d85d2ebed47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingGrid" +msgstr "スナップ: グリッド" + +# English: "Snapping: Lines" +# Source-Hash: 9096c5f9d8ce0f7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingLines" +msgstr "スナップ: 線" + +# English: "Snapping mode — click or press Shift to cycle" +# Source-Hash: b663afbf0835cf32 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" +msgstr "スナップモード — クリックまたは Shift で切り替え" + +# English: "Snapping: Off" +# Source-Hash: d00e984fc88f71aa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingOff" +msgstr "スナップ: オフ" + +# English: "Straight continuation is unavailable for curved or freehand fences" +# Source-Hash: 6be9d0d312200761 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" +msgstr "曲線・フリーハンドのフェンスでは直線の連続配置は使えません" + +# English: "Straight: Continuous" +# Source-Hash: 0d56b1f0a0493302 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuous" +msgstr "直線: 連続" + +# English: "Straight fence continuation — click or press C to toggle" +# Source-Hash: c034e85d3e7fd27e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" +msgstr "直線フェンスの連続配置 — クリックまたは C で切り替え" + +# English: "Straight: Single" +# Source-Hash: e3f679cdb4a3d603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightSingle" +msgstr "直線: 単体" + +# English: "Add point" +# Source-Hash: 9a56d90d461b4bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPoint" +msgstr "点を追加" + +# English: "Add point · click the first to close" +# Source-Hash: c3dc3918c823a5e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPointClickTheFirstTo" +msgstr "点を追加 · 最初の点をクリックして閉じる" + +# English: "Brush size" +# Source-Hash: d8cf72d482d56cd7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.brushSize" +msgstr "ブラシサイズ" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancel" +msgstr "キャンセル" + +# English: "Cancel picking" +# Source-Hash: 104fdab7934ec66c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelPicking" +msgstr "選択をキャンセル" + +# English: "Cancel stroke" +# Source-Hash: c318cf0ce70b8977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelStroke" +msgstr "ストロークをキャンセル" + +# English: "Done" +# Source-Hash: 16552c2580bbe8b8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.done" +msgstr "完了" + +# English: "Draw the mezzanine" +# Source-Hash: e297db9ca0ce6d31 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheMezzanine" +msgstr "中二階を描く" + +# English: "Draw the opening" +# Source-Hash: c8be604718a7a9fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheOpening" +msgstr "開口を描く" + +# English: "Draw the terrace" +# Source-Hash: 231386ae6d534163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheTerrace" +msgstr "テラスを描く" + +# English: "Exit smart measure" +# Source-Hash: e092e803eb60af69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.exitSmartMeasure" +msgstr "スマート計測を終了" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finish" +msgstr "完了" + +# English: "Finish at the edge" +# Source-Hash: f74bd937ae2e3a40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finishAtTheEdge" +msgstr "端で完了" + +# English: "Free move" +# Source-Hash: 21029cf1c45697cc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMove" +msgstr "自由移動" + +# English: "Free move · slide doors and windows" +# Source-Hash: 3247d8689bed6ff6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" +msgstr "自由移動 · ドアと窓をスライド" + +# English: "Hold to pick a material" +# Source-Hash: b5ad765b43e75a5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.holdToPickAMaterial" +msgstr "押したままで素材を取得" + +# English: "Inspect surface dimensions" +# Source-Hash: 46b53c5449c98964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.inspectSurfaceDimensions" +msgstr "面の寸法を確認" + +# English: "Paint with this material" +# Source-Hash: 63f1f7006ae14177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.paintWithThisMaterial" +msgstr "この素材で塗る" + +# English: "Pick target height" +# Source-Hash: 188f55cc5afbe34f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pickTargetHeight" +msgstr "目標の高さを選ぶ" + +# English: "Pin measurement lens" +# Source-Hash: 2880fc9752eababa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pinMeasurementLens" +msgstr "計測レンズを固定" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.place" +msgstr "配置" + +# English: "Place inside the room" +# Source-Hash: 82010a7fd6350c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.placeInsideTheRoom" +msgstr "部屋の中に配置" + +# English: "Press anywhere, drag to the opposite corner" +# Source-Hash: 8b97f4503edb27b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" +msgstr "どこかを押して、対角までドラッグ" + +# English: "Raise or lower" +# Source-Hash: 786f0b1c81b2a6c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.raiseOrLower" +msgstr "上げる・下げる" + +# English: "Remove a painted part, or reset a surface" +# Source-Hash: b033f46ae3a0cbb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" +msgstr "塗装部分を削除、または面をリセット" + +# English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeLastPoint" +msgstr "最後の点を削除" + +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.resize" +msgstr "サイズ変更" + +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotate45" +msgstr "±45°回転" + +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotateTheSelection45" +msgstr "選択を±45°回転" + +# English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" +msgstr "1つの角、次に対角を設定" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.cancel" +msgstr "キャンセル" + +# English: "Force place" +# Source-Hash: ed7f3d8417a3e074 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.forcePlace" +msgstr "強制配置" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.place" +msgstr "配置" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.rotate" +msgstr "回転" + +# English: "Guided constraints bypassed" +# Source-Hash: e9dfc2a178bca5f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +#, fuzzy +msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" +msgstr "ガイド制約を無視中" + +# English: "Angle" +# Source-Hash: c69865ec5d9d1ac9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.angle.g10zaw" +msgstr "角度" + +# English: "Arm length m" +# Source-Hash: 9c562983f6fed685 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.armLengthM.1jclqgw" +msgstr "アーム長 m" + +# English: "Attachment side" +# Source-Hash: 38461bc364321749 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.attachmentSide.6n9s5v" +msgstr "接続側" + +# English: "Auto hangers" +# Source-Hash: 441124187e7da2fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoHangers.10554ka" +msgstr "自動吊り金具" + +# English: "Auto miter corners" +# Source-Hash: e20033e4b0045f1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoMiterCorners.zxr5lt" +msgstr "コーナーを自動留め" + +# English: "Auto span" +# Source-Hash: 92f3a8b63963daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoSpan.1gas9r6" +msgstr "自動スパン" + +# English: "Back ratio" +# Source-Hash: 8b2dfb9b9137fda0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.backRatio.1ughd99" +msgstr "背面比率" + +# English: "Base height" +# Source-Hash: 75dfae8d9f03fa0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseHeight.1hs4zdf" +msgstr "ベースの高さ" + +# English: "Base style" +# Source-Hash: 1cc2da12e8463e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseStyle.24vty5" +msgstr "ベースのスタイル" + +# English: "Beam height" +# Source-Hash: f9d95c6df06b6223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamHeight.1kq0oh1" +msgstr "梁せい" + +# English: "Beam width" +# Source-Hash: a133f8d23b1274dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamWidth.1q1mzbe" +msgstr "梁幅" + +# English: "Body shape" +# Source-Hash: b98c0d0189526335 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bodyShape.arvkuw" +msgstr "本体の形状" + +# English: "Bracket style" +# Source-Hash: 2251ecab18467631 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bracketStyle.1vvoptm" +msgstr "ブラケットのスタイル" + +# English: "Branch angle" +# Source-Hash: 5e9d4a4fcea481ca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.branchAngle.v56zme" +msgstr "分岐角度" + +# English: "Cab height" +# Source-Hash: dec709f1f9c714ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cabHeight.1yn34mk" +msgstr "かごの高さ" + +# English: "Canopy form" +# Source-Hash: 87863f8366e1c88a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.canopyForm.1czzggp" +msgstr "庇の形状" + +# English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cap.s0thej" +msgstr "キャップ" + +# English: "Cap overhang" +# Source-Hash: 2f2e49598ec8188e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capOverhang.kxxz9j" +msgstr "キャップの張り出し" + +# English: "Cap shape" +# Source-Hash: 8523c086e95b83e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capShape.17mbxhu" +msgstr "キャップの形状" + +# English: "Cap thickness" +# Source-Hash: acd98600a518dd95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capThickness.u835q9" +msgstr "キャップの厚さ" + +# English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.carcassHeight.1aks0zy" +msgstr "本体の高さ" + +# English: "Cleanout style" +# Source-Hash: 827709caa79db37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cleanoutStyle.127zz6z" +msgstr "掃除口のスタイル" + +# English: "Collar diameter" +# Source-Hash: a549dc98e2a1bd07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarDiameter.1d5k28r" +msgstr "カラー径" + +# English: "Collar height" +# Source-Hash: aa0232fc6f3eeadc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarHeight.1dqwsrf" +msgstr "カラーの高さ" + +# English: "Collar shape" +# Source-Hash: cbd8079c3f540a4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarShape.1gacv11" +msgstr "カラーの形状" + +# English: "Collar width" +# Source-Hash: 557cde7f95616454 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarWidth.qvc9ik" +msgstr "カラー幅" + +# English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.columns.1sy73ks" +msgstr "列" + +# English: "Conical full circle" +# Source-Hash: cd5b2f70924b7c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" +msgstr "円錐(全周)" + +# English: "Connection mode" +# Source-Hash: dc90f3abb935e7e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionMode.1nty478" +msgstr "接続モード" + +# English: "Connection offset" +# Source-Hash: 1a9a49d7135d4f15 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionOffset.2m70s8" +msgstr "接続オフセット" + +# English: "Corner bevel" +# Source-Hash: 69be301fcb8030e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cornerBevel.16iniho" +msgstr "コーナーの面取り" + +# English: "Covering type" +# Source-Hash: b997a96a9dc7e450 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.coveringType.95yd3k" +msgstr "仕上げの種類" + +# English: "Cricket height" +# Source-Hash: 03b33160d8baa966 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketHeight.pok31n" +msgstr "クリケットの高さ" + +# English: "Cricket length" +# Source-Hash: 21552eee05ccb5d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketLength.i2aht8" +msgstr "クリケットの長さ" + +# English: "Cricket side" +# Source-Hash: c13b707bfdeac855 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketSide.1xnwil7" +msgstr "クリケットの側" + +# English: "Cricket style" +# Source-Hash: 4ee7089fa1b3e63b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketStyle.10j0lat" +msgstr "クリケットのスタイル" + +# English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curb.5xi2ol" +msgstr "立ち上がり" + +# English: "Curb height" +# Source-Hash: 8da648df210dbc0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curbHeight.1yn97sg" +msgstr "立ち上がりの高さ" + +# English: "Curve offset" +# Source-Hash: 0c01b9848f5d9673 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curveOffset.l4xhvr" +msgstr "曲線のオフセット" + +# English: "Damper angle" +# Source-Hash: 69179f4caa384585 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.damperAngle.h5fxn7" +msgstr "ダンパー角度" + +# English: "Deck thickness" +# Source-Hash: 15cff91231e806a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.deckThickness.4jmqze" +msgstr "デッキの厚さ" + +# English: "Default level id" +# Source-Hash: eff1e7dc0901b9d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.defaultLevelId.xmrjzp" +msgstr "既定の階層 ID" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.depth.1lu11u2" +msgstr "奥行き" + +# English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter.s3robe" +msgstr "直径" + +# English: "Diameter2" +# Source-Hash: dab4100e83e4e6b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter2.pm6cl4" +msgstr "直径2" + +# English: "Door duration ms" +# Source-Hash: 5747145349432396 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorDurationMs.13nx909" +msgstr "ドア動作時間 ms" + +# English: "Door height" +# Source-Hash: 6f4aca26196c221f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorHeight.92r9k4" +msgstr "ドアの高さ" + +# English: "Door panel style" +# Source-Hash: 990ae81752236e3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorPanelStyle.2qog4q" +msgstr "ドアパネルのスタイル" + +# English: "Door style" +# Source-Hash: c94e002123bb1199 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorStyle.gerpos" +msgstr "ドアのスタイル" + +# English: "Door width" +# Source-Hash: 91aeb04b0b7b605e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorWidth.sy732l" +msgstr "ドア幅" + +# English: "Downspout enabled" +# Source-Hash: baad1238f70f79ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutEnabled.1gn00nv" +msgstr "縦樋を有効化" + +# English: "Downspout position" +# Source-Hash: e288531858c4c44d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutPosition.fxcsop" +msgstr "縦樋の位置" + +# English: "Duct material" +# Source-Hash: 861d28317325cf61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ductMaterial.i8eonq" +msgstr "ダクトの材料" + +# English: "Dutch gablet rake" +# Source-Hash: ad8ab4ae38c3299c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchGabletRake.6paydp" +msgstr "入母屋の破風" + +# English: "Dutch hip height ratio" +# Source-Hash: 913d25cf2c5ff179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" +msgstr "入母屋の高さ比" + +# English: "Dutch hip width ratio" +# Source-Hash: f4a80233d49f2b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" +msgstr "入母屋の幅比" + +# English: "Dutch top rake thickness" +# Source-Hash: 92021b0e37f9a25a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" +msgstr "入母屋上部破風の厚さ" + +# English: "Dutch waist length ratio" +# Source-Hash: d0a2ec13d71ce659 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" +msgstr "入母屋の腰の長さ比" + +# English: "Dwell ms" +# Source-Hash: 993396ccca2cd1d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dwellMs.1lkk0o5" +msgstr "停止時間 ms" + +# English: "Edge inset" +# Source-Hash: 9cf550eb019bca1b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.edgeInset.1obeimx" +msgstr "縁の内側オフセット" + +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.elevation.4jbp44" +msgstr "高さ" + +# English: "End cap left" +# Source-Hash: 1fc91958e30dbeaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapLeft.19jel4v" +msgstr "左エンドキャップ" + +# English: "End cap right" +# Source-Hash: 4c9ae62e0ed70635 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapRight.lhse9c" +msgstr "右エンドキャップ" + +# English: "End caps" +# Source-Hash: a9cba06250228a55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCaps.4fsb5b" +msgstr "エンドキャップ" + +# English: "End post" +# Source-Hash: ebad74353b5a7610 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endPost.1guwsk0" +msgstr "端の支柱" + +# English: "Equipment type" +# Source-Hash: ecbe36df4238439e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.equipmentType.1mc2ywt" +msgstr "機器の種類" + +# English: "Fill to floor" +# Source-Hash: 66f1018b50c2e426 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fillToFloor.1oppnkf" +msgstr "床まで埋める" + +# English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.finial.1ertf4s" +msgstr "フィニアル" + +# English: "Fitting type" +# Source-Hash: 28cd69e506709f16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fittingType.18mz17s" +msgstr "継手の種類" + +# English: "Flashing height" +# Source-Hash: 3c1aec5e6fb180cf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingHeight.3e8nn6" +msgstr "水切りの高さ" + +# English: "Flashing projection" +# Source-Hash: 327ba762101db94d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingProjection.hhf0fk" +msgstr "水切りの出" + +# English: "Flue count" +# Source-Hash: 9473e2ab53370b40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueCount.j3t2ci" +msgstr "煙道の数" + +# English: "Flue diameter" +# Source-Hash: fd0b65425ed3df8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueDiameter.cjhubg" +msgstr "煙道の直径" + +# English: "Flue height" +# Source-Hash: 07ebe5e3780c624b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueHeight.1dhc4y4" +msgstr "煙道の高さ" + +# English: "Flue shape" +# Source-Hash: ff8319792830d591 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueShape.1ealiy4" +msgstr "煙道の形状" + +# English: "Flue spacing" +# Source-Hash: 5acf77854a0030ec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueSpacing.exhzco" +msgstr "煙道の間隔" + +# English: "Footing style" +# Source-Hash: 0a64209c0edacaa1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.footingStyle.e3btg6" +msgstr "フーチングのスタイル" + +# English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameDepth.1934a5n" +msgstr "枠の奥行き" + +# English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameThickness.1agxzd8" +msgstr "枠の厚さ" + +# English: "Framing strategy" +# Source-Hash: 9a3495a1e3181b56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.framingStrategy.3jx9qe" +msgstr "下地の方式" + +# English: "From level id" +# Source-Hash: 49f329aab535fd3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fromLevelId.15ieejs" +msgstr "開始階層 ID" + +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gableOverhang.2z0obw" +msgstr "妻側のけらば" + +# English: "Gambrel lower height ratio" +# Source-Hash: 517caa8af48afb22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" +msgstr "腰折れ下部の高さ比" + +# English: "Gambrel lower width ratio" +# Source-Hash: 03ecb8f3b625d49d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" +msgstr "腰折れ下部の幅比" + +# English: "Gap x" +# Source-Hash: f3ca5555e8c54375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapX.wjxa7" +msgstr "隙間 x" + +# English: "Gap y" +# Source-Hash: 2736f7c5dfc7d917 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapY.mkbl8" +msgstr "隙間 y" + +# English: "Glass thickness" +# Source-Hash: 5d03ca50d712339b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.glassThickness.s97b0x" +msgstr "ガラスの厚さ" + +# English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.groundClearance.4os7f0" +msgstr "地面とのクリアランス" + +# English: "Guard infill" +# Source-Hash: 6a22a09b67309545 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.guardInfill.16heiig" +msgstr "手すり壁の充填" + +# English: "Gutter enabled" +# Source-Hash: 98dfdac04af3c762 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterEnabled.1oxiltf" +msgstr "雨樋を有効化" + +# English: "Gutter profile" +# Source-Hash: 4bdc3cdf0fe0ac3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" -msgstr "フェンスに門を配置" +msgid "editor.inspector.field.gutterProfile.1ngzqi3" +msgstr "雨樋の形状" -# Source-Hash: 3754255b8affc7a0 +# English: "Gutter size" +# Source-Hash: 76555670cba9bb13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place passage on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" -msgstr "フェンスに通路を配置" +msgid "editor.inspector.field.gutterSize.jgavtz" +msgstr "雨樋のサイズ" -# Source-Hash: a8328c8964e947fa +# English: "Hanger max reach" +# Source-Hash: c14d3aa4a98efa98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a material to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" -msgstr "塗る素材を選択" +msgid "editor.inspector.field.hangerMaxReach.1869epv" +msgstr "吊りの最大到達距離" + +# English: "Hanger spacing" +# Source-Hash: dfbc357fb556559b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerSpacing.i2b5d7" +msgstr "吊りの間隔" + +# English: "Hanger style" +# Source-Hash: 3ce5e50c572644ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerStyle.l7v7gb" +msgstr "吊りのスタイル" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height.7puwki" +msgstr "高さ" + +# English: "Height2" +# Source-Hash: d0b836103c795726 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height2.7b3jlc" +msgstr "高さ2" + +# English: "Height above ridge" +# Source-Hash: 681fd794e601ace5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.heightAboveRidge.1k99brk" +msgstr "棟からの高さ" + +# English: "High edge height" +# Source-Hash: b4905e5ec62a170f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highEdgeHeight.1s7v90t" +msgstr "高い側の高さ" + +# English: "High overhang" +# Source-Hash: c136d5cd7f3ce04a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highOverhang.67w55d" +msgstr "高い側の出" + +# English: "High side mode" +# Source-Hash: 8b4cd537cc650dac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highSideMode.1rc1r5j" +msgstr "高い側のモード" + +# English: "Hood overhang" +# Source-Hash: c38e4e73ed9779f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hoodOverhang.1q1hcin" +msgstr "フードの張り出し" + +# English: "Infill placement" +# Source-Hash: 9884ee52a0e84c0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.infillPlacement.11dxzpo" +msgstr "充填の位置" + +# English: "Inlet shape" +# Source-Hash: 37fde9f9290ddc59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.inletShape.ypayh2" +msgstr "入口の形状" + +# English: "Inner radius" +# Source-Hash: d840917007332b94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.innerRadius.1fkmg0j" +msgstr "内半径" + +# English: "Insulated" +# Source-Hash: c8ce389856a90ae2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulated.iwziey" +msgstr "断熱" + +# English: "Insulation r" +# Source-Hash: 043a7a10b6c91f4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulationR.663ufx" +msgstr "断熱 R" + +# English: "Lantern height" +# Source-Hash: fe661655ba41b709 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternHeight.oz7zpm" +msgstr "ランタンの高さ" + +# English: "Lantern top scale" +# Source-Hash: ab22022e7d5597af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternTopScale.hkmpp2" +msgstr "ランタン上部の比率" + +# English: "Ledger depth" +# Source-Hash: 17f854a76ebc033a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerDepth.7av8k9" +msgstr "受け材の奥行き" + +# English: "Ledger height" +# Source-Hash: 547d3a8a9e3e24f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerHeight.zo2oif" +msgstr "受け材の高さ" + +# English: "Ledger vertical offset" +# Source-Hash: 88603f884243c81e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" +msgstr "受け材の垂直オフセット" + +# English: "Left end condition" +# Source-Hash: 6519f495202b830d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftEndCondition.1syqlaw" +msgstr "左端の条件" + +# English: "Left overhang" +# Source-Hash: d041b8217c356977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftOverhang.1ydl9ma" +msgstr "左の張り出し" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.length.1kpdcfp" +msgstr "長さ" + +# English: "Liquid diameter" +# Source-Hash: d6deaf0ceb9f43af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.liquidDiameter.w3pk7c" +msgstr "液管の直径" + +# English: "Louver count" +# Source-Hash: 45aca762426392fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.louverCount.lhw2gr" +msgstr "ルーバーの数" + +# English: "Low beam inset" +# Source-Hash: c645a86c55eb0075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowBeamInset.1g7yw5l" +msgstr "低い梁の内側オフセット" -# Source-Hash: 6f95d7116aea5738 +# English: "Low edge height" +# Source-Hash: 7e7b896682c78c5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Angles" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingAngles" -msgstr "スナップ: 角度" +msgid "editor.inspector.field.lowEdgeHeight.47mae1" +msgstr "低い側の高さ" -# Source-Hash: 2d6d8d85d2ebed47 +# English: "Low overhang" +# Source-Hash: 9deb775fc64854b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Grid" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingGrid" -msgstr "スナップ: グリッド" +msgid "editor.inspector.field.lowOverhang.t4nz1h" +msgstr "低い側の出" -# Source-Hash: 9096c5f9d8ce0f7c +# English: "Mansard steep height ratio" +# Source-Hash: 9aa621d270fe64b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Lines" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingLines" -msgstr "スナップ: 線" +msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" +msgstr "マンサード急勾配部の高さ比" -# Source-Hash: b663afbf0835cf32 +# English: "Mansard steep width ratio" +# Source-Hash: 7cb03b27c632949b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping mode — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" -msgstr "スナップモード — クリックまたは Shift で切り替え" +msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" +msgstr "マンサード急勾配部の幅比" -# Source-Hash: d00e984fc88f71aa +# English: "Match host roof material" +# Source-Hash: 95772435857a7424 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Off" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingOff" -msgstr "スナップ: オフ" +msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" +msgstr "母屋根の素材に合わせる" -# Source-Hash: 6be9d0d312200761 +# English: "Match host roof structure" +# Source-Hash: dc22618e31654bde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight continuation is unavailable for curved or freehand fences" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" -msgstr "曲線・フリーハンドのフェンスでは直線の連続配置は使えません" +msgid "editor.inspector.field.matchHostRoofStructure.bojht3" +msgstr "母屋根の構造に合わせる" -# Source-Hash: 0d56b1f0a0493302 +# English: "Mount" +# Source-Hash: 06b36a09a84fa8b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Continuous" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuous" -msgstr "直線: 連続" +msgid "editor.inspector.field.mount.h80tay" +msgstr "取り付け" -# Source-Hash: c034e85d3e7fd27e +# English: "Mounting type" +# Source-Hash: a288700f96d2b362 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight fence continuation — click or press C to toggle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" -msgstr "直線フェンスの連続配置 — クリックまたは C で切り替え" +msgid "editor.inspector.field.mountingType.1xvin1e" +msgstr "取り付けの種類" -# Source-Hash: e3f679cdb4a3d603 +# English: "Neck height" +# Source-Hash: 3087bc9b219af9ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Single" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightSingle" -msgstr "直線: 単体" +msgid "editor.inspector.field.neckHeight.19ym8n1" +msgstr "首の高さ" -# Source-Hash: 9a56d90d461b4bca +# English: "Opacity" +# Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.addPoint" -msgstr "点を追加" +msgid "editor.inspector.field.opacity.1pmpfd2" +msgstr "不透明度" -# Source-Hash: c3dc3918c823a5e6 +# English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point · click the first to close" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.addPointClickTheFirstTo" -msgstr "点を追加 · 最初の点をクリックして閉じる" +msgid "editor.inspector.field.openingAngle.brhi7c" +msgstr "開き角度" -# Source-Hash: d8cf72d482d56cd7 +# English: "Opening offset" +# Source-Hash: 1d714b576a057902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brush size" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.brushSize" -msgstr "ブラシサイズ" +msgid "editor.inspector.field.openingOffset.wpoqt6" +msgstr "開口のオフセット" -# Source-Hash: c0774c185060fc13 +# English: "Opening side" +# Source-Hash: 55e5629865efd03d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.cancel" -msgstr "キャンセル" +msgid "editor.inspector.field.openingSide.18l2iwe" +msgstr "開く側" -# Source-Hash: 104fdab7934ec66c +# English: "Operation state" +# Source-Hash: 9824d357692ecb8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel picking" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.cancelPicking" -msgstr "選択をキャンセル" +msgid "editor.inspector.field.operationState.1lwrz07" +msgstr "開閉状態" -# Source-Hash: c318cf0ce70b8977 +# English: "Outlet shape" +# Source-Hash: 4f1cbc14dcfb8f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel stroke" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.cancelStroke" -msgstr "ストロークをキャンセル" +msgid "editor.inspector.field.outletShape.lgpqhx" +msgstr "出口の形状" -# Source-Hash: 16552c2580bbe8b8 +# English: "Overhang" +# Source-Hash: 88540def2ce16ac5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.done" -msgstr "完了" +msgid "editor.inspector.field.overhang.b5ljpv" +msgstr "張り出し" -# Source-Hash: e297db9ca0ce6d31 +# English: "Panel height" +# Source-Hash: 50d7e5e370667f63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the mezzanine" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.drawTheMezzanine" -msgstr "中二階を描く" +msgid "editor.inspector.field.panelHeight.1kf31vk" +msgstr "パネルの高さ" -# Source-Hash: c8be604718a7a9fd +# English: "Panel width" +# Source-Hash: 068d9e0adabc4578 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the opening" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.drawTheOpening" -msgstr "開口を描く" +msgid "editor.inspector.field.panelWidth.946581" +msgstr "パネル幅" -# Source-Hash: 231386ae6d534163 +# English: "Pattern alignment" +# Source-Hash: aca94a3adc0b0f01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the terrace" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.drawTheTerrace" -msgstr "テラスを描く" +msgid "editor.inspector.field.patternAlignment.jd1ht0" +msgstr "パターンの揃え" -# Source-Hash: e092e803eb60af69 +# English: "Pattern count" +# Source-Hash: 9e9f9408ec2c8f0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit smart measure" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.exitSmartMeasure" -msgstr "スマート計測を終了" +msgid "editor.inspector.field.patternCount.1r37pve" +msgstr "パターン数" -# Source-Hash: b3907da0ad234f10 +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.finish" -msgstr "完了" +msgid "editor.inspector.field.patternDistribution.fmam51" +msgstr "パターンの配分" -# Source-Hash: f74bd937ae2e3a40 +# English: "Pattern remainder" +# Source-Hash: 63f833f219b9a592 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish at the edge" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.finishAtTheEdge" -msgstr "端で完了" +msgid "editor.inspector.field.patternRemainder.i47p5y" +msgstr "パターンの余り" -# Source-Hash: 21029cf1c45697cc +# English: "Picket profile" +# Source-Hash: e0b8f766411329ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.freeMove" -msgstr "自由移動" +msgid "editor.inspector.field.picketProfile.1xgzuyc" +msgstr "ピケットの形状" -# Source-Hash: 3247d8689bed6ff6 +# English: "Picket rail count" +# Source-Hash: dfac7d68ea16e8a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move · slide doors and windows" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" -msgstr "自由移動 · ドアと窓をスライド" +msgid "editor.inspector.field.picketRailCount.atgt6w" +msgstr "ピケットのレール数" -# Source-Hash: b5ad765b43e75a5c +# English: "Picket spacing" +# Source-Hash: cd2652254a81d288 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to pick a material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.holdToPickAMaterial" -msgstr "押したままで素材を取得" +msgid "editor.inspector.field.picketSpacing.p8d98w" +msgstr "ピケットの間隔" -# Source-Hash: 46b53c5449c98964 +# English: "Picket top" +# Source-Hash: 462f6cb03b23d773 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inspect surface dimensions" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.inspectSurfaceDimensions" -msgstr "面の寸法を確認" +msgid "editor.inspector.field.picketTop.1tskplu" +msgstr "ピケットの上端" -# Source-Hash: 63f1f7006ae14177 +# English: "Picket top clearance" +# Source-Hash: 0a4dbbe1cf592326 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint with this material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.paintWithThisMaterial" -msgstr "この素材で塗る" +msgid "editor.inspector.field.picketTopClearance.16eq1k0" +msgstr "ピケット上端のクリアランス" -# Source-Hash: 188f55cc5afbe34f +# English: "Picket variation" +# Source-Hash: c99ccea380ca41bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick target height" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.pickTargetHeight" -msgstr "目標の高さを選ぶ" +msgid "editor.inspector.field.picketVariation.f7vl1c" +msgstr "ピケットのばらつき" -# Source-Hash: 2880fc9752eababa +# English: "Picket width" +# Source-Hash: 798382373625fecd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin measurement lens" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.pinMeasurementLens" -msgstr "計測レンズを固定" +msgid "editor.inspector.field.picketWidth.3fr3rh" +msgstr "ピケットの幅" -# Source-Hash: 722741535b346e09 +# English: "Pipe material" +# Source-Hash: 48ce215c3be034c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.place" -msgstr "配置" +msgid "editor.inspector.field.pipeMaterial.1ggc2wy" +msgstr "配管の材料" -# Source-Hash: 82010a7fd6350c93 +# English: "Pitch" +# Source-Hash: 75e3449fc1776d09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place inside the room" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.placeInsideTheRoom" -msgstr "部屋の中に配置" +msgid "editor.inspector.field.pitch.xe6ekx" +msgstr "勾配" -# Source-Hash: 8b97f4503edb27b9 +# English: "Position" +# Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Press anywhere, drag to the opposite corner" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" -msgstr "どこかを押して、対角までドラッグ" +msgid "editor.inspector.field.position.1quewx6" +msgstr "位置" -# Source-Hash: 786f0b1c81b2a6c0 +# English: "Post bracing" +# Source-Hash: d090baf9f08c2280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise or lower" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.raiseOrLower" -msgstr "上げる・下げる" +msgid "editor.inspector.field.postBracing.crq8nn" +msgstr "支柱の筋交い" -# Source-Hash: b033f46ae3a0cbb7 +# English: "Post cap" +# Source-Hash: 6467b580eed1d333 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove a painted part, or reset a surface" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" -msgstr "塗装部分を削除、または面をリセット" +msgid "editor.inspector.field.postCap.1xcvxrv" +msgstr "支柱キャップ" -# Source-Hash: 0dc6d121791696f7 +# English: "Post count" +# Source-Hash: e1d91cdd16cc6593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.removeLastPoint" -msgstr "最後の点を削除" +msgid "editor.inspector.field.postCount.22r56c" +msgstr "支柱の数" -# Source-Hash: f85bdac0a1d16603 +# English: "Post depth" +# Source-Hash: d6d8b8653cb43624 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.resize" -msgstr "サイズ変更" +msgid "editor.inspector.field.postDepth.1ko6j1m" +msgstr "支柱の奥行き" -# Source-Hash: 8a665ecaeffa9308 +# English: "Post inset" +# Source-Hash: dd071e84e259ee48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.rotate45" -msgstr "±45°回転" +msgid "editor.inspector.field.postInset.le36li" +msgstr "支柱の内側オフセット" -# Source-Hash: 6099ee99f8c38700 +# English: "Post layout mode" +# Source-Hash: 88c5d586822e58f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.rotateTheSelection45" -msgstr "選択を±45°回転" +msgid "editor.inspector.field.postLayoutMode.16jre62" +msgstr "支柱配置モード" -# Source-Hash: c93ae898b4d59f6f +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set one corner, then the opposite" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" -msgstr "1つの角、次に対角を設定" +msgid "editor.inspector.field.postSize.1jodxek" +msgstr "支柱のサイズ" -# Source-Hash: c0774c185060fc13 +# English: "Post spacing" +# Source-Hash: c8e8490c957e01f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.itemHelper.cancel" -msgstr "キャンセル" +msgid "editor.inspector.field.postSpacing.voppfi" +msgstr "支柱の間隔" -# Source-Hash: ed7f3d8417a3e074 +# English: "Post through" +# Source-Hash: b98cbe8478508d41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.itemHelper.forcePlace" -msgstr "強制配置" +msgid "editor.inspector.field.postThrough.1abgu4a" +msgstr "支柱を貫通" -# Source-Hash: 722741535b346e09 +# English: "Post width" +# Source-Hash: d1492df1fbb22c29 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.itemHelper.place" -msgstr "配置" +msgid "editor.inspector.field.postWidth.rk26fz" +msgstr "支柱の幅" -# Source-Hash: 306d409f8a1cf480 +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.itemHelper.rotate" -msgstr "回転" +msgid "editor.inspector.field.profile.1kwlci6" +msgstr "形状" -# Source-Hash: e9dfc2a178bca5f7 +# English: "Projection" +# Source-Hash: d01cd36d3939abab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guided constraints bypassed" -#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" -msgstr "ガイド制約を無視中" +msgid "editor.inspector.field.projection.jhtig0" +msgstr "出" -# Source-Hash: c69865ec5d9d1ac9 +# English: "Purlin height" +# Source-Hash: 800a7a9159519b46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.angle.g10zaw" -msgstr "角度" +msgid "editor.inspector.field.purlinHeight.eo9cjm" +msgstr "母屋の高さ" -# Source-Hash: 9c562983f6fed685 +# English: "Purlin spacing" +# Source-Hash: da4d0d12715d6c33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arm length m" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.armLengthM.1jclqgw" -msgstr "アーム長 m" +msgid "editor.inspector.field.purlinSpacing.4gbzb2" +msgstr "母屋の間隔" -# Source-Hash: 38461bc364321749 +# English: "Purlin width" +# Source-Hash: b4774bcbdf599812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.attachmentSide.6n9s5v" -msgstr "接続側" +#, fuzzy +msgid "editor.inspector.field.purlinWidth.r6tu3j" +msgstr "母屋の幅" -# Source-Hash: 441124187e7da2fc +# English: "Rafter end inset" +# Source-Hash: 2a4dd54f8a8c15ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoHangers.10554ka" -msgstr "自動吊り金具" +msgid "editor.inspector.field.rafterEndInset.14c0wy5" +msgstr "垂木端の内側オフセット" -# Source-Hash: e20033e4b0045f1c +# English: "Rafter height" +# Source-Hash: 5a80a11b97c0e043 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto miter corners" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoMiterCorners.zxr5lt" -msgstr "コーナーを自動留め" +msgid "editor.inspector.field.rafterHeight.1e1n99w" +msgstr "垂木の高さ" -# Source-Hash: 92f3a8b63963daeb +# English: "Rafter spacing" +# Source-Hash: b6348d554660af5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoSpan.1gas9r6" -msgstr "自動スパン" +msgid "editor.inspector.field.rafterSpacing.19subc0" +msgstr "垂木の間隔" -# Source-Hash: 8b2dfb9b9137fda0 +# English: "Rafter width" +# Source-Hash: 112c447946c3beaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.backRatio.1ughd99" -msgstr "背面比率" +msgid "editor.inspector.field.rafterWidth.eksj4t" +msgstr "垂木の幅" -# Source-Hash: 75dfae8d9f03fa0e +# English: "Railing height" +# Source-Hash: 7020d720876a2106 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseHeight.1hs4zdf" -msgstr "ベースの高さ" +msgid "editor.inspector.field.railingHeight.6wf4bm" +msgstr "手すりの高さ" -# Source-Hash: 1cc2da12e8463e85 +# English: "Railing mode" +# Source-Hash: 3e89ce193885b5b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseStyle.24vty5" -msgstr "ベースのスタイル" +msgid "editor.inspector.field.railingMode.ov7xpu" +msgstr "手すりのモード" -# Source-Hash: f9d95c6df06b6223 +# English: "Railing style" +# Source-Hash: 174342bb31a86afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamHeight.1kq0oh1" -msgstr "梁せい" +msgid "editor.inspector.field.railingStyle.1nyd2u" +msgstr "手すりのスタイル" -# Source-Hash: a133f8d23b1274dc +# English: "Resize lock" +# Source-Hash: a44eedc895b95474 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamWidth.1q1mzbe" -msgstr "梁幅" +msgid "editor.inspector.field.resizeLock.b2hbr0" +msgstr "比率を固定" -# Source-Hash: b98c0d0189526335 +# English: "Return diameter" +# Source-Hash: edd319917231def8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bodyShape.arvkuw" -msgstr "本体の形状" +msgid "editor.inspector.field.returnDiameter.166ozhe" +msgstr "還気の直径" -# Source-Hash: 2251ecab18467631 +# English: "Return height" +# Source-Hash: dafa151b183daa77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bracket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bracketStyle.1vvoptm" -msgstr "ブラケットのスタイル" +msgid "editor.inspector.field.returnHeight.b9sq6y" +msgstr "還気の高さ" -# Source-Hash: 5e9d4a4fcea481ca +# English: "Return shape" +# Source-Hash: cd55fd029ad2803b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Branch angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.branchAngle.v56zme" -msgstr "分岐角度" +msgid "editor.inspector.field.returnShape.1e3a27i" +msgstr "還気の形状" -# Source-Hash: dec709f1f9c714ee +# English: "Return width" +# Source-Hash: 6a7c411667124177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cabHeight.1yn34mk" -msgstr "かごの高さ" +msgid "editor.inspector.field.returnWidth.z9r3h3" +msgstr "還気の幅" -# Source-Hash: 87863f8366e1c88a +# English: "Right end condition" +# Source-Hash: 460bd11bfc2e5697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy form" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.canopyForm.1czzggp" -msgstr "庇の形状" +msgid "editor.inspector.field.rightEndCondition.tjlw3l" +msgstr "右端の条件" -# Source-Hash: d52b53241789ee63 +# English: "Right overhang" +# Source-Hash: 1ed35f8d27ae5583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cap.s0thej" -msgstr "キャップ" +msgid "editor.inspector.field.rightOverhang.yqbeft" +msgstr "右の張り出し" -# Source-Hash: 2f2e49598ec8188e +# English: "Roof height" +# Source-Hash: 39c02aa2d97a62ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capOverhang.kxxz9j" -msgstr "キャップの張り出し" +msgid "editor.inspector.field.roofHeight.1uhy6qy" +msgstr "屋根の高さ" -# Source-Hash: 8523c086e95b83e2 +# English: "Roof style" +# Source-Hash: d62f8833d77ca8a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capShape.17mbxhu" -msgstr "キャップの形状" +msgid "editor.inspector.field.roofStyle.pawqfy" +msgstr "屋根のスタイル" -# Source-Hash: acd98600a518dd95 +# English: "Roof thickness" +# Source-Hash: 0ddd561a4126b826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capThickness.u835q9" -msgstr "キャップの厚さ" +msgid "editor.inspector.field.roofThickness.1xlr3rx" +msgstr "屋根の厚さ" -# Source-Hash: 7af3b69b4b5aeb19 +# English: "Roof type" +# Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carcass height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.carcassHeight.1aks0zy" -msgstr "本体の高さ" +msgid "editor.inspector.field.roofType.1m9kv5h" +msgstr "屋根の種類" -# Source-Hash: 827709caa79db37f +# English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cleanoutStyle.127zz6z" -msgstr "掃除口のスタイル" +msgid "editor.inspector.field.rotation.ly2rj" +msgstr "回転" -# Source-Hash: a549dc98e2a1bd07 +# English: "Rows" +# Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarDiameter.1d5k28r" -msgstr "カラー径" +msgid "editor.inspector.field.rows.1ba1neg" +msgstr "行" -# Source-Hash: aa0232fc6f3eeadc +# English: "Scale" +# Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarHeight.1dqwsrf" -msgstr "カラーの高さ" +msgid "editor.inspector.field.scale.b6rpxt" +msgstr "縮尺" -# Source-Hash: cbd8079c3f540a4c +# English: "Seam detail" +# Source-Hash: adb6face4bac5245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarShape.1gacv11" -msgstr "カラーの形状" +msgid "editor.inspector.field.seamDetail.197za48" +msgstr "継ぎ目の詳細" -# Source-Hash: 557cde7f95616454 +# English: "Segment type" +# Source-Hash: 4b2aed9d56cc4dee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarWidth.qvc9ik" -msgstr "カラー幅" +msgid "editor.inspector.field.segmentType.l667sy" +msgstr "セグメントの種類" -# Source-Hash: 30edd1c180aac9b4 +# English: "Shaft style" +# Source-Hash: a7794a81141b4de4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.columns.1sy73ks" -msgstr "列" +msgid "editor.inspector.field.shaftStyle.1i5ampa" +msgstr "シャフトのスタイル" -# Source-Hash: cd5b2f70924b7c2d +# English: "Shaft wall thickness" +# Source-Hash: 8eb6438543b18cdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical full circle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" -msgstr "円錐(全周)" +msgid "editor.inspector.field.shaftWallThickness.bduslr" +msgstr "シャフト壁の厚さ" -# Source-Hash: dc90f3abb935e7e2 +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionMode.1nty478" -msgstr "接続モード" +msgid "editor.inspector.field.shape.iq9t52" +msgstr "形状" -# Source-Hash: 1a9a49d7135d4f15 +# English: "Shape2" +# Source-Hash: 44a8ab89c854cad1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionOffset.2m70s8" -msgstr "接続オフセット" +msgid "editor.inspector.field.shape2.fi0nx8" +msgstr "形状2" -# Source-Hash: 69be301fcb8030e2 +# English: "Shed high side" +# Source-Hash: fcee9712879570ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner bevel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cornerBevel.16iniho" -msgstr "コーナーの面取り" +msgid "editor.inspector.field.shedHighSide.bzzddg" +msgstr "片流れの高い側" -# Source-Hash: b997a96a9dc7e450 +# English: "Shingle thickness" +# Source-Hash: f5a931cd6e97e5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.coveringType.95yd3k" -msgstr "仕上げの種類" +msgid "editor.inspector.field.shingleThickness.1hn5wxr" +msgstr "シングルの厚さ" -# Source-Hash: 03b33160d8baa966 +# English: "Shoulder extent" +# Source-Hash: b14990b3e4222dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketHeight.pok31n" -msgstr "クリケットの高さ" +msgid "editor.inspector.field.shoulderExtent.11j8uiv" +msgstr "ショルダーの範囲" -# Source-Hash: 21552eee05ccb5d6 +# English: "Shoulder height" +# Source-Hash: 505de343ec5010e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketLength.i2aht8" -msgstr "クリケットの長さ" +msgid "editor.inspector.field.shoulderHeight.xyqn6g" +msgstr "ショルダーの高さ" -# Source-Hash: c13b707bfdeac855 +# English: "Shoulder style" +# Source-Hash: 58d48c01b7ec1a40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketSide.1xnwil7" -msgstr "クリケットの側" +msgid "editor.inspector.field.shoulderStyle.1hkcv2w" +msgstr "ショルダーのスタイル" -# Source-Hash: 4ee7089fa1b3e63b +# English: "Show center column" +# Source-Hash: af84db1026bf2e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketStyle.10j0lat" -msgstr "クリケットのスタイル" +msgid "editor.inspector.field.showCenterColumn.1uggg45" +msgstr "中央の柱を表示" -# Source-Hash: b8ff053666ee326b +# English: "Show infill" +# Source-Hash: 80214dc086ebc1b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curb.5xi2ol" -msgstr "立ち上がり" +msgid "editor.inspector.field.showInfill.33rv7u" +msgstr "充填を表示" -# Source-Hash: 8da648df210dbc0d +# English: "Show step supports" +# Source-Hash: f02d6cdfb4c6ad08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curbHeight.1yn97sg" -msgstr "立ち上がりの高さ" +msgid "editor.inspector.field.showStepSupports.1kg8h8m" +msgstr "段の支持材を表示" -# Source-Hash: 0c01b9848f5d9673 +# English: "Side flashing" +# Source-Hash: 5d1147c774718b76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curveOffset.l4xhvr" -msgstr "曲線のオフセット" +msgid "editor.inspector.field.sideFlashing.cv744o" +msgstr "側面の水切り" -# Source-Hash: 69179f4caa384585 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.damperAngle.h5fxn7" -msgstr "ダンパー角度" +msgid "editor.inspector.field.size.1a4x3zw" +msgstr "サイズ" -# Source-Hash: 15cff91231e806a4 +# English: "Skylight type" +# Source-Hash: fbdbc6c97c75b030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.deckThickness.4jmqze" -msgstr "デッキの厚さ" +msgid "editor.inspector.field.skylightType.1a5vj3i" +msgstr "天窓の種類" -# Source-Hash: eff1e7dc0901b9d0 +# English: "Slab opening mode" +# Source-Hash: c9a6561cb3c3da82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.defaultLevelId.xmrjzp" -msgstr "既定の階層 ID" +msgid "editor.inspector.field.slabOpeningMode.g4f730" +msgstr "スラブ開口のモード" -# Source-Hash: 99ac4d03c3061f3a +# English: "Slat gap" +# Source-Hash: ef0efc845ccdba78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.depth.1lu11u2" -msgstr "奥行き" +msgid "editor.inspector.field.slatGap.slmk93" +msgstr "スラットの隙間" -# Source-Hash: 016d8e70618916d4 +# English: "Slide direction" +# Source-Hash: 4daab7a2ce2ecef3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter.s3robe" -msgstr "直径" +msgid "editor.inspector.field.slideDirection.1527q13" +msgstr "スライド方向" -# Source-Hash: dab4100e83e4e6b3 +# English: "Span" +# Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter2.pm6cl4" -msgstr "直径2" +msgid "editor.inspector.field.span.1bmmuw1" +msgstr "スパン" -# Source-Hash: 5747145349432396 +# English: "Speed" +# Source-Hash: 3b006be0b1f0493f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door duration ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorDurationMs.13nx909" -msgstr "ドア動作時間 ms" +msgid "editor.inspector.field.speed.2w2xwg" +msgstr "速度" -# Source-Hash: 6f4aca26196c221f +# English: "Spin speed" +# Source-Hash: c8cfb8336a240cb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorHeight.92r9k4" -msgstr "ドアの高さ" +msgid "editor.inspector.field.spinSpeed.1ozx746" +msgstr "回転速度" -# Source-Hash: 990ae81752236e3a +# English: "Stair type" +# Source-Hash: c9f54c3b258961c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door panel style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorPanelStyle.2qog4q" -msgstr "ドアパネルのスタイル" +msgid "editor.inspector.field.stairType.1u05e02" +msgstr "階段の種類" -# Source-Hash: c94e002123bb1199 +# English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorStyle.gerpos" -msgstr "ドアのスタイル" +msgid "editor.inspector.field.standoff.4wv2g0" +msgstr "スタンドオフ" -# Source-Hash: 91aeb04b0b7b605e +# English: "Standoff height" +# Source-Hash: 0464d4efa1e80c62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorWidth.sy732l" -msgstr "ドア幅" +msgid "editor.inspector.field.standoffHeight.1qdnff" +msgstr "スタンドオフの高さ" -# Source-Hash: baad1238f70f79ea +# English: "Start post" +# Source-Hash: 85da28b0b26b8c2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutEnabled.1gn00nv" -msgstr "縦樋を有効化" +msgid "editor.inspector.field.startPost.1ood9kv" +msgstr "始端の支柱" -# Source-Hash: e288531858c4c44d +# English: "Step count" +# Source-Hash: d5dc1ba8cca3193d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutPosition.fxcsop" -msgstr "縦樋の位置" +msgid "editor.inspector.field.stepCount.nzorrw" +msgstr "段数" -# Source-Hash: 861d28317325cf61 +# English: "Strap spacing" +# Source-Hash: cadbb9cadd6a5e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ductMaterial.i8eonq" -msgstr "ダクトの材料" +msgid "editor.inspector.field.strapSpacing.1cyt3dc" +msgstr "バンドの間隔" -# Source-Hash: ad8ab4ae38c3299c +# English: "Strap style" +# Source-Hash: c017cd56b3ae5865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch gablet rake" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchGabletRake.6paydp" -msgstr "入母屋の破風" +msgid "editor.inspector.field.strapStyle.fh1oy4" +msgstr "バンドのスタイル" -# Source-Hash: 913d25cf2c5ff179 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" -msgstr "入母屋の高さ比" +msgid "editor.inspector.field.style.nf3vcm" +msgstr "スタイル" -# Source-Hash: f4a80233d49f2b20 +# English: "Suction diameter" +# Source-Hash: 7b3f016989c631a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" -msgstr "入母屋の幅比" +msgid "editor.inspector.field.suctionDiameter.nutxtj" +msgstr "吸入管の直径" -# Source-Hash: 92021b0e37f9a25a +# English: "Supply diameter" +# Source-Hash: 3f1e0fd30e5b1f39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch top rake thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" -msgstr "入母屋上部破風の厚さ" +msgid "editor.inspector.field.supplyDiameter.h5ldh5" +msgstr "給気の直径" -# Source-Hash: d0a2ec13d71ce659 +# English: "Supply height" +# Source-Hash: 05c9b608538e181d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch waist length ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" -msgstr "入母屋の腰の長さ比" +msgid "editor.inspector.field.supplyHeight.zy6ru1" +msgstr "給気の高さ" -# Source-Hash: 993396ccca2cd1d5 +# English: "Supply shape" +# Source-Hash: 3c9400722baa474d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dwell ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dwellMs.1lkk0o5" -msgstr "停止時間 ms" +msgid "editor.inspector.field.supplyShape.k39wnb" +msgstr "給気の形状" -# Source-Hash: 9cf550eb019bca1b +# English: "Supply width" +# Source-Hash: af7619025438f995 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edge inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.edgeInset.1obeimx" -msgstr "縁の内側オフセット" +msgid "editor.inspector.field.supplyWidth.g66axi" +msgstr "給気の幅" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Support offset" +# Source-Hash: 617e198cf3f214d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.elevation.4jbp44" -msgstr "高さ" +msgid "editor.inspector.field.supportOffset.1w4cocr" +msgstr "支持のオフセット" -# Source-Hash: 1fc91958e30dbeaf +# English: "Sweep angle" +# Source-Hash: 2686abc093f3dfb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapLeft.19jel4v" -msgstr "左エンドキャップ" +msgid "editor.inspector.field.sweepAngle.ucmmlw" +msgstr "スイープ角度" -# Source-Hash: 4c9ae62e0ed70635 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapRight.lhse9c" -msgstr "右エンドキャップ" +msgid "editor.inspector.field.system.13qbhrw" +msgstr "システム" -# Source-Hash: a9cba06250228a55 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCaps.4fsb5b" -msgstr "エンドキャップ" +msgid "editor.inspector.field.terminal.1501knl" +msgstr "端末" -# Source-Hash: ebad74353b5a7610 +# English: "Terminal type" +# Source-Hash: de1777c521046f7d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endPost.1guwsk0" -msgstr "端の支柱" +msgid "editor.inspector.field.terminalType.1ne8lgb" +msgstr "端末の種類" -# Source-Hash: ecbe36df4238439e +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.equipmentType.1mc2ywt" -msgstr "機器の種類" +msgid "editor.inspector.field.thickness.1l8hu45" +msgstr "厚さ" -# Source-Hash: 66f1018b50c2e426 +# English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fillToFloor.1oppnkf" -msgstr "床まで埋める" +msgid "editor.inspector.field.tiltAngle.1312yw1" +msgstr "傾斜角度" -# Source-Hash: 1cfe33235b30b1d8 +# English: "To level id" +# Source-Hash: 7057972ff4c78dc7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.finial.1ertf4s" -msgstr "フィニアル" +msgid "editor.inspector.field.toLevelId.8i5qx7" +msgstr "終了階層 ID" -# Source-Hash: 28cd69e506709f16 +# English: "Top landing depth" +# Source-Hash: fa8fc3d5c612e774 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fittingType.18mz17s" -msgstr "継手の種類" +msgid "editor.inspector.field.topLandingDepth.ki7gk2" +msgstr "上部の踊り場の奥行き" -# Source-Hash: 3c1aec5e6fb180cf +# English: "Top landing mode" +# Source-Hash: 6e4a0b98089ec2cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingHeight.3e8nn6" -msgstr "水切りの高さ" +msgid "editor.inspector.field.topLandingMode.zk0h7e" +msgstr "上部の踊り場のモード" -# Source-Hash: 327ba762101db94d +# English: "Top rail height" +# Source-Hash: c182bb3c8a701085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingProjection.hhf0fk" -msgstr "水切りの出" +msgid "editor.inspector.field.topRailHeight.fscisv" +msgstr "上部レールの高さ" -# Source-Hash: 9473e2ab53370b40 +# English: "Transition mode" +# Source-Hash: 8c5502f1d8087b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueCount.j3t2ci" -msgstr "煙道の数" +msgid "editor.inspector.field.transitionMode.f3evgt" +msgstr "トランジションのモード" -# Source-Hash: fd0b65425ed3df8b +# English: "Transition width" +# Source-Hash: 96eb5997f0aa2dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueDiameter.cjhubg" -msgstr "煙道の直径" +msgid "editor.inspector.field.transitionWidth.rtgz3m" +msgstr "トランジションの幅" -# Source-Hash: 07ebe5e3780c624b +# English: "Vane count" +# Source-Hash: 812d27902509339f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueHeight.1dhc4y4" -msgstr "煙道の高さ" +msgid "editor.inspector.field.vaneCount.55vtye" +msgstr "羽根の数" -# Source-Hash: ff8319792830d591 +# English: "Wall height" +# Source-Hash: 1e7dcd717c8ac239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueShape.1ealiy4" -msgstr "煙道の形状" +msgid "editor.inspector.field.wallHeight.18vmh5c" +msgstr "壁の高さ" -# Source-Hash: 5acf77854a0030ec +# English: "Wall shell" +# Source-Hash: f14bdaed6101896a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueSpacing.exhzco" -msgstr "煙道の間隔" +msgid "editor.inspector.field.wallShell.vqsyhv" +msgstr "壁の外殻" -# Source-Hash: 0a64209c0edacaa1 +# English: "Wall skirt height" +# Source-Hash: 801268585c9a679b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.footingStyle.e3btg6" -msgstr "フーチングのスタイル" +msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" +msgstr "腰壁の高さ" -# Source-Hash: fd413477f1a9e5ed +# English: "Wall thickness" +# Source-Hash: 6b377d0ba1565477 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameDepth.1934a5n" -msgstr "枠の奥行き" +msgid "editor.inspector.field.wallThickness.17wn4iv" +msgstr "壁の厚さ" -# Source-Hash: 6aa2318fa815e5f2 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameThickness.1agxzd8" -msgstr "枠の厚さ" +msgid "editor.inspector.field.width.gfxz9b" +msgstr "幅" -# Source-Hash: 9a3495a1e3181b56 +# English: "Width2" +# Source-Hash: de05070474f589b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing strategy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.framingStrategy.3jx9qe" -msgstr "下地の方式" +msgid "editor.inspector.field.width2.1nqawvr" +msgstr "幅2" -# Source-Hash: 49f329aab535fd3a +# English: "With back" +# Source-Hash: 6199b641888a83fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fromLevelId.15ieejs" -msgstr "開始階層 ID" +msgid "editor.inspector.field.withBack.1ay8nus" +msgstr "背板付き" -# Source-Hash: dbd062efc7f4fd7f +# English: "With bottom" +# Source-Hash: 699934d8b9571b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gableOverhang.2z0obw" -msgstr "妻側のけらば" +msgid "editor.inspector.field.withBottom.1x1z1vo" +msgstr "底板付き" -# Source-Hash: 517caa8af48afb22 +# English: "With sides" +# Source-Hash: 03987b9aadaab50f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" -msgstr "腰折れ下部の高さ比" +msgid "editor.inspector.field.withSides.uq066x" +msgstr "側板付き" -# Source-Hash: 03ecb8f3b625d49d +# English: "Advanced" +# Source-Hash: f571cb2d06ae01ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" -msgstr "腰折れ下部の幅比" +msgid "editor.inspector.group.advanced.qwfkor" +msgstr "詳細" -# Source-Hash: f3ca5555e8c54375 +# English: "Air" +# Source-Hash: a886619d15bae47e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap x" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapX.wjxa7" -msgstr "隙間 x" +msgid "editor.inspector.group.air.15jotdj" +msgstr "空気" -# Source-Hash: 2736f7c5dfc7d917 +# English: "Appearance" +# Source-Hash: b69fc1f8128958fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap y" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapY.mkbl8" -msgstr "隙間 y" +msgid "editor.inspector.group.appearance.dli9wf" +msgstr "外観" -# Source-Hash: 5d03ca50d712339b +# English: "Body" +# Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.glassThickness.s97b0x" -msgstr "ガラスの厚さ" +msgid "editor.inspector.group.body.ign4wl" +msgstr "本体" -# Source-Hash: 4fc3e60be28f60d5 +# English: "Cab" +# Source-Hash: fdf7108a011bd94b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.groundClearance.4os7f0" -msgstr "地面とのクリアランス" +msgid "editor.inspector.group.cab.o4z1ox" +msgstr "かご" -# Source-Hash: 6a22a09b67309545 +# English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.guardInfill.16heiig" -msgstr "手すり壁の充填" +msgid "editor.inspector.group.cabinet.rzvymr" +msgstr "キャビネット" -# Source-Hash: 98dfdac04af3c762 +# English: "Cap" +# Source-Hash: d52b53241789ee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterEnabled.1oxiltf" -msgstr "雨樋を有効化" +msgid "editor.inspector.group.cap.s0thej" +msgstr "キャップ" -# Source-Hash: 4bdc3cdf0fe0ac3c +# English: "Collar" +# Source-Hash: 93a545688c917e69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterProfile.1ngzqi3" -msgstr "雨樋の形状" +msgid "editor.inspector.group.collar.1am2iqo" +msgstr "カラー" -# Source-Hash: 76555670cba9bb13 +# English: "Connection" +# Source-Hash: 7a9c55b1c5a6e61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterSize.jgavtz" -msgstr "雨樋のサイズ" +msgid "editor.inspector.group.connection.2r1h4p" +msgstr "接続" -# Source-Hash: c14d3aa4a98efa98 +# English: "Connections" +# Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger max reach" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerMaxReach.1869epv" -msgstr "吊りの最大到達距離" +msgid "editor.inspector.group.connections.ai4w8u" +msgstr "接続" -# Source-Hash: dfbc357fb556559b +# English: "Construction" +# Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerSpacing.i2b5d7" -msgstr "吊りの間隔" +msgid "editor.inspector.group.construction.j6p9kc" +msgstr "構造" -# Source-Hash: 3ce5e50c572644ed +# English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerStyle.l7v7gb" -msgstr "吊りのスタイル" +msgid "editor.inspector.group.cricket.6iwg8g" +msgstr "クリケット" -# Source-Hash: 646ef0fbcbc58edf +# English: "Curb" +# Source-Hash: b8ff053666ee326b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height.7puwki" -msgstr "高さ" +msgid "editor.inspector.group.curb.5xi2ol" +msgstr "立ち上がり" -# Source-Hash: d0b836103c795726 +# English: "Curve points" +# Source-Hash: 3b9c59e996dcc9c5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height2.7b3jlc" -msgstr "高さ2" +msgid "editor.inspector.group.curvePoints.ztdcnn" +msgstr "曲線の点" -# Source-Hash: 681fd794e601ace5 +# English: "Dimensions" +# Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height above ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.heightAboveRidge.1k99brk" -msgstr "棟からの高さ" +msgid "editor.inspector.group.dimensions.pewa8g" +msgstr "寸法" -# Source-Hash: b4905e5ec62a170f +# English: "Doors" +# Source-Hash: 20f60b98de67849b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highEdgeHeight.1s7v90t" -msgstr "高い側の高さ" +msgid "editor.inspector.group.doors.18w2kpa" +msgstr "ドア" -# Source-Hash: c136d5cd7f3ce04a +# English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highOverhang.67w55d" -msgstr "高い側の出" +msgid "editor.inspector.group.dormer.1fjs5tq" +msgstr "ドーマー" -# Source-Hash: 8b4cd537cc650dac +# English: "Dormer roof" +# Source-Hash: cda262793b4cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High side mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highSideMode.1rc1r5j" -msgstr "高い側のモード" +msgid "editor.inspector.group.dormerRoof.u9uy2g" +msgstr "ドーマー屋根" -# Source-Hash: c38e4e73ed9779f4 +# English: "Drainage" +# Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hood overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hoodOverhang.1q1hcin" -msgstr "フードの張り出し" +msgid "editor.inspector.group.drainage.7easy0" +msgstr "排水" -# Source-Hash: 9884ee52a0e84c0d +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.infillPlacement.11dxzpo" -msgstr "充填の位置" +msgid "editor.inspector.group.elevation.4jbp44" +msgstr "高さ" -# Source-Hash: 37fde9f9290ddc59 +# English: "End caps" +# Source-Hash: a9cba06250228a55 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.inletShape.ypayh2" -msgstr "入口の形状" +msgid "editor.inspector.group.endCaps.4fsb5b" +msgstr "エンドキャップ" -# Source-Hash: d840917007332b94 +# English: "Equipment" +# Source-Hash: 228f2206862e41cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.innerRadius.1fkmg0j" -msgstr "内半径" +msgid "editor.inspector.group.equipment.1xtq0ev" +msgstr "機器" -# Source-Hash: c8ce389856a90ae2 +# English: "Face" +# Source-Hash: a19276baa3688cbf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulated.iwziey" -msgstr "断熱" +msgid "editor.inspector.group.face.1531fr0" +msgstr "面" -# Source-Hash: 043a7a10b6c91f4f +# English: "Fitting" +# Source-Hash: fb34ee7c59cb84ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation r" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulationR.663ufx" -msgstr "断熱 R" +msgid "editor.inspector.group.fitting.171z1r4" +msgstr "継手" -# Source-Hash: fe661655ba41b709 +# English: "Flues" +# Source-Hash: f817c09ef0bb6d69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternHeight.oz7zpm" -msgstr "ランタンの高さ" +msgid "editor.inspector.group.flues.nq6pye" +msgstr "煙道" -# Source-Hash: ab22022e7d5597af +# English: "Footprint" +# Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern top scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternTopScale.hkmpp2" -msgstr "ランタン上部の比率" +msgid "editor.inspector.group.footprint.g8q2pg" +msgstr "フットプリント" -# Source-Hash: 17f854a76ebc033a +# English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerDepth.7av8k9" -msgstr "受け材の奥行き" +msgid "editor.inspector.group.frame.q7q9fq" +msgstr "枠" -# Source-Hash: 547d3a8a9e3e24f5 +# English: "Geometry" +# Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerHeight.zo2oif" -msgstr "受け材の高さ" +msgid "editor.inspector.group.geometry.a21os1" +msgstr "ジオメトリ" -# Source-Hash: 88603f884243c81e +# English: "Grid" +# Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger vertical offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" -msgstr "受け材の垂直オフセット" +msgid "editor.inspector.group.grid.d8gwxd" +msgstr "グリッド" -# Source-Hash: 6519f495202b830d +# English: "Hangers" +# Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftEndCondition.1syqlaw" -msgstr "左端の条件" +msgid "editor.inspector.group.hangers.agnfzn" +msgstr "吊り金具" -# Source-Hash: d041b8217c356977 +# English: "Hardware" +# Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftOverhang.1ydl9ma" -msgstr "左の張り出し" +msgid "editor.inspector.group.hardware.1nygwm1" +msgstr "金物" -# Source-Hash: 643687bc3adaac76 +# English: "Hung wall" +# Source-Hash: 2a47557eb4a5f766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.length.1kpdcfp" -msgstr "長さ" +msgid "editor.inspector.group.hungWall.1ftr1gz" +msgstr "吊り壁" -# Source-Hash: d6deaf0ceb9f43af +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.liquidDiameter.w3pk7c" -msgstr "液管の直径" +msgid "editor.inspector.group.insulation.1kyo5e7" +msgstr "断熱材" -# Source-Hash: 45aca762426392fa +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louver count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.louverCount.lhw2gr" -msgstr "ルーバーの数" +msgid "editor.inspector.group.lantern.1h7o2xb" +msgstr "ランタン" -# Source-Hash: c645a86c55eb0075 +# English: "Levels" +# Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low beam inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowBeamInset.1g7yw5l" -msgstr "低い梁の内側オフセット" +msgid "editor.inspector.group.levels.1s6yaru" +msgstr "階層" -# Source-Hash: 7e7b896682c78c5b +# English: "Line" +# Source-Hash: 1a849e52a46d5087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowEdgeHeight.47mae1" -msgstr "低い側の高さ" +msgid "editor.inspector.group.line.166mv5z" +msgstr "線" -# Source-Hash: 9deb775fc64854b2 +# English: "Lines" +# Source-Hash: 9ab4f7a738ebee13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowOverhang.t4nz1h" -msgstr "低い側の出" +msgid "editor.inspector.group.lines.12bghr0" +msgstr "線" -# Source-Hash: 9aa621d270fe64b1 +# English: "Motion" +# Source-Hash: c0ff16d77ebc9593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" -msgstr "マンサード急勾配部の高さ比" +msgid "editor.inspector.group.motion.1be7sjr" +msgstr "動き" -# Source-Hash: 7cb03b27c632949b +# English: "Mounting" +# Source-Hash: 81d365ccef73068c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" -msgstr "マンサード急勾配部の幅比" +msgid "editor.inspector.group.mounting.feutd2" +msgstr "取り付け方法" -# Source-Hash: 95772435857a7424 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" -msgstr "母屋根の素材に合わせる" +msgid "editor.inspector.group.opening.dfet3" +msgstr "開口" -# Source-Hash: dc22618e31654bde +# English: "Panel dimensions" +# Source-Hash: f46314c54627858d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofStructure.bojht3" -msgstr "母屋根の構造に合わせる" +msgid "editor.inspector.group.panelDimensions.b59qe6" +msgstr "パネルの寸法" -# Source-Hash: 06b36a09a84fa8b1 +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mount.h80tay" -msgstr "取り付け" +msgid "editor.inspector.group.patternDistribution.fmam51" +msgstr "パターンの配分" -# Source-Hash: a288700f96d2b362 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mountingType.1xvin1e" -msgstr "取り付けの種類" +msgid "editor.inspector.group.placement.13lcvts" +msgstr "配置" -# Source-Hash: 3087bc9b219af9ef +# English: "Position" +# Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Neck height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.neckHeight.19ym8n1" -msgstr "首の高さ" +msgid "editor.inspector.group.position.1quewx6" +msgstr "位置" -# Source-Hash: 1c0b8a5503c5daeb +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.opacity.1pmpfd2" -msgstr "不透明度" +msgid "editor.inspector.group.profile.1kwlci6" +msgstr "形状" -# Source-Hash: 51b7ab6a0a78d90a +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingAngle.brhi7c" -msgstr "開き角度" +msgid "editor.inspector.group.railing.19a5bnr" +msgstr "手すり" -# Source-Hash: 1d714b576a057902 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingOffset.wpoqt6" -msgstr "開口のオフセット" +msgid "editor.inspector.group.return.1klrjrz" +msgstr "還気" -# Source-Hash: 55e5629865efd03d +# English: "Settings" +# Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingSide.18l2iwe" -msgstr "開く側" +msgid "editor.inspector.group.settings.ktdb8o" +msgstr "設定" -# Source-Hash: 9824d357692ecb8a +# English: "Shaft" +# Source-Hash: 4fc63ad21de30103 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Operation state" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.operationState.1lwrz07" -msgstr "開閉状態" +msgid "editor.inspector.group.shaft.mad9cv" +msgstr "シャフト" -# Source-Hash: 4f1cbc14dcfb8f3b +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.outletShape.lgpqhx" -msgstr "出口の形状" +msgid "editor.inspector.group.shape.iq9t52" +msgstr "形状" -# Source-Hash: 88540def2ce16ac5 +# English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.overhang.b5ljpv" -msgstr "張り出し" +msgid "editor.inspector.group.shoulder.1nyfx25" +msgstr "ショルダー" -# Source-Hash: 50d7e5e370667f63 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelHeight.1kf31vk" -msgstr "パネルの高さ" +msgid "editor.inspector.group.size.1a4x3zw" +msgstr "サイズ" -# Source-Hash: 068d9e0adabc4578 +# English: "Structure" +# Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelWidth.946581" -msgstr "パネル幅" +msgid "editor.inspector.group.structure.gezdra" +msgstr "構造" -# Source-Hash: aca94a3adc0b0f01 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternAlignment.jd1ht0" -msgstr "パターンの揃え" +msgid "editor.inspector.group.style.nf3vcm" +msgstr "スタイル" -# Source-Hash: 9e9f9408ec2c8f0b +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternCount.1r37pve" -msgstr "パターン数" +msgid "editor.inspector.group.supply.1oy2zwu" +msgstr "給気" -# Source-Hash: 4712c449e9a42d3b +# English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternDistribution.fmam51" -msgstr "パターンの配分" +msgid "editor.inspector.group.surface.wtamqk" +msgstr "面" -# Source-Hash: 63f833f219b9a592 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern remainder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternRemainder.i47p5y" -msgstr "パターンの余り" +msgid "editor.inspector.group.terminal.1501knl" +msgstr "端末" -# Source-Hash: e0b8f766411329ff +# English: "Topology" +# Source-Hash: 399fc3e62f491f08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketProfile.1xgzuyc" -msgstr "ピケットの形状" +msgid "editor.inspector.group.topology.18t8dyc" +msgstr "トポロジー" -# Source-Hash: dfac7d68ea16e8a8 +# English: "Transform" +# Source-Hash: f425799b82e085ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket rail count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketRailCount.atgt6w" -msgstr "ピケットのレール数" +msgid "editor.inspector.group.transform.1c55ayj" +msgstr "変形" -# Source-Hash: cd2652254a81d288 +# English: "Trap" +# Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketSpacing.p8d98w" -msgstr "ピケットの間隔" +msgid "editor.inspector.group.trap.1qc7vhe" +msgstr "トラップ" -# Source-Hash: 462f6cb03b23d773 +# English: "Type" +# Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTop.1tskplu" -msgstr "ピケットの上端" +msgid "editor.inspector.group.type.1m2zofh" +msgstr "種類" -# Source-Hash: 0a4dbbe1cf592326 +# English: "Abs" +# Source-Hash: c8fd17853839516f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.picketTopClearance.16eq1k0" -msgstr "ピケット上端のクリアランス" +#, fuzzy +msgid "editor.inspector.option.abs.132mz8b" +msgstr "ABS" -# Source-Hash: c99ccea380ca41bf +# English: "Access panel" +# Source-Hash: fd9eb1a16cb6b996 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket variation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketVariation.f7vl1c" -msgstr "ピケットのばらつき" +msgid "editor.inspector.option.accessPanel.13sptb5" +msgstr "点検口" -# Source-Hash: 798382373625fecd +# English: "Air handler" +# Source-Hash: db8064add8c27a5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketWidth.3fr3rh" -msgstr "ピケットの幅" +msgid "editor.inspector.option.airHandler.1crnyg3" +msgstr "エアハンドラー" -# Source-Hash: 48ce215c3be034c7 +# English: "Alternating" +# Source-Hash: d8234782848be927 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pipeMaterial.1ggc2wy" -msgstr "配管の材料" +msgid "editor.inspector.option.alternating.5jashi" +msgstr "交互" -# Source-Hash: 75e3449fc1776d09 +# English: "Arched" +# Source-Hash: 63cbda7b09662368 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pitch.xe6ekx" -msgstr "勾配" +msgid "editor.inspector.option.arched.14188w0" +msgstr "アーチ型" -# Source-Hash: 9da0cbfad144d737 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.position.1quewx6" -msgstr "位置" +msgid "editor.inspector.option.auto.540b06" +msgstr "自動" -# Source-Hash: d090baf9f08c2280 +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post bracing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postBracing.crq8nn" -msgstr "支柱の筋交い" +msgid "editor.inspector.option.automatic.r122ro" +msgstr "自動" -# Source-Hash: 6467b580eed1d333 +# English: "Back" +# Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCap.1xcvxrv" -msgstr "支柱キャップ" +msgid "editor.inspector.option.back.1hzmxtu" +msgstr "戻る" -# Source-Hash: e1d91cdd16cc6593 +# English: "Balusters" +# Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCount.22r56c" -msgstr "支柱の数" +msgid "editor.inspector.option.balusters.1j3w6ng" +msgstr "手すり子" -# Source-Hash: d6d8b8653cb43624 +# English: "Band" +# Source-Hash: f211248ec8619e4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postDepth.1ko6j1m" -msgstr "支柱の奥行き" +msgid "editor.inspector.option.band.160umau" +msgstr "帯" -# Source-Hash: dd071e84e259ee48 +# English: "Base plate" +# Source-Hash: 8f9ff05c6a3b08a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postInset.le36li" -msgstr "支柱の内側オフセット" +msgid "editor.inspector.option.basePlate.17fcg8k" +msgstr "ベースプレート" -# Source-Hash: 88c5d586822e58f0 +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post layout mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postLayoutMode.16jre62" -msgstr "支柱配置モード" +msgid "editor.inspector.option.boards.u14hig" +msgstr "板" -# Source-Hash: faa36308c1ecb2a9 +# English: "Bookshelf" +# Source-Hash: c0fee00bdfb09d37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSize.1jodxek" -msgstr "支柱のサイズ" +msgid "editor.inspector.option.bookshelf.ls0zqk" +msgstr "本棚" -# Source-Hash: c8e8490c957e01f3 +# English: "Both" +# Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSpacing.voppfi" -msgstr "支柱の間隔" +msgid "editor.inspector.option.both.mkyb42" +msgstr "両方" -# Source-Hash: b98cbe8478508d41 +# English: "Bottom" +# Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post through" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postThrough.1abgu4a" -msgstr "支柱を貫通" +msgid "editor.inspector.option.bottom.9mpruy" +msgstr "下" -# Source-Hash: d1492df1fbb22c29 +# English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postWidth.rk26fz" -msgstr "支柱の幅" +msgid "editor.inspector.option.box.1nbjcuq" +msgstr "ボックス" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Break" +# Source-Hash: 2c2815df4f724abe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.profile.1kwlci6" -msgstr "形状" +msgid "editor.inspector.option.break.17spnvc" +msgstr "分割" -# Source-Hash: d01cd36d3939abab +# English: "Butterfly" +# Source-Hash: 0d5a25303d3d8058 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.projection.jhtig0" -msgstr "出" +msgid "editor.inspector.option.butterfly.pvv0g4" +msgstr "バタフライ" -# Source-Hash: 800a7a9159519b46 +# English: "Cable" +# Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinHeight.eo9cjm" -msgstr "母屋の高さ" +msgid "editor.inspector.option.cable.fp50zq" +msgstr "ケーブル" -# Source-Hash: da4d0d12715d6c33 +# English: "Cast iron" +# Source-Hash: 69129e514347672a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinSpacing.4gbzb2" -msgstr "母屋の間隔" +msgid "editor.inspector.option.castIron.1q2z4qk" +msgstr "鋳鉄" -# Source-Hash: b4774bcbdf599812 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinWidth.r6tu3j" -msgstr "母屋の幅" +msgid "editor.inspector.option.ceiling.1t2zaqo" +msgstr "天井" -# Source-Hash: 2a4dd54f8a8c15ef +# English: "Center" +# Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter end inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterEndInset.14c0wy5" -msgstr "垂木端の内側オフセット" +msgid "editor.inspector.option.center.kzdxno" +msgstr "中央" -# Source-Hash: 5a80a11b97c0e043 +# English: "Center opening" +# Source-Hash: 65f410261609060b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterHeight.1e1n99w" -msgstr "垂木の高さ" +msgid "editor.inspector.option.centerOpening.bn1rvw" +msgstr "中央開き" -# Source-Hash: b6348d554660af5b +# English: "Cleanout" +# Source-Hash: cf5f9756cc866f3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterSpacing.19subc0" -msgstr "垂木の間隔" +msgid "editor.inspector.option.cleanout.lww8bm" +msgstr "掃除口" -# Source-Hash: 112c447946c3beaa +# English: "Concrete pad" +# Source-Hash: e71d1d949f1d1b41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterWidth.eksj4t" -msgstr "垂木の幅" +msgid "editor.inspector.option.concretePad.m1zdij" +msgstr "コンクリートパッド" -# Source-Hash: 7020d720876a2106 +# English: "Condenser" +# Source-Hash: a3804c4f5e6aefbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingHeight.6wf4bm" -msgstr "手すりの高さ" +msgid "editor.inspector.option.condenser.a74nps" +msgstr "室外機" -# Source-Hash: 3e89ce193885b5b4 +# English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingMode.ov7xpu" -msgstr "手すりのモード" +msgid "editor.inspector.option.corbeled.bovs4v" +msgstr "持ち送り" -# Source-Hash: 174342bb31a86afc +# English: "Count" +# Source-Hash: 8a4beb9a57edc26e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingStyle.1nyd2u" -msgstr "手すりのスタイル" +msgid "editor.inspector.option.count.1qoi4r8" +msgstr "数" -# Source-Hash: a44eedc895b95474 +# English: "Coupling" +# Source-Hash: db1f8f141714b363 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.resizeLock.b2hbr0" -msgstr "比率を固定" +msgid "editor.inspector.option.coupling.111aap2" +msgstr "カップリング" -# Source-Hash: edd319917231def8 +# English: "Covering specific" +# Source-Hash: 7a9a92d2e59a3bbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnDiameter.166ozhe" -msgstr "還気の直径" +msgid "editor.inspector.option.coveringSpecific.1h9fo8w" +msgstr "仕上げ固有" -# Source-Hash: dafa151b183daa77 +# English: "Cross" +# Source-Hash: d500c4b7827d3390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnHeight.b9sq6y" -msgstr "還気の高さ" +msgid "editor.inspector.option.cross.1elubzv" +msgstr "クロス" -# Source-Hash: cd55fd029ad2803b +# English: "Cubby" +# Source-Hash: 7ca7f00b3ba531f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnShape.1e3a27i" -msgstr "還気の形状" +msgid "editor.inspector.option.cubby.ivq4qc" +msgstr "小物入れ" -# Source-Hash: 6a7c411667124177 +# English: "Curved" +# Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnWidth.z9r3h3" -msgstr "還気の幅" +msgid "editor.inspector.option.curved.6noz1s" +msgstr "曲線" -# Source-Hash: 460bd11bfc2e5697 +# English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightEndCondition.tjlw3l" -msgstr "右端の条件" +msgid "editor.inspector.option.cylinder.eol6vj" +msgstr "円柱" -# Source-Hash: 1ed35f8d27ae5583 +# English: "Damper" +# Source-Hash: 53f224e2828d5ab7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightOverhang.yqbeft" -msgstr "右の張り出し" +msgid "editor.inspector.option.damper.wgri80" +msgstr "ダンパー" -# Source-Hash: 39c02aa2d97a62ba +# English: "Destination" +# Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofHeight.1uhy6qy" -msgstr "屋根の高さ" +msgid "editor.inspector.option.destination.gfzm1r" +msgstr "接続先" -# Source-Hash: d62f8833d77ca8a1 +# English: "Diffuser" +# Source-Hash: bfb3561e28d92a39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofStyle.pawqfy" -msgstr "屋根のスタイル" +msgid "editor.inspector.option.diffuser.1igj0jh" +msgstr "ディフューザー" -# Source-Hash: 0ddd561a4126b826 +# English: "Dog ear" +# Source-Hash: 6cff941e10ff22b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofThickness.1xlr3rx" -msgstr "屋根の厚さ" +msgid "editor.inspector.option.dogEar.xhtvjt" +msgstr "ドッグイヤー" -# Source-Hash: 3a08ede9e51d5395 +# English: "Dome" +# Source-Hash: de47e23eb81efe02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofType.1m9kv5h" -msgstr "屋根の種類" +msgid "editor.inspector.option.dome.1c87e56" +msgstr "ドーム" -# Source-Hash: 01989595ee7c1dc0 +# English: "Double" +# Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rotation.ly2rj" -msgstr "回転" +msgid "editor.inspector.option.double.13h5354" +msgstr "両開き" -# Source-Hash: c913b494c3901a17 +# English: "Duct board" +# Source-Hash: 72a84a9762461745 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rows.1ba1neg" -msgstr "行" +msgid "editor.inspector.option.ductBoard.lc3o4t" +msgstr "ダクトボード" -# Source-Hash: 8210f376c0d29acc +# English: "Dutch" +# Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.scale.b6rpxt" -msgstr "縮尺" +msgid "editor.inspector.option.dutch.1djx429" +msgstr "入母屋" -# Source-Hash: adb6face4bac5245 +# English: "Elbow" +# Source-Hash: e6637bddef6aabd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seam detail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.seamDetail.197za48" -msgstr "継ぎ目の詳細" +msgid "editor.inspector.option.elbow.1flup74" +msgstr "エルボ" -# Source-Hash: 4b2aed9d56cc4dee +# English: "End" +# Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.segmentType.l667sy" -msgstr "セグメントの種類" +msgid "editor.inspector.option.end.1llf32i" +msgstr "終点" -# Source-Hash: a7794a81141b4de4 +# English: "End cap" +# Source-Hash: ada9fc2eec2c7d61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftStyle.1i5ampa" -msgstr "シャフトのスタイル" +msgid "editor.inspector.option.endCap.1p4v4na" +msgstr "エンドキャップ" -# Source-Hash: 8eb6438543b18cdb +# English: "Equal fit" +# Source-Hash: 73916b8395208874 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftWallThickness.bduslr" -msgstr "シャフト壁の厚さ" +msgid "editor.inspector.option.equalFit.11vw52c" +msgstr "均等配置" -# Source-Hash: 85ea1d3c8b749680 +# English: "Fixed count" +# Source-Hash: 83579a6e420df2ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape.iq9t52" -msgstr "形状" +msgid "editor.inspector.option.fixedCount.1o3dznu" +msgstr "固定数" -# Source-Hash: 44a8ab89c854cad1 +# English: "Fixed spacing" +# Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape2.fi0nx8" -msgstr "形状2" +msgid "editor.inspector.option.fixedSpacing.hl4dr4" +msgstr "固定間隔" -# Source-Hash: fcee9712879570ae +# English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed high side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shedHighSide.bzzddg" -msgstr "片流れの高い側" +msgid "editor.inspector.option.flat.ly0bei" +msgstr "陸屋根" -# Source-Hash: f5a931cd6e97e5e2 +# English: "Flex" +# Source-Hash: a9cad0badcc16693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shingleThickness.1hn5wxr" -msgstr "シングルの厚さ" +msgid "editor.inspector.option.flex.ktpamq" +msgstr "フレキシブル" -# Source-Hash: b14990b3e4222dd3 +# English: "Floating" +# Source-Hash: 7843c5142b7d066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder extent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderExtent.11j8uiv" -msgstr "ショルダーの範囲" +msgid "editor.inspector.option.floating.1oadpln" +msgstr "浮き" -# Source-Hash: 505de343ec5010e6 +# English: "Floor" +# Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderHeight.xyqn6g" -msgstr "ショルダーの高さ" +msgid "editor.inspector.option.floor.q9caf1" +msgstr "階" -# Source-Hash: 58d48c01b7ec1a40 +# English: "Flush" +# Source-Hash: eaaa20f3b603140f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderStyle.1hkcv2w" -msgstr "ショルダーのスタイル" +msgid "editor.inspector.option.flush.olwwu5" +msgstr "面一" -# Source-Hash: af84db1026bf2e6c +# English: "Front" +# Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show center column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showCenterColumn.1uggg45" -msgstr "中央の柱を表示" +msgid "editor.inspector.option.front.uhugl4" +msgstr "前面" -# Source-Hash: 80214dc086ebc1b1 +# English: "Furnace" +# Source-Hash: bccfc0565157dae6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showInfill.33rv7u" -msgstr "充填を表示" +msgid "editor.inspector.option.furnace.w0mzpf" +msgstr "暖房炉" -# Source-Hash: f02d6cdfb4c6ad08 +# English: "Gable" +# Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show step supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showStepSupports.1kg8h8m" -msgstr "段の支持材を表示" +msgid "editor.inspector.option.gable.1ekyx6" +msgstr "切妻" -# Source-Hash: 5d1147c774718b76 +# English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side flashing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sideFlashing.cv744o" -msgstr "側面の水切り" +msgid "editor.inspector.option.gambrel.1yo90d9" +msgstr "腰折れ" -# Source-Hash: 60bb3338e0ad558c +# English: "Generic" +# Source-Hash: 3c4900155c96b139 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.size.1a4x3zw" -msgstr "サイズ" +msgid "editor.inspector.option.generic.1074oey" +msgstr "汎用" -# Source-Hash: fbdbc6c97c75b030 +# English: "Glass" +# Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.skylightType.1a5vj3i" -msgstr "天窓の種類" +msgid "editor.inspector.option.glass.163tc5n" +msgstr "ガラス" -# Source-Hash: c9a6561cb3c3da82 +# English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab opening mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slabOpeningMode.g4f730" -msgstr "スラブ開口のモード" +msgid "editor.inspector.option.glassFrame.jp9s3m" +msgstr "ガラス枠" -# Source-Hash: ef0efc845ccdba78 +# English: "Globe" +# Source-Hash: 2d4223f80dc3945c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slatGap.slmk93" -msgstr "スラットの隙間" +msgid "editor.inspector.option.globe.1avkqoy" +msgstr "グローブ" -# Source-Hash: 4daab7a2ce2ecef3 +# English: "Grounded" +# Source-Hash: 398af4f45ba60c85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slideDirection.1527q13" -msgstr "スライド方向" +msgid "editor.inspector.option.grounded.jwoq73" +msgstr "接地" -# Source-Hash: 93b34a5b546664c4 +# English: "Guard" +# Source-Hash: 4972a0e68d69be58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.span.1bmmuw1" -msgstr "スパン" +msgid "editor.inspector.option.guard.1m575se" +msgstr "手すり壁" -# Source-Hash: 3b006be0b1f0493f +# English: "Half round" +# Source-Hash: 67be98efb58a7767 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.speed.2w2xwg" -msgstr "速度" +msgid "editor.inspector.option.halfRound.m5hob0" +msgstr "半丸" -# Source-Hash: c8cfb8336a240cb1 +# English: "Hidden" +# Source-Hash: 229199467e9cf2db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spin speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.spinSpeed.1ozx746" -msgstr "回転速度" +msgid "editor.inspector.option.hidden.gp48rd" +msgstr "非表示" -# Source-Hash: c9f54c3b258961c7 +# English: "Hip" +# Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stairType.1u05e02" -msgstr "階段の種類" +msgid "editor.inspector.option.hip.r0jnjy" +msgstr "寄棟" -# Source-Hash: 0af6194a8b8d8be6 +# English: "Horizontal" +# Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoff.4wv2g0" -msgstr "スタンドオフ" +msgid "editor.inspector.option.horizontal.tyapah" +msgstr "水平" -# Source-Hash: 0464d4efa1e80c62 +# English: "Include" +# Source-Hash: 6a9b4142ab179557 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoffHeight.1qdnff" -msgstr "スタンドオフの高さ" +msgid "editor.inspector.option.include.1a27vnr" +msgstr "含める" -# Source-Hash: 85da28b0b26b8c2a +# English: "Independent high beam" +# Source-Hash: 8061b426b9d1b5fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.startPost.1ood9kv" -msgstr "始端の支柱" +msgid "editor.inspector.option.independentHighBeam.1tn0zvy" +msgstr "独立した高い梁" -# Source-Hash: d5dc1ba8cca3193d +# English: "Industrial" +# Source-Hash: 1dc686f2068f976b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stepCount.nzorrw" -msgstr "段数" +msgid "editor.inspector.option.industrial.8sd9u" +msgstr "インダストリアル" -# Source-Hash: cadbb9cadd6a5e6f +# English: "Inline" +# Source-Hash: 0c9b1ec8947ee177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapSpacing.1cyt3dc" -msgstr "バンドの間隔" +msgid "editor.inspector.option.inline.1uh05lg" +msgstr "インライン" -# Source-Hash: c017cd56b3ae5865 +# English: "Integrated" +# Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapStyle.fh1oy4" -msgstr "バンドのスタイル" +msgid "editor.inspector.option.integrated.1xd9lv0" +msgstr "一体型" -# Source-Hash: 1a79a485d91f3f28 +# English: "Joined" +# Source-Hash: bb197b9219f32603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.style.nf3vcm" -msgstr "スタイル" +msgid "editor.inspector.option.joined.1x2o8ds" +msgstr "結合済み" -# Source-Hash: 7b3f016989c631a7 +# English: "Kickout" +# Source-Hash: 0299cc0ee05d17fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Suction diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.suctionDiameter.nutxtj" -msgstr "吸入管の直径" +msgid "editor.inspector.option.kickout.vt8s6p" +msgstr "キックアウト" -# Source-Hash: 3f1e0fd30e5b1f39 +# English: "Knee" +# Source-Hash: a0712c8461bb0758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyDiameter.h5ldh5" -msgstr "給気の直径" +msgid "editor.inspector.option.knee.186hcui" +msgstr "ニー" -# Source-Hash: 05c9b608538e181d +# English: "K style" +# Source-Hash: 083febe9876348ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyHeight.zy6ru1" -msgstr "給気の高さ" +msgid "editor.inspector.option.kStyle.m8kfwz" +msgstr "K型" -# Source-Hash: 3c9400722baa474d +# English: "Landing" +# Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyShape.k39wnb" -msgstr "給気の形状" +msgid "editor.inspector.option.landing.tyqarg" +msgstr "踊り場" -# Source-Hash: af7619025438f995 +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyWidth.g66axi" -msgstr "給気の幅" +msgid "editor.inspector.option.lantern.1h7o2xb" +msgstr "ランタン" -# Source-Hash: 617e198cf3f214d3 +# English: "Leave" +# Source-Hash: 9477f663422698ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Support offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supportOffset.1w4cocr" -msgstr "支持のオフセット" +msgid "editor.inspector.option.leave.1xf0sbk" +msgstr "離れる" -# Source-Hash: 2686abc093f3dfb1 +# English: "Left" +# Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sweepAngle.ucmmlw" -msgstr "スイープ角度" +msgid "editor.inspector.option.left.14n06mo" +msgstr "左" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Level" +# Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.system.13qbhrw" -msgstr "システム" +msgid "editor.inspector.option.level.i4lhtp" +msgstr "階層" -# Source-Hash: ccb3afc48bd9a284 +# English: "Low profile" +# Source-Hash: a281a83a9fef6b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminal.1501knl" -msgstr "端末" +msgid "editor.inspector.option.lowProfile.1h9lgzg" +msgstr "ロープロファイル" -# Source-Hash: de1777c521046f7d +# English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminalType.1ne8lgb" -msgstr "端末の種類" +msgid "editor.inspector.option.mansard.dwzck9" +msgstr "マンサード" -# Source-Hash: fdbb503f0941bd56 +# English: "Manual" +# Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.thickness.1l8hu45" -msgstr "厚さ" +msgid "editor.inspector.option.manual.1wu7r43" +msgstr "手動" -# Source-Hash: 58cb60c14826f1d0 +# English: "Maximum spacing" +# Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.tiltAngle.1312yw1" -msgstr "傾斜角度" +msgid "editor.inspector.option.maximumSpacing.n1fzl4" +msgstr "最大間隔" -# Source-Hash: 7057972ff4c78dc7 +# English: "Metal" +# Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.toLevelId.8i5qx7" -msgstr "終了階層 ID" +msgid "editor.inspector.option.metal.1az9fgc" +msgstr "金属" -# Source-Hash: fa8fc3d5c612e774 +# English: "Metal panel" +# Source-Hash: c6185ecb219a9435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingDepth.ki7gk2" -msgstr "上部の踊り場の奥行き" +msgid "editor.inspector.option.metalPanel.1xdz476" +msgstr "金属パネル" -# Source-Hash: 6e4a0b98089ec2cd +# English: "Minimal" +# Source-Hash: 2fd92342f3986843 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingMode.zk0h7e" -msgstr "上部の踊り場のモード" +msgid "editor.inspector.option.minimal.fwmiwg" +msgstr "ミニマル" -# Source-Hash: c182bb3c8a701085 +# English: "Mono" +# Source-Hash: 5d8a5912056f83ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top rail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topRailHeight.fscisv" -msgstr "上部レールの高さ" +msgid "editor.inspector.option.mono.oszvd6" +msgstr "片流れ" -# Source-Hash: 8c5502f1d8087b7b +# English: "None" +# Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionMode.f3evgt" -msgstr "トランジションのモード" +msgid "editor.inspector.option.none.deku7v" +msgstr "なし" -# Source-Hash: 96eb5997f0aa2dde +# English: "Omit" +# Source-Hash: 7b6e4a6b86ba5112 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionWidth.rtgz3m" -msgstr "トランジションの幅" +msgid "editor.inspector.option.omit.okcs7w" +msgstr "省略" -# Source-Hash: 812d27902509339f +# English: "Open" +# Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vane count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.vaneCount.55vtye" -msgstr "羽根の数" +msgid "editor.inspector.option.open.n6hn1l" +msgstr "開く" -# Source-Hash: 1e7dcd717c8ac239 +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallHeight.18vmh5c" -msgstr "壁の高さ" +msgid "editor.inspector.option.opening.dfet3" +msgstr "開口" -# Source-Hash: f14bdaed6101896a +# English: "Open rack" +# Source-Hash: cc72b9bda6b30b5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallShell.vqsyhv" -msgstr "壁の外殻" +msgid "editor.inspector.option.openRack.mvt1x0" +msgstr "オープンラック" -# Source-Hash: 801268585c9a679b +# English: "Oval" +# Source-Hash: ed6d7b3dd9d5fc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall skirt height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" -msgstr "腰壁の高さ" +msgid "editor.inspector.option.oval.1jiqzxx" +msgstr "楕円" -# Source-Hash: 6b377d0ba1565477 +# English: "Picket" +# Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallThickness.17wn4iv" -msgstr "壁の厚さ" +msgid "editor.inspector.option.picket.zyeoy1" +msgstr "ピケット" -# Source-Hash: 3a0ccd6c46877a87 +# English: "Pointed" +# Source-Hash: 180ffb8b75c977aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width.gfxz9b" -msgstr "幅" +msgid "editor.inspector.option.pointed.effedk" +msgstr "尖頭" -# Source-Hash: de05070474f589b4 +# English: "Post and rail" +# Source-Hash: fca7919fb0c0669c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width2.1nqawvr" -msgstr "幅2" +msgid "editor.inspector.option.postAndRail.lajk2u" +msgstr "支柱とレール" -# Source-Hash: 6199b641888a83fa +# English: "Preserve high edge" +# Source-Hash: a78b28a428b27e20 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBack.1ay8nus" -msgstr "背板付き" +msgid "editor.inspector.option.preserveHighEdge.y6ywf0" +msgstr "高い側を保つ" -# Source-Hash: 699934d8b9571b64 +# English: "Preserve low edge" +# Source-Hash: 325c6f53f6dc7634 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBottom.1x1z1vo" -msgstr "底板付き" +msgid "editor.inspector.option.preserveLowEdge.196m5pk" +msgstr "低い側を保つ" -# Source-Hash: 03987b9aadaab50f +# English: "Preserve pitch" +# Source-Hash: 95c0b860d22a62cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With sides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withSides.uq066x" -msgstr "側板付き" +msgid "editor.inspector.option.preservePitch.1uq973r" +msgstr "勾配を保つ" -# Source-Hash: f571cb2d06ae01ae +# English: "Privacy" +# Source-Hash: d4e11841abf22593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Advanced" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.advanced.qwfkor" -msgstr "詳細" +msgid "editor.inspector.option.privacy.1th4hrb" +msgstr "目隠し" -# Source-Hash: a886619d15bae47e +# English: "Purlins" +# Source-Hash: 1a8c46c7712a83b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.air.15jotdj" -msgstr "空気" +msgid "editor.inspector.option.purlins.od124c" +msgstr "母屋" -# Source-Hash: b69fc1f8128958fc +# English: "Pvc" +# Source-Hash: a9650f6f94de9247 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.appearance.dli9wf" -msgstr "外観" +msgid "editor.inspector.option.pvc.1oc4u2w" +msgstr "PVC" -# Source-Hash: faaa4f6b40001667 +# English: "Pyramid" +# Source-Hash: 47587b825e9a696b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.body.ign4wl" -msgstr "本体" +msgid "editor.inspector.option.pyramid.17ucsqv" +msgstr "ピラミッド" -# Source-Hash: fdf7108a011bd94b +# English: "Rafters" +# Source-Hash: a9cb232546779779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cab.o4z1ox" -msgstr "かご" +msgid "editor.inspector.option.rafters.16tzbby" +msgstr "垂木" -# Source-Hash: 5737c4567e0f6863 +# English: "Rail" +# Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cabinet.rzvymr" -msgstr "キャビネット" +msgid "editor.inspector.option.rail.39zqc9" +msgstr "レール" -# Source-Hash: d52b53241789ee63 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cap.s0thej" -msgstr "キャップ" +msgid "editor.inspector.option.raised.4kyx9h" +msgstr "上げ" -# Source-Hash: 93a545688c917e69 +# English: "Rect" +# Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.collar.1am2iqo" -msgstr "カラー" +msgid "editor.inspector.option.rect.tpfvaf" +msgstr "矩形" -# Source-Hash: 7a9c55b1c5a6e61d +# English: "Reducer" +# Source-Hash: 68206f70689b2340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connection.2r1h4p" -msgstr "接続" +msgid "editor.inspector.option.reducer.18byt67" +msgstr "レジューサー" -# Source-Hash: d613df280aa13b42 +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connections.ai4w8u" -msgstr "接続" +msgid "editor.inspector.option.return.1klrjrz" +msgstr "還気" -# Source-Hash: ce1429556f57d7ee +# English: "Return grille" +# Source-Hash: 63b39b1681f74fe4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.construction.j6p9kc" -msgstr "構造" +msgid "editor.inspector.option.returnGrille.r28ncm" +msgstr "還気グリル" -# Source-Hash: f8ba7d73d3006d57 +# English: "Right" +# Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cricket.6iwg8g" -msgstr "クリケット" +msgid "editor.inspector.option.right.8humpx" +msgstr "右" -# Source-Hash: b8ff053666ee326b +# English: "Round" +# Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curb.5xi2ol" -msgstr "立ち上がり" +msgid "editor.inspector.option.round.1vx0wyj" +msgstr "丸" -# Source-Hash: 3b9c59e996dcc9c5 +# English: "Rounded" +# Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve points" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curvePoints.ztdcnn" -msgstr "曲線の点" +msgid "editor.inspector.option.rounded.199nwpm" +msgstr "角丸" -# Source-Hash: 12df69f42beb0643 +# English: "Sanitary tee" +# Source-Hash: dc67a1cdb2482f23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dimensions.pewa8g" -msgstr "寸法" +msgid "editor.inspector.option.sanitaryTee.oro3d4" +msgstr "排水T字管" -# Source-Hash: 20f60b98de67849b +# English: "Scalloped" +# Source-Hash: beb3fec22c287185 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Doors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.doors.18w2kpa" -msgstr "ドア" +msgid "editor.inspector.option.scalloped.1eltba0" +msgstr "スカラップ" -# Source-Hash: 6b6f8068bbc4c902 +# English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormer.1fjs5tq" -msgstr "ドーマー" +msgid "editor.inspector.option.scoop.qy5zdv" +msgstr "スクープ" -# Source-Hash: cda262793b4cc754 +# English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormerRoof.u9uy2g" -msgstr "ドーマー屋根" +msgid "editor.inspector.option.segmentedPanel.5r3hwl" +msgstr "分割パネル" -# Source-Hash: 00a4416e0a33d856 +# English: "Shed" +# Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.drainage.7easy0" -msgstr "排水" +msgid "editor.inspector.option.shed.6t994v" +msgstr "片流れ" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Sheet metal" +# Source-Hash: 7fbc26cd497cfd12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.elevation.4jbp44" -msgstr "高さ" +msgid "editor.inspector.option.sheetMetal.l164lv" +msgstr "板金" -# Source-Hash: a9cba06250228a55 +# English: "Shingle" +# Source-Hash: 26e2c261223cbb46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.endCaps.4fsb5b" -msgstr "エンドキャップ" +msgid "editor.inspector.option.shingle.pfmynh" +msgstr "シングル" -# Source-Hash: 228f2206862e41cf +# English: "Shingled" +# Source-Hash: e9046e9823b400a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.equipment.1xtq0ev" -msgstr "機器" +msgid "editor.inspector.option.shingled.1nm9inf" +msgstr "シングル葺き" -# Source-Hash: a19276baa3688cbf +# English: "Simple" +# Source-Hash: a05a390ae205fdd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.face.1531fr0" -msgstr "面" +msgid "editor.inspector.option.simple.12mfu73" +msgstr "シンプル" -# Source-Hash: fb34ee7c59cb84ac +# English: "Single" +# Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.fitting.171z1r4" -msgstr "継手" +msgid "editor.inspector.option.single.1uzxurt" +msgstr "片開き" -# Source-Hash: f817c09ef0bb6d69 +# English: "Single left" +# Source-Hash: aee3b2bd78c87d0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flues" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.flues.nq6pye" -msgstr "煙道" +msgid "editor.inspector.option.singleLeft.1atdh02" +msgstr "片開き(左)" -# Source-Hash: a3c10560fa3c1612 +# English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.footprint.g8q2pg" -msgstr "フットプリント" +msgid "editor.inspector.option.singleRight.1axbu67" +msgstr "片開き(右)" -# Source-Hash: ce3a6c03765dbbd8 +# English: "Slant box" +# Source-Hash: 7672d2764445c110 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.frame.q7q9fq" -msgstr "枠" +msgid "editor.inspector.option.slantBox.1ta99vy" +msgstr "斜めボックス" -# Source-Hash: 523b9912e283b644 +# English: "Slat" +# Source-Hash: 405e07a8cc2c88e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.geometry.a21os1" -msgstr "ジオメトリ" +msgid "editor.inspector.option.slat.1p9njbz" +msgstr "スラット" -# Source-Hash: 7e4a0a38f694be6b +# English: "Sliding" +# Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.grid.d8gwxd" -msgstr "グリッド" +msgid "editor.inspector.option.sliding.cd0jox" +msgstr "引き戸" -# Source-Hash: 30d8dba84c42040e +# English: "Slope" +# Source-Hash: e6d82e606b8c9301 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hangers.agnfzn" -msgstr "吊り金具" +msgid "editor.inspector.option.slope.1bn2qw" +msgstr "勾配" -# Source-Hash: 8c1bc3a078056ff7 +# English: "Sloped" +# Source-Hash: f6998707123fe72d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hardware.1nygwm1" -msgstr "金物" +msgid "editor.inspector.option.sloped.lo4sh0" +msgstr "傾斜" -# Source-Hash: 2a47557eb4a5f766 +# English: "Solid" +# Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hung wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hungWall.1ftr1gz" -msgstr "吊り壁" +msgid "editor.inspector.option.solid.o4uy6s" +msgstr "ソリッド" -# Source-Hash: 7d6ed24c02ab0c5c +# English: "Solid panel" +# Source-Hash: 8008b6f17089beaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.insulation.1kyo5e7" -msgstr "断熱材" +msgid "editor.inspector.option.solidPanel.2yaw62" +msgstr "不透明パネル" -# Source-Hash: d4c6c2495c233bb5 +# English: "Spiral" +# Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lantern.1h7o2xb" -msgstr "ランタン" +msgid "editor.inspector.option.spiral.91hcl4" +msgstr "らせん" -# Source-Hash: cc3d601d59e2e26c +# English: "Splash" +# Source-Hash: beef1cf79388b77b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.levels.1s6yaru" -msgstr "階層" +msgid "editor.inspector.option.splash.1uke4cm" +msgstr "跳ね返り" -# Source-Hash: 1a849e52a46d5087 +# English: "Spread" +# Source-Hash: 324677a79ab15e22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.line.166mv5z" -msgstr "線" +msgid "editor.inspector.option.spread.1g1zysu" +msgstr "広がり" -# Source-Hash: 9ab4f7a738ebee13 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lines.12bghr0" -msgstr "線" +msgid "editor.inspector.option.square.1xl1lk6" +msgstr "正方形" -# Source-Hash: c0ff16d77ebc9593 +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Motion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.motion.1be7sjr" -msgstr "動き" +msgid "editor.inspector.option.stair.1xf86e" +msgstr "階段" -# Source-Hash: 81d365ccef73068c +# English: "Standard" +# Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.mounting.feutd2" -msgstr "取り付け方法" +msgid "editor.inspector.option.standard.vuco" +msgstr "標準" -# Source-Hash: e04298381837252f +# English: "Start" +# Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.opening.dfet3" -msgstr "開口" +msgid "editor.inspector.option.start.30xvgf" +msgstr "始点" -# Source-Hash: f46314c54627858d +# English: "Step" +# Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.panelDimensions.b59qe6" -msgstr "パネルの寸法" +msgid "editor.inspector.option.step.jtn9kf" +msgstr "段" -# Source-Hash: 4712c449e9a42d3b +# English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.group.patternDistribution.fmam51" -msgstr "パターンの配分" +#, fuzzy +msgid "editor.inspector.option.stepped.naxzh0" +msgstr "段状" -# Source-Hash: b3be684ac62fbf53 +# English: "Straight" +# Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.placement.13lcvts" -msgstr "配置" +msgid "editor.inspector.option.straight.lxqbfr" +msgstr "直進" -# Source-Hash: 9da0cbfad144d737 +# English: "Strap" +# Source-Hash: b9b45ac5ba1daeee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.position.1quewx6" -msgstr "位置" +msgid "editor.inspector.option.strap.14q0ps9" +msgstr "バンド" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.profile.1kwlci6" -msgstr "形状" +msgid "editor.inspector.option.supply.1oy2zwu" +msgstr "給気" -# Source-Hash: 240ed9c77627b9a1 +# English: "Supply register" +# Source-Hash: 00e7a2b3b2aa36ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.railing.19a5bnr" -msgstr "手すり" +msgid "editor.inspector.option.supplyRegister.1lzdbbn" +msgstr "給気口" -# Source-Hash: 2e0aa7113b021c67 +# English: "Tapered" +# Source-Hash: f86f1f22230596b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.return.1klrjrz" -msgstr "還気" +msgid "editor.inspector.option.tapered.3y5blo" +msgstr "テーパー" -# Source-Hash: 83909f82446ee9c3 +# English: "Target spacing" +# Source-Hash: 5fed23c2217b83fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.settings.ktdb8o" -msgstr "設定" +msgid "editor.inspector.option.targetSpacing.14uibc9" +msgstr "目標の間隔" -# Source-Hash: 4fc63ad21de30103 +# English: "Tee" +# Source-Hash: f3ceed7f867f090f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shaft.mad9cv" -msgstr "シャフト" +msgid "editor.inspector.option.tee.7d6tjp" +msgstr "T字管" -# Source-Hash: 85ea1d3c8b749680 +# English: "Tilted" +# Source-Hash: 787005b26aec58a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shape.iq9t52" -msgstr "形状" +msgid "editor.inspector.option.tilted.6n0u5j" +msgstr "傾斜" -# Source-Hash: 8c6e3985459dacf9 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shoulder.1nyfx25" -msgstr "ショルダー" +msgid "editor.inspector.option.top.2nypcs" +msgstr "上" -# Source-Hash: 60bb3338e0ad558c +# English: "Transition" +# Source-Hash: e6657000554dfe53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.size.1a4x3zw" -msgstr "サイズ" +msgid "editor.inspector.option.transition.1rziz9e" +msgstr "トランジション" -# Source-Hash: a20c89205a4448d2 +# English: "Vent" +# Source-Hash: 04bc0909a133a7e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.structure.gezdra" -msgstr "構造" +msgid "editor.inspector.option.vent.xda5j2" +msgstr "換気口" -# Source-Hash: 1a79a485d91f3f28 +# English: "Walk on" +# Source-Hash: 7eee9aff0dd520b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.style.nf3vcm" -msgstr "スタイル" +msgid "editor.inspector.option.walkOn.1n5sg9l" +msgstr "歩行面" -# Source-Hash: b45f382a67d6c0eb +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.supply.1oy2zwu" -msgstr "給気" +msgid "editor.inspector.option.wall.blj585" +msgstr "壁" -# Source-Hash: 78a454b7ac7a3cd9 +# English: "Wall abutment" +# Source-Hash: f3578f7177ad0054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.surface.wtamqk" -msgstr "面" +msgid "editor.inspector.option.wallAbutment.lza941" +msgstr "壁との取り合い" -# Source-Hash: ccb3afc48bd9a284 +# English: "Wall ledger" +# Source-Hash: 8dcf182f91ba3245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.terminal.1501knl" -msgstr "端末" +msgid "editor.inspector.option.wallLedger.5m8ana" +msgstr "壁の受け材" -# Source-Hash: 399fc3e62f491f08 +# English: "Wall shelf" +# Source-Hash: 0adda30be5f17975 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Topology" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.topology.18t8dyc" -msgstr "トポロジー" +msgid "editor.inspector.option.wallShelf.yiozfl" +msgstr "壁棚" -# Source-Hash: f425799b82e085ed +# English: "Waste" +# Source-Hash: 29d8590334492601 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transform" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.transform.1c55ayj" -msgstr "変形" +msgid "editor.inspector.option.waste.3xrp79" +msgstr "排水" -# Source-Hash: b778a315e82065a0 +# English: "Wye" +# Source-Hash: 00249876bb37079f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.trap.1qc7vhe" -msgstr "トラップ" +msgid "editor.inspector.option.wye.14vupc8" +msgstr "Y字管" -# Source-Hash: c44f7ab8bbd24858 +# English: "Collapse" +# Source-Hash: 179b025e25f9a091 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.group.type.1m2zofh" -msgstr "種類" +msgid "editor.layers.collapse" +msgstr "折りたたむ" -# Source-Hash: c8fd17853839516f +# English: "Ceiling ({area})" +# Source-Hash: dbeae4c43f49327f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Abs" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.abs.132mz8b" -msgstr "ABS" +msgid "editor.layers.defaultName.ceiling" +msgstr "天井({area})" -# Source-Hash: fd9eb1a16cb6b996 +# English: "Collection" +# Source-Hash: d29165c9d4bfc3cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Access panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.accessPanel.13sptb5" -msgstr "点検口" +msgid "editor.layers.defaultName.collection" +msgstr "コレクション" -# Source-Hash: db8064add8c27a5c +# English: "Flight ({width}×{length}m)" +# Source-Hash: 8f536b859be662a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air handler" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.airHandler.1crnyg3" -msgstr "エアハンドラー" +msgid "editor.layers.defaultName.flight" +msgstr "階段({width}×{length}m)" -# Source-Hash: d8234782848be927 +# English: "Landing ({width}×{length}m)" +# Source-Hash: 46dcbf4548374073 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alternating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.alternating.5jashi" -msgstr "交互" +msgid "editor.layers.defaultName.landing" +msgstr "踊り場({width}×{length}m)" -# Source-Hash: 63cbda7b09662368 +# English: "Node" +# Source-Hash: 3ea44453fcd08a6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arched" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.arched.14188w0" -msgstr "アーチ型" +msgid "editor.layers.defaultName.node" +msgstr "ノード" -# Source-Hash: 05b65242d8c48791 +# English: "Roof ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 93f3a04e15b6fb49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.auto.540b06" -msgstr "自動" +msgid "editor.layers.defaultName.roof" +msgstr "屋根({count, plural, other {# セグメント}})" -# Source-Hash: d18036b75b2c3e4f +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" +# Source-Hash: a730fab01c172704 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.automatic.r122ro" -msgstr "自動" +msgid "editor.layers.defaultName.roofSegment" +msgstr "{roofType, select, hip {寄棟} gable {切妻} shed {片流れ} gambrel {腰折れ} dutch {入母屋} mansard {マンサード} flat {陸屋根} conical {円錐} barrel {ヴォールト} other {屋根}}({width}×{depth}m)" -# Source-Hash: 7ceade8f25b58fe9 +# English: "Room ({area})" +# Source-Hash: 54efcb0756c2f2c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.back.1hzmxtu" -msgstr "戻る" +msgid "editor.layers.defaultName.room" +msgstr "部屋({area})" -# Source-Hash: 3eda29328f91485d +# English: "Slab ({area})" +# Source-Hash: e38a4b9986a9d21a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.balusters.1j3w6ng" -msgstr "手すり子" +msgid "editor.layers.defaultName.slab" +msgstr "スラブ({area})" -# Source-Hash: f211248ec8619e4c +# English: "Staircase ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 57398365d4b9066f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Band" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.band.160umau" -msgstr "帯" +msgid "editor.layers.defaultName.staircase" +msgstr "階段({count, plural, other {# セグメント}})" -# Source-Hash: 8f9ff05c6a3b08a6 +# English: "Zone ({area})" +# Source-Hash: a17f8fc29aae896e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base plate" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.basePlate.17fcg8k" -msgstr "ベースプレート" +msgid "editor.layers.defaultName.zone" +msgstr "ゾーン({area})" -# Source-Hash: 19ae2360207ecf5a +# English: "Expand" +# Source-Hash: 5cabcabb8bc14809 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.option.boards.u14hig" -msgstr "板" +msgid "editor.layers.expand" +msgstr "展開" -# Source-Hash: c0fee00bdfb09d37 +# English: "{levelName} options" +# Source-Hash: 6731e9dd94e8b268 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bookshelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bookshelf.ls0zqk" -msgstr "本棚" +msgid "editor.layers.levelOptions" +msgstr "{levelName}のオプション" -# Source-Hash: 69578b7dd7723fa1 +# English: "Rename" +# Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx #, fuzzy -msgid "editor.inspector.option.both.mkyb42" -msgstr "両方" +msgid "editor.layers.rename" +msgstr "名前を変更" -# Source-Hash: b3a163b453896bef +# English: "{role, select, room {Room colour} other {Zone colour}}" +# Source-Hash: 21eafa019ad62fea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.bottom.9mpruy" -msgstr "下" +msgid "editor.layers.spaceColour" +msgstr "{role, select, room {部屋の色} other {ゾーンの色}}" -# Source-Hash: e9a89a5e4ac8cd61 +# English: "Unit colour" +# Source-Hash: f5de042416a2c04f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.box.1nbjcuq" -msgstr "ボックス" +msgid "editor.layers.unitColour" +msgstr "ユニットの色" -# Source-Hash: 2c2815df4f724abe +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Break" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.break.17spnvc" -msgstr "分割" +msgid "editor.levelDuplicateDialog.cancel" +msgstr "キャンセル" -# Source-Hash: 0d5a25303d3d8058 +# English: "Choose what to copy from {levelName}." +# Source-Hash: 07c11e7e4d4681d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Butterfly" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.butterfly.pvv0g4" -msgstr "バタフライ" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" +msgstr "{levelName}からコピーする内容を選択してください。" -# Source-Hash: 5e2429ced8144ab4 +# English: "Choose what to copy from this level." +# Source-Hash: 9500d4378a319532 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cable.fp50zq" -msgstr "ケーブル" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" +msgstr "この階層からコピーする内容を選択してください。" -# Source-Hash: 69129e514347672a +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cast iron" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.castIron.1q2z4qk" -msgstr "鋳鉄" +msgid "editor.levelDuplicateDialog.duplicate" +msgstr "複製" -# Source-Hash: 4275c05172abd7fb +# English: "Duplicate Level" +# Source-Hash: a56e3eb43e60b45c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.ceiling.1t2zaqo" -msgstr "天井" +msgid "editor.levelDuplicateDialog.duplicateLevel" +msgstr "階層を複製" -# Source-Hash: 5211341ed18f33f6 +# English: "Everything" +# Source-Hash: e4c6ab1ea473a082 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.center.kzdxno" -msgstr "中央" +msgid "editor.levelDuplicateDialog.everything" +msgstr "すべて" -# Source-Hash: 65f410261609060b +# English: "Structure, finishes, and placed items, without guide references." +# Source-Hash: ac3f119e560388ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.centerOpening.bn1rvw" -msgstr "中央開き" +msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" +msgstr "構造、仕上げ、配置済みアイテム(ガイド参照を除く)。" -# Source-Hash: cf5f9756cc866f3e +# English: "Structure + furniture" +# Source-Hash: acbdf968391d5273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cleanout.lww8bm" -msgstr "掃除口" +msgid "editor.levelDuplicateDialog.structureFurniture" +msgstr "構造 + 家具" -# Source-Hash: e71d1d949f1d1b41 +# English: "Structure + materials" +# Source-Hash: ce682864a140fe75 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Concrete pad" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.concretePad.m1zdij" -msgstr "コンクリートパッド" +msgid "editor.levelDuplicateDialog.structureMaterials" +msgstr "構造 + マテリアル" -# Source-Hash: a3804c4f5e6aefbc +# English: "Structure, materials, furniture, and references." +# Source-Hash: 5ee44d53847c5ea2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Condenser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.condenser.a74nps" -msgstr "室外機" +msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" +msgstr "構造、マテリアル、家具、参照。" -# Source-Hash: 6786ac75cbe9af57 +# English: "Structure only" +# Source-Hash: 95aa1b43dbdb3f02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corbeled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.corbeled.bovs4v" -msgstr "持ち送り" +msgid "editor.levelDuplicateDialog.structureOnly" +msgstr "構造のみ" -# Source-Hash: 8a4beb9a57edc26e +# English: "Structure with the current material and finish assignments." +# Source-Hash: 2c53b46c9ca9fe2f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.count.1qoi4r8" -msgstr "数" +msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" +msgstr "現在のマテリアルと仕上げの割り当てを含む構造。" -# Source-Hash: db1f8f141714b363 +# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." +# Source-Hash: 3350e9008eaba15d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coupling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.coupling.111aap2" -msgstr "カップリング" +msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" +msgstr "壁、スラブ、屋根、階段、窓、ドア(仕上げなし)。" -# Source-Hash: 7a9a92d2e59a3bbc +# English: "Copy" +# Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering specific" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.coveringSpecific.1h9fo8w" -msgstr "仕上げ固有" +msgid "editor.levelWallCopyPrompt.copy" +msgstr "コピー" -# Source-Hash: d500c4b7827d3390 +# English: "Copy exterior walls from the level {direction}?" +# Source-Hash: 8ccaf4c98e335cc8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cross" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cross.1elubzv" -msgstr "クロス" +msgid "editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel" +msgstr "{direction, select, below {下} above {上} other {{direction}}}の階層から外壁をコピーしますか?" -# Source-Hash: 7ca7f00b3ba531f4 +# English: "No thanks" +# Source-Hash: f6ad0e813d2945bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cubby" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cubby.ivq4qc" -msgstr "小物入れ" +msgid "editor.levelWallCopyPrompt.noThanks" +msgstr "結構です" -# Source-Hash: a028e08962c388e0 +# English: "{sourceName}’s outside walls, in place, so this level lines up with it." +# Source-Hash: d2aecc3c47120ad5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.curved.6noz1s" -msgstr "曲線" +msgid "editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace" +msgstr "{sourceName}の外壁を同じ位置にコピーし、この階層の位置をそろえます。" -# Source-Hash: c65f02bcdc3231d6 +# English: "Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies." +# Source-Hash: b6317f13da65f81e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cylinder" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.cylinder.eol6vj" -msgstr "円柱" +msgid "editor.lib.buildingFoundation.basementExcavation" +msgstr "地盤の高い側で地面を {depth} 掘り込み、壁の外面より内側を掘削します。住宅周囲の地盤はそのままです。" -# Source-Hash: 53f224e2828d5ab7 +# English: "The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 16c97d232e4c05dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.damper.wgri80" -msgstr "ダンパー" +msgid "editor.lib.buildingFoundation.basementTopOverTheGround" +msgstr "地下壁の天端は地盤の高い側より {distance} 上にありますが、基礎は {asked} 上に設定されています。" -# Source-Hash: 748877ae309c313c +# English: "The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 9b781eb9749c9af3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.destination.gfzm1r" -msgstr "接続先" +msgid "editor.lib.buildingFoundation.basementTopUnderTheGround" +msgstr "地下壁の天端は地盤の高い側より {distance} 下にありますが、基礎は {asked} 上に設定されています。" -# Source-Hash: bfb3561e28d92a39 +# English: "Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1)." +# Source-Hash: 6903234208cb7eb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diffuser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.diffuser.1igj0jh" -msgstr "ディフューザー" +msgid "editor.lib.buildingFoundation.crawlSpaceDugOut" +msgstr "床下:根太下に {clearance} を確保するため、最大 {dug} 掘り下げています(IRC R317.1)。" -# Source-Hash: 6cff941e10ff22b3 +# English: "Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall." +# Source-Hash: fee27efc9440d33c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dog ear" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dogEar.xhtvjt" -msgstr "ドッグイヤー" +msgid "editor.lib.buildingFoundation.crippleWalls" +msgstr "地面からの立ち上がりが {stem} を超える部分では、立ち上がりを地盤面から {least} まで下げ、その上に {cripples, plural, other {# 枚のクリップルウォール}}を設けます(高さ最大 {tallest})。" -# Source-Hash: de47e23eb81efe02 +# English: "The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it." +# Source-Hash: d9f9c3b0c3978923 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dome" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dome.1c87e56" -msgstr "ドーム" +msgid "editor.lib.buildingFoundation.daylightWalls" +msgstr "住宅の範囲で地盤が {fall} 下がっています。低い側の壁は地面から最大 {exposed} 出ています。" -# Source-Hash: 3cd71355956a1821 +# English: "That is {fill} of fill. A raised floor on a stem wall suits a floor this high." +# Source-Hash: a62040ee19eb7321 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.double.13h5354" -msgstr "両開き" +msgid "editor.lib.buildingFoundation.deepFill" +msgstr "盛土が {fill} になります。この高さの床には、立ち上がりの上の高床が適しています。" -# Source-Hash: 72a84a9762461745 +# English: "That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope." +# Source-Hash: 7f8966c9c28a5d64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct board" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.ductBoard.lc3o4t" -msgstr "ダクトボード" +msgid "editor.lib.buildingFoundation.deepFillOnASlope" +msgstr "低い側の盛土が {fill} になります。この傾斜には、立ち上がりの上の高床が適しています。" -# Source-Hash: 6949d667043bee0b +# English: "The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: a2434c0de9dae5e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dutch.1djx429" -msgstr "入母屋" +msgid "editor.lib.buildingFoundation.floorOverTheGround" +msgstr "床は地盤の高い側より {distance} 上にありますが、基礎は {asked} 上に設定されています。" -# Source-Hash: e6637bddef6aabd6 +# English: "The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 6dcf21da285e3910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.elbow.1flup74" -msgstr "エルボ" +msgid "editor.lib.buildingFoundation.floorUnderTheGround" +msgstr "床は地盤の高い側より {distance} 下にありますが、基礎は {asked} 上に設定されています。" -# Source-Hash: 135874d189176d13 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: b4e4be1da470f91e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.end.1llf32i" -msgstr "終点" +msgid "editor.lib.buildingFoundation.footingSteps" +msgstr "住宅の範囲で地盤が {fall} 下がっています。フーチングは最も急な辺に沿って {steps, plural, other {# 回}}段差を付けます(1 段 {riser})。" -# Source-Hash: ada9fc2eec2c7d61 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: bea524db96134378 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.endCap.1p4v4na" -msgstr "エンドキャップ" +msgid "editor.lib.buildingFoundation.footingStepsUpTo" +msgstr "住宅の範囲で地盤が {fall} 下がっています。フーチングは最も急な辺に沿って {steps, plural, other {# 回}}段差を付けます(1 段最大 {riser})。" -# Source-Hash: 73916b8395208874 +# English: "The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1))." +# Source-Hash: 31192a74aa068c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal fit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.equalFit.11vw52c" -msgstr "均等配置" +msgid "editor.lib.buildingFoundation.footingTooNarrow" +msgstr "フーチングの幅は {width} です。1500 psf の地盤では、{storeys, plural, other {# 階建て}}で {asked} 必要です(IRC Table R403.1(1))。" -# Source-Hash: 83579a6e420df2ec +# English: "The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: c9f4ef98e9cd7d9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedCount.1o3dznu" -msgstr "固定数" +msgid "editor.lib.buildingFoundation.footingTooShallow" +msgstr "フーチングの底面は地盤面下 {depth} です。{least} 以上、または凍結深度まで下げてください(IRC R403.1.4)。" -# Source-Hash: 4fb65101d0b1a222 +# English: "The ground is held {least} under the slab’s top on every side: cut {cut}." +# Source-Hash: 8e9be841759f6e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedSpacing.hl4dr4" -msgstr "固定間隔" +msgid "editor.lib.buildingFoundation.gradingCut" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:切土 {cut}。" -# Source-Hash: 9cab4d4c95f63cdd +# English: "The ground is held {least} under the slab’s top on every side: cut {cut} on the high side." +# Source-Hash: 779f127732e5589b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flat.ly0bei" -msgstr "陸屋根" +msgid "editor.lib.buildingFoundation.gradingCutOnASlope" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:高い側で切土 {cut}。" -# Source-Hash: a9cad0badcc16693 +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}." +# Source-Hash: 954523c1f6ed230c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flex" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flex.ktpamq" -msgstr "フレキシブル" +msgid "editor.lib.buildingFoundation.gradingFilled" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:盛土 {fill}。" -# Source-Hash: 7843c5142b7d066d +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}." +# Source-Hash: b55f69797190bfee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floating.1oadpln" -msgstr "浮き" +msgid "editor.lib.buildingFoundation.gradingFilledAndCut" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:盛土 {fill}、切土 {cut}。" -# Source-Hash: d7148fa065bc709b +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side." +# Source-Hash: bae5f3ae42970e8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floor.q9caf1" -msgstr "階" +msgid "editor.lib.buildingFoundation.gradingFilledAndCutOnASlope" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:低い側で最大 {fill} 盛土、高い側で {cut} 切土。" -# Source-Hash: eaaa20f3b603140f +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side." +# Source-Hash: 4a06302380f3f85d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flush" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flush.olwwu5" -msgstr "面一" +msgid "editor.lib.buildingFoundation.gradingFilledOnASlope" +msgstr "スラブ周囲の地盤は全周でスラブ天端から {least} 下に整地されます:低い側で最大 {fill} 盛土。" -# Source-Hash: e360f89d3c0ec456 +# English: "The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less." +# Source-Hash: 64931230719b598b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.front.uhugl4" -msgstr "前面" +msgid "editor.lib.buildingFoundation.groundCutRoundTheStem" +msgstr "立ち上がりの天端を地盤から {least} 上に保つため、高い側で地盤を {cut} 切土します(IRC R317.1)。床を上げると切土が減ります。" -# Source-Hash: bccfc0565157dae6 +# English: "Raise it on a stem wall" +# Source-Hash: 3613643e58e4b6d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.furnace.w0mzpf" -msgstr "暖房炉" +msgid "editor.lib.buildingFoundation.raiseItOnAStemWall" +msgstr "床を立ち上がりの上に上げる" -# Source-Hash: deaffa095d126dfe +# English: "Raise the floor" +# Source-Hash: f69b48072be7f6ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gable.1ekyx6" -msgstr "切妻" +msgid "editor.lib.buildingFoundation.raiseTheFloor" +msgstr "床を上げる" -# Source-Hash: 7f53d9ffca39984a +# English: "Seat the basement wall’s top {asked} over the high side of the ground" +# Source-Hash: c1e5c4d64f7952a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gambrel.1yo90d9" -msgstr "腰折れ" +msgid "editor.lib.buildingFoundation.seatBasementWallTop" +msgstr "地下壁の天端を地盤の高い側から {asked} 上に据える" -# Source-Hash: 3c4900155c96b139 +# English: "Seat the floor’s top {asked} over the high side of the ground" +# Source-Hash: 3f82cd72a6654fdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Generic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.generic.1074oey" -msgstr "汎用" +msgid "editor.lib.buildingFoundation.seatFloorTop" +msgstr "床上面を地盤の高い側から {asked} 上に据える" -# Source-Hash: 8ab91094cf650204 +# English: "Seat the ground floor {asked} over grade" +# Source-Hash: b4bbe36aae57a0d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glass.163tc5n" -msgstr "ガラス" +msgid "editor.lib.buildingFoundation.seatGroundFloor" +msgstr "1階を地盤面から {asked} 上に据える" -# Source-Hash: 88810658a2b97bc6 +# English: "Seat it at {asked}" +# Source-Hash: bc5256a4749abb35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass frame" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glassFrame.jp9s3m" -msgstr "ガラス枠" +msgid "editor.lib.buildingFoundation.seatItAt" +msgstr "{asked} に据える" -# Source-Hash: 2d4223f80dc3945c +# English: "Seat the raised floor’s top {asked} over the high side of the ground" +# Source-Hash: 2becc7a7c8c48a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Globe" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.globe.1avkqoy" -msgstr "グローブ" +msgid "editor.lib.buildingFoundation.seatRaisedFloorTop" +msgstr "高床の床上面を地盤の高い側から {asked} 上に据える" -# Source-Hash: 398af4f45ba60c85 +# English: "Seat the slab’s top {asked} over the high side of the ground" +# Source-Hash: 88e009e2eb3ec2a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.grounded.jwoq73" -msgstr "接地" +msgid "editor.lib.buildingFoundation.seatSlabTop" +msgstr "スラブ天端を地盤の高い側から {asked} 上に据える" -# Source-Hash: 4972a0e68d69be58 +# English: "Set it {height}" +# Source-Hash: ccb9193f87264256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.guard.1m575se" -msgstr "手すり壁" +msgid "editor.lib.buildingFoundation.setIt" +msgstr "{height} に設定" -# Source-Hash: 67be98efb58a7767 +# English: "Set it {depth} deep" +# Source-Hash: e004c8ca1e6b6117 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.halfRound.m5hob0" -msgstr "半丸" +msgid "editor.lib.buildingFoundation.setItDeep" +msgstr "深さ {depth} に設定" -# Source-Hash: 229199467e9cf2db +# English: "The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1)." +# Source-Hash: e884fcda7ee630ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hidden" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hidden.gp48rd" -msgstr "非表示" +msgid "editor.lib.buildingFoundation.sillTooNearTheGround" +msgstr "土台と床組は地面から {height} の高さにあります。地面から {least} 以上離してください(IRC R317.1)。" -# Source-Hash: 9eda0ea8d447066d +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: fe154a985943c7ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hip.r0jnjy" -msgstr "寄棟" +msgid "editor.lib.buildingFoundation.slabEdgeSteps" +msgstr "住宅の範囲で地盤が {fall} 下がっています。スラブ端部は最も急な辺に沿って {steps, plural, other {# 回}}段差を付けます(1 段 {riser})。" -# Source-Hash: ac9c79f08ef130a1 +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: ee469b2069a20e5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.horizontal.tyapah" -msgstr "水平" +msgid "editor.lib.buildingFoundation.slabEdgeStepsUpTo" +msgstr "住宅の範囲で地盤が {fall} 下がっています。スラブ端部は最も急な辺に沿って {steps, plural, other {# 回}}段差を付けます(1 段最大 {riser})。" -# Source-Hash: 6a9b4142ab179557 +# English: "The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: e45d256982a22d6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.include.1a27vnr" -msgstr "含める" +msgid "editor.lib.buildingFoundation.slabEdgeTooShallow" +msgstr "スラブ端部の底面は地盤面下 {depth} です。{least} 以上、または凍結深度まで下げてください(IRC R403.1.4)。" -# Source-Hash: 8061b426b9d1b5fa +# English: "Widen it to {width}" +# Source-Hash: 70fa99a8b7e7c762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Independent high beam" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.independentHighBeam.1tn0zvy" -msgstr "独立した高い梁" +msgid "editor.lib.buildingFoundation.widenItTo" +msgstr "幅を {width} に広げる" -# Source-Hash: 1dc686f2068f976b +# English: "Add or remove objects from the selection" +# Source-Hash: 395fd0821b203517 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Industrial" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.industrial.8sd9u" -msgstr "インダストリアル" +msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" +msgstr "選択にオブジェクトを追加・除外" -# Source-Hash: 0c9b1ec8947ee177 +# English: "Adjust one side’s corner radius" +# Source-Hash: 8428cb0af9c6d6a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inline" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.inline.1uh05lg" -msgstr "インライン" +msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" +msgstr "片側の角の半径を調整" -# Source-Hash: 88d5b9ecb242c650 +# English: "Clear the selection (or click outside)" +# Source-Hash: d63b45c195713993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.integrated.1xd9lv0" -msgstr "一体型" +msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" +msgstr "選択を解除(または外側をクリック)" -# Source-Hash: bb197b9219f32603 +# English: "Click a handle dot to show move arrows" +# Source-Hash: 40b1afbeadbd6213 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Joined" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.joined.1x2o8ds" -msgstr "結合済み" +msgid "editor.lib.contextualHelp.clickAHandleDotToShow" +msgstr "ハンドルの点をクリックして移動矢印を表示" -# Source-Hash: 0299cc0ee05d17fc +# English: "Click or drag the selection to move it as one" +# Source-Hash: 0b82a35c71549580 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kickout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kickout.vt8s6p" -msgstr "キックアウト" +msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" +msgstr "選択をクリックまたはドラッグしてまとめて移動" -# Source-Hash: a0712c8461bb0758 +# English: "Click the handle dot to show move + rotate handles" +# Source-Hash: 73a761f393f69cb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Knee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.knee.186hcui" -msgstr "ニー" +msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" +msgstr "ハンドルの点をクリックして移動・回転ハンドルを表示" -# Source-Hash: 083febe9876348ec +# English: "Detach the joint while dragging an arrow" +# Source-Hash: c0045cef191fee69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "K style" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kStyle.m8kfwz" -msgstr "K型" +msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" +msgstr "矢印のドラッグ中に接合を外す" -# Source-Hash: de9566487b17a73b +# English: "Drag left or right to rotate selected object" +# Source-Hash: a019c8948c104a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.landing.tyqarg" -msgstr "踊り場" +msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" +msgstr "左右にドラッグして選択オブジェクトを回転" -# Source-Hash: d4c6c2495c233bb5 +# English: "Drag selected movable object" +# Source-Hash: 7db0364deb12f06e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lantern.1h7o2xb" -msgstr "ランタン" +msgid "editor.lib.contextualHelp.dragSelectedMovableObject" +msgstr "選択した可動オブジェクトをドラッグ" -# Source-Hash: 9477f663422698ae +# English: "Group selection (session only)" +# Source-Hash: 0f258dd389afdc73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leave" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.leave.1xf0sbk" -msgstr "離れる" +msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" +msgstr "選択をグループ化(このセッションのみ)" -# Source-Hash: f365c6b6dd9ebab9 +# English: "Hold to rotate freely" +# Source-Hash: 85330d62893a45c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.left.14n06mo" -msgstr "左" +msgid "editor.lib.contextualHelp.holdToRotateFreely" +msgstr "押したままで自由に回転" -# Source-Hash: 0608743fc1736d86 +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.level.i4lhtp" -msgstr "階層" +msgid "editor.lib.contextualHelp.rotate45" +msgstr "±45°回転" -# Source-Hash: a281a83a9fef6b88 +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low profile" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lowProfile.1h9lgzg" -msgstr "ロープロファイル" +msgid "editor.lib.contextualHelp.rotateTheSelection45" +msgstr "選択を±45°回転" -# Source-Hash: 7f7bb0e875af3260 +# English: "Rotating freely (no angle step)" +# Source-Hash: 0f6b418b18ff96f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.mansard.dwzck9" -msgstr "マンサード" +msgid "editor.lib.contextualHelp.rotatingFreely" +msgstr "自由回転中(角度ステップなし)" -# Source-Hash: 387082cd5f118bed +# English: "Switch the rotation axis (Y → X → Z)" +# Source-Hash: 7ed174d6b03563b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.manual.1wu7r43" -msgstr "手動" +msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" +msgstr "回転軸を切り替え(Y → X → Z)" -# Source-Hash: 321ca5101ab61db7 +# English: "Ungroup session selection" +# Source-Hash: d6f2c5cea683de62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.maximumSpacing.n1fzl4" -msgstr "最大間隔" +msgid "editor.lib.contextualHelp.ungroupSessionSelection" +msgstr "セッションのグループを解除" -# Source-Hash: 8e464a8f52254f27 +# English: "Continuous" +# Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metal.1az9fgc" -msgstr "金属" +msgid "editor.lib.continuation.continuous" +msgstr "連続" -# Source-Hash: c6185ecb219a9435 +# English: "Continuous canopy" +# Source-Hash: ebf0eb7af4636a16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metalPanel.1xdz476" -msgstr "金属パネル" +msgid "editor.lib.continuation.continuousCanopy" +msgstr "連続キャノピー" -# Source-Hash: 2fd92342f3986843 +# English: "Continuous run" +# Source-Hash: 1d8e87cd807e8d40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Minimal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.minimal.fwmiwg" -msgstr "ミニマル" +msgid "editor.lib.continuation.continuousRun" +msgstr "連続配置" -# Source-Hash: 5d8a5912056f83ce +# English: "Curved fence" +# Source-Hash: 8990683667567c6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mono" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.mono.oszvd6" -msgstr "片流れ" +msgid "editor.lib.continuation.curvedFence" +msgstr "曲線フェンス" -# Source-Hash: f2953d2931e36d0c +# English: "Freehand fence" +# Source-Hash: edf1b9a2ad3ab910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.none.deku7v" -msgstr "なし" +msgid "editor.lib.continuation.freehandFence" +msgstr "フリーハンドのフェンス" -# Source-Hash: 7b6e4a6b86ba5112 +# English: "Place multiple" +# Source-Hash: 2cdb7c1c7cd7c4fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Omit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.omit.okcs7w" -msgstr "省略" +msgid "editor.lib.continuation.placeMultiple" +msgstr "複数配置" -# Source-Hash: 3e52f04f92e08070 +# English: "Place once" +# Source-Hash: 90bf945df6f9c979 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.open.n6hn1l" -msgstr "開く" +msgid "editor.lib.continuation.placeOnce" +msgstr "1回だけ配置" -# Source-Hash: e04298381837252f +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.opening.dfet3" -msgstr "開口" +msgid "editor.lib.continuation.polygonRoom" +msgstr "多角形の部屋" -# Source-Hash: cc72b9bda6b30b5c +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rack" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.openRack.mvt1x0" -msgstr "オープンラック" +msgid "editor.lib.continuation.rectangleRoom" +msgstr "矩形の部屋" -# Source-Hash: ed6d7b3dd9d5fc69 +# English: "Single cabinet" +# Source-Hash: 7fd85d0408141157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Oval" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.oval.1jiqzxx" -msgstr "楕円" +msgid "editor.lib.continuation.singleCabinet" +msgstr "単体キャビネット" -# Source-Hash: 9295ddc2805b21e2 +# English: "Single canopy" +# Source-Hash: dd1bdbc26165baa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.picket.zyeoy1" -msgstr "ピケット" +msgid "editor.lib.continuation.singleCanopy" +msgstr "単体キャノピー" -# Source-Hash: 180ffb8b75c977aa +# English: "Single fence" +# Source-Hash: 5fb1a69316be983e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pointed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.pointed.effedk" -msgstr "尖頭" +msgid "editor.lib.continuation.singleFence" +msgstr "単体フェンス" -# Source-Hash: fca7919fb0c0669c +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post and rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.postAndRail.lajk2u" -msgstr "支柱とレール" +msgid "editor.lib.continuation.walls" +msgstr "壁" -# Source-Hash: a78b28a428b27e20 +# English: "Cut opening" +# Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve high edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveHighEdge.y6ywf0" -msgstr "高い側を保つ" +msgid "editor.lib.hudTitle.cutOpening" +msgstr "開口を切る" -# Source-Hash: 325c6f53f6dc7634 +# English: "Divide room" +# Source-Hash: 60e8d235f3084a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve low edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveLowEdge.196m5pk" -msgstr "低い側を保つ" +msgid "editor.lib.hudTitle.divideRoom" +msgstr "部屋を分割" -# Source-Hash: 95c0b860d22a62cb +# English: "Edit {kind}" +# Source-Hash: aa496ec33d21ab03 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve pitch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preservePitch.1uq973r" -msgstr "勾配を保つ" +msgid "editor.lib.hudTitle.editKind" +msgstr "{kind}を編集" -# Source-Hash: d4e11841abf22593 +# English: "Erase" +# Source-Hash: f3ea3efa7102844f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Privacy" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.privacy.1th4hrb" -msgstr "目隠し" +msgid "editor.lib.hudTitle.erase" +msgstr "消去" -# Source-Hash: 1a8c46c7712a83b6 +# English: "Level terrain" +# Source-Hash: 3f907e76526472bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlins" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.purlins.od124c" -msgstr "母屋" +msgid "editor.lib.hudTitle.levelTerrain" +msgstr "地形を平らにする" -# Source-Hash: a9650f6f94de9247 +# English: "Lower terrain" +# Source-Hash: 4e911c300d74eb47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pvc" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pvc.1oc4u2w" -msgstr "PVC" +msgid "editor.lib.hudTitle.lowerTerrain" +msgstr "地形を下げる" -# Source-Hash: 47587b825e9a696b +# English: "Mezzanine" +# Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pyramid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pyramid.17ucsqv" -msgstr "ピラミッド" +msgid "editor.lib.hudTitle.mezzanine" +msgstr "中二階" -# Source-Hash: a9cb232546779779 +# English: "Move {kind}" +# Source-Hash: c48c4129c9ed249a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rafters.16tzbby" -msgstr "垂木" +msgid "editor.lib.hudTitle.moveKind" +msgstr "{kind}を移動" -# Source-Hash: 947eacd2689daced +# English: "Move room" +# Source-Hash: 89f6b7ca8904c656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rail.39zqc9" -msgstr "レール" +msgid "editor.lib.hudTitle.moveRoom" +msgstr "部屋を移動" -# Source-Hash: bd50bd18e682db41 +# English: "Move selection" +# Source-Hash: 9c707069572fb3db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.raised.4kyx9h" -msgstr "上げ" +msgid "editor.lib.hudTitle.moveSelection" +msgstr "選択を移動" -# Source-Hash: 3b6f225112035fde +# English: "Paint" +# Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rect.tpfvaf" -msgstr "矩形" +msgid "editor.lib.hudTitle.paint" +msgstr "塗装" -# Source-Hash: 68206f70689b2340 +# English: "Paint a rectangle" +# Source-Hash: ca197314cb70aa56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reducer" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.reducer.18byt67" -msgstr "レジューサー" +msgid "editor.lib.hudTitle.paintARectangle" +msgstr "矩形を塗る" -# Source-Hash: 2e0aa7113b021c67 +# English: "Paint a shape" +# Source-Hash: ea4ce729e17254f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.return.1klrjrz" -msgstr "還気" +msgid "editor.lib.hudTitle.paintAShape" +msgstr "形を塗る" -# Source-Hash: 63b39b1681f74fe4 +# English: "Pick material" +# Source-Hash: 01359502b07284d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return grille" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.returnGrille.r28ncm" -msgstr "還気グリル" +msgid "editor.lib.hudTitle.pickMaterial" +msgstr "素材を取得" -# Source-Hash: b34d5ee0f218221d +# English: "Raise terrain" +# Source-Hash: 4066e8a3f60b1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.right.8humpx" -msgstr "右" +msgid "editor.lib.hudTitle.raiseTerrain" +msgstr "地形を上げる" -# Source-Hash: ad0457800a1f0eb4 +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.round.1vx0wyj" -msgstr "丸" +msgid "editor.lib.hudTitle.resize" +msgstr "サイズ変更" -# Source-Hash: fc826da6c99303e6 +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rounded.199nwpm" -msgstr "角丸" +msgid "editor.lib.hudTitle.rotate" +msgstr "回転" -# Source-Hash: dc67a1cdb2482f23 +# English: "Select" +# Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sanitary tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.sanitaryTee.oro3d4" -msgstr "排水T字管" +msgid "editor.lib.hudTitle.select" +msgstr "選択" -# Source-Hash: beb3fec22c287185 +# English: "Smart measure" +# Source-Hash: ec1f1d68af469867 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scalloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scalloped.1eltba0" -msgstr "スカラップ" +msgid "editor.lib.hudTitle.smartMeasure" +msgstr "スマート計測" -# Source-Hash: e16f85e4bebfefb1 +# English: "Smooth terrain" +# Source-Hash: 5ba5f22bd1a69e09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scoop" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scoop.qy5zdv" -msgstr "スクープ" +msgid "editor.lib.hudTitle.smoothTerrain" +msgstr "地形を滑らかにする" -# Source-Hash: f9a95d8407fafda4 +# English: "Split {kind}" +# Source-Hash: cbb4687261739397 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segmented panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.segmentedPanel.5r3hwl" -msgstr "分割パネル" +msgid "editor.lib.hudTitle.splitKind" +msgstr "{kind}を分割" -# Source-Hash: c8d0df27faa3feaa +# English: "Terrace" +# Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.shed.6t994v" -msgstr "片流れ" +msgid "editor.lib.hudTitle.terrace" +msgstr "テラス" -# Source-Hash: 7fbc26cd497cfd12 +# English: "{title} ({exterior} exterior, {interior} interior)" +# Source-Hash: 322452fdf0850c8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheet metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/tool-shortcuts.ts #, fuzzy -msgid "editor.inspector.option.sheetMetal.l164lv" -msgstr "板金" +msgid "editor.lib.toolShortcuts.wallsTooltip" +msgstr "{title}(外壁 {exterior}、内壁 {interior})" -# Source-Hash: 26e2c261223cbb46 +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingle.pfmynh" -msgstr "シングル" +msgid "editor.lib.wallDrawVariant.polygonRoom" +msgstr "多角形の部屋" -# Source-Hash: e9046e9823b400a0 +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingled.1nm9inf" -msgstr "シングル葺き" +msgid "editor.lib.wallDrawVariant.rectangleRoom" +msgstr "矩形の部屋" -# Source-Hash: a05a390ae205fdd8 +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Simple" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.simple.12mfu73" -msgstr "シンプル" +msgid "editor.lib.wallDrawVariant.walls" +msgstr "壁" -# Source-Hash: 62ab2eb3c775da95 +# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" +# Source-Hash: c2626e1fbf89b3c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/registry-action-contributions.tsx #, fuzzy -msgid "editor.inspector.option.single.1uzxurt" -msgstr "片開き" +msgid "editor.mechanismAction" +msgstr "{action, select, close {閉じる} open {開く} stop {停止} other {再生}}" -# Source-Hash: aee3b2bd78c87d0b +# English: "Curve" +# Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleLeft.1atdh02" -msgstr "片開き(左)" +msgid "editor.nodeActionMenu.curve" +msgstr "曲線" -# Source-Hash: cc43ffbef4ac7ae0 +# English: "Cut out" +# Source-Hash: 5af05fb6661e4d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleRight.1axbu67" -msgstr "片開き(右)" +msgid "editor.nodeActionMenu.cutOut" +msgstr "切り抜き" -# Source-Hash: 7672d2764445c110 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slant box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slantBox.1ta99vy" -msgstr "斜めボックス" +msgid "editor.nodeActionMenu.delete" +msgstr "削除" -# Source-Hash: 405e07a8cc2c88e1 +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slat.1p9njbz" -msgstr "スラット" +msgid "editor.nodeActionMenu.duplicate" +msgstr "複製" -# Source-Hash: 18cbc73c5e1590aa +# English: "Edit mesh" +# Source-Hash: 94b212bd79c05141 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sliding.cd0jox" -msgstr "引き戸" +msgid "editor.nodeActionMenu.editMesh" +msgstr "メッシュを編集" -# Source-Hash: e6d82e606b8c9301 +# English: "Find in catalog" +# Source-Hash: ce90cf37224f73cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slope.1bn2qw" -msgstr "勾配" +msgid "editor.nodeActionMenu.findInCatalog" +msgstr "カタログで探す" -# Source-Hash: f6998707123fe72d +# English: "Group selection" +# Source-Hash: d5e550c345e7161c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sloped.lo4sh0" -msgstr "傾斜" +msgid "editor.nodeActionMenu.groupSelection" +msgstr "選択をグループ化" -# Source-Hash: ef3e765e692faf4c +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solid.o4uy6s" -msgstr "ソリッド" +msgid "editor.nodeActionMenu.move" +msgstr "移動" -# Source-Hash: 8008b6f17089beaf +# English: "Rotate left" +# Source-Hash: 473a9d4463761107 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solidPanel.2yaw62" -msgstr "不透明パネル" +msgid "editor.nodeActionMenu.rotateLeft" +msgstr "左に回転" -# Source-Hash: f9a4a870cb3d3c41 +# English: "Rotate right" +# Source-Hash: b1bf4e65c8d8c8ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.spiral.91hcl4" -msgstr "らせん" +msgid "editor.nodeActionMenu.rotateRight" +msgstr "右に回転" -# Source-Hash: beef1cf79388b77b +# English: "Ungroup selection" +# Source-Hash: 6f4dbbc4a3f58fca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Splash" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.splash.1uke4cm" -msgstr "跳ね返り" +msgid "editor.nodeActionMenu.ungroupSelection" +msgstr "選択のグループを解除" -# Source-Hash: 324677a79ab15e22 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spread" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.spread.1g1zysu" -msgstr "広がり" +msgid "editor.nodeDisplay.ceiling" +msgstr "天井" -# Source-Hash: 39287346366fc206 +# English: "Column" +# Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Square" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.square.1xl1lk6" -msgstr "正方形" +msgid "editor.nodeDisplay.column" +msgstr "柱" -# Source-Hash: 53d7084bc41c1ad6 +# English: "Door" +# Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stair.1xf86e" -msgstr "階段" +msgid "editor.nodeDisplay.door" +msgstr "ドア" -# Source-Hash: d998c212030bc7c2 +# English: "Elevator" +# Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.standard.vuco" -msgstr "標準" +msgid "editor.nodeDisplay.elevator" +msgstr "エレベーター" -# Source-Hash: 0a26a1e61c825660 +# English: "Fence" +# Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.start.30xvgf" -msgstr "始点" +msgid "editor.nodeDisplay.fence" +msgstr "フェンス" -# Source-Hash: ed3989f23a2d185e +# English: "Guide image" +# Source-Hash: 4c3ff8cdc6feda97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.step.jtn9kf" -msgstr "段" +msgid "editor.nodeDisplay.guide" +msgstr "ガイド画像" -# Source-Hash: 2c6b1cf17a973d67 +# English: "Item" +# Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stepped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stepped.naxzh0" -msgstr "段状" +msgid "editor.nodeDisplay.item" +msgstr "アイテム" -# Source-Hash: 4ee35f740730ae87 +# English: "Roof" +# Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.straight.lxqbfr" -msgstr "直進" +msgid "editor.nodeDisplay.roof" +msgstr "屋根" -# Source-Hash: b9b45ac5ba1daeee +# English: "Roof segment" +# Source-Hash: a130b4eec084c79a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.strap.14q0ps9" -msgstr "バンド" +msgid "editor.nodeDisplay.roofSegment" +msgstr "屋根セグメント" -# Source-Hash: b45f382a67d6c0eb +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supply.1oy2zwu" -msgstr "給気" +msgid "editor.nodeDisplay.room" +msgstr "部屋" -# Source-Hash: 00e7a2b3b2aa36ac +# English: "3D Scan" +# Source-Hash: b22b2f20cc7cd416 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply register" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supplyRegister.1lzdbbn" -msgstr "給気口" +msgid "editor.nodeDisplay.scan" +msgstr "3Dスキャン" -# Source-Hash: f86f1f22230596b7 +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tapered" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tapered.3y5blo" -msgstr "テーパー" +msgid "editor.nodeDisplay.selection" +msgstr "選択" -# Source-Hash: 5fed23c2217b83fb +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.targetSpacing.14uibc9" -msgstr "目標の間隔" +msgid "editor.nodeDisplay.slab" +msgstr "スラブ" -# Source-Hash: f3ceed7f867f090f +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tee.7d6tjp" -msgstr "T字管" +msgid "editor.nodeDisplay.stair" +msgstr "階段" -# Source-Hash: 787005b26aec58a3 +# English: "Stair segment" +# Source-Hash: 52de428c16bb81e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilted" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tilted.6n0u5j" -msgstr "傾斜" +msgid "editor.nodeDisplay.stairSegment" +msgstr "階段セグメント" -# Source-Hash: 27644f0497fd9849 +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.top.2nypcs" -msgstr "上" +msgid "editor.nodeDisplay.wall" +msgstr "壁" -# Source-Hash: e6657000554dfe53 +# English: "Window" +# Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.transition.1rziz9e" -msgstr "トランジション" +msgid "editor.nodeDisplay.window" +msgstr "窓" -# Source-Hash: 04bc0909a133a7e9 +# English: "Zone" +# Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vent" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.vent.xda5j2" -msgstr "換気口" +msgid "editor.nodeDisplay.zone" +msgstr "ゾーン" -# Source-Hash: 7eee9aff0dd520b6 +# English: "At the high side of the ground, to scale" +# Source-Hash: 59310bd629d358d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk on" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.walkOn.1n5sg9l" -msgstr "歩行面" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe" +msgstr "地盤の高い側(縮尺どおり)" -# Source-Hash: 2210e5ee50b74dc9 +# English: "At the high side of the ground, to scale; the dashed line is the grade" +# Source-Hash: 1d006e84523a03c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wall.blj585" -msgstr "壁" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe2" +msgstr "地盤の高い側(縮尺どおり)。破線は地盤面" -# Source-Hash: f3578f7177ad0054 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall abutment" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallAbutment.lza941" -msgstr "壁との取り合い" +msgid "editor.panels.buildingFoundationSection.auto" +msgstr "自動" -# Source-Hash: 8dcf182f91ba3245 +# English: "Basement slab" +# Source-Hash: ca97295ab420e54a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall ledger" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallLedger.5m8ana" -msgstr "壁の受け材" +msgid "editor.panels.buildingFoundationSection.basementSlab" +msgstr "地下スラブ" -# Source-Hash: 0adda30be5f17975 +# English: "Basement wall" +# Source-Hash: 8691c5ac35304434 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallShelf.yiozfl" -msgstr "壁棚" +msgid "editor.panels.buildingFoundationSection.basementWall" +msgstr "地下壁" -# Source-Hash: 29d8590334492601 +# English: "Poured concrete: 8 in for most basements (IRC Table R404.1.2(1))." +# Source-Hash: fecad6f58f5f6e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.waste.3xrp79" -msgstr "排水" +msgid "editor.panels.buildingFoundationSection.basementWallHint" +msgstr "現場打ちコンクリート:ほとんどの地下室で 8 in(IRC Table R404.1.2(1))。" -# Source-Hash: 00249876bb37079f +# English: "Basement wall (its own)" +# Source-Hash: 1f0c089c4a7cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wye" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wye.14vupc8" -msgstr "Y字管" +msgid "editor.panels.buildingFoundationSection.basementWallItsOwn" +msgstr "地下壁(地下階の壁)" -# Source-Hash: 179b025e25f9a091 +# English: "Basement wall {thickness}" +# Source-Hash: c672e131662d2766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.collapse" -msgstr "折りたたむ" +msgid "editor.panels.buildingFoundationSection.basementWallThickness" +msgstr "地下壁 {thickness}" -# Source-Hash: dbeae4c43f49327f +# English: "Basement wall top over grade" +# Source-Hash: 29a11cb98f792822 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.ceiling" -msgstr "天井({area})" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGrade" +msgstr "地盤面からの地下壁天端" -# Source-Hash: d29165c9d4bfc3cb +# English: "Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement." +# Source-Hash: 4f96c4f9effefccf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.collection" -msgstr "コレクション" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGradeHint" +msgstr "住宅沿いで最も高い地盤面から測ります。この上に土台と床組が載るため、地面から 8 in 以上離します(IRC R317.1)。半地下にする場合は高く設定します。" -# Source-Hash: 8f536b859be662a0 +# English: "Carpet" +# Source-Hash: e653669adc447902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.flight" -msgstr "階段({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.carpet" +msgstr "カーペット" -# Source-Hash: 46dcbf4548374073 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.landing" -msgstr "踊り場({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.ceiling" +msgstr "天井" -# Source-Hash: 3ea44453fcd08a6e +# English: "Ceramic tile" +# Source-Hash: 299c16d7ad11f5e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Node" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.node" -msgstr "ノード" +msgid "editor.panels.buildingFoundationSection.ceramicTile" +msgstr "セラミックタイル" -# Source-Hash: 93f3a04e15b6fb49 +# English: "Close the outside walls into a loop to draw the foundation under them." +# Source-Hash: 317b5b94671d871a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roof" -msgstr "屋根({count, plural, other {# セグメント}})" +msgid "editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA" +msgstr "外壁を閉じたループにすると、その下に基礎が描画されます。" -# Source-Hash: a730fab01c172704 +# English: "Crawl space under joists" +# Source-Hash: 3e736505db2a19c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roofSegment" -msgstr "{roofType, select, hip {寄棟} gable {切妻} shed {片流れ} gambrel {腰折れ} dutch {入母屋} mansard {マンサード} flat {陸屋根} conical {円錐} barrel {ヴォールト} other {屋根}}({width}×{depth}m)" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoists" +msgstr "根太下の床下高さ" -# Source-Hash: 54efcb0756c2f2c1 +# English: "Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it." +# Source-Hash: 61f898724893d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.room" -msgstr "部屋({area})" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint" +msgstr "土台に載る根太の下端から測ります(IRC R317.1:地面まで 18 in)。床下はこの高さまで掘り下げます。" -# Source-Hash: e38a4b9986a9d21a +# English: "Custom" +# Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.slab" -msgstr "スラブ({area})" +msgid "editor.panels.buildingFoundationSection.custom" +msgstr "カスタム" -# Source-Hash: 57398365d4b9066f +# English: "Delete the basement and it stands on its raised floor again." +# Source-Hash: 8629f9746eac5bfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.staircase" -msgstr "階段({count, plural, other {# セグメント}})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor" +msgstr "地下室を削除すると、再び高床の上に建ちます。" -# Source-Hash: a17f8fc29aae896e +# English: "Delete the basement and it stands on its slab again." +# Source-Hash: f99636a38ec14785 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.zone" -msgstr "ゾーン({area})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementSlab" +msgstr "地下室を削除すると、再びスラブの上に建ちます。" -# Source-Hash: 5cabcabb8bc14809 +# English: "{depth} deep" +# Source-Hash: 09f12189bbbf8cbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.expand" -msgstr "展開" +msgid "editor.panels.buildingFoundationSection.depthDeep" +msgstr "深さ {depth}" -# Source-Hash: 6731e9dd94e8b268 +# English: "{depth} down" +# Source-Hash: 164f8aea5d46f232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{levelName} options" -#: packages/editor/src/components/ui/floating-level-selector.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.levelOptions" -msgstr "{levelName}のオプション" +msgid "editor.panels.buildingFoundationSection.depthDown" +msgstr "根入れ {depth}" -# Source-Hash: d6952b4e54d246dd +# English: "{depth} I-joist" +# Source-Hash: a6010b7488316ac1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.rename" -msgstr "名前を変更" +msgid "editor.panels.buildingFoundationSection.depthIJoist" +msgstr "{depth} Iジョイスト" -# Source-Hash: 21eafa019ad62fea +# English: "Draw the foundation" +# Source-Hash: 4db57e6b9b2a22f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{role, select, room {Room colour} other {Zone colour}}" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx -#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.spaceColour" -msgstr "{role, select, room {部屋の色} other {ゾーンの色}}" +msgid "editor.panels.buildingFoundationSection.drawTheFoundation" +msgstr "基礎を描画" -# Source-Hash: f5de042416a2c04f +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit colour" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.unitColour" -msgstr "ユニットの色" +msgid "editor.panels.buildingFoundationSection.drywall" +msgstr "石膏ボード" -# Source-Hash: c0774c185060fc13 +# English: "Drywall ceiling" +# Source-Hash: cea6df381e71f25f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.cancel" -msgstr "キャンセル" +msgid "editor.panels.buildingFoundationSection.drywallCeiling" +msgstr "石膏ボード天井" -# Source-Hash: 07c11e7e4d4681d6 +# English: "Edge under grade" +# Source-Hash: 027b063ba3959c44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from {levelName}." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" -msgstr "{levelName}からコピーする内容を選択してください。" +msgid "editor.panels.buildingFoundationSection.edgeUnderGrade" +msgstr "端部の根入れ深さ" -# Source-Hash: 9500d4378a319532 +# English: "IRC R403.1.4: at least 12 in, or the frost line." +# Source-Hash: a114cbbf9fff17fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from this level." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" -msgstr "この階層からコピーする内容を選択してください。" +msgid "editor.panels.buildingFoundationSection.edgeUnderGradeHint" +msgstr "IRC R403.1.4:12 in 以上、または凍結深度まで。" -# Source-Hash: 34dc96fe77a31e51 +# English: "Edge width" +# Source-Hash: ea863dda5621adfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicate" -msgstr "複製" +msgid "editor.panels.buildingFoundationSection.edgeWidth" +msgstr "端部の幅" -# Source-Hash: a56e3eb43e60b45c +# English: "Edge {width} wide" +# Source-Hash: 542f041267191a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate Level" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicateLevel" -msgstr "階層を複製" +msgid "editor.panels.buildingFoundationSection.edgeWidthWide" +msgstr "端部の幅 {width}" -# Source-Hash: e4c6ab1ea473a082 +# English: "Engineered wood" +# Source-Hash: 5c0c1a6c73eb46e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Everything" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.everything" -msgstr "すべて" +msgid "editor.panels.buildingFoundationSection.engineeredWood" +msgstr "複合フローリング" -# Source-Hash: ac3f119e560388ab +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, finishes, and placed items, without guide references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" -msgstr "構造、仕上げ、配置済みアイテム(ガイド参照を除く)。" +msgid "editor.panels.buildingFoundationSection.finish" +msgstr "仕上げ" -# Source-Hash: acbdf968391d5273 +# English: "Finish floor over grade" +# Source-Hash: b97e6e3652b2ea63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + furniture" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFurniture" -msgstr "構造 + 家具" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGrade" +msgstr "地盤面からの床高" -# Source-Hash: ce682864a140fe75 +# English: "Measured over the highest grade under the outside walls. The foundation grows down the slope from there." +# Source-Hash: 326641cd3bd5968a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + materials" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterials" -msgstr "構造 + マテリアル" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGradeHint" +msgstr "外壁下の最も高い地盤面から測ります。基礎はそこから斜面に沿って下へ伸びます。" -# Source-Hash: 5ee44d53847c5ea2 +# English: "Floor framing" +# Source-Hash: bcc3c6e3240bd6da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, materials, furniture, and references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" -msgstr "構造、マテリアル、家具、参照。" +msgid "editor.panels.buildingFoundationSection.floorFraming" +msgstr "床組" -# Source-Hash: 95aa1b43dbdb3f02 +# English: "Floor framing and sill {depth}" +# Source-Hash: 2d7eaba6b2a7787b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure only" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureOnly" -msgstr "構造のみ" +msgid "editor.panels.buildingFoundationSection.floorFramingAndSill" +msgstr "床組と土台 {depth}" -# Source-Hash: 2c53b46c9ca9fe2f +# English: "Floor top down to the stem: flooring, subfloor, joists, sill." +# Source-Hash: 60bbbc62b12ee1b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure with the current material and finish assignments." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" -msgstr "現在のマテリアルと仕上げの割り当てを含む構造。" +msgid "editor.panels.buildingFoundationSection.floorFramingHint" +msgstr "床上面から立ち上がりまで:床仕上げ材、床下地、根太、土台。" -# Source-Hash: 3350e9008eaba15d +# English: "Floor framing {depth}, sill {sill} included" +# Source-Hash: b3f975c3aa4056d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" -msgstr "壁、スラブ、屋根、階段、窓、ドア(仕上げなし)。" +msgid "editor.panels.buildingFoundationSection.floorFramingSillIncluded" +msgstr "床組 {depth}(土台 {sill} を含む)" -# Source-Hash: 395fd0821b203517 +# English: "Flooring" +# Source-Hash: 3e6a58401ab2fc64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add or remove objects from the selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" -msgstr "選択にオブジェクトを追加・除外" +msgid "editor.panels.buildingFoundationSection.flooring" +msgstr "床仕上げ材" -# Source-Hash: 8428cb0af9c6d6a0 +# English: "Footing thickness" +# Source-Hash: e7ca47bb438592ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Adjust one side’s corner radius" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" -msgstr "片側の角の半径を調整" +msgid "editor.panels.buildingFoundationSection.footingThickness" +msgstr "フーチングの厚さ" -# Source-Hash: d63b45c195713993 +# English: "Footing under grade" +# Source-Hash: 3b481ad3c192b6a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear the selection (or click outside)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" -msgstr "選択を解除(または外側をクリック)" +msgid "editor.panels.buildingFoundationSection.footingUnderGrade" +msgstr "フーチングの根入れ深さ" -# Source-Hash: 40b1afbeadbd6213 +# English: "IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line." +# Source-Hash: d1ed081aa7773257 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a handle dot to show move arrows" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickAHandleDotToShow" -msgstr "ハンドルの点をクリックして移動矢印を表示" +msgid "editor.panels.buildingFoundationSection.footingUnderGradeHint" +msgstr "IRC R403.1.4:外側と掘り下げた床下の両方で、隣接する地面から 12 in 以上。凍結深度が深い場合はそこまで下げます。" -# Source-Hash: 0b82a35c71549580 +# English: "Footing width" +# Source-Hash: a4bdf4be3447baec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click or drag the selection to move it as one" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" -msgstr "選択をクリックまたはドラッグしてまとめて移動" +msgid "editor.panels.buildingFoundationSection.footingWidth" +msgstr "フーチングの幅" -# Source-Hash: 73a761f393f69cb9 +# English: "Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys." +# Source-Hash: bfb55823e6b7d61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click the handle dot to show move + rotate handles" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" -msgstr "ハンドルの点をクリックして移動・回転ハンドルを表示" +msgid "editor.panels.buildingFoundationSection.footingWidthHint" +msgstr "Table R403.1(1)、1500 psf の地盤:1・2・3 階建てでそれぞれ 12・15・23 in。" -# Source-Hash: c0045cef191fee69 +# English: "Footing {width} × {thickness}" +# Source-Hash: dd973bd2c5207dd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detach the joint while dragging an arrow" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" -msgstr "矢印のドラッグ中に接合を外す" +msgid "editor.panels.buildingFoundationSection.footingWidthThickness" +msgstr "フーチング {width} × {thickness}" -# Source-Hash: a019c8948c104a9e +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag left or right to rotate selected object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" -msgstr "左右にドラッグして選択オブジェクトを回転" +msgid "editor.panels.buildingFoundationSection.foundation" +msgstr "基礎" -# Source-Hash: 7db0364deb12f06e +# English: "Grade beam depth" +# Source-Hash: c4460990c10fbb11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag selected movable object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragSelectedMovableObject" -msgstr "選択した可動オブジェクトをドラッグ" +msgid "editor.panels.buildingFoundationSection.gradeBeamDepth" +msgstr "地中梁のせい" -# Source-Hash: 0f258dd389afdc73 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection (session only)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" -msgstr "選択をグループ化(このセッションのみ)" +msgid "editor.panels.buildingFoundationSection.gradeBeamOnPiles" +msgstr "杭上の地中梁" -# Source-Hash: 85330d62893a45c3 +# English: "Grade beam width" +# Source-Hash: 2208ea6b7967afc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to rotate freely" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.holdToRotateFreely" -msgstr "押したままで自由に回転" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidth" +msgstr "地中梁の幅" -# Source-Hash: 8a665ecaeffa9308 +# English: "Grade beam {width} × {depth}" +# Source-Hash: e70be1cfffd2567a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotate45" -msgstr "±45°回転" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidthDepth" +msgstr "地中梁 {width} × {depth}" -# Source-Hash: 6099ee99f8c38700 +# English: "Half the edge’s depth under the slab, 6 to 12 in" +# Source-Hash: 08e07ba612a2515a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotateTheSelection45" -msgstr "選択を±45°回転" +msgid "editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder" +msgstr "スラブ下の端部深さの半分(6〜12 in)" -# Source-Hash: 0f6b418b18ff96f3 +# English: "Hardwood" +# Source-Hash: af3644ad05550f7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotating freely (no angle step)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotatingFreely" -msgstr "自由回転中(角度ステップなし)" +msgid "editor.panels.buildingFoundationSection.hardwood" +msgstr "無垢フローリング" -# Source-Hash: 7ed174d6b03563b1 +# English: "{height} crawl" +# Source-Hash: d0ab8d2735f2e1a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch the rotation axis (Y → X → Z)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" -msgstr "回転軸を切り替え(Y → X → Z)" +msgid "editor.panels.buildingFoundationSection.heightCrawl" +msgstr "床下 {height}" -# Source-Hash: d6f2c5cea683de62 +# English: "{height} FF" +# Source-Hash: 70a0363496dbb627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup session selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.ungroupSessionSelection" -msgstr "セッションのグループを解除" +msgid "editor.panels.buildingFoundationSection.heightFinishFloor" +msgstr "床高 {height}" -# Source-Hash: 384842ae1e1c6825 +# English: "{height} top" +# Source-Hash: 28fbe28f080c2e49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuous" -msgstr "連続" +msgid "editor.panels.buildingFoundationSection.heightTop" +msgstr "天端 {height}" -# Source-Hash: ebf0eb7af4636a16 +# English: "I-joists" +# Source-Hash: 61dc716260daf09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousCanopy" -msgstr "連続キャノピー" +msgid "editor.panels.buildingFoundationSection.iJoists" +msgstr "Iジョイスト" -# Source-Hash: 1d8e87cd807e8d40 +# English: "Its layers make {depth} with the sill; the framing follows them once a layer is set." +# Source-Hash: dcaf89ca7f4ba36a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous run" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousRun" -msgstr "連続配置" +msgid "editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill" +msgstr "層の合計は土台を含めて {depth} です。層を設定すると、床組がそれに合わせて変わります。" -# Source-Hash: 8990683667567c6b +# English: "Its walls are poured under the house’s outside walls." +# Source-Hash: 47f3b8abf6c71c0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.curvedFence" -msgstr "曲線フェンス" +msgid "editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe" +msgstr "壁は住宅の外壁の下に打設されます。" -# Source-Hash: edf1b9a2ad3ab910 +# English: "Its walls are the basement’s own, on their footing." +# Source-Hash: 2ad8c11c90166f43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Freehand fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.freehandFence" -msgstr "フリーハンドのフェンス" +msgid "editor.panels.buildingFoundationSection.itsWallsAreTheBasementS" +msgstr "壁は地下階自身の壁で、それぞれのフーチングの上に建ちます。" -# Source-Hash: 2cdb7c1c7cd7c4fd +# English: "Joists" +# Source-Hash: c7aa596536ceeecb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place multiple" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeMultiple" -msgstr "複数配置" +msgid "editor.panels.buildingFoundationSection.joists" +msgstr "根太" -# Source-Hash: 90bf945df6f9c979 +# English: "Kind" +# Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place once" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeOnce" -msgstr "1回だけ配置" +msgid "editor.panels.buildingFoundationSection.kind" +msgstr "種類" -# Source-Hash: 8ec6152e139b0e3f +# English: "{label} (auto)" +# Source-Hash: bc4b8b385cd50d18 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.polygonRoom" -msgstr "多角形の部屋" +msgid "editor.panels.buildingFoundationSection.labelAuto" +msgstr "{label}(自動)" -# Source-Hash: 83a45603ac7f41e8 +# English: "Laminate" +# Source-Hash: 826e189e08abac28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.rectangleRoom" -msgstr "矩形の部屋" +msgid "editor.panels.buildingFoundationSection.laminate" +msgstr "ラミネート" -# Source-Hash: 7fd85d0408141157 +# English: "Least depth under grade" +# Source-Hash: 6ace3330ae71b24d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single cabinet" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCabinet" -msgstr "単体キャビネット" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGrade" +msgstr "最小根入れ深さ" -# Source-Hash: dd1bdbc26165baa5 +# English: "The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further." +# Source-Hash: 5e390c119e4d6da5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCanopy" -msgstr "単体キャノピー" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGradeHint" +msgstr "地中梁は少なくともこの深さまで地盤面下に入ります(IRC R403.1.4:12 in、または凍結深度)。せいの大きい梁はさらに深くなります。" -# Source-Hash: 5fb1a69316be983e +# English: "Least step run" +# Source-Hash: 2e918ebc24201841 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleFence" -msgstr "単体フェンス" +msgid "editor.panels.buildingFoundationSection.leastStepRun" +msgstr "段の最小水平長さ" -# Source-Hash: aac9a732d2159ccc +# English: "IRC R403.1.5: the top of a footing is level; it steps where the ground falls." +# Source-Hash: 95c2ee3d275f29cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.walls" -msgstr "壁" +msgid "editor.panels.buildingFoundationSection.leastStepRunHint" +msgstr "IRC R403.1.5:フーチングの上端は水平とし、地盤が下がる所で段差を付けます。" -# Source-Hash: 4e4f1a24e4a9d61e +# English: "Lumber" +# Source-Hash: 73dcfae611575500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.cutOpening" -msgstr "開口を切る" +msgid "editor.panels.buildingFoundationSection.lumber" +msgstr "製材" -# Source-Hash: 60e8d235f3084a9e +# English: "Name the material" +# Source-Hash: 3053c459862812ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divide room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.divideRoom" -msgstr "部屋を分割" +msgid "editor.panels.buildingFoundationSection.nameTheMaterial" +msgstr "材料名を入力" -# Source-Hash: aa496ec33d21ab03 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.editKind" -msgstr "{kind}を編集" +msgid "editor.panels.buildingFoundationSection.off" +msgstr "オフ" -# Source-Hash: f3ea3efa7102844f +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.erase" -msgstr "消去" +msgid "editor.panels.buildingFoundationSection.osb" +msgstr "OSB" -# Source-Hash: 3f907e76526472bf +# English: "OSB subfloor" +# Source-Hash: 097c64d90d9fb848 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.levelTerrain" -msgstr "地形を平らにする" +msgid "editor.panels.buildingFoundationSection.osbSubfloor" +msgstr "OSB 床下地" -# Source-Hash: 4e911c300d74eb47 +# English: "Panel" +# Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lower terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.lowerTerrain" -msgstr "地形を下げる" +msgid "editor.panels.buildingFoundationSection.panel" +msgstr "面材" -# Source-Hash: 194a147d4618a994 +# English: "Pile diameter" +# Source-Hash: 1372cffe6283ee90 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.mezzanine" -msgstr "中二階" +msgid "editor.panels.buildingFoundationSection.pileDiameter" +msgstr "杭径" -# Source-Hash: c48c4129c9ed249a +# English: "Piles are sized by the engineer." +# Source-Hash: 58bdc4ad2c360017 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveKind" -msgstr "{kind}を移動" +msgid "editor.panels.buildingFoundationSection.pileDiameterHint" +msgstr "杭の寸法は構造設計者が決定します。" -# Source-Hash: 89f6b7ca8904c656 +# English: "Pile {diameter} @ {spacing}, {length} deep" +# Source-Hash: 2e583e4622318f58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveRoom" -msgstr "部屋を移動" +msgid "editor.panels.buildingFoundationSection.pileDiameterSpacingDeep" +msgstr "杭 {diameter} @ {spacing}、深さ {length}" -# Source-Hash: 9c707069572fb3db +# English: "Pile length" +# Source-Hash: 5e99bac8b7ba3af9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move selection" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveSelection" -msgstr "選択を移動" +msgid "editor.panels.buildingFoundationSection.pileLength" +msgstr "杭長" -# Source-Hash: cf2e870fce0f2f51 +# English: "Pile spacing" +# Source-Hash: 7dfd658f6006b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paint" -msgstr "塗装" +msgid "editor.panels.buildingFoundationSection.pileSpacing" +msgstr "杭の間隔" -# Source-Hash: ca197314cb70aa56 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a rectangle" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintARectangle" -msgstr "矩形を塗る" +msgid "editor.panels.buildingFoundationSection.plaster" +msgstr "漆喰" -# Source-Hash: ea4ce729e17254f8 +# English: "Plaster ceiling" +# Source-Hash: a1f03abd876ee26f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a shape" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintAShape" -msgstr "形を塗る" +msgid "editor.panels.buildingFoundationSection.plasterCeiling" +msgstr "漆喰天井" -# Source-Hash: 01359502b07284d4 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick material" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.pickMaterial" -msgstr "素材を取得" +msgid "editor.panels.buildingFoundationSection.plywood" +msgstr "合板" -# Source-Hash: 4066e8a3f60b1711 +# English: "Plywood subfloor" +# Source-Hash: ded7bab2a8fe5793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.raiseTerrain" -msgstr "地形を上げる" +msgid "editor.panels.buildingFoundationSection.plywoodSubfloor" +msgstr "合板床下地" -# Source-Hash: f85bdac0a1d16603 +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.resize" -msgstr "サイズ変更" +msgid "editor.panels.buildingFoundationSection.preset" +msgstr "プリセット" -# Source-Hash: 306d409f8a1cf480 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.rotate" -msgstr "回転" +msgid "editor.panels.buildingFoundationSection.raisedFloor" +msgstr "高床" -# Source-Hash: a4395168488bb476 +# English: "Foundation section: basement, its wall's top {top} over grade, its floor {floor} under it" +# Source-Hash: f17c359b6460adc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.select" -msgstr "選択" +msgid "editor.panels.buildingFoundationSection.sectionBasement" +msgstr "基礎断面:地下室。壁の天端は地盤面から {top} 上、床は地盤面から {floor} 下" -# Source-Hash: ec1f1d68af469867 +# English: "Foundation section: raised floor on a stem wall, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: d292ee971f5b54a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart measure" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smartMeasure" -msgstr "スマート計測" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloor" +msgstr "基礎断面:立ち上がりの上の高床。床高は地盤面から {floor}、根太下の床下は {crawl}" -# Source-Hash: 5ba5f22bd1a69e09 +# English: "Foundation section: raised floor on a stem wall on piles, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: 786e74b8b53f3dab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smooth terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smoothTerrain" -msgstr "地形を滑らかにする" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles" +msgstr "基礎断面:杭上の立ち上がりの上の高床。床高は地盤面から {floor}、根太下の床下は {crawl}" -# Source-Hash: cbb4687261739397 +# English: "Foundation section: slab on grade, finish floor {floor} over grade" +# Source-Hash: bfb5167b838b50c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.splitKind" -msgstr "{kind}を分割" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGrade" +msgstr "基礎断面:土間スラブ。床高は地盤面から {floor}" -# Source-Hash: 96281876ce53ba08 +# English: "Foundation section: slab on grade on piles, finish floor {floor} over grade" +# Source-Hash: 5a7458213c388c8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.terrace" -msgstr "テラス" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles" +msgstr "基礎断面:杭上の土間スラブ。床高は地盤面から {floor}" -# Source-Hash: 8ec6152e139b0e3f +# English: "Sill" +# Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.polygonRoom" -msgstr "多角形の部屋" +msgid "editor.panels.buildingFoundationSection.sill" +msgstr "土台" -# Source-Hash: 83a45603ac7f41e8 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.rectangleRoom" -msgstr "矩形の部屋" +msgid "editor.panels.buildingFoundationSection.size" +msgstr "サイズ" -# Source-Hash: aac9a732d2159ccc +# English: "{size} joists" +# Source-Hash: 2406f7fa46d251de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.walls" -msgstr "壁" +msgid "editor.panels.buildingFoundationSection.sizeJoists" +msgstr "{size} 根太" +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# Source-Hash: c2626e1fbf89b3c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" -#: packages/editor/src/components/editor/registry-action-contributions.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.mechanismAction" -msgstr "{action, select, close {閉じる} open {開く} stop {停止} other {再生}}" +msgid "editor.panels.buildingFoundationSection.slab" +msgstr "スラブ" +# English: "IRC R506.1 asks 3½ in at least." +# Source-Hash: 97296dd13f940848 # Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.curve" -msgstr "曲線" +msgid "editor.panels.buildingFoundationSection.slabHint" +msgstr "IRC R506.1 では 3½ in 以上が必要です。" +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# Source-Hash: 5af05fb6661e4d6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut out" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.cutOut" -msgstr "切り抜き" +msgid "editor.panels.buildingFoundationSection.slabOnGrade" +msgstr "土間スラブ" +# English: "Slab {thickness}" +# Source-Hash: 714bbb73fd4e68b9 # Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.delete" -msgstr "削除" +msgid "editor.panels.buildingFoundationSection.slabThickness" +msgstr "スラブ {thickness}" +# English: "Slab top over grade" +# Source-Hash: 4e6765b0d6a9bbe5 # Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.duplicate" -msgstr "複製" +msgid "editor.panels.buildingFoundationSection.slabTopOverGrade" +msgstr "地盤面からのスラブ天端" +# English: "The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in)." +# Source-Hash: 7a6001a64711e175 # Review: required -# Source-Hash: 94b212bd79c05141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit mesh" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.editMesh" -msgstr "メッシュを編集" +msgid "editor.panels.buildingFoundationSection.slabTopOverGradeHint" +msgstr "スラブ周囲の地盤は全周でこの高さに整地されます。下がる所は盛土、上がる所は切土します(IRC R317.1:8 in)。" +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c # Review: required -# Source-Hash: ce90cf37224f73cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in catalog" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.findInCatalog" -msgstr "カタログで探す" +msgid "editor.panels.buildingFoundationSection.spacing" +msgstr "間隔" +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 # Review: required -# Source-Hash: d5e550c345e7161c -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.groupSelection" -msgstr "選択をグループ化" +msgid "editor.panels.buildingFoundationSection.spacingOnCentre" +msgstr "芯々 {spacing}" +# English: "Stem top over grade" +# Source-Hash: 097812a3bcb6d256 # Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.move" -msgstr "移動" +msgid "editor.panels.buildingFoundationSection.stemTopOverGrade" +msgstr "地盤面からの立ち上がり天端" +# English: "IRC R317.1 keeps wood framing 8 in off the ground." +# Source-Hash: eabab1d380a0b860 # Review: required -# Source-Hash: 473a9d4463761107 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate left" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateLeft" -msgstr "左に回転" +msgid "editor.panels.buildingFoundationSection.stemTopOverGradeHint" +msgstr "IRC R317.1 では、木部を地面から 8 in 離します。" +# English: "Stem wall" +# Source-Hash: ce9da5096b1feb02 # Review: required -# Source-Hash: b1bf4e65c8d8c8ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate right" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateRight" -msgstr "右に回転" +msgid "editor.panels.buildingFoundationSection.stemWall" +msgstr "立ち上がり" +# English: "Stem wall and footing" +# Source-Hash: b0a49e954f1e5a99 # Review: required -# Source-Hash: 6f4dbbc4a3f58fca -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.ungroupSelection" -msgstr "選択のグループを解除" +msgid "editor.panels.buildingFoundationSection.stemWallAndFooting" +msgstr "立ち上がりとフーチング" -# Source-Hash: 4275c05172abd7fb +# English: "Table R404.1.2: 6 in for one storey, 8 in for more." +# Source-Hash: 4ea0e2b0609f6137 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.ceiling" -msgstr "天井" +msgid "editor.panels.buildingFoundationSection.stemWallHint" +msgstr "Table R404.1.2:1 階建てで 6 in、それ以上で 8 in。" -# Source-Hash: 3530ac1f4046cd22 +# English: "Stem wall {thickness}" +# Source-Hash: 965e29b358d63d58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.column" -msgstr "柱" +msgid "editor.panels.buildingFoundationSection.stemWallThickness" +msgstr "立ち上がり {thickness}" -# Source-Hash: 787e10f5db16904e +# English: "Step down the slope" +# Source-Hash: 2a131b783b8e35ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.door" -msgstr "ドア" +msgid "editor.panels.buildingFoundationSection.stepDownTheSlope" +msgstr "斜面に沿って段差を付ける" -# Source-Hash: ab5cbab7be160a1c +# English: "Step height" +# Source-Hash: 2d15d12859e8f169 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.elevator" -msgstr "エレベーター" +msgid "editor.panels.buildingFoundationSection.stepHeight" +msgstr "段差の高さ" -# Source-Hash: 02042fde61b5aa11 +# English: "Subfloor sheathing" +# Source-Hash: 438e69e35a342a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.fence" -msgstr "フェンス" +msgid "editor.panels.buildingFoundationSection.subfloorSheathing" +msgstr "床下地材" -# Source-Hash: 4c3ff8cdc6feda97 +# English: "Tallest stem before stepping" +# Source-Hash: 1170f2affa3a3f4b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.guide" -msgstr "ガイド画像" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeStepping" +msgstr "段差なしの最大立ち上がり" -# Source-Hash: 2743a0e9d2743807 +# English: "A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it." +# Source-Hash: 9485def39ac25b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.item" -msgstr "アイテム" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint" +msgstr "立ち上がりが地面からこれより高くなる辺は、斜面に沿って段差を付けます。立ち上がりは地盤面からの天端の高さまで下がり、その上にクリップルウォールを設けます。" -# Source-Hash: f5f730969df63d1d +# English: "Taper" +# Source-Hash: 6063ae2877faa35d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roof" -msgstr "屋根" +msgid "editor.panels.buildingFoundationSection.taper" +msgstr "テーパー" -# Source-Hash: a130b4eec084c79a +# English: "Taper {taper} at 45°" +# Source-Hash: 4c008bc3ff2c404c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roofSegment" -msgstr "屋根セグメント" +msgid "editor.panels.buildingFoundationSection.taperAt45" +msgstr "テーパー {taper}(45°)" -# Source-Hash: 612dcd5bf06a79c4 +# English: "The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider." +# Source-Hash: c2d8477f6b02f2a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.room" -msgstr "部屋" +msgid "editor.panels.buildingFoundationSection.taperHint" +msgstr "端部の内面は底から垂直に立ち上がり、45° でスラブへすぼまります。「自動」ではスラブ下の端部深さの半分(6〜12 in)とするため、端部が深くなっても幅は広がらず、垂直部分が伸びます。" -# Source-Hash: b22b2f20cc7cd416 +# English: "The house stands on a basement: its walls are the foundation walls, so only their footing is drawn." +# Source-Hash: 8d646e2d19be2e57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D Scan" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.scan" -msgstr "3Dスキャン" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnABasement" +msgstr "住宅は地下室の上に建っています。地下室の壁が基礎壁となるため、描画されるのはそのフーチングのみです。" -# Source-Hash: 170b2049ef0f9131 +# English: "The house stands on its basement, cut into the ground: the ground round it is left as it lies." +# Source-Hash: 82f250b688c1079e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selection" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.selection" -msgstr "選択" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement" +msgstr "住宅は地盤に掘り込んだ地下室の上に建っています。周囲の地盤はそのままです。" -# Source-Hash: 8775bc8dc9593964 +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.slab" -msgstr "スラブ" +msgid "editor.panels.buildingFoundationSection.thickness" +msgstr "厚さ" -# Source-Hash: 53d7084bc41c1ad6 +# English: "{title} material" +# Source-Hash: 4cf16973c8dd006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stair" -msgstr "階段" +msgid "editor.panels.buildingFoundationSection.titleMaterial" +msgstr "{title}の材料" -# Source-Hash: 52de428c16bb81e6 +# English: "Turned-down edge" +# Source-Hash: 4707e4ae34281fd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stairSegment" -msgstr "階段セグメント" +msgid "editor.panels.buildingFoundationSection.turnedDownEdge" +msgstr "端部の立ち下がり" -# Source-Hash: 2210e5ee50b74dc9 +# English: "Vinyl plank" +# Source-Hash: 385c128fc1026239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.wall" -msgstr "壁" +msgid "editor.panels.buildingFoundationSection.vinylPlank" +msgstr "ビニル床材" -# Source-Hash: aa3c35ed33e181e5 +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.window" -msgstr "窓" +msgid "editor.panels.buildingFoundationSection.woodPlank" +msgstr "板張り" -# Source-Hash: a107b4d76d6972e3 +# English: "Wood plank ceiling" +# Source-Hash: 931afc430cda5de8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.zone" -msgstr "ゾーン" +msgid "editor.panels.buildingFoundationSection.woodPlankCeiling" +msgstr "板張り天井" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.cancel" msgstr "キャンセル" +# English: "Collection name…" # Source-Hash: 6dbabf32ff8c6f37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection name…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collectionName" msgstr "コレクション名…" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collections" msgstr "コレクション" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.delete" msgstr "削除" +# English: "Delete \"{name}\"?" # Source-Hash: c53d71b05b8163b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete \"{name}\"?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.deleteName" msgstr "「{name}」を削除しますか?" +# English: "New" # Source-Hash: e9f7cceb39860c56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.new" msgstr "新規" +# English: "No collections yet. Create one to group elements together." # Source-Hash: 649bc1b1799f2311 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No collections yet. Create one to group elements together." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" msgstr "コレクションはまだありません。作成すると要素をまとめられます。" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.rename" msgstr "名前を変更" -# Source-Hash: 2000cf124b4e1139 +# English: "Along house" +# Source-Hash: 0a605a51555bb514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.alongHouse" +msgstr "住宅と平行" + +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boards" +msgstr "板材" + +# English: "{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." +# Source-Hash: 2bc05aaf1fc289b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boardsSummary" +msgstr "{label} のデッキ板 {thickness} × {width} in、隙間 {gap} in、{direction, select, parallel {住宅と平行に張り、根太は住宅と直交} diagonal {住宅に対して 45° に張る} other {住宅から外向きに張り、根太は住宅と平行で、住宅と直交する梁が支える}}。根太は芯々 {spacing} in。" + +# English: "Deck boards" +# Source-Hash: 24c6255bfa1abd8f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.deckBoards" +msgstr "デッキ板" + +# English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.diagonal" +msgstr "斜め" + +# English: "Gap" +# Source-Hash: df708ece3529bcba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.gap" +msgstr "隙間" + +# English: "Out from house" +# Source-Hash: 3660fcdd12159277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.outFromHouse" +msgstr "住宅と直交" + +# English: "Run" +# Source-Hash: 9ac7e4955593cc34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.run" +msgstr "張り方向" + # English: "A thicker floor grows upward: the rooms below keep their height." +# Source-Hash: 2000cf124b4e1139 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" msgstr "床を厚くすると上方向に伸び、下の部屋の高さは変わりません。" -# Source-Hash: 0d57958b99b6b352 +# English: "Its layers make {thickness}; the thickness follows them on the first change." +# Source-Hash: 767f8969959d1c48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.buildUpFollows" +msgstr "層の合計は {thickness} です。最初の変更で、厚さが層の合計に合わせて変わります。" + # English: "Edge finish" +# Source-Hash: 0d57958b99b6b352 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.edgeFinish" msgstr "縁の仕上げ" -# Source-Hash: 8e458d8b21f26f09 +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floor" +msgstr "床" + +# English: "Floor build-up" +# Source-Hash: 097b19040c031ae4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floorBuildUp" +msgstr "床の構成" + # English: "Floor plate" +# Source-Hash: 8e458d8b21f26f09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlate" msgstr "床版" +# English: "Floor plate {thicknessLabel} thick, resting on the walls below" # Source-Hash: 2bb71e5633801afb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate {thicknessLabel} thick, resting on the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn" msgstr "厚さ {thicknessLabel} の床版、下の壁の上に載ります" -# Source-Hash: 73614b8faa059cd6 +# English: "Floor thickness {thickness}" +# Source-Hash: 68a6aff9bf44f9c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy -msgid "editor.panels.floorFoundationPanel.floorThickness" -msgstr "床の厚さ" +msgid "editor.panels.floorFoundationPanel.floorThicknessValue" +msgstr "床の厚さ {thickness}" +# English: "Floor top +{height}" # Source-Hash: a40a1fbacd5d2551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top +{height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopAbove" msgstr "床上面 +{height}" +# English: "Floor top {height}" # Source-Hash: 5c3849e15933737f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopValue" msgstr "床上面 {height}" +# English: "Foundation finish" # Source-Hash: 19802dd369749ea7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationFinish" msgstr "基礎の仕上げ" +# English: "Foundation height" # Source-Hash: 3a4599be1ad687be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationHeight" msgstr "基礎の高さ" +# English: "Floor plate" # Source-Hash: 8e458d8b21f26f09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kicker" msgstr "床版" +# English: "Floor plate · {level}" # Source-Hash: 66d52bfbb94f7bb3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerLevel" msgstr "床版 · {level}" +# English: "Floor plate · {level} · rests on {below}" # Source-Hash: 0189e5a1987b4ec0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level} · rests on {below}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerRestsOn" msgstr "床版 · {level} · {below}の上" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" # Source-Hash: 648ef10535b0af73 # Review: required -# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.lowerTogether" msgstr "{count, plural, =2 {両方を下げる} other {{number} 個すべてを下げる}}" +# English: "Not to scale" # Source-Hash: c2fcc43bb7f1c5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not to scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.notToScale" msgstr "縮尺なし" +# English: "On the ground" # Source-Hash: e9440fa9f770b7a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.onTheGround" msgstr "地面上" +# English: "Raised on a foundation" # Source-Hash: 7c3692a361075ecf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised on a foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raisedOnAFoundation" msgstr "基礎の上に上げる" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" # Source-Hash: 769c3ca1ca0cd4f0 # Review: required -# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raiseTogether" msgstr "{count, plural, =2 {両方を上げる} other {{number} 個すべてを上げる}}" +# English: "Floor plate · {thickness}" # Source-Hash: 719ae422da204ca7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {thickness}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorPlate" msgstr "床版 · {thickness}" +# English: "Floor top" # Source-Hash: 069d64adffa05db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorTop" msgstr "床上面" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundation" msgstr "基礎" +# English: "Foundation · {foundation}" # Source-Hash: 7759a59b8685c885 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation · {foundation}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundationKind" msgstr "基礎 · {foundation}" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.ground" msgstr "地面" -# Source-Hash: 51e274b2c90e2cf3 +# English: "{layer} · {size}" +# Source-Hash: 1a0f891c672247b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.section.layer" +msgstr "{layer} · {size}" + # English: "Slab · {thickness}" +# Source-Hash: 51e274b2c90e2cf3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.slab" msgstr "スラブ · {thickness}" +# English: "Walls below" # Source-Hash: 790a0b5c6dbf0e9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.wallsBelow" msgstr "下の壁" -# Source-Hash: 90e96ae5f3d3f69c +# English: "Floor plate {thickness} thick: {layers}; resting on the walls below" +# Source-Hash: 7c1a423934a360f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayers" +msgstr "厚さ {thickness} の床版:{layers}。下の壁の上に載ります" + +# English: "{layer} {size}" +# Source-Hash: 0c5b48217a0cf663 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayerSize" +msgstr "{layer} {size}" + # English: "Slab {thickness} thick on the ground, floor top {top} above the ground" +# Source-Hash: 90e96ae5f3d3f69c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionOnGround" msgstr "厚さ {thickness} のスラブ、地面の上、床上面は地面から {top}" +# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" # Source-Hash: 71ed2295d9932ed0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionRaised" msgstr "厚さ {thickness} のスラブ、{foundation}基礎の上、床上面は地面から {top}" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slab" msgstr "スラブ" +# English: "Slab thickness" # Source-Hash: ca51d643691c29ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slabThickness" msgstr "スラブの厚さ" +# English: "The floor's outside edge, all around the footprint." # Source-Hash: 678c39468593275b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The floor's outside edge, all around the footprint." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll" msgstr "フットプリント全周にわたる床の外縁。" +# English: "The foundation stands on the ground; the slab sits on it." # Source-Hash: 6638ed5caa43bdb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The foundation stands on the ground; the slab sits on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround" msgstr "基礎は地面の上に、スラブは基礎の上に載ります。" +# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." # Source-Hash: 5321416b442f23b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation" msgstr "スラブは基礎(または地面)の上に載ります。厚くすると床上面とその上のすべてが上がります。" +# English: "Under the floor" # Source-Hash: 044ccae8bec574e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Under the floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.underTheFloor" msgstr "床下" +# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." # Source-Hash: 6aca44df3e582a81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt" msgstr "中二階のハッチ:中二階の床だけを切り抜きます。" +# English: "Also cut the ceiling below" # Source-Hash: 2ce79639d42da658 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the ceiling below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow" msgstr "下の天井も切り抜く" +# English: "Also cut the floor above" # Source-Hash: e360814f64792300 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the floor above" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheFloorAbove" msgstr "上の床も切り抜く" +# English: "{area} · Drag its corners to change the shape." # Source-Hash: c25cddf8b831c2b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Drag its corners to change the shape." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaDragCorners" msgstr "{area} · 角をドラッグして形状を変更します。" +# English: "{area} · Shape follows its owner." # Source-Hash: 32b168cf010a5daa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Shape follows its owner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaOwned" msgstr "{area} · 形状は所有元に従います。" -# Source-Hash: b26de372b4b0eaac +# English: "{area} · Drag its sides or corners to resize it; it moves with the stair." +# Source-Hash: 71b5c7184cbd2e5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-opening-panel.tsx +#, fuzzy +msgid "editor.panels.floorOpeningPanel.areaStairResize" +msgstr "{area} · 辺または角をドラッグしてサイズを変更します。階段と一緒に動きます。" + # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.delete" msgstr "削除" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.done" msgstr "完了" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kicker" msgstr "開口" +# English: "Opening · {level}" # Source-Hash: 079ad92dac7947cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerLevel" msgstr "開口 · {level}" +# English: "Opening · in {room}" # Source-Hash: baf2c2c209637e5f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoom" msgstr "開口 · {room}内" +# English: "Opening · in {room} · {level}" # Source-Hash: 1712c164335ecd86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room} · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoomLevel" msgstr "開口 · {room}内 · {level}" +# English: "On {level}, as one void with this opening." # Source-Hash: 88e673123b9dbfe1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On {level}, as one void with this opening." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.oneVoidWith" msgstr "{level}で、この開口と一体の吹き抜けになります。" +# English: "Owned by Elevator" # Source-Hash: c38959fc8fd3e59d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByElevator" msgstr "エレベーターに属する" +# English: "Owned by Staircase" # Source-Hash: 01494805cf60d78d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByStaircase" msgstr "階段に属する" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.room" msgstr "部屋" +# English: "There is no floor above this storey." # Source-Hash: 1405b914a324b153 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no floor above this storey." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis" msgstr "この階の上に床はありません。" +# English: "There is no storey below." # Source-Hash: 776170dcae88e969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no storey below." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoStoreyBelow" msgstr "下の階はありません。" +# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" # Source-Hash: 0fdd1877a0f2d777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.title" msgstr "{source, select, stair {階段の開口} elevator {エレベーターの開口} ceiling {天井の開口} other {床の開口}}" -# Source-Hash: fa35c7653e1f649e +# English: "A raised floor on a stem wall of its own." +# Source-Hash: 1839fff3aba02d40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aRaisedFloorOnAStem" +msgstr "独立した立ち上がりの上の高床。" + +# English: "A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round." +# Source-Hash: 52eead98a170fd6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aSlabOfItsOwn" +msgstr "独立したスラブ(ポーチ、パティオ、ガレージの土間)。周囲の地盤は全周で天端から 8 in 下に保たれます。" + +# English: "Click its corners; click the first again, or press Enter, to close it." +# Source-Hash: 96853854f04b1514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.clickItsCornersClickTheFirst" +msgstr "角をクリックし、最初の角をもう一度クリックするか Enter を押して閉じます。" + +# English: "Close the outside walls into a loop first, or draw one" +# Source-Hash: 4655068154039021 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA" +msgstr "先に外壁を閉じたループにするか、輪郭を描いてください" + +# English: "Delete this outline" +# Source-Hash: 8b8a23b925c30027 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.deleteThisOutline" +msgstr "この輪郭を削除" + +# English: "Draw" +# Source-Hash: b5c56dd8c68de309 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.draw" +msgstr "描画" + +# English: "Edit it myself" +# Source-Hash: e9c386def6e2dadf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.editItMyself" +msgstr "自分で編集" + +# English: "Follow the walls" +# Source-Hash: 86c8ecac2691c892 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.followTheWalls" +msgstr "壁に合わせる" + +# English: "{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it." +# Source-Hash: ee78cc2aa3f77ca1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.footingsDrawn" +msgstr "{footings, plural, other {フーチング # 個}}を描画済み:平面図でクリックすると、サイズ変更や移動ができます。" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.foundation" +msgstr "基礎" + +# English: "House foundation" +# Source-Hash: 5ec26bb3cea817e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.houseFoundation" +msgstr "住宅の基礎" + +# English: "How far its top sits under the house’s floor: a porch slab steps down from the door" +# Source-Hash: 0140833a30e08d70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.howFarItsTopSitsUnder" +msgstr "天端を住宅の床からどれだけ下げるか:ポーチのスラブはドアから一段下がります" + +# English: "Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be." +# Source-Hash: 3b101db8d8839574 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd" +msgstr "独自の輪郭:平面図または 3D で角や辺をドラッグします。壁を動かしても変わりません。" + +# English: "Outline" +# Source-Hash: 3e6d52c82419517d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outline" +msgstr "輪郭" + +# English: "Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts." +# Source-Hash: 78d4f6a686211e44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors" +msgstr "スラブと高床の輪郭(ポーチ、パティオ、ガレージの土間)、および内部の耐力壁や支柱の下のフーチング。" + +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.raisedFloor" +msgstr "高床" + +# English: "Re-trace from walls" +# Source-Hash: e6eaea778b041b10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.reTraceFromWalls" +msgstr "壁からなぞり直す" + +# English: "Set the outline back to the outside faces of the walls, keeping it your own" +# Source-Hash: 5181a67d6940cccb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.setTheOutlineBackToThe" +msgstr "輪郭を壁の外面に戻します(独自の輪郭のまま)" + +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.slab" +msgstr "スラブ" + +# English: "The house’s" +# Source-Hash: 523f999e5b2d497e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own. +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHouses" +msgstr "住宅の基礎" + +# English: "The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline." +# Source-Hash: 3e4d5b90c1ab8eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHousesOwnFoundation" +msgstr "住宅自体の基礎。壁の輪郭の代わりにこの輪郭で、下の住宅の設定どおりに打設します。" + +# English: "This outline" +# Source-Hash: 51be023b5f1983f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.thisOutline" +msgstr "この輪郭" + +# English: "Top under the floor" +# Source-Hash: 85eca682e0102861 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.topUnderTheFloor" +msgstr "床からの下がり" + +# English: "Traced from the outside walls: it moves when they do." +# Source-Hash: 1f7c9880395d456a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt" +msgstr "外壁をなぞった輪郭:外壁を動かすと一緒に動きます。" + # English: "One of them already wraps another corner. Unwrap it first." +# Source-Hash: fa35c7653e1f649e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.alreadyJoined" msgstr "どちらかがすでに別のコーナーを回っています。先に解除してください。" +# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" # Source-Hash: dd87eb1983876459 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.between" msgstr "{kind, select, item {間にアイテムがあります。} door {間にドアがあります。} window {間に窓があります。} shelf {間に棚があります。} other {間に何かがあります。}}" +# English: "These windows cannot be merged." # Source-Hash: 58aea0b9789ae280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "These windows cannot be merged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cannotMerge" msgstr "これらの窓は結合できません。" +# English: "Their walls meet at too sharp or too flat an angle for a corner window." # Source-Hash: 15170cfd9d666c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls meet at too sharp or too flat an angle for a corner window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cornerAngle" msgstr "壁の角度が鋭すぎるか平らすぎて、コーナー窓にできません。" +# English: "The window selected first is taller than the other wall. Select the other one first." # Source-Hash: f2818a1e20ebd6ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window selected first is taller than the other wall. Select the other one first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.heightExceedsWall" msgstr "最初に選んだ窓がもう一方の壁より高いです。先にもう一方を選んでください。" +# English: "They are too far apart. Windows merge with at most {reach} between them." # Source-Hash: 7c97fb4af929d4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "They are too far apart. Windows merge with at most {reach} between them." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAdjacent" msgstr "離れすぎています。窓は間隔が {reach} 以内のときに結合します。" +# English: "One of them is too far from the corner. A window joins one within {reach} of it." # Source-Hash: 57a281bdc4229b6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them is too far from the corner. A window joins one within {reach} of it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAtCorner" msgstr "どちらかがコーナーから離れすぎています。窓は {reach} 以内の窓と結合します。" +# English: "A roof or dormer window cannot join another window." # Source-Hash: e7d41ce7879ec075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A roof or dormer window cannot join another window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notOnWall" msgstr "屋根窓やドーマー窓は他の窓と結合できません。" +# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" # Source-Hash: eaad567d5e4257dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.overlap" msgstr "{kind, select, item {コーナーまでずらすとアイテムと重なります。} door {コーナーまでずらすとドアと重なります。} window {コーナーまでずらすと窓と重なります。} shelf {コーナーまでずらすと棚と重なります。} other {コーナーまでずらすと何かと重なります。}}" +# English: "Their walls do not meet at a corner." # Source-Hash: ecb092821ed5b5e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls do not meet at a corner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.wallsNotMeeting" msgstr "それぞれの壁がコーナーで接していません。" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.mixed" msgstr "混在" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-panel-sheet.tsx #, fuzzy msgid "editor.panels.mobilePanelSheet.close" msgstr "閉じる" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.delete" msgstr "削除" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.duplicate" msgstr "複製" +# English: "Edit properties" # Source-Hash: 7eb955d866c2668b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit properties" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editProperties" msgstr "プロパティを編集" +# English: "Edit {resolvedLabel}" # Source-Hash: 89f25aa13e8192bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {resolvedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editResolvedlabel" msgstr "{resolvedLabel}を編集" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.move" msgstr "移動" +# English: "Currently {currentLabel}" # Source-Hash: 72be05e1796526c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {currentLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.currentlyCurrentlabel" msgstr "現在:{currentLabel}" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.customHeight" msgstr "カスタムの高さ" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.followsLevel" msgstr "階層に従う" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.height" msgstr "高さ" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.top" msgstr "上" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx #, fuzzy msgid "editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects" msgstr "{label}(このセッションのみ)。通常クリックで全メンバーを再選択します。プロジェクトには保存されません。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.delete" msgstr "削除" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.duplicate" msgstr "複製" +# English: "Group" # Source-Hash: b0e7e3b47dfb5a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.group" msgstr "グループ化" +# English: "Group (Ctrl/Cmd+G)" # Source-Hash: ca0a95fe0b08b8b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group (Ctrl/Cmd+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.groupCtrlCmdG" msgstr "グループ化(Ctrl/Cmd+G)" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects" msgstr "{label}(このセッションのみ)。通常クリックで全メンバーを再選択します。プロジェクトには保存されません。" +# English: "{length} selected" # Source-Hash: fc98ef29c2842341 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length} selected" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.lengthSelected" msgstr "{length} 個選択中" +# English: "Merge windows" # Source-Hash: 323aaf1ebd04ada7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindows" msgstr "窓を結合" +# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" # Source-Hash: 7855d03a6140af77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow" msgstr "コーナー窓に結合:両方とも最初に選んだ窓の高さ、窓台、種類になります" +# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" # Source-Hash: b79da96502d2e1c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth" msgstr "両方にまたがる1つの窓に結合:最初に選んだ窓の種類とスタイルを保ちます" +# English: "Select building" # Source-Hash: f409934681a046e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectBuilding" msgstr "建物を選択" +# English: "Select the whole building to move all its levels" # Source-Hash: c62a73873bd0164d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select the whole building to move all its levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove" msgstr "建物全体を選ぶとすべての階層を移動できます" +# English: "Ungroup" # Source-Hash: 7bd5f29203a28efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroup" msgstr "グループ解除" +# English: "Ungroup (Ctrl/Cmd+Shift+G)" # Source-Hash: aec9c3983a440d4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup (Ctrl/Cmd+Shift+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG" msgstr "グループ解除(Ctrl/Cmd+Shift+G)" +# English: "Unwrap the corner" # Source-Hash: 420385f27c627cee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCorner" msgstr "コーナーを解除" +# English: "Unwrap the corner: both windows stay, each framed at the corner" # Source-Hash: 7906c9f4baebc70b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner: both windows stay, each framed at the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay" msgstr "コーナーを解除:両方の窓は残り、それぞれコーナーで枠が付きます" -# Source-Hash: 170b2049ef0f9131 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/panel-manager.tsx +#, fuzzy +msgid "editor.panels.panelManager.foundation" +msgstr "基礎" + # English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-manager.tsx #, fuzzy msgid "editor.panels.panelManager.selection" msgstr "選択" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.back" msgstr "戻る" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.close" msgstr "閉じる" +# English: "Close {title}" # Source-Hash: 2dff25ca30c80559 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.closeTitle" msgstr "{title}を閉じる" +# English: "Collapse panel" # Source-Hash: 4f15c3e0fe6178b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collapsePanel" msgstr "パネルを折りたたむ" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collections" msgstr "コレクション" +# English: "Expand panel" # Source-Hash: 64916cc9ea80f43f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.expandPanel" msgstr "パネルを展開" +# English: "Manage collections…" # Source-Hash: 8a9a25621f200922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manage collections…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.manageCollections" msgstr "コレクションを管理…" +# English: "Open {title}" # Source-Hash: 2ff02ed91f54235c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.openTitle" msgstr "{title}を開く" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.reset" msgstr "リセット" +# English: "“{title}” hit an error and was unloaded for this session." # Source-Hash: 04a212d6317f6787 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "“{title}” hit an error and was unloaded for this session." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.titleHitAnErrorAndWas" msgstr "「{title}」でエラーが発生したため、このセッションではアンロードされました。" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.parametricFieldControl.mixed" msgstr "混在" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.actions" msgstr "操作" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.delete" msgstr "削除" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.deleteWithContents" msgstr "内容ごと削除" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.move" msgstr "移動" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.cancel" msgstr "キャンセル" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.capture" msgstr "キャプチャ" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.center" msgstr "中央" +# English: "Clear Scale" # Source-Hash: a5b965dfb0bf2cd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clearScale" msgstr "縮尺をクリア" +# English: "Click both ends of a known distance on the plan, then type its real length." # Source-Hash: 4edc6e45b0c19447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click both ends of a known distance on the plan, then type its real length." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clickBothEndsOfAKnown" msgstr "図面上で既知の距離の両端をクリックし、実際の長さを入力します。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.delete" msgstr "削除" +# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." # Source-Hash: 59f7d81e8d36a157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.drawALineOverAKnown" msgstr "図面上の既知の寸法に線を引き、実際の長さを入力すると画像の縮尺を正確に合わせられます。" +# English: "Edit Scale" # Source-Hash: 42f6d0e4f065f114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.editScale" msgstr "縮尺を編集" +# English: "Guide Image" # Source-Hash: e156c84cbc90e0a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.guideImage" msgstr "ガイド画像" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hide" msgstr "非表示" +# English: "Hide Scale" # Source-Hash: dd282ce13b9085ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hideScale" msgstr "縮尺を隠す" +# English: "Image" # Source-Hash: fc6eaa65315f601d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.image" msgstr "画像" +# English: "Lock" # Source-Hash: 241399e11d93bd7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.lock" msgstr "ロック" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.move" msgstr "移動" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.opacity" msgstr "不透明度" +# English: "Overlay image unavailable. Replace the image to restore it." # Source-Hash: 6ca213a6b299f19f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overlay image unavailable. Replace the image to restore it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage" msgstr "重ね合わせ画像が利用できません。画像を置き換えると復元されます。" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.position" msgstr "位置" +# English: "Quick Actions" # Source-Hash: 8b003432b36230c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Quick Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.quickActions" msgstr "クイック操作" +# English: "Reference Scale" # Source-Hash: dcd4728e9ca90181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.referenceScale" msgstr "参照縮尺" +# English: "Replace" # Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replace" msgstr "置き換え" +# English: "Replacing..." # Source-Hash: 08b45f9ba66eab3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replacing..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replacing" msgstr "置き換え中..." +# English: "Reset Image Scale" # Source-Hash: 2714b5a81cedb625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Image Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetImageScale" msgstr "画像の縮尺をリセット" +# English: "Reset Rotation" # Source-Hash: a05d3e429ae259ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetRotation" msgstr "回転をリセット" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rot" msgstr "回転" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rotation" msgstr "回転" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scale" msgstr "縮尺" +# English: "Scaled · {scale}" # Source-Hash: e7d0e7d41a775fd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaled" msgstr "縮尺設定済み · {scale}" +# English: "Scaled (hidden) · {scale}" # Source-Hash: cd92eebfb30e708f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled (hidden) · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaledHidden" msgstr "縮尺設定済み(非表示)· {scale}" +# English: "Scale & Opacity" # Source-Hash: bfd20999de7f216a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale & Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaleOpacity" msgstr "縮尺と不透明度" +# English: "Set Scale" # Source-Hash: 8d8777505acdf78e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.setScale" msgstr "縮尺を設定" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.show" msgstr "表示" +# English: "Show Scale" # Source-Hash: 78be2337f0e81973 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.showScale" msgstr "縮尺を表示" +# English: "Uncalibrated" # Source-Hash: ad85a193442c8f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncalibrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.uncalibrated" msgstr "未校正" +# English: "Unlock" # Source-Hash: 6c500cc253ab3fd4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unlock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.unlock" msgstr "ロック解除" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.xyz" msgstr "XYZ" +# English: "+ Add mezzanine" # Source-Hash: e01549105012f8d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.addMezzanine" msgstr "+ 中二階を追加" +# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." # Source-Hash: 167b0fe7fdbcd51d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness" msgstr "描画されたスラブ:ここで厚さと仕上げを設定します。フットプリントの高さや基礎はありません。" +# English: "At floor level" # Source-Hash: 6cb0969b65fb60bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At floor level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.atFloorLevel" msgstr "床レベル" -# Source-Hash: e8563d5ae1ee7390 +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beam" +msgstr "梁" + +# English: "Beam size" +# Source-Hash: 4b03c226d5f88a42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beamSize" +msgstr "梁のサイズ" + # English: "Built on" +# Source-Hash: e8563d5ae1ee7390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtOn" msgstr "建築先" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtWith" msgstr "構成" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceiling" msgstr "天井" +# English: "At {height}" # Source-Hash: 9bf177f59f971033 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceilingAt" msgstr "{height} の位置" +# English: "Close with walls" # Source-Hash: 48768dc0fc7b86a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close with walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.closeWithWalls" msgstr "壁で閉じる" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" # Source-Hash: 75c84b4f21aefde4 # Review: required -# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloor" msgstr "{ambiguous, select, yes {床は複数の描画スラブで、主に{name}} other {床は描画スラブです:{name}}}{shared, plural, =0 {} other {({sharedCount} 部屋と共有)}}" +# English: "This floor is a drawn slab: select it to edit or delete it." # Source-Hash: 97e0fab8ffda6d6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor is a drawn slab: select it to edit or delete it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloorSwitchHint" msgstr "この床は描画スラブです。選択して編集または削除してください。" +# English: "Drawn slab" # Source-Hash: ad5426620188242c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlab" msgstr "描画スラブ" +# English: "Drawn slab at {height}" # Source-Hash: 0da0d104eaefbbad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab at {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlabAt" msgstr "描画スラブ({height})" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.editShape" msgstr "形状を編集" +# English: "Flat, at wall height" # Source-Hash: fadf264be55063e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat, at wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.flatAtWallHeight" msgstr "平ら(壁の高さ)" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floor" msgstr "階" +# English: "{distance} down" # Source-Hash: 996e8191c069e779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorDown" msgstr "{distance} 下" +# English: "Floor thickness" # Source-Hash: 73614b8faa059cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorThickness" msgstr "床の厚さ" +# English: "{distance} up" # Source-Hash: 79cdde853c3e8f61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorUp" msgstr "{distance} 上" -# Source-Hash: 77bc12b7aba0eb60 +# English: "{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: b6b724f9f4db6d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutoHint" +msgstr "{posts, plural, other {支柱 {postCount} 本}}と{beams, plural, other {梁 {beamCount} 本}}。外側の各角に支柱があり、{room, select, deck {デッキ} patio {パティオ} other {ポーチ}}と連動します。" + +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutomatic" +msgstr "自動" + +# English: "Manual" +# Source-Hash: 387082cd5f118bed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManual" +msgstr "手動" + +# English: "Kept as you edited them. Automatic rebuilds them from these settings." +# Source-Hash: 287400b1b4b46abd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManualHint" +msgstr "編集した状態のまま保持されます。「自動」にすると、これらの設定から作り直します。" + +# English: "Off" +# Source-Hash: 50accd740fa2e736 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOff" +msgstr "オフ" + +# English: "None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: 4dfa7082b7e30280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOffHint" +msgstr "なし。「自動」にすると外側の各角に支柱を、その上に梁を配置し、{room, select, deck {デッキ} patio {パティオ} other {ポーチ}}の上に屋根を架けられるようにします。" + # English: "Hand-drawn ceiling" +# Source-Hash: 77bc12b7aba0eb60 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.handDrawnCeiling" msgstr "手描きの天井" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.height" msgstr "高さ" +# English: "Include floor" # Source-Hash: 4d35023efe090b82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeFloor" msgstr "床を含める" +# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" # Source-Hash: 6398ac77644eb5f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includePart" msgstr "{part, select, floor {床を含める} walls {壁を含める} other {天井を含める}}" +# English: "Include walls" # Source-Hash: 3629e64a6e796ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.includeWalls" +msgstr "壁を含める" + +# English: "Keep outside dimensions" +# Source-Hash: f6d07fc7f239278b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" +msgstr "外形寸法を保つ" + +# English: "Lowered {lift}" +# Source-Hash: 544410e3cb0e0b07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.lowered" +msgstr "{lift} 下げる" + +# English: "+ New floor" +# Source-Hash: c2daf78b740e4397 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.newFloor" +msgstr "+ 新しい床" + +# English: "No floor" +# Source-Hash: ba9edc7910a60cea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.noFloor" +msgstr "床なし" + +# English: "Nothing to change." +# Source-Hash: ea2675041df74f40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.nothingToChange" +msgstr "変更する内容はありません。" + +# English: "Not included" +# Source-Hash: ebe0d375d7938c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.notIncluded" +msgstr "含まれていません" + +# English: "On the foundation" +# Source-Hash: f084b3795e3edfb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheFoundation" +msgstr "基礎の上" + +# English: "On the ground" +# Source-Hash: e9440fa9f770b7a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheGround" +msgstr "地面上" + +# English: "On the walls below" +# Source-Hash: a5deb36c485d91bd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheWallsBelow" +msgstr "下の壁の上" + +# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: 2256aec0d5bdc3ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.openSides" +msgstr "{openSides, plural, other {{openSideCount} 個の開放辺}}" + +# English: "Partial · {coverage, number, percent} covered" +# Source-Hash: f704520c5e5b92b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialCovered" +msgstr "部分 · {coverage, number, percent} を覆う" + +# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: b648bee0231a41cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialWalls" +msgstr "部分 · {walls, plural, other {{wallCount} 枚の壁}}、{openSides, plural, other {{openSideCount} 個の開放辺}}" + +# English: "Posts and beam" +# Source-Hash: 4b2a51c39b382184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postsAndBeam" +msgstr "支柱と梁" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.includeWalls" -msgstr "壁を含める" +msgid "editor.panels.roomConstructionRows.postSize" +msgstr "支柱のサイズ" -# Source-Hash: f6d07fc7f239278b +# English: "Post spacing (max)" +# Source-Hash: cc25439729a97f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep outside dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" -msgstr "外形寸法を保つ" +msgid "editor.panels.roomConstructionRows.postSpacingMax" +msgstr "支柱の間隔(最大)" -# Source-Hash: 544410e3cb0e0b07 +# English: "Raised {lift}" +# Source-Hash: e503250e8ecb0030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lowered {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.lowered" -msgstr "{lift} 下げる" +msgid "editor.panels.roomConstructionRows.raised" +msgstr "{lift} 上げ" -# Source-Hash: c2daf78b740e4397 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ New floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.newFloor" -msgstr "+ 新しい床" +msgid "editor.panels.roomConstructionRows.raisedFloor" +msgstr "高床" -# Source-Hash: ba9edc7910a60cea +# English: "Replace" +# Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.noFloor" -msgstr "床なし" +msgid "editor.panels.roomConstructionRows.replace" +msgstr "置き換え" -# Source-Hash: ea2675041df74f40 +# English: "Room type" +# Source-Hash: be1c56bb3ddc63c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nothing to change." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.nothingToChange" -msgstr "変更する内容はありません。" +msgid "editor.panels.roomConstructionRows.roomType" +msgstr "部屋の種類" -# Source-Hash: ebe0d375d7938c93 +# English: "Deck" +# Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not included" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.notIncluded" -msgstr "含まれていません" +msgid "editor.panels.roomConstructionRows.roomTypeDeck" +msgstr "デッキ" -# Source-Hash: e9440fa9f770b7a4 +# English: "Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling." +# Source-Hash: f0028986efecf7d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheGround" -msgstr "地面上" +msgid "editor.panels.roomConstructionRows.roomTypeDeckHint" +msgstr "屋外:木製の床組と支柱の上にデッキ板を張り、住宅の床より 1 in 下げます。天井なし。" -# Source-Hash: a5deb36c485d91bd +# English: "Patio" +# Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheWallsBelow" -msgstr "下の壁の上" +msgid "editor.panels.roomConstructionRows.roomTypePatio" +msgstr "パティオ" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 2256aec0d5bdc3ea +# English: "Outdoors and open: a concrete slab 1½ in down from the house, no ceiling." +# Source-Hash: 5e4ea6a084f67808 # Review: required -# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.openSides" -msgstr "{openSides, plural, other {{openSideCount} 個の開放辺}}" +msgid "editor.panels.roomConstructionRows.roomTypePatioHint" +msgstr "屋外・開放:住宅の床より 1½ in 低いコンクリートスラブ。天井なし。" -# Source-Hash: f704520c5e5b92b2 +# English: "Porch" +# Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partial · {coverage, number, percent} covered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialCovered" -msgstr "部分 · {coverage, number, percent} を覆う" +msgid "editor.panels.roomConstructionRows.roomTypePorch" +msgstr "ポーチ" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: b648bee0231a41cd +# English: "Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s." +# Source-Hash: af2c941e8ef874bf # Review: required -# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialWalls" -msgstr "部分 · {walls, plural, other {{wallCount} 枚の壁}}、{openSides, plural, other {{openSideCount} 個の開放辺}}" +msgid "editor.panels.roomConstructionRows.roomTypePorchHint" +msgstr "屋外(天井あり):住宅の床より 1½ in 低いコンクリートスラブ。天井は住宅と同じ高さ。" -# Source-Hash: e503250e8ecb0030 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.raised" -msgstr "{lift} 上げ" +msgid "editor.panels.roomConstructionRows.roomTypeRoom" +msgstr "部屋" -# Source-Hash: 0cb87327b8076151 +# English: "An ordinary room inside the house." +# Source-Hash: fb0a6cc444058631 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.replace" -msgstr "置き換え" +msgid "editor.panels.roomConstructionRows.roomTypeRoomHint" +msgstr "住宅内の通常の部屋。" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.select" msgstr "選択" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.shape" msgstr "形状" +# English: "Turning walls off keeps the ones shared with {rooms}." # Source-Hash: aafd84823715d698 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turning walls off keeps the ones shared with {rooms}." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sharedWallsNote" msgstr "壁をオフにしても、{rooms}と共有する壁は残ります。" +# English: "Sits on: {name} · {preset}" # Source-Hash: c98df900d43f77bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sits on: {name} · {preset}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sitsOnPreset" msgstr "設置先:{name} · {preset}" -# Source-Hash: a17a79e835fa2064 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.slab" +msgstr "スラブ" + # English: "the next room" +# Source-Hash: a17a79e835fa2064 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.theNextRoom" msgstr "隣の部屋" +# English: "Use existing" # Source-Hash: ebae699e0cb18ea6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use existing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.useExisting" msgstr "既存を使用" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" # Source-Hash: 9976021d9162cd5e # Review: required -# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallCount" msgstr "{walls, plural, other {{wallCount} 枚の壁}}" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.walls" msgstr "壁" +# English: "Walls thicken inward, so the building's outer size stays the same." # Source-Hash: 6c69eb46a0c40d52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls thicken inward, so the building's outer size stays the same." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding" msgstr "壁は内側に厚くなるため、建物の外形寸法は変わりません。" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.cutOpening" msgstr "開口を切る" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.done" msgstr "完了" +# English: "Edit floor" # Source-Hash: 2c89abb1778a3f9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.editFloor" msgstr "床を編集" +# English: "Elevator opening" # Source-Hash: ca18a6c5398fc8ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.elevatorOpening" msgstr "エレベーター開口" +# English: "No openings yet. Cut one for a stair void or a hatch." # Source-Hash: eafea43e795f3708 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No openings yet. Cut one for a stair void or a hatch." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.noOpeningsYetCutOneFor" msgstr "開口はまだありません。階段の吹き抜けやハッチ用に切り抜いてください。" +# English: "Opening {number}" # Source-Hash: 60fac94ffa614238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openingNumber" msgstr "開口 {number}" +# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" # Source-Hash: 7aafbf90d659dcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openings" msgstr "{count, plural, =0 {開口なし} other {開口 {number} 個}}" +# English: "Stair opening" # Source-Hash: c1a1794d31990f5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.stairOpening" msgstr "階段の開口" +# English: "Add stairs" # Source-Hash: f0a46329fef44d94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addStairs" msgstr "階段を追加" +# English: "Add to a unit" # Source-Hash: 8a2ab34a61b9d181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to a unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addToAUnit" msgstr "ユニットに追加" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.area" msgstr "面積" +# English: "Browse" # Source-Hash: 88f1a727e5528c10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.browse" msgstr "閲覧" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.builtWith" msgstr "構成" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.ceiling" msgstr "天井" +# English: "Delete room" # Source-Hash: a5e4fe4960deced9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.deleteRoom" msgstr "部屋を削除" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.editShape" msgstr "形状を編集" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.floor" msgstr "階" +# English: "{height} headroom" # Source-Hash: d17c3b63449c052b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{height} headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.headroom" msgstr "頭上空間 {height}" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.height" msgstr "高さ" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.hideCode" msgstr "コードを隠す" +# English: "Room · {level}" # Source-Hash: e93969f5a7cbb140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerLevel" msgstr "部屋 · {level}" +# English: "Mezzanine · in {room}" # Source-Hash: f4ad56b4e0c014b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerMezzanineIn" msgstr "中二階 · {room}内" +# English: "Room · not enclosed" # Source-Hash: de37205633316011 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · not enclosed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerNotEnclosed" msgstr "部屋 · 囲まれていない" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.loading" msgstr "読み込み中…" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanine" msgstr "中二階" +# English: "Mezzanine name" # Source-Hash: 97de3df03c4a0be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineName" msgstr "中二階の名前" +# English: "Mezzanine names" # Source-Hash: 42cfb1bcb80d09e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineNames" msgstr "中二階の名前" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.newUnit" msgstr "新規ユニット" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.none" msgstr "なし" +# English: "Not enough headroom" # Source-Hash: e92d6108b57df9e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not enough headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.notEnoughHeadroom" msgstr "頭上空間が不足" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" # Source-Hash: 81a6770092b05d48 # Review: required -# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openingCounts" msgstr "{doors, plural, other {ドア {doorCount} 枚}} · {windows, plural, other {窓 {windowCount} 枚}}" +# English: "{area} · Close its walls to build its floor and ceiling." # Source-Hash: a8cd9507ce472a48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Close its walls to build its floor and ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openRoomArea" msgstr "{area} · 壁を閉じると床と天井が作られます。" +# English: "Over the mezzanine" # Source-Hash: f8aa48ec39a15616 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Over the mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.overTheMezzanine" msgstr "中二階の上" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.perimeter" msgstr "周長" +# English: "Pick" # Source-Hash: ed2aaf0a8d5a9b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pick" msgstr "選択" +# English: "Picking it adds a number" # Source-Hash: 7baaebbd411cc837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picking it adds a number" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pickingItAddsANumber" msgstr "選ぶと番号が付きます" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.remove" msgstr "削除" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnit" msgstr "ユニットから外す" +# English: "Remove from {unit}" # Source-Hash: a11d13b92b3cefdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from {unit}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnitNamed" msgstr "{unit}から外す" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.rename" msgstr "名前を変更" +# English: "{name} · Rename" # Source-Hash: 3562f9bea0a4a8f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{name} · Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.renameNamed" msgstr "{name} · 名前を変更" +# English: "Revert" # Source-Hash: d0d4e919bb58a977 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Revert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.revert" msgstr "元に戻す" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.room" msgstr "部屋" +# English: "Room colour — used in the plan and labels" # Source-Hash: 15ae260fae44f42f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room colour — used in the plan and labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomColourUsedInThePlan" msgstr "部屋の色 — 平面図とラベルで使用" +# English: "Room name" # Source-Hash: bf75e739dd7e5877 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomName" msgstr "部屋名" +# English: "Room names" # Source-Hash: 2ecf8c059646873b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomNames" msgstr "部屋名" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.scriptedFinish" msgstr "スクリプトの仕上げ" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.shape" msgstr "形状" +# English: "Show mezzanine names" # Source-Hash: e51144ca1759a7bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showMezzanineNames" msgstr "中二階の名前を表示" +# English: "Show room names" # Source-Hash: 875b7303c47df33f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showRoomNames" msgstr "部屋名を表示" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.stairs" msgstr "階段" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.unit" msgstr "住戸" +# English: "Use" # Source-Hash: 91436d6b374e8263 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.use" msgstr "使用" +# English: "{used} on this level" # Source-Hash: 61880aff6cd698c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{used} on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.usedOnThisLevel" msgstr "この階層に {used} 個" +# English: "{use} “{name}”" # Source-Hash: 611604740ed4549c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{use} “{name}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.useTyped" msgstr "「{name}」を{use}" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.viewCode" msgstr "コードを表示" +# English: "The script could not be read." # Source-Hash: ba15840e60ce672b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The script could not be read." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "editor.panels.scriptUnreadable" msgstr "スクリプトを読み込めませんでした。" +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.polygon" msgstr "多角形" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.rectangle" msgstr "長方形" +# English: "Delete region" # Source-Hash: 5dc88f90e6aa4129 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete region" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.deleteRegion" msgstr "領域を削除" +# English: "Paint regions" # Source-Hash: d81d8cbadf5d4f33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint regions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.paintRegions" msgstr "塗装領域" -# Review: required -# Source-Hash: 89ee318aad485f83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Adjust design" +# Source-Hash: 89ee318aad485f83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.adjustDesign" msgstr "デザインを調整" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.back" msgstr "戻る" -# Review: required -# Source-Hash: ba9b68175315e177 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit parts" +# Source-Hash: ba9b68175315e177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.editParts" msgstr "パーツを編集" -# Review: required -# Source-Hash: 07c62820f014ed6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid attachment" +# Source-Hash: 07c62820f014ed6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidAttachment" msgstr "取り付けが無効です" -# Review: required -# Source-Hash: f862aea8d7957c55 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid edit" +# Source-Hash: f862aea8d7957c55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidEdit" msgstr "編集が無効です" -# Review: required -# Source-Hash: 190cf30b8edd8dd1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid placement" +# Source-Hash: 190cf30b8edd8dd1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPlacement" msgstr "配置が無効です" -# Review: required -# Source-Hash: 45bd2ec690cc48dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid position" +# Source-Hash: 45bd2ec690cc48dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPosition" msgstr "位置が無効です" -# Review: required -# Source-Hash: faccab895717c598 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid reset" +# Source-Hash: faccab895717c598 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidReset" msgstr "リセットが無効です" -# Review: required -# Source-Hash: 28fe6e9deddde6e8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "pos" +# Source-Hash: 28fe6e9deddde6e8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.pos" msgstr "位置" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.position" msgstr "位置" -# Review: required -# Source-Hash: 3fce677837d5a876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reset this part" +# Source-Hash: 3fce677837d5a876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.resetThisPart" msgstr "このパーツをリセット" +# English: "Reload editor" # Source-Hash: 5eab376947d29a50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload editor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.reloadEditor" msgstr "エディターを再読み込み" -# Review: required -# Source-Hash: 1063f1e315258b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close riser diagram" +# Source-Hash: 1063f1e315258b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.close" msgstr "立て管図を閉じる" -# Review: required -# Source-Hash: c2d59ee62035974c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV plumbing isometric" +# Source-Hash: c2d59ee62035974c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvPlumbingIsometric" msgstr "排水・通気配管のアイソメ図" -# Review: required -# Source-Hash: 3501a7c8a93d3b19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV riser diagram" +# Source-Hash: 3501a7c8a93d3b19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvRiserDiagram" msgstr "排水・通気の立て管図" -# Review: required -# Source-Hash: 225e3e3dc3bb01ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram." +# Source-Hash: 225e3e3dc3bb01ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.noDrainWasteOrVentPipes" msgstr "排水・汚水・通気の配管はまだありません。配管を描くと立て管図が表示されます。" -# Review: required -# Source-Hash: bc8e172ba55160ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Riser Diagram" +# Source-Hash: bc8e172ba55160ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.riserDiagram" msgstr "立て管図" -# Review: required -# Source-Hash: 6a1132961f17f320 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" P-trap" +# Source-Hash: 6a1132961f17f320 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.trap" msgstr "{diameter}\" Pトラップ" -# Review: required -# Source-Hash: 04bc0909a133a7e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vent" +# Source-Hash: 04bc0909a133a7e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.vent" msgstr "換気口" -# Review: required -# Source-Hash: 02f3eef9f0fb6d9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" VTR" +# Source-Hash: 02f3eef9f0fb6d9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Vent through roof: where a plumbing vent stack exits the roof. #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.ventTermination" msgstr "{diameter}\" 屋上通気口" -# Review: required -# Source-Hash: 29d8590334492601 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waste" +# Source-Hash: 29d8590334492601 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.waste" msgstr "排水" +# English: "{current} · {distance} above {shared}" # Source-Hash: 0f8d128255bf4afa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} above {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.above" msgstr "{current} · {shared}より {distance} 上" +# English: "{current} · {distance} below {shared}" # Source-Hash: 8416f433385a2936 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} below {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.below" msgstr "{current} · {shared}より {distance} 下" +# English: "{current} · level with {shared}" # Source-Hash: 6dc18e9fada391f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · level with {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.levelWith" msgstr "{current} · {shared}と同じ高さ" -# Source-Hash: 612dcd5bf06a79c4 +# English: "Cannot read \"{input}\"" +# Source-Hash: 782bc47cb30d5c66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.cannotReadInput" +msgstr "「{input}」を読み取れません" + +# English: "Dimension value" +# Source-Hash: 70ca08939d758332 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.dimensionValue" +msgstr "寸法値" + +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.floor" +msgstr "床" + +# English: "Type a size to move the wall" +# Source-Hash: de281c37906e7d14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.typeASizeToMoveThe" +msgstr "寸法を入力して壁を移動" + # English: "Room" +# Source-Hash: 612dcd5bf06a79c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.room" msgstr "部屋" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.zone" msgstr "ゾーン" +# English: "Attic" # Source-Hash: 7da15e7c88dab485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.attic" msgstr "屋根裏" +# English: "Balcony" # Source-Hash: 52adf6ccf54fd772 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balcony" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.balcony" msgstr "バルコニー" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Basement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.basement" msgstr "地下1階" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bathroom" msgstr "浴室" +# English: "Bedroom" # Source-Hash: f25239fe3891ea8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bedroom" msgstr "寝室" +# English: "Boiler room" # Source-Hash: 77d15570e2eba719 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boiler room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.boilerRoom" msgstr "ボイラー室" +# English: "Carport" # Source-Hash: 5fbe77592f8f0ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carport" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.carport" msgstr "カーポート" +# English: "Cellar" # Source-Hash: f31dc305ce3a803a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cellar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.cellar" msgstr "地下貯蔵庫" +# English: "Closet" # Source-Hash: e9af4141e3f8bdd1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.closet" msgstr "クローゼット" +# English: "Corridor" # Source-Hash: 388530b7b9c6647d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corridor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.corridor" msgstr "廊下" +# English: "Courtyard" # Source-Hash: 4d4a23b87e911d2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Courtyard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.courtyard" msgstr "中庭" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.deck" msgstr "デッキ" +# English: "Den" # Source-Hash: 18d7f85ff03c43c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Den" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.den" msgstr "書斎" +# English: "Dining room" # Source-Hash: 4424343cad1f08b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dining room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.diningRoom" msgstr "ダイニング" +# English: "Dressing room" # Source-Hash: 5efd0185e89ee84e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dressing room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.dressingRoom" msgstr "ドレッシングルーム" +# English: "En-suite" # Source-Hash: f45768e27780dc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "En-suite" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.enSuite" msgstr "専用浴室" +# English: "Entrance" # Source-Hash: 51c664d19e5ea703 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Entrance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.entrance" msgstr "玄関" +# English: "Family room" # Source-Hash: 4b32a81a5abf006a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Family room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.familyRoom" msgstr "ファミリールーム" +# English: "Gallery" # Source-Hash: e87e6bd9a7d6d667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gallery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gallery" msgstr "ギャラリー" +# English: "Games room" # Source-Hash: cf7cc0ba06d5eea1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Games room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gamesRoom" msgstr "ゲームルーム" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garage" msgstr "ガレージ" +# English: "Garden" # Source-Hash: 5073b3c2989a626d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garden" msgstr "庭" +# English: "Garden shed" # Source-Hash: 52fe13434cdb7b27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gardenShed" msgstr "物置小屋" +# English: "Guest room" # Source-Hash: a2712156dcaec1ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guest room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.guestRoom" msgstr "客室" +# English: "Gym" # Source-Hash: a1128cf188432bb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gym" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gym" msgstr "ジム" +# English: "Hallway" # Source-Hash: 18664ee5a1abd06d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hallway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.hallway" msgstr "廊下" +# English: "Home cinema" # Source-Hash: 7dd957fc73d4d73a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Home cinema" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.homeCinema" msgstr "ホームシアター" +# English: "Kids' room" # Source-Hash: e1579515f44233ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kids' room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kidsRoom" msgstr "子ども部屋" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchen" msgstr "キッチン" +# English: "Kitchenette" # Source-Hash: 06eb4c431b9b09b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchenette" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchenette" msgstr "簡易キッチン" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.landing" msgstr "踊り場" +# English: "Laundry" # Source-Hash: 558bcafa2ec70ded # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Laundry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.laundry" msgstr "ランドリー" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.library" msgstr "ライブラリ" +# English: "Living room" # Source-Hash: 2204538bab0e77ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.livingRoom" msgstr "リビングルーム" +# English: "Loft" # Source-Hash: bb14c08f0c3db4ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.loft" msgstr "ロフト" +# English: "Lounge" # Source-Hash: a6a0e8bf816cbcb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lounge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.lounge" msgstr "ラウンジ" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mezzanine" msgstr "中二階" +# English: "Mudroom" # Source-Hash: 6b2c82e5fbccb28b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mudroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mudroom" msgstr "マッドルーム" +# English: "Nursery" # Source-Hash: e3c3b9b917744680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nursery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.nursery" msgstr "子供部屋(乳児)" +# English: "Office" # Source-Hash: c1f1fcd9cb418340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Office" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.office" msgstr "オフィス" +# English: "Pantry" # Source-Hash: 5d98d41ec10bd05e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pantry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.pantry" msgstr "パントリー" +# English: "Patio" # Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Patio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.patio" msgstr "パティオ" +# English: "Playroom" # Source-Hash: c3cfcccc167b1c19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Playroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.playroom" msgstr "プレイルーム" +# English: "Pool area" # Source-Hash: 2e6324beaed18dd5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pool area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.poolArea" msgstr "プールエリア" +# English: "Porch" # Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Porch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.porch" msgstr "ポーチ" +# English: "Powder room" # Source-Hash: 04cb81f4a4ae4ff3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Powder room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.powderRoom" msgstr "パウダールーム" +# English: "Primary bedroom" # Source-Hash: 599c44d857832cd0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Primary bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.primaryBedroom" msgstr "主寝室" +# English: "Reading nook" # Source-Hash: 9a6ac3f1cdd8de2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reading nook" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.readingNook" msgstr "読書スペース" +# English: "Bathrooms" # Source-Hash: 01f8cc22989822c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathrooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.bathrooms" msgstr "浴室" +# English: "Circulation" # Source-Hash: 9ec82561de8cb788 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Circulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.circulation" msgstr "動線" +# English: "Living" # Source-Hash: f74a48b6b49644d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.living" msgstr "リビング" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.mezzanine" msgstr "中二階" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.outdoor" msgstr "屋外" +# English: "Sleeping" # Source-Hash: 5df703d5b5e6ef7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sleeping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.sleeping" msgstr "寝室" +# English: "Utility & storage" # Source-Hash: c9d15ec19f814524 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility & storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.utility" msgstr "ユーティリティと収納" +# English: "Work & hobbies" # Source-Hash: fbe13f7ab45e4cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Work & hobbies" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.work" msgstr "仕事と趣味" +# English: "Shower room" # Source-Hash: 10ccb6591f6ad207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shower room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.showerRoom" msgstr "シャワールーム" +# English: "Stairwell" # Source-Hash: 5254168cc5712cb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairwell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.stairwell" msgstr "階段室" +# English: "Storage" # Source-Hash: d1596647cd84ade3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.storage" msgstr "収納" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Studio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.studio" msgstr "スタジオ" +# English: "Study" # Source-Hash: ebe68e83e73021ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Study" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.study" msgstr "書斎" +# English: "Sunroom" # Source-Hash: d69a0244649771ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.sunroom" msgstr "サンルーム" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.terrace" msgstr "テラス" +# English: "Toilet" # Source-Hash: a0cb3e6f5cf4fbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Toilet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.toilet" msgstr "トイレ" +# English: "Utility room" # Source-Hash: 6c01526f89f3106b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.utilityRoom" msgstr "ユーティリティ" +# English: "Veranda" # Source-Hash: fc42d89621ae7bac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veranda" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.veranda" msgstr "ベランダ" +# English: "Walk-in closet" # Source-Hash: b45d7730e896fd99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk-in closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.walkInCloset" msgstr "ウォークインクローゼット" +# English: "Workshop" # Source-Hash: 399f27253ef956cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Workshop" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.workshop" msgstr "作業場" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.settings" msgstr "設定" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.site" msgstr "敷地" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.all" msgstr "すべて" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.community" msgstr "コミュニティ" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.all" msgstr "すべて" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.community" msgstr "コミュニティ" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.library" msgstr "ライブラリ" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.mine" msgstr "マイアイテム" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch" msgstr "「{search}」の結果はありません" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.search" msgstr "検索..." +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.library" msgstr "ライブラリ" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.mine" msgstr "マイアイテム" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.noResultsForSearch" msgstr "「{search}」の結果はありません" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.search" msgstr "検索..." -# Source-Hash: 3c67cde0578ac829 +# English: "Build Tools" +# Source-Hash: fb66c49eee49a3bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools" +msgstr "建築ツール" + +# English: "Camera" +# Source-Hash: 14622745dbf93f4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera" +msgstr "カメラ" + +# English: "Direct Manipulation" +# Source-Hash: 677783232bea72a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation" +msgstr "直接操作" + +# English: "Drawing Tools" +# Source-Hash: 8e67947d43b76596 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools" +msgstr "描画ツール" + +# English: "Editor Navigation" +# Source-Hash: 8cdda30f3be2bac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation" +msgstr "エディターのナビゲーション" + +# English: "Item Placement" +# Source-Hash: 9a996e3570d615ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement" +msgstr "アイテムの配置" + +# English: "Keyboard shortcuts" +# Source-Hash: fc18926f9bd9eebd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts" +msgstr "キーボードショートカット" + +# English: "Modes & History" +# Source-Hash: cbb4c3e007d70aa0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory" +msgstr "モードと履歴" + +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection" +msgstr "選択" + +# English: "Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely." +# Source-Hash: 8a708ce860d787e8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints" +msgstr "ショートカットは状況に応じて動作が変わります。ガイド制約は既定で有効です。操作中に Shift を押し続けると自由に作成できます。" + # English: "Add level" +# Source-Hash: 3c67cde0578ac829 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addLevel" msgstr "階層を追加" +# English: "Add point" # Source-Hash: 9a56d90d461b4bca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addPoint" msgstr "点を追加" +# English: "Area:" # Source-Hash: fedccec504a9ae33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.area" msgstr "面積:" +# English: "Base elevation" # Source-Hash: 87b6880f6de7a45a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.baseElevation" msgstr "基準高さ" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.building" msgstr "建物" +# English: "Add new level" # Source-Hash: 7c90f458259d5cd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add new level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel" msgstr "新しい階層を追加" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.building" msgstr "建物" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.cameraSnapshot" msgstr "カメラスナップショット" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.capture" msgstr "キャプチャ" +# English: "Clear selection" # Source-Hash: 71f2343818c55e0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSelection" msgstr "選択を解除" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSnapshot" msgstr "スナップショットを消去" +# English: "Close walls around a space to make a room." # Source-Hash: def517c77fda49ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close walls around a space to make a room." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo" msgstr "空間を壁で囲むと部屋になります。" +# English: "Collection colour" # Source-Hash: 2b9af8da4b34cbfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection colour" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour" msgstr "コレクションの色" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collections" msgstr "コレクション" +# English: "Delete collection" # Source-Hash: 6f4751c26f12aef9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete collection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection" msgstr "コレクションを削除" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.delete" msgstr "削除" +# English: "Delete level" # Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.deleteLevel" msgstr "階層を削除" +# English: "Draw a zone" # Source-Hash: 6552e45eecc0a227 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.drawAZone" msgstr "ゾーンを描画" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicate" msgstr "複製" +# English: "Duplicate level" # Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevel" msgstr "階層を複製" +# English: "Duplicate level with options" # Source-Hash: c038947f317cabba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level with options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevelWithOptions" msgstr "オプションを指定して階層を複製" +# English: "Duplicate with options..." # Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateWithOptions" msgstr "オプションを指定して複製..." +# English: "Elements" # Source-Hash: 7c4196b9e791c195 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.elements" msgstr "要素" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.guide" msgstr "ガイド" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.hide" msgstr "非表示" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.level" msgstr "階層" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.newUnit" msgstr "新規ユニット" +# English: "No buildings yet" # Source-Hash: 0192748d50f0e5f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No buildings yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noBuildingsYet" msgstr "建物はまだありません" +# English: "No elements on this level" # Source-Hash: 1be310702a12e433 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No elements on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noElementsOnThisLevel" msgstr "この階層に要素はありません" +# English: "No levels yet" # Source-Hash: 0956af88ced36992 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No levels yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noLevelsYet" msgstr "階層はまだありません" +# English: "{count, plural, one {# object selected} other {# objects selected}}" # Source-Hash: 45c41bfb2ae19627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# object selected} other {# objects selected}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.objectsSelected" msgstr "{count, plural, other {# 個のオブジェクトを選択中}}" +# English: "Other zones" # Source-Hash: a5434e26e5cb1b26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.otherZones" msgstr "その他のゾーン" +# English: "Perimeter:" # Source-Hash: 2aa38d84079d4dba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.perimeter" msgstr "周長:" +# English: "Property Line" # Source-Hash: 4004b9a8e4f8c9d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Property Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.propertyLine" msgstr "敷地境界線" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.rooms" msgstr "部屋" +# English: "Select a level to view content" # Source-Hash: ca39c0581c7318e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view content" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.selectALevelToViewContent" msgstr "階層を選択して内容を表示" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.show" msgstr "表示" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.site" msgstr "敷地" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.takeSnapshot" msgstr "スナップショットを撮る" +# English: "The ground level cannot be deleted" # Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted" msgstr "1階の階層は削除できません" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot" msgstr "カメラスナップショット" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot" msgstr "スナップショットを消去" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.hide" msgstr "非表示" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.show" msgstr "表示" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot" msgstr "スナップショットを撮る" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot" msgstr "スナップショットを更新" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot" msgstr "スナップショットを表示" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.units" msgstr "単位" +# English: "Delete unit" # Source-Hash: 149fec3fd1f08042 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit" msgstr "ユニットを削除" +# English: "Focused" # Source-Hash: 930399d3fb247a7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.focused" msgstr "フォーカス中" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit" msgstr "ユニットから削除" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.room" msgstr "部屋" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.zone" msgstr "ゾーン" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.updateSnapshot" msgstr "スナップショットを更新" +# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" # Source-Hash: 28a12e6806c091b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadingProgress" msgstr "{assetType, select, guide {ガイドをアップロード中... {progress}%} other {3Dスキャンをアップロード中... {progress}%}}" +# English: "Upload scan/floorplan" # Source-Hash: de389a2a46d53384 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload scan/floorplan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadScanFloorplan" msgstr "3Dスキャン/平面図をアップロード" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.viewSnapshot" msgstr "スナップショットを表示" +# English: "In {unitName}" # Source-Hash: ba9f809cb032ee57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "In {unitName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname" msgstr "{unitName}内" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.actions" msgstr "操作" +# English: "Add one" # Source-Hash: fcc43f22937d1536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add one" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.addOne" msgstr "追加する" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.cameraSnapshot" msgstr "カメラスナップショット" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.clearSnapshot" msgstr "スナップショットを消去" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.delete" msgstr "削除" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.deleteWithContents" msgstr "内容ごと削除" +# English: "No zones on this level." # Source-Hash: 22ca3935e8754072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones on this level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.noZonesOnThisLevel" msgstr "この階層にゾーンはありません。" +# English: "Save to catalog" # Source-Hash: 941ea30f406dcfa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save to catalog" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.saveToCatalog" msgstr "カタログに保存" +# English: "Select a level to view and create zones" # Source-Hash: 98662dfb371cac13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view and create zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.selectALevelToViewAnd" msgstr "階層を選択してゾーンを表示・作成" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.takeSnapshot" msgstr "スナップショットを撮る" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.updateSnapshot" msgstr "スナップショットを更新" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.viewSnapshot" msgstr "スナップショットを表示" +# English: "\"{label}\" plugin crashed" # Source-Hash: 58b05af5b5724efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "\"{label}\" plugin crashed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.labelPluginCrashed" msgstr "プラグイン「{label}」がクラッシュしました" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.loading" msgstr "読み込み中…" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.plugins" msgstr "プラグイン" +# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." # Source-Hash: c6312309378aaaea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd" msgstr "このパネルでエラーが発生したため、このセッションではアンロードされました。エディターの他の部分には影響ありません。再読み込みして再試行してください。" -# Review: required -# Source-Hash: 2ab3b6983e384ecc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ceiling attachment" +# Source-Hash: 2ab3b6983e384ecc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.ceiling" msgstr "天井に取り付け" -# Review: required -# Source-Hash: 54a00bbb699e3778 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof attachment" +# Source-Hash: 54a00bbb699e3778 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.roof" msgstr "屋根に取り付け" -# Review: required -# Source-Hash: e45fee9c27f6ae69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall attachment" +# Source-Hash: e45fee9c27f6ae69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.wall" msgstr "壁に取り付け" +# English: "The editor scene failed to render" # Source-Hash: 834de1a7027c4da1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The editor scene failed to render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.theEditorSceneFailedToRender" msgstr "エディターのシーンを表示できませんでした" +# English: "A curtain wall takes no corner window" # Source-Hash: 151f7c7e4110016f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curtain wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i" msgstr "カーテンウォールにはコーナー窓を付けられません" +# English: "A curved wall takes no corner window" # Source-Hash: 97f5e044d46501fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curved wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5" msgstr "曲面の壁にはコーナー窓を付けられません" +# English: "Add a corner · click the first to close" # Source-Hash: 776d8ebece7fe1bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a corner · click the first to close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.addACornerClickTheFirst.o4r67t" msgstr "角を追加 · 最初の点をクリックして閉じる" -# Source-Hash: 6a0745dd047b149a +# English: "Add a wall · each one starts where the last ended" +# Source-Hash: d0ddef756fb49fbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.addAWallEachOneStarts.4zkf12" +msgstr "壁を追加 · 前の壁の終点から次の壁が始まる" + # English: "Another opening is in the way at the corner" +# Source-Hash: 6a0745dd047b149a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0" msgstr "角に別の開口があります" +# English: "Auto hangers" # Source-Hash: 441124187e7da2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangers.10554ka" msgstr "自動吊り金具" +# English: "Auto hangers - click or press H to toggle" # Source-Hash: 47a7f9a6a81156c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers - click or press H to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersClickOrPressH.rwa3ka" msgstr "自動吊り金具 - クリックまたは H で切り替え" +# English: "Auto hangers: Off" # Source-Hash: d7944f31cba90e26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOff.7yx01x" msgstr "自動吊り金具: オフ" +# English: "Auto hangers: On" # Source-Hash: 295882d015d08742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOn.10b1umh" msgstr "自動吊り金具: オン" +# English: "Bypass snapping" # Source-Hash: 8c039e5182b01c4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bypass snapping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.bypassSnapping.w5lmdv" msgstr "スナップを無視" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancel.ew9em3" msgstr "キャンセル" +# English: "Cancel move" # Source-Hash: 5b33b576fd30b74e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelMove.lvp31c" msgstr "移動をキャンセル" +# English: "Cancel run / exit" # Source-Hash: 136fa29b9ae419bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel run / exit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelRunExit.zqjf3h" msgstr "経路をキャンセル / 終了" +# English: "Cancel start point" # Source-Hash: 75b406824be91ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel start point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelStartPoint.1xbq2lh" msgstr "始点をキャンセル" +# English: "Close the outline" +# Source-Hash: e9b0a295122e7ad4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.closeTheOutline.f6cvzi" +msgstr "輪郭を閉じる" + +# English: "Corner window" # Source-Hash: ffa5aaa1746303c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cornerWindow.1avkzpo" msgstr "コーナー窓" +# English: "Cuts" # Source-Hash: b0afdd855a02e4a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cuts.1y0axps" msgstr "分割" +# English: "Cuts: {value}" # Source-Hash: dc9fe7601cc525f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValue.ouzhvb" msgstr "分割: {value}" +# English: "Cuts: {value}, even" # Source-Hash: a2e13bc4dc82624c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}, even" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValueEven.1ngsz3" msgstr "分割: {value}、等分" +# English: "Cycle mono / gable / butterfly" # Source-Hash: 04152b4cec726f34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle mono / gable / butterfly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleMonoGableButterfly.1poga3m" msgstr "片流れ / 切妻 / バタフライを切り替え" +# English: "Cycle snapping mode" # Source-Hash: 4fb6c83edb6652b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle snapping mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleSnappingMode.1ct0gn6" msgstr "スナップモードを切り替え" +# English: "Door style — click or press L to cycle" # Source-Hash: 5d550a3ac76279de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorStyleClickOrPressL.33pe65" msgstr "ドアのスタイル — クリックまたは L で切り替え" +# English: "Door type — click or press O to cycle" # Source-Hash: 3272a7df5d7c6a9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorTypeClickOrPressO.1hhh8gt" msgstr "ドアの種類 — クリックまたは O で切り替え" +# English: "Drop downspout from outlet" # Source-Hash: 366222b4435eefda # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drop downspout from outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.dropDownspoutFromOutlet.1k4h1m4" msgstr "排水口から竪樋を下ろす" +# English: "Duct diameter down / up" # Source-Hash: 03f306371f345007 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct diameter down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.ductDiameterDownUp.1wdz5vq" msgstr "ダクト径を小さく / 大きく" +# English: "End run / cancel" # Source-Hash: 7b4137e27f8327d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End run / cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.endRunCancel.59dh2w" msgstr "経路を終了 / キャンセル" -# Source-Hash: 938aacb119b29dc1 +# English: "End the run" +# Source-Hash: b714e91c31108a71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.endTheRun.1c1raig" +msgstr "連続描画を終了" + # English: "Exit" +# Source-Hash: 938aacb119b29dc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exit.engmg5" msgstr "終了" +# English: "Exit drawing" # Source-Hash: 7cb4e0688cabcb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit drawing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exitDrawing.9noxyb" msgstr "描画を終了" +# English: "Finish and continue" # Source-Hash: 47025a103c456125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish and continue" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishAndContinue.1gpfsks" msgstr "完了して続ける" +# English: "Finish ceiling" # Source-Hash: 000918429b74128f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishCeiling.1c1lfel" msgstr "天井を完了" +# English: "Finish grid axis" # Source-Hash: 51a80a0dba5a6f30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishGridAxis.1wvrfhr" msgstr "通り芯を完了" +# English: "Finish measurement" # Source-Hash: 87c7b98fc2e010e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMeasurement.1e4gv16" msgstr "計測を完了" +# English: "Finish multi-point witnesses" # Source-Hash: 5e1a0af7dc506826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish multi-point witnesses" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMultiPointWitnesses.8wzskz" msgstr "複数点の参照を完了" +# English: "Finish slab" # Source-Hash: 30fc0862501ba2e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishSlab.xmnm8e" msgstr "スラブを完了" -# Source-Hash: 66ed7c7598d3aa29 +# English: "Finish the run" +# Source-Hash: 54ae97157eacbc56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.finishTheRun.ayliww" +msgstr "連続描画を完了" + # English: "Flip side" +# Source-Hash: 66ed7c7598d3aa29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.flipSide.172xku9" msgstr "向きを反転" +# English: "Follow: trace a lineset" # Source-Hash: f5717f6a0905ffab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow: trace a lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.followTraceALineset.mgquwe" msgstr "追従: 冷媒配管に沿って配管" +# English: "Force place" # Source-Hash: ed7f3d8417a3e074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.forcePlace.1hq1nr" msgstr "強制配置" -# Source-Hash: cc0c14065951444d +# English: "Free angle" +# Source-Hash: 64b4ac6d3da25183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freeAngle.1ptblho" +msgstr "自由角度" + +# English: "Free place" +# Source-Hash: 446b8aac1e5e8422 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freePlace.1s1uusw" +msgstr "自由配置" + # English: "Free placement" +# Source-Hash: cc0c14065951444d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.freePlacement.1nztdac" msgstr "自由配置" +# English: "Go vertical ↕, click to place" # Source-Hash: f098a27096a9c1ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Go vertical ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.goVerticalClickToPlace.dg8g6m" msgstr "垂直に切り替え ↕、クリックで配置" +# English: "Highlight outlet" # Source-Hash: fd47f1820d8799c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Highlight outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.highlightOutlet.65hoq8" msgstr "排水口を強調" +# English: "Join as a corner window" # Source-Hash: eb90058d0f856f67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Join as a corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.joinAsACornerWindow.jvdp4j" msgstr "コーナー窓として結合" +# English: "Alt + drag" # Source-Hash: 225b3161f2e59757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alt + drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.altDrag.ai3wn9" msgstr "Alt + ドラッグ" +# English: "Backspace" # Source-Hash: dd66f883b173fc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Backspace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.backspace.qy6qx8" msgstr "Backspace" +# English: "Click" # Source-Hash: a18387bc0be54c32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.click.m8sun" msgstr "クリック" +# English: "Click again" # Source-Hash: db9e60c1d30b8499 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click again" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.clickAgain.190ws9j" msgstr "もう一度クリック" +# English: "Command" # Source-Hash: f400330b9c9c12e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Command" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.command.1j2mkia" msgstr "Command" +# English: "Control" # Source-Hash: 976c63285a510b09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Control" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.control.10fbl8e" msgstr "Ctrl" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.delete.oay2cq" msgstr "削除" +# English: "Double click" # Source-Hash: 88888c9d2719fa92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.doubleClick.vlma6o" msgstr "ダブルクリック" +# English: "Drag" # Source-Hash: dfb85b2addb7b9fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.drag.8pwtpf" msgstr "ドラッグ" +# English: "Enter" # Source-Hash: ed9140d42148c99b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.enter.1ctpui5" msgstr "Enter" +# English: "Hover" # Source-Hash: 9195fa8b9c8ebdfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hover.6xw79p" msgstr "ホバー" +# English: "Hover a duct end" # Source-Hash: 2d78441b741bc562 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a duct end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverADuctEnd.19axr3b" msgstr "ダクトの端にホバー" +# English: "Hover a pipe end" # Source-Hash: 6083405086f61c76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a pipe end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverAPipeEnd.1jbum5f" msgstr "配管の端にホバー" +# English: "Hover gutter" # Source-Hash: a5a337eb76fdb066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverGutter.1dwcsno" msgstr "雨樋にホバー" +# English: "Left click" # Source-Hash: a58533594411e088 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.leftClick.s4gwfc" msgstr "左クリック" +# English: "Middle click" # Source-Hash: bc4ab608ec3d3b4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Middle click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.middleClick.1jbaan4" msgstr "中クリック" +# English: "Right click" # Source-Hash: 6791879c651108c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rightClick.1ovld0h" msgstr "右クリック" -# Source-Hash: c836b21b42198868 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.key.room.1f3jkpm" +msgstr "部屋" + # English: "R / Shift+R" +# Source-Hash: c836b21b42198868 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rShiftR.13bnobh" msgstr "R / Shift+R" +# English: "Scroll" # Source-Hash: 9812aa06d8bb7544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scroll.ai6wr0" msgstr "スクロール" +# English: "Scroll wheel" # Source-Hash: f2174536230657c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll wheel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scrollWheel.18j0ix7" msgstr "スクロールホイール" +# English: "Space" # Source-Hash: 82dd231086d87f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Space" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.space.1hrh82t" msgstr "Space" +# English: "Tab" # Source-Hash: 9e68aa2f472cad56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.tab.1xsal0s" msgstr "Tab" +# English: "Mount: floor / ceiling / wall" # Source-Hash: 4b221b73f9496a49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount: floor / ceiling / wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.mountFloorCeilingWall.32dxpv" msgstr "取付: 床 / 天井 / 壁" +# English: "No room for the matching window on the other wall" # Source-Hash: 225edfb1e45aed95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No room for the matching window on the other wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noRoomForTheMatchingWindow.1755948" msgstr "もう一方の壁に対応する窓を置く余地がありません" -# Source-Hash: a7045d6b678bcd13 +# English: "Not a rectangle: draw this roof instead" +# Source-Hash: e367d28a655f1cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.notARectangleDrawThisRoof.100pa5g" +msgstr "長方形ではありません: 代わりにこの屋根を描画" + # English: "No wall meets here" +# Source-Hash: a7045d6b678bcd13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noWallMeetsHere.19n99yc" msgstr "ここで交わる壁はありません" +# English: "Number of cuts — scroll or click to change" # Source-Hash: a639341dc21fed2c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Number of cuts — scroll or click to change" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.numberOfCutsScrollOrClick.1kqe84b" msgstr "分割数 — スクロールまたはクリックで変更" +# English: "Pick witness point" # Source-Hash: af01bde3ae4b7e59 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick witness point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pickWitnessPoint.k3hgh1" msgstr "参照点を選ぶ" +# English: "Pipe size down / up" # Source-Hash: ecfd58576b28a9f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe size down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pipeSizeDownUp.wp04og" msgstr "管径を小さく / 大きく" -# Source-Hash: 7a1eb7b64f06bad0 +# English: "Place a corner of the foundation" +# Source-Hash: e0162c885a961919 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j" +msgstr "基礎の角を配置" + # English: "Place and continue" +# Source-Hash: 7a1eb7b64f06bad0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeAndContinue.qfwkrq" msgstr "配置して続ける" +# English: "Place block" # Source-Hash: c6cda39e2e4b5d3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBlock.1aylwrx" msgstr "ブロックを配置" +# English: "Place box vent on roof" # Source-Hash: 45a743c52a1d72de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place box vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBoxVentOnRoof.kf88e5" msgstr "屋根にボックス換気口を配置" +# English: "Place cabinet" # Source-Hash: 75142dd1d557efe2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCabinet.b0ouyk" msgstr "キャビネットを配置" +# English: "Place canopy or set the next run point" # Source-Hash: da603a713d4c9909 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place canopy or set the next run point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCanopyOrSetTheNext.12l7zf8" msgstr "キャノピーを配置、または次の点を設定" +# English: "Place chimney on roof" # Source-Hash: 8b3a30c6b7a5612d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place chimney on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeChimneyOnRoof.19dbqu" msgstr "屋根に煙突を配置" +# English: "Place column" # Source-Hash: 710d41e4e2f259fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeColumn.1w2vq86" msgstr "柱を配置" +# English: "Place cupola on roof" # Source-Hash: ee1da4e12a71d9a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cupola on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCupolaOnRoof.ofx3bn" msgstr "屋根にキューポラを配置" +# English: "Place dimension line when needed" # Source-Hash: 19c1fe8c46d213a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dimension line when needed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj" msgstr "必要に応じて寸法線を配置" +# English: "Place door on wall" # Source-Hash: 58f3e7efb6ea120b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place door on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDoorOnWall.1jwadf" msgstr "壁にドアを配置" +# English: "Place dormer on roof" # Source-Hash: de04e9d03d937c97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dormer on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDormerOnRoof.1w518a6" msgstr "屋根にドーマーを配置" +# English: "Place elevator" # Source-Hash: c581645f2ee447c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeElevator.1f65zva" msgstr "エレベーターを配置" +# English: "Place eyebrow vent on roof" # Source-Hash: 096b8d6482717358 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place eyebrow vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn" msgstr "屋根に眉型換気口を配置" +# English: "Place fitting" # Source-Hash: e8d42c1c8370773f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeFitting.1pmx3vn" msgstr "継手を配置" +# English: "Place gutter on roof eave" # Source-Hash: 2340a4b70cb0d9ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gutter on roof eave" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeGutterOnRoofEave.llwzf9" msgstr "軒に雨樋を配置" +# English: "Place item" # Source-Hash: 706a8eafb6212833 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItem.teynf" msgstr "アイテムを配置" +# English: "Place it (locked to 45°)" # Source-Hash: 1815c482e249932e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place it (locked to 45°)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItLockedTo45.16e4csk" msgstr "配置(45°に固定)" +# English: "Place measurement point" # Source-Hash: 4a14340657532838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place measurement point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeMeasurementPoint.bsatli" msgstr "計測点を配置" +# English: "Placement" # Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placement.13lcvts" msgstr "配置" +# English: "Placement: Auto" # Source-Hash: 2bb2ce6276c69b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementAuto.1caa52d" msgstr "配置: 自動" +# English: "Placement: Ground" # Source-Hash: ed78ac5354a1dcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementGround.1ruz145" msgstr "配置: 地面" +# English: "Placement: Roof" # Source-Hash: a066a2c9d5e65a1a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementRoof.renkw8" msgstr "配置: 屋根" +# English: "Placement surface - click or press P to cycle" # Source-Hash: 8f74dbe5064c4c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement surface - click or press P to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementSurfaceClickOrPressP.w56h19" msgstr "配置面 - クリックまたは P で切り替え" +# English: "Placement type" # Source-Hash: fe119955c925feaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementType.1mdpl60" msgstr "配置の種類" +# English: "Placement type — click or press I to toggle" # Source-Hash: 33043a35500168f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type — click or press I to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementTypeClickOrPressI.10cgxh2" msgstr "配置の種類 — クリックまたは I で切り替え" +# English: "Place on a fence" # Source-Hash: 9945f412e6a9a446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place on a fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeOnAFence.1dptstr" msgstr "フェンスに配置" +# English: "Place register" # Source-Hash: 6708118775a7da7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRegister.1uebycd" msgstr "吹出口を配置" +# English: "Place ridge vent on roof" # Source-Hash: ebd2731f5c79306e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place ridge vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRidgeVentOnRoof.2tpd8r" msgstr "屋根に棟換気を配置" +# English: "Place shelf" # Source-Hash: 9b3c66a867c23326 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeShelf.y4l3iw" msgstr "棚を配置" +# English: "Place skylight on roof" # Source-Hash: f11cf5c1c9fe862d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place skylight on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSkylightOnRoof.e215pe" msgstr "屋根に天窓を配置" +# English: "Place solar panel array on roof" # Source-Hash: e989a58d6709cc16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place solar panel array on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn" msgstr "屋根に太陽光パネルを配置" +# English: "Place spawn point" # Source-Hash: b67f7036d5180cf5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSpawnPoint.205o25" msgstr "スポーン地点を配置" +# English: "Place stairs" # Source-Hash: 6c7f32a17e5636c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeStairs.zwxxu2" msgstr "階段を配置" +# English: "Place trap" # Source-Hash: 641c0d3debd798a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTrap.1gxemn3" msgstr "トラップを配置" +# English: "Place turbine vent on roof" # Source-Hash: 6f0a6f6c4b7588a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place turbine vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTurbineVentOnRoof.1iqeluf" msgstr "屋根にタービン換気扇を配置" +# English: "Place unit" # Source-Hash: e65cf5edb4f51ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeUnit.8zkx4q" msgstr "ユニットを配置" +# English: "Place window on wall" # Source-Hash: 86ce1c730154f68b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place window on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWindowOnWall.1k3qnfb" msgstr "壁に窓を配置" +# English: "Place without the corner snap" # Source-Hash: 2b612180a7617901 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place without the corner snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWithoutTheCornerSnap.1nifqew" msgstr "角のスナップなしで配置" -# Source-Hash: 0dc6d121791696f7 +# English: "Point inside a room, porch or deck" +# Source-Hash: dd2ea442152dca5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.pointInsideARoomPorchOr.58w64l" +msgstr "部屋・ポーチ・デッキの内側にカーソルを合わせる" + # English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastPoint.2dmdcx" msgstr "最後の点を削除" +# English: "Remove last witness" # Source-Hash: 8b74b1d962be10fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last witness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastWitness.1b6vvzw" msgstr "最後の参照を削除" -# Source-Hash: 306d409f8a1cf480 +# English: "Ridge direction" +# Source-Hash: 1185bd2407dd9172 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirection.5m1369" +msgstr "棟の向き" + +# English: "Ridge direction - click or press R to turn it 90°" +# Source-Hash: 4352028e50450055 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk" +msgstr "棟の向き - クリックまたは R で 90° 回転" + +# English: "Ridge: Long side" +# Source-Hash: 96c889f047db9de5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeLongSide.ew0euv" +msgstr "棟: 長辺" + +# English: "Ridge: Short side" +# Source-Hash: 58143198d3f2a265 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeShortSide.gye8pb" +msgstr "棟: 短辺" + # English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate.1thqa0a" msgstr "回転" +# English: "Rotate ±45°" # Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45.9zq68u" msgstr "±45°回転" +# English: "Rotate ±45° (floor / ceiling)" # Source-Hash: be2240289aa74df5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45° (floor / ceiling)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45FloorCeiling.m9t57d" msgstr "±45°回転(床 / 天井)" +# English: "Rotate ghost ±15°" # Source-Hash: d5fb78b07c0f8138 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ghost ±15°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateGhost15.rsa52c" msgstr "プレビューを±15°回転" +# English: "Rotate or flip the run side" # Source-Hash: 912d340cd16e27fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate or flip the run side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateOrFlipTheRunSide.t5ior7" msgstr "配置側を回転または反転" -# Source-Hash: ad1dc0b5536cceef -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate roof direction 90°" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.rotateRoofDirection90.tvbji2" -msgstr "屋根の向きを90°回転" - +# English: "Rotate spawn point" # Source-Hash: fa24651e9e5f595a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateSpawnPoint.1opcuu7" msgstr "スポーン地点を回転" +# English: "Round / rectangular / oval" # Source-Hash: 2d693d87fe9b59e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round / rectangular / oval" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.roundRectangularOval.e4nh6x" msgstr "円形 / 矩形 / 楕円" -# Source-Hash: f7d2c9cdcc637c97 +# English: "Set beam start / end; the next beam starts there" +# Source-Hash: ee4ca2612f1ea085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setBeamStartEndTheNext.di3bzj" +msgstr "梁の始点 / 終点を設定、次の梁はそこから始まる" + # English: "Set fence start / end" +# Source-Hash: f7d2c9cdcc637c97 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setFenceStartEnd.1phbwsq" msgstr "フェンスの始点 / 終点を設定" -# Source-Hash: c93ae898b4d59f6f +# English: "Set footing start / end, or place a pad" +# Source-Hash: b9693a8a1f7cd6c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setFootingStartEndOrPlace.nnv1av" +msgstr "フーチングの始点 / 終点を設定、または独立フーチングを配置" + # English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setOneCornerThenTheOpposite.8tsl49" msgstr "1つの角、次に対角を設定" +# English: "Set roof footprint" # Source-Hash: 4f55171f10c6ceeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set roof footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setRoofFootprint.p50n0o" msgstr "屋根の外形を設定" -# Source-Hash: 0243859b1d64c21e -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set wall start / end" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.setWallStartEnd.1r8ubcx" -msgstr "壁の始点 / 終点を設定" - +# English: "Slope / level" # Source-Hash: c2806cce9612fa50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope / level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.slopeLevel.ftqp67" msgstr "勾配 / 水平" +# English: "Snap onto the run" # Source-Hash: 06c0ec40606db679 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap onto the run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.snapOntoTheRun.86yhmp" msgstr "経路にスナップ" +# English: "Split at the marks" # Source-Hash: e214cccbac9c4fd2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split at the marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.splitAtTheMarks.x5tpdd" msgstr "マークで分割" +# English: "Start grid axis" # Source-Hash: 94455c35bfeac6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startGridAxis.k92yza" msgstr "通り芯を開始" +# English: "Start lineset" # Source-Hash: 2edb4c29bbe7c513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLineset.23q8dh" msgstr "冷媒配管を開始" +# English: "Start liquid line" # Source-Hash: 9e50f1b30e325208 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start liquid line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLiquidLine.1rlxsgd" msgstr "液管を開始" +# English: "Start run" # Source-Hash: 877ddcb778c211cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startRun.r6fc5u" msgstr "経路を開始" +# English: "Start segment" # Source-Hash: 8a62bee64174bbc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startSegment.rolym6" msgstr "セグメントを開始" +# English: "Step back or cancel" # Source-Hash: 6cc5dc51a8e66779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step back or cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stepBackOrCancel.zbfdkn" msgstr "1つ戻る、またはキャンセル" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.style.nf3vcm" msgstr "スタイル" +# English: "Style: Barn" # Source-Hash: 81f595b1c47c0080 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleBarn.k9n1x" msgstr "スタイル: 納屋" +# English: "Style: Casement" # Source-Hash: 3a82c718fe215273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCasement.ziklec" msgstr "スタイル: 開き" +# English: "Style: Craftsman" # Source-Hash: 072e3331d1d105c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCraftsman.52e6a9" msgstr "スタイル: クラフツマン" +# English: "Style: Double-hung" # Source-Hash: b64d07557dec1714 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleDoubleHung.1lg8pkg" msgstr "スタイル: 上げ下げ(両側)" +# English: "Style: French" # Source-Hash: 55ef8761a537637f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleFrench.1ue3j64" msgstr "スタイル: フレンチ" +# English: "Style: Glass" # Source-Hash: bc8a751d87536dc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGlass.qudnb4" msgstr "スタイル: ガラス" +# English: "Style: Grid" # Source-Hash: 40c1eb0bc83e207d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGrid.1vuq4po" msgstr "スタイル: 格子" +# English: "Style: Half-louvered" # Source-Hash: f516886bab2e7f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHalfLouvered.13b1few" msgstr "スタイル: 半ルーバー" +# English: "Style: Horizontal bands" # Source-Hash: 64e4389a695371a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHorizontalBands.12ethxk" msgstr "スタイル: 横帯" +# English: "Style: Modern" # Source-Hash: ff6d226a425b00c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleModern.1fh8hhn" msgstr "スタイル: モダン" +# English: "Style: Panel" # Source-Hash: c6d17ba078ff47e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePanel.922jze" msgstr "スタイル: パネル" +# English: "Style: Paneled glass" # Source-Hash: 5e6baad0d2ce3a62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePaneledGlass.17a17en" msgstr "スタイル: パネルガラス" +# English: "Style: Shaker" # Source-Hash: 6314388e6832c252 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleShaker.49z17k" msgstr "スタイル: シェーカー" +# English: "Style: Single" # Source-Hash: a0f8e524f3ca121f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSingle.6f3cm0" msgstr "スタイル: 単体" +# English: "Style: Six-panel" # Source-Hash: 368279df3a472c86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSixPanel.u49laj" msgstr "スタイル: 6パネル" +# English: "Style: Sliding" # Source-Hash: 8f3c2571d8168fee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSliding.q81s0i" msgstr "スタイル: 引き違い" +# English: "Style: Tall grid" # Source-Hash: aa9d9eb137ca38d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTallGrid.1eotg3x" msgstr "スタイル: 縦長格子" +# English: "Style: Transom" # Source-Hash: bc164648722b63e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTransom.kb9bh6" msgstr "スタイル: 欄間" +# English: "Style: Triple-hung" # Source-Hash: 9a218f0c9ceb4697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTripleHung.pxnqi3" msgstr "スタイル: 3段上げ下げ" +# English: "Style: Wide grid" # Source-Hash: 9f6df1344bd3d694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleWideGrid.12n27et" msgstr "スタイル: 横長格子" +# English: "Switch rotation axis (Y → X → Z)" # Source-Hash: 711d4050c0cb95d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch rotation axis (Y → X → Z)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.switchRotationAxisYXZ.15lohm6" msgstr "回転軸を切り替え(Y → X → Z)" +# English: "The walls meet at too sharp or too flat an angle" # Source-Hash: d650c71e042c2598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The walls meet at too sharp or too flat an angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWallsMeetAtTooSharp.kh3wi3" msgstr "壁の交わる角度が鋭すぎるか浅すぎます" +# English: "The window waiting there is taller than this wall" # Source-Hash: ab206ac56e65f190 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window waiting there is taller than this wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4" msgstr "そこにある窓はこの壁より高いです" +# English: "Three or more walls meet here" # Source-Hash: 9c1f47138ed5c7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Three or more walls meet here" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.threeOrMoreWallsMeetHere.rgifif" msgstr "ここで3枚以上の壁が交わります" +# English: "Trace ceiling outline" # Source-Hash: c3ca56fa28e15f07 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace ceiling outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceCeilingOutline.1b639wv" msgstr "天井の輪郭をなぞる" +# English: "Trace slab outline" # Source-Hash: bdf71499d30a29de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace slab outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceSlabOutline.z0j2lc" msgstr "スラブの輪郭をなぞる" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.type.1m2zofh" msgstr "種類" +# English: "Type: Awning" # Source-Hash: b54eaaff3349dcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeAwning.1cj59al" msgstr "種類: 突き出し" +# English: "Type: Barn" # Source-Hash: f399572a27417c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBarn.sbnygq" msgstr "種類: 納屋" +# English: "Type: Bay" # Source-Hash: a1df9782200b5f1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBay.1200hc1" msgstr "種類: 出窓" +# English: "Type: Bow" # Source-Hash: ff069a390e8e3f66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBow.14s2t9p" msgstr "種類: 弓形出窓" +# English: "Type: Cabinet" # Source-Hash: bdac5f3f2ea70fff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCabinet.1r68je5" msgstr "種類: キャビネット" +# English: "Type: Casement" # Source-Hash: e0754fc03997cbf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCasement.176uo9j" msgstr "種類: 開き" +# English: "Type: Double" # Source-Hash: 47d873446d6e173b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDouble.af7w4i" msgstr "種類: 両開き" +# English: "Type: Double Hung" # Source-Hash: 5942e484513aaab5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDoubleHung.t482fu" msgstr "種類: 上げ下げ(両側)" +# English: "Type: Fixed" # Source-Hash: f9497fbfceefda27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFixed.v1cjez" msgstr "種類: はめ殺し" +# English: "Type: Folding" # Source-Hash: 0dcf07acc9cbf000 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFolding.sxxyng" msgstr "種類: 折れ戸" +# English: "Type: French" # Source-Hash: bff7331f87f279aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFrench.1y57z1b" msgstr "種類: フレンチ" +# English: "Type: Hinged" # Source-Hash: 9d2c3b82079eb769 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeHinged.1xrcfju" msgstr "種類: 開き戸" +# English: "Type: Island" # Source-Hash: b8001b90ff522838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Island" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeIsland.ufr3ce" msgstr "種類: アイランド" +# English: "Type: Louvered" # Source-Hash: 516833c5160d412e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeLouvered.oxl1cz" msgstr "種類: ルーバー" +# English: "Type: Pocket" # Source-Hash: 291272bf848ad6ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typePocket.hc65x9" msgstr "種類: 引き込み" +# English: "Type: Single Hung" # Source-Hash: 46d0e53d6ae07c72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSingleHung.p53pyl" msgstr "種類: 上げ下げ(片側)" +# English: "Type: Sliding" # Source-Hash: b11115b50df8faac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSliding.nwmr3f" msgstr "種類: 引き戸" +# English: "Vertical stack ↕, click to place" # Source-Hash: bff0c1694ee0a37e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical stack ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.verticalStackClickToPlace.ijcn4q" msgstr "立て管 ↕、クリックで配置" +# English: "Waste / vent" # Source-Hash: bac471ee67af6412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste / vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.wasteVent.1hvi3vj" msgstr "排水 / 通気" +# English: "Window style — click or press L to cycle" # Source-Hash: 1c744b7e24fc786e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowStyleClickOrPressL.ah7qm9" msgstr "窓のスタイル — クリックまたは L で切り替え" +# English: "Window type — click or press O to cycle" # Source-Hash: dd6f0ab314fea26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowTypeClickOrPressO.wv4x35" msgstr "窓の種類 — クリックまたは O で切り替え" -# Review: required -# Source-Hash: a48c4b2e4d71b08d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length: {field} m" +# Source-Hash: a48c4b2e4d71b08d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.lengthField" msgstr "長さ:{field} m" -# Review: required -# Source-Hash: e53695d6669245c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tab: direction" +# Source-Hash: e53695d6669245c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.tabDirection" msgstr "Tab:方向" -# Review: required -# Source-Hash: abb1d8c8bfb41acf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "type a length" +# Source-Hash: abb1d8c8bfb41acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.typeALength" msgstr "長さを入力" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.plugins" msgstr "プラグイン" +# English: "2D" # Source-Hash: c54f9bac358893d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "2D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view2d" msgstr "2D" +# English: "3D" # Source-Hash: f78f2fa035f25f85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view3d" msgstr "3D" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.auto" msgstr "自動" +# English: "Browse plugins" # Source-Hash: 7890c110650ad1e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.browsePlugins" msgstr "プラグインを探す" +# English: "Close {label}" # Source-Hash: 3b11e7af681a9dcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeLabel" msgstr "{label}を閉じる" +# English: "Close split (\\)" # Source-Hash: 3f00e92517e3f4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close split (\\)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeSplit" msgstr "分割を閉じる (\\)" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.ground" msgstr "地面" +# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" # Source-Hash: 0d472502a4d36e86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundSolid" msgstr "{standing, select, partly {一部地盤面下 · 地面ソリッド} below {地盤面下 · 地面ソリッド} other {地面ソリッド}}" +# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" # Source-Hash: 1b6798c307ac1b96 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundVeiled" msgstr "{standing, select, partly {一部地盤面下 · 地面を透過} below {地盤面下 · 地面を透過} other {地面を透過}}" +# English: "Keep the ground solid" # Source-Hash: ebdc976a98e82626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep the ground solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.keepTheGroundSolid" msgstr "地面をソリッドのままにする" +# English: "More views" # Source-Hash: 5f827686a006d88c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.moreViews" msgstr "その他のビュー" +# English: "Pane {pane} views" # Source-Hash: f175bbcf457ab73c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane {pane} views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.paneViews" msgstr "ペイン {pane} のビュー" +# English: "Pin {label}" # Source-Hash: 33c20cc33dce9a9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinLabel" msgstr "{label}をピン留め" +# English: "Pin to the view bar" # Source-Hash: 3f7231b1eb2f93f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin to the view bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinToTheViewBar" msgstr "ビューバーにピン留め" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.solid" +msgstr "ソリッド" + +# English: "Split (\\)" +# Source-Hash: 98f22f3f89f17686 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split" +msgstr "分割 (\\)" + +# English: "Split" +# Source-Hash: e4cd64b1680ac0d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split2" +msgstr "分割" + +# English: "Swap panes" +# Source-Hash: 15b0adc705923a27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.swapPanes" +msgstr "ペインを入れ替え" + +# English: "Unpin from the view bar" +# Source-Hash: 9781bcdb7a8549e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinFromTheViewBar" +msgstr "ビューバーからピン留めを外す" + +# English: "Unpin {label}" +# Source-Hash: f667358dec4abdea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinLabel" +msgstr "{label}のピン留めを外す" + +# English: "Veil the ground over a level below grade" +# Source-Hash: e96581f0bbf9fa91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.veilTheGroundOverALevel" +msgstr "地盤面下の階層の上で地面を透過" + +# English: "Views" +# Source-Hash: fb82f09f8ed3059e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy -msgid "editor.views.viewBar.solid" -msgstr "ソリッド" +msgid "editor.views.viewBar.views" +msgstr "ビュー" + +# English: "You can retry the scene or return home without reloading the whole app shell." +# Source-Hash: fdece5c2158dfe88 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.youCanRetryTheSceneOr" +msgstr "アプリ全体を再読み込みせずに、シーンを再試行するかホームに戻れます。" + +# English: "A framing beam drawn end to end: a porch header, a deck beam, a girder." +# Source-Hash: bafa390e07160f3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.description" +msgstr "端から端まで描く構造用の梁:ポーチの桁、デッキの梁、大梁など。" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.label" +msgstr "梁" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.actions" +msgstr "操作" + +# English: "Add posts" +# Source-Hash: 59f845b250e0df6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.addPosts" +msgstr "支柱を追加" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beam" +msgstr "梁" + +# English: "Beam {size}" +# Source-Hash: 26e977da636cc882 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beamSize" +msgstr "梁 {size}" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.delete" +msgstr "削除" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.depth" +msgstr "せい" + +# English: "Dropped" +# Source-Hash: 73694f3f45cf2ee1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.dropped" +msgstr "下がり" + +# English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.flush" +msgstr "面一" + +# English: "Follow the ceiling plane" +# Source-Hash: 0ee3edea19fd153e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.followTheCeilingPlane" +msgstr "天井面に合わせる" + +# English: "{length} long; its underside {underside} over the floor, the ceiling plane {plane}." +# Source-Hash: 193b75ac869c251d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.lengthLongItsUnderside" +msgstr "長さ {length}。下端は床から {underside}、天井面は {plane}。" + +# English: "Material" +# Source-Hash: 964b86ae1474ff98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.material" +msgstr "材料" -# Source-Hash: 98f22f3f89f17686 +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split (\\)" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split" -msgstr "分割 (\\)" +msgid "node.beam.panel.move" +msgstr "移動" -# Source-Hash: e4cd64b1680ac0d2 +# English: "1 ply" +# Source-Hash: fa876c632c284d81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split2" -msgstr "分割" +msgid "node.beam.panel.n1Ply" +msgstr "1 枚" -# Source-Hash: 15b0adc705923a27 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swap panes" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.swapPanes" -msgstr "ペインを入れ替え" +msgid "node.beam.panel.placement" +msgstr "配置" -# Source-Hash: 9781bcdb7a8549e2 +# English: "{plies} plies" +# Source-Hash: 6ac7987d650b9742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin from the view bar" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinFromTheViewBar" -msgstr "ビューバーからピン留めを外す" +msgid "node.beam.panel.pliesPlies" +msgstr "{plies} 枚" -# Source-Hash: f667358dec4abdea +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin {label}" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinLabel" -msgstr "{label}のピン留めを外す" +msgid "node.beam.panel.size" +msgstr "サイズ" -# Source-Hash: e96581f0bbf9fa91 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veil the ground over a level below grade" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.veilTheGroundOverALevel" -msgstr "地盤面下の階層の上で地面を透過" +msgid "node.beam.panel.top" +msgstr "天端" -# Source-Hash: fb82f09f8ed3059e +# English: "Top elevation" +# Source-Hash: 5b4c960036b3d05b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Views" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.views" -msgstr "ビュー" +msgid "node.beam.panel.topElevation" +msgstr "天端高さ" -# Source-Hash: fdece5c2158dfe88 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "You can retry the scene or return home without reloading the whole app shell." -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.youCanRetryTheSceneOr" -msgstr "アプリ全体を再読み込みせずに、シーンを再試行するかホームに戻れます。" +msgid "node.beam.panel.width" +msgstr "幅" +# English: "A topology-backed solid edited directly in the canvas." # Source-Hash: a273dacdf51b26f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A topology-backed solid edited directly in the canvas." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.description" msgstr "キャンバス上で直接編集できる、トポロジーを持つソリッド。" +# English: "Block" # Source-Hash: 279f456ebb2f5da0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.label" msgstr "ブロック" +# English: "Small louvered exhaust vent that sits on a roof slope." # Source-Hash: b2a371b4041b5e38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Small louvered exhaust vent that sits on a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.description" msgstr "屋根勾配に載る小型のルーバー付き排気口。" +# English: "Box Vent" # Source-Hash: f736ca24e84825c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.label" msgstr "ボックス換気口" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: c0e9837e67038e92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Flange" +# Source-Hash: c0e9837e67038e92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseFlange" msgstr "ベースフランジ" -# Review: required -# Source-Hash: fa9118f6f4f020d7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Height" +# Source-Hash: fa9118f6f4f020d7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseHeight" msgstr "ベースの高さ" -# Review: required -# Source-Hash: 0869f5a92dabb4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Inset" +# Source-Hash: 0869f5a92dabb4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseInset" msgstr "ベースの内寄せ" -# Review: required -# Source-Hash: e9a89a5e4ac8cd61 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.box" msgstr "ボックス" -# Review: required -# Source-Hash: f736ca24e84825c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Vent" +# Source-Hash: f736ca24e84825c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.boxVent" msgstr "ボックス換気口" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cap" msgstr "キャップ" -# Review: required -# Source-Hash: 0ce3b53766b550e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap Height" +# Source-Hash: 0ce3b53766b550e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.capHeight" msgstr "キャップの高さ" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cornerBevel" msgstr "角の面取り" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dome" msgstr "ドーム" -# Review: required -# Source-Hash: 1c5c81aa21193d07 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome Curvature" +# Source-Hash: 1c5c81aa21193d07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.domeCurvature" msgstr "ドームの曲率" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: 8cac057f7ae6804d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Height" +# Source-Hash: 8cac057f7ae6804d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.gapHeight" msgstr "隙間の高さ" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 8a9c2ea0783a0e53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hood Overhang" +# Source-Hash: 8a9c2ea0783a0e53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.hoodOverhang" msgstr "フードの張り出し" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: 71d82d47e11d2564 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Taper" +# Source-Hash: 71d82d47e11d2564 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.topTaper" msgstr "上部のテーパー" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.width" msgstr "幅" +# English: "A building container holding one or more levels." # Source-Hash: f49d8836ae037dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A building container holding one or more levels." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.description" msgstr "1つ以上の階層を含む建物コンテナ。" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.label" msgstr "建物" -# Review: required -# Source-Hash: 5a8274cbe3034558 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Baskets" +# Source-Hash: 5a8274cbe3034558 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.baskets" msgstr "バスケット" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottom" msgstr "下" -# Review: required -# Source-Hash: 486501f027037732 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Freezer" +# Source-Hash: 486501f027037732 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottomFreezer" msgstr "下段冷凍" -# Review: required -# Source-Hash: 375acc0016b3aa14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bowls" +# Source-Hash: 375acc0016b3aa14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bowls" msgstr "シンク槽" -# Review: required -# Source-Hash: 8449b59bf054c27d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Burners on" +# Source-Hash: 8449b59bf054c27d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.burnersOn" msgstr "点火" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.chimney" msgstr "煙突" -# Review: required -# Source-Hash: a6b76187db231ae7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Decrease {label}" +# Source-Hash: a6b76187db231ae7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.decrease" msgstr "{label}を減らす" -# Review: required -# Source-Hash: 787e10f5db16904e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door" +# Source-Hash: 787e10f5db16904e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.door" msgstr "ドア" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.double" msgstr "両開き" -# Review: required -# Source-Hash: 63fe49d73892aac8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer" +# Source-Hash: 63fe49d73892aac8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawer" msgstr "引き出し" - -# Review: required -# Source-Hash: f98242a5dacd33f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Drawers" +# Source-Hash: f98242a5dacd33f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawers" msgstr "引き出し" -# Review: required -# Source-Hash: 3af5347c94ae6443 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip hinge" +# Source-Hash: 3af5347c94ae6443 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipHinge" msgstr "丁番を反転" -# Review: required -# Source-Hash: c930f681e61c029c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip the door hinge to the opposite side" +# Source-Hash: c930f681e61c029c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipTheDoorHingeToThe" msgstr "扉の丁番を反対側にする" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.fridge" msgstr "冷蔵庫" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.gas" msgstr "ガス" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.glass" msgstr "ガラス" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.height" msgstr "高さ" -# Review: required -# Source-Hash: 6f434b84c8b59b8c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hob" +# Source-Hash: 6f434b84c8b59b8c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.hob" msgstr "コンロ" -# Review: required -# Source-Hash: d52570d41b4df776 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase {label}" +# Source-Hash: d52570d41b4df776 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.increase" msgstr "{label}を増やす" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.induction" msgstr "IH" -# Review: required -# Source-Hash: 54800340a0eeab23 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Layout" +# Source-Hash: 54800340a0eeab23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.layout" msgstr "レイアウト" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.left" msgstr "左" -# Review: required -# Source-Hash: 12ade82be3830a0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Micro" +# Source-Hash: 12ade82be3830a0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.micro" msgstr "電子レンジ" -# Review: required -# Source-Hash: a06a50e63f0c240e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move down" +# Source-Hash: a06a50e63f0c240e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveDown" msgstr "下へ移動" -# Review: required -# Source-Hash: d49ea60a2312df2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move up" +# Source-Hash: d49ea60a2312df2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveUp" msgstr "上へ移動" -# Review: required -# Source-Hash: 596023c8b9a8f2e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven" +# Source-Hash: 596023c8b9a8f2e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.oven" msgstr "オーブン" -# Review: required -# Source-Hash: 83b20797fb0a01a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pullout" +# Source-Hash: 83b20797fb0a01a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.pullout" msgstr "引き出し式" -# Review: required -# Source-Hash: 43c251f465b647ce -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rack" +# Source-Hash: 43c251f465b647ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.rack" msgstr "ラック" -# Review: required -# Source-Hash: 3c2b3026d37cbcc2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove compartment" +# Source-Hash: 3c2b3026d37cbcc2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.removeCompartment" msgstr "区画を削除" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.right" msgstr "右" -# Review: required -# Source-Hash: d7145f84f8e0e3fe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelf" +# Source-Hash: d7145f84f8e0e3fe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelf" msgstr "棚" -# Review: required -# Source-Hash: 5fbb88f1f079b6db -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves" +# Source-Hash: 5fbb88f1f079b6db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelves" msgstr "棚板" -# Review: required -# Source-Hash: 47173085127029f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves inside" +# Source-Hash: 47173085127029f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelvesInside" msgstr "内部の棚板" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.single" msgstr "片開き" -# Review: required -# Source-Hash: 522a7535fc93fb4f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink" +# Source-Hash: 522a7535fc93fb4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.sink" msgstr "シンク" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.style" msgstr "スタイル" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.surface" msgstr "面" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.top" msgstr "上" -# Review: required -# Source-Hash: eaadfd2d19b27d87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Freezer" +# Source-Hash: eaadfd2d19b27d87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topFreezer" msgstr "上段冷凍" -# Review: required -# Source-Hash: 04f3a3462ef5d247 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top grate" +# Source-Hash: 04f3a3462ef5d247 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topGrate" msgstr "上部ごとく" -# Review: required -# Source-Hash: 4a616e1d2ca55bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tray" +# Source-Hash: 4a616e1d2ca55bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.tray" msgstr "トレー" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.type" msgstr "種類" -# Review: required -# Source-Hash: 6c4de540d19316ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Washer" +# Source-Hash: 6c4de540d19316ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.washer" msgstr "食洗機" -# Review: required -# Source-Hash: 5211a0caf3518125 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wire" +# Source-Hash: 5211a0caf3518125 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.wire" msgstr "ワイヤー" +# English: "A configurable parametric base cabinet." # Source-Hash: 693a0d8896f5cc43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable parametric base cabinet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.description" msgstr "設定可能なパラメトリックのベースキャビネット。" -# Review: required -# Source-Hash: b0568ea943addd5b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom freezer" +# Source-Hash: b0568ea943addd5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.bottomFreezer" msgstr "下段冷凍" -# Review: required -# Source-Hash: da78819903ae0863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cooktop type" +# Source-Hash: da78819903ae0863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.cooktopType" msgstr "コンロの種類" -# Review: required -# Source-Hash: c608188f58ed1cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door direction" +# Source-Hash: c608188f58ed1cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.doorDirection" msgstr "扉の開き方向" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.double" msgstr "両開き" -# Review: required -# Source-Hash: f7fb8f6393cc4cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge type" +# Source-Hash: f7fb8f6393cc4cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.fridgeType" msgstr "冷蔵庫の種類" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.gas" msgstr "ガス" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.glass" msgstr "ガラス" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.induction" msgstr "IH" -# Review: required -# Source-Hash: 76df477d42cec629 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Item options" +# Source-Hash: 76df477d42cec629 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.itemOptions" msgstr "アイテムのオプション" -# Review: required -# Source-Hash: bca1b184af1c0c4b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left hinge" +# Source-Hash: bca1b184af1c0c4b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.leftHinge" msgstr "左丁番" -# Review: required -# Source-Hash: e32da7429c85f1a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right hinge" +# Source-Hash: e32da7429c85f1a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.rightHinge" msgstr "右丁番" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.single" msgstr "片開き" -# Review: required -# Source-Hash: 4bc3042055df1e85 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink layout" +# Source-Hash: 4bc3042055df1e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.sinkLayout" msgstr "シンクの構成" -# Review: required -# Source-Hash: 29cffe75281dbea6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top freezer" +# Source-Hash: 29cffe75281dbea6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.topFreezer" msgstr "上段冷凍" -# Review: required -# Source-Hash: 09c2cb2ea7b1be3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose {label}" +# Source-Hash: 09c2cb2ea7b1be3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.chooseLabel" msgstr "{label}を選択" -# Review: required -# Source-Hash: 1dd2cac6488ddc30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No kitchen items match. Try “fridge”, “sink”, or “cabinet”." +# Source-Hash: 1dd2cac6488ddc30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge" msgstr "一致するキッチンアイテムはありません。「冷蔵庫」「シンク」「キャビネット」で試してください。" -# Review: required -# Source-Hash: 6752675ca6280ddd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search fridge, sink, cabinets…" +# Source-Hash: 6752675ca6280ddd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchFridgeSinkCabinets" msgstr "冷蔵庫、シンク、キャビネットを検索…" -# Review: required -# Source-Hash: fe3c6d57a07c323d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search kitchen items" +# Source-Hash: fe3c6d57a07c323d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchKitchenItems" msgstr "キッチンアイテムを検索" -# Review: required -# Source-Hash: d44a46146db2e44c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliances & fixtures" +# Source-Hash: d44a46146db2e44c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.appliances" msgstr "家電・設備" -# Review: required -# Source-Hash: d4d3ad7c099b6513 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinets" +# Source-Hash: d4d3ad7c099b6513 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.cabinets" msgstr "キャビネット" -# Review: required -# Source-Hash: 465db52f98c2d94f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside" +# Source-Hash: 465db52f98c2d94f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBeside" msgstr "横に追加" -# Review: required -# Source-Hash: 9733e2ad85deb6d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside selected item" +# Source-Hash: 9733e2ad85deb6d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBesideSelectedItem" msgstr "選択したアイテムの横に追加" -# Review: required -# Source-Hash: 4b2f676fb4c9ec63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add {label}" +# Source-Hash: 4b2f676fb4c9ec63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addLabel" msgstr "{label}を追加" -# Review: required -# Source-Hash: 5122b8e9b4ec6a00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "An upper cabinet attached to this item will be removed to make room." +# Source-Hash: 5122b8e9b4ec6a00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis" msgstr "スペースを空けるため、このアイテムに付いている吊り戸棚は削除されます。" -# Review: required -# Source-Hash: 5737c4567e0f6863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cabinet" msgstr "キャビネット" -# Review: required -# Source-Hash: c0774c185060fc13 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cancel" msgstr "キャンセル" -# Review: required -# Source-Hash: f42912b116015f58 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "This {width} item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again." +# Source-Hash: f42912b116015f58 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cannotFit" msgstr "この {width} のアイテムは、現在のキャビネット列と壁・コーナーの制限内に収まりません。端のキャビネットを動かすか空きを作ってから、もう一度お試しください。" -# Review: required -# Source-Hash: b7eeb26a356d03c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Inserts on the left of the selected item.} other {Inserts on the right of the selected item.}}" +# Source-Hash: b7eeb26a356d03c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.insertsBeside" msgstr "{side, select, left {選択したアイテムの左側に挿入します。} other {選択したアイテムの右側に挿入します。}}" -# Review: required -# Source-Hash: f65332177316d11d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The kitchen changed. Select an item again to refresh the preview." +# Source-Hash: f65332177316d11d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenChanged" msgstr "キッチンが変更されました。プレビューを更新するにはアイテムを選び直してください。" -# Review: required -# Source-Hash: 5b6990b17111fd6b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run preview, highlighted item is the proposed change" +# Source-Hash: 5b6990b17111fd6b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs" msgstr "キッチン列のプレビュー。ハイライトされたアイテムが変更案です" -# Review: required -# Source-Hash: cb59562ba7eb1d25 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left side" +# Source-Hash: cb59562ba7eb1d25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.leftSide" msgstr "左側" -# Review: required -# Source-Hash: 766354d53f17d78c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: moves {distance}" +# Source-Hash: 766354d53f17d78c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.moved" msgstr "{name}:{distance} 移動" -# Review: required -# Source-Hash: df1083d9ffc2c59d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {width}" +# Source-Hash: df1083d9ffc2c59d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.previewItem" msgstr "{name}:{width}" -# Review: required -# Source-Hash: ada7fbece9ca9e3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace selected item" +# Source-Hash: ada7fbece9ca9e3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceSelectedItem" msgstr "選択したアイテムを置き換え" -# Review: required -# Source-Hash: ab8b52d2704537d2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replaces only the selected item." +# Source-Hash: ab8b52d2704537d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem" msgstr "選択したアイテムだけを置き換えます。" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWith" msgstr "置き換え…" -# Review: required -# Source-Hash: 948fe657641f298c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with {label}" +# Source-Hash: 948fe657641f298c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWithLabel" msgstr "{label}に置き換え" -# Review: required -# Source-Hash: 154f9fa86b7670ba -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {before} → {after}" +# Source-Hash: 154f9fa86b7670ba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.resized" msgstr "{name}:{before} → {after}" -# Review: required -# Source-Hash: 4ecc493400fb6aa2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right side" +# Source-Hash: 4ecc493400fb6aa2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.rightSide" msgstr "右側" -# Review: required -# Source-Hash: f2c30e7e21cf27e6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to update this kitchen." +# Source-Hash: f2c30e7e21cf27e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen" msgstr "このキッチンを更新できません。" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.label" msgstr "モジュール式キャビネット" -# Review: required -# Source-Hash: 740e0a4e4afa78ea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished height extends {overflow} mm above the ceiling." +# Source-Hash: 740e0a4e4afa78ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.aboveCeiling" msgstr "仕上がり高さが天井を {overflow} mm 超えています。" -# Review: required -# Source-Hash: e3b4d2ed4f6f5efb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add chimney" +# Source-Hash: e3b4d2ed4f6f5efb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addChimney" msgstr "レンジフードを追加" -# Review: required -# Source-Hash: 90bdc92c0a91d060 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add compartment" +# Source-Hash: 90bdc92c0a91d060 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addCompartment" msgstr "区画を追加" -# Review: required -# Source-Hash: ece05d9b6f4b616d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add wall cabinet" +# Source-Hash: ece05d9b6f4b616d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addWallCabinet" msgstr "吊り戸棚を追加" -# Review: required -# Source-Hash: f571cb2d06ae01ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Advanced" +# Source-Hash: f571cb2d06ae01ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.advanced" msgstr "詳細" -# Review: required -# Source-Hash: 7f4dcb90defe1ac2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance" +# Source-Hash: 7f4dcb90defe1ac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.appliance" msgstr "家電" -# Review: required -# Source-Hash: bd5e3f533412ff6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance front" +# Source-Hash: bd5e3f533412ff6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.applianceFront" msgstr "家電の前面" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.auto" msgstr "自動" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.bar" msgstr "バー" -# Review: required -# Source-Hash: 133893b177bd05d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Cabinet" +# Source-Hash: 133893b177bd05d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.baseCabinet" msgstr "ベースキャビネット" -# Review: required -# Source-Hash: d63f16df2c754126 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet construction, compartments, and opening preview." +# Source-Hash: d63f16df2c754126 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview" msgstr "キャビネットの構造、区画、開閉プレビュー。" -# Review: required -# Source-Hash: d62dc35dac835228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet panel" +# Source-Hash: d62dc35dac835228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetPanel" msgstr "キャビネットパネル" -# Review: required -# Source-Hash: 81caf4809d58b5f5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet Type" +# Source-Hash: 81caf4809d58b5f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetType" msgstr "キャビネットの種類" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.center" msgstr "中央" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.close" msgstr "閉じる" -# Review: required -# Source-Hash: cf29134333e47184 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close cabinet" +# Source-Hash: cf29134333e47184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.closeCabinet" msgstr "キャビネットを閉じる" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.commonWallCabinetHeightsUseThe" msgstr "一般的な吊り戸棚の高さです。任意の高さには下のスライダーを使います。" -# Review: required -# Source-Hash: fe9e63d5c5087386 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compartments" +# Source-Hash: fe9e63d5c5087386 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.compartments" msgstr "区画" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cutout" msgstr "切り欠き" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: 82c9ea6f403fc934 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit kitchen run" +# Source-Hash: 82c9ea6f403fc934 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.editKitchenRun" msgstr "キッチン列を編集" -# Review: required -# Source-Hash: 7e9bbeb035fc73e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fill to ceiling" +# Source-Hash: 7e9bbeb035fc73e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.fillToCeiling" msgstr "天井まで" -# Review: required -# Source-Hash: b3907da0ad234f10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.finish" msgstr "完了" -# Review: required -# Source-Hash: c1796152935221ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front construction" +# Source-Hash: c1796152935221ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.frontConstruction" msgstr "前面の構造" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.handles" msgstr "取っ手" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.heightPreset" msgstr "高さプリセット" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.hole" msgstr "穴" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.inset" msgstr "埋め込み" -# Review: required -# Source-Hash: 1176d2568d093bf1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen" +# Source-Hash: 1176d2568d093bf1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchen" msgstr "キッチン" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchenRun" msgstr "キッチン列" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.knob" msgstr "つまみ" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.mounting" msgstr "取り付け方法" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.none" msgstr "なし" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.open" msgstr "開く" -# Review: required -# Source-Hash: b8ecc16c07d66f42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open Animation" +# Source-Hash: b8ecc16c07d66f42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openAnimation" msgstr "開閉アニメーション" -# Review: required -# Source-Hash: 2d4adbbd70c3ae6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open cabinet" +# Source-Hash: 2d4adbbd70c3ae6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openCabinet" msgstr "キャビネットを開く" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.overlay" msgstr "かぶせ" -# Review: required -# Source-Hash: a35b7357943a5cae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel-ready" +# Source-Hash: a35b7357943a5cae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.panelReady" msgstr "パネル対応" -# Review: required -# Source-Hash: 3d0d4eee59177824 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Planning checks" +# Source-Hash: 3d0d4eee59177824 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.planningChecks" msgstr "プランのチェック" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.play" msgstr "再生" -# Review: required -# Source-Hash: c530f8af9dfd846f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play animation" +# Source-Hash: c530f8af9dfd846f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.playAnimation" msgstr "アニメーションを再生" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.position" msgstr "位置" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.raisedArch" msgstr "アーチ型" -# Review: required -# Source-Hash: a8639a97ab07cd59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove wall cabinet" +# Source-Hash: a8639a97ab07cd59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.removeWallCabinet" msgstr "吊り戸棚を削除" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.replaceWith" msgstr "置き換え…" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.revealGap" msgstr "目地幅" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.shaker" msgstr "シェーカー" -# Review: required -# Source-Hash: c378ed346bf83375 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared countertop, plinth, and finishes" +# Source-Hash: c378ed346bf83375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.sharedCountertopPlinthAndFinishes" msgstr "共通のカウンター・台輪・仕上げ" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.slab" msgstr "スラブ" -# Review: required -# Source-Hash: 8606fe2c60764a95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler." +# Source-Hash: 8606fe2c60764a95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.solidMatchingFrontBetweenTheCabinet" msgstr "キャビネットと壁の間をふさぐ同じ仕上げの前板です。カウンターと台輪はこのフィラーの上にも続きます。" -# Review: required -# Source-Hash: 5a4d5353cc496a63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard width" +# Source-Hash: 5a4d5353cc496a63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.standardWidth" msgstr "標準幅" -# Review: required -# Source-Hash: 4a784e3e95a08715 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop" +# Source-Hash: 4a784e3e95a08715 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stop" msgstr "停止" -# Review: required -# Source-Hash: 72c9fcd7a0eb5380 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop animation" +# Source-Hash: 72c9fcd7a0eb5380 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stopAnimation" msgstr "アニメーションを停止" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: bfd618690e0c2f24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Cabinet" +# Source-Hash: bfd618690e0c2f24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.tallCabinet" msgstr "トールキャビネット" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.top" msgstr "上" -# Review: required -# Source-Hash: d09654ebd12a538a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Cabinet" +# Source-Hash: d09654ebd12a538a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCabinet" msgstr "上部キャビネット" -# Review: required -# Source-Hash: bc19e7805b3e3703 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top / Ceiling" +# Source-Hash: bc19e7805b3e3703 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCeiling" msgstr "上部 / 天井" -# Review: required -# Source-Hash: 29ea8a68eed99925 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top depth" +# Source-Hash: 29ea8a68eed99925 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topDepth" msgstr "上部の奥行き" -# Review: required -# Source-Hash: 2d40f591e7dcbf5a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top height" +# Source-Hash: 2d40f591e7dcbf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topHeight" msgstr "上部の高さ" -# Review: required -# Source-Hash: 3b878d6015c03945 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trim / Soffit" +# Source-Hash: 3b878d6015c03945 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.trimSoffit" msgstr "幕板 / 下がり天井" -# Review: required -# Source-Hash: a6e5be20001e8d26 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Uses the cabinet front style, reveal, handle, and front material settings below." +# Source-Hash: a6e5be20001e8d26 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.usesTheCabinetFrontStyleReveal" msgstr "下の前面スタイル、目地、取っ手、前面材質の設定を使います。" -# Review: required -# Source-Hash: cd55643590c64a42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall filler" +# Source-Hash: cd55643590c64a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.wallFiller" msgstr "壁側フィラー" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.width" msgstr "幅" -# Review: required -# Source-Hash: 4ef03cfd6782316b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base cabinet" +# Source-Hash: 4ef03cfd6782316b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.baseCabinet" msgstr "ベースキャビネット" -# Review: required -# Source-Hash: d353fff221a78228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dishwasher" +# Source-Hash: d353fff221a78228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.dishwasher" msgstr "食洗機" -# Review: required -# Source-Hash: ecb596567b8b7179 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer cabinet" +# Source-Hash: ecb596567b8b7179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.drawerCabinet" msgstr "引き出しキャビネット" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.fridge" msgstr "冷蔵庫" -# Review: required -# Source-Hash: 72d587d58b645a81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas cooktop" +# Source-Hash: 72d587d58b645a81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.gasCooktop" msgstr "ガスコンロ" -# Review: required -# Source-Hash: 6b02c36e6aac2cd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction cooktop" +# Source-Hash: 6b02c36e6aac2cd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.inductionCooktop" msgstr "IHコンロ" -# Review: required -# Source-Hash: d5fcb790aef707bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven Tower" +# Source-Hash: d5fcb790aef707bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.ovenTower" msgstr "オーブントール" -# Review: required -# Source-Hash: edd4d1ab7525dcaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink cabinet" +# Source-Hash: edd4d1ab7525dcaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.sinkCabinet" msgstr "シンクキャビネット" -# Review: required -# Source-Hash: 9b7427a8954f618b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Pantry" +# Source-Hash: 9b7427a8954f618b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.tallPantry" msgstr "トールパントリー" -# Review: required -# Source-Hash: 3bd9629eb17cc572 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall cabinet" +# Source-Hash: 3bd9629eb17cc572 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.wallCabinet" msgstr "吊り戸棚" -# Review: required -# Source-Hash: 7c40d26770eede82 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add left" +# Source-Hash: 7c40d26770eede82 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addLeft" msgstr "左に追加" -# Review: required -# Source-Hash: 25082cdf77e219c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add right" +# Source-Hash: 25082cdf77e219c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addRight" msgstr "右に追加" - -# Review: required -# Source-Hash: 4b1204c1b706f618 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Align cabinets & handles" +# Source-Hash: 4b1204c1b706f618 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.alignCabinetsHandles" msgstr "キャビネットと取っ手を揃える" -# Review: required -# Source-Hash: 8c73c7a9406db5ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Applies depth, carcass, plinth, and countertop thickness to this run." +# Source-Hash: 8c73c7a9406db5ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop" msgstr "この列に奥行き・本体・台輪・カウンター厚さを適用します。" -# Review: required -# Source-Hash: ef576e6c8f9ebe5f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} module copy} other {Create {number} module copies}}" +# Source-Hash: ef576e6c8f9ebe5f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreate" msgstr "{count, plural, other {モジュールのコピーを {number} 個作成}}" -# Review: required -# Source-Hash: 0eec3888096306a1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} {name} copy} other {Create {number} {name} copies}}" +# Source-Hash: 0eec3888096306a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreateNamed" msgstr "{count, plural, other {{name} のコピーを {number} 個作成}}" -# Review: required -# Source-Hash: 8cdc0b808b551d3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a copy count from 1 to 20 and spacing from 0 to 2 m" +# Source-Hash: 8cdc0b808b551d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayInvalidOptions" msgstr "コピー数は 1〜20、間隔は 0〜2 m で選んでください" -# Review: required -# Source-Hash: cb89fe4161e87d68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "There is not enough room in this run for the requested array" +# Source-Hash: cb89fe4161e87d68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoRoom" msgstr "この列には指定した配列を置くだけのスペースがありません" -# Review: required -# Source-Hash: 075d027345689724 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a standard or appliance module as the source" +# Source-Hash: 075d027345689724 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoSource" msgstr "標準モジュールか家電モジュールを元として選んでください" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.auto" msgstr "自動" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.back" msgstr "戻る" -# Review: required -# Source-Hash: b2c3a03bf67a97a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Balances standard base cabinets while keeping appliance and corner-filler widths fixed." +# Source-Hash: b2c3a03bf67a97a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping" msgstr "家電とコーナーフィラーの幅は固定したまま、標準ベースキャビネットの幅を均等にします。" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.bar" msgstr "バー" -# Review: required -# Source-Hash: ccec5da17ca39eee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar counter" +# Source-Hash: ccec5da17ca39eee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barCounter" msgstr "バーカウンター" -# Review: required -# Source-Hash: 97c1605fff221752 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar depth" +# Source-Hash: 97c1605fff221752 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barDepth" msgstr "バーの奥行き" -# Review: required -# Source-Hash: 9514a6ddebdca34e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar height" +# Source-Hash: 9514a6ddebdca34e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barHeight" msgstr "バーの高さ" -# Review: required -# Source-Hash: 7af3b69b4b5aeb19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.carcassHeight" msgstr "本体の高さ" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.center" msgstr "中央" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.commonWallCabinetHeightsUseThe" msgstr "一般的な吊り戸棚の高さです。任意の高さには下のスライダーを使います。" -# Review: required -# Source-Hash: 88ccc87b9b45a464 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies" +# Source-Hash: 88ccc87b9b45a464 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copies" msgstr "コピー" -# Review: required -# Source-Hash: d215c489b8385d9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space." +# Source-Hash: d215c489b8385d9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure" msgstr "コピーには元のキャビネットの構造と付属の吊り戸棚が含まれます。既存のモジュールは動かず、指定した配列は列の空きスペースに収まる必要があります。" -# Review: required -# Source-Hash: 221d1b64649b08f8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner fillers cannot be used as array sources" +# Source-Hash: 221d1b64649b08f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cornerFillersCannotBeUsedAs" msgstr "コーナーフィラーは配列の元にできません" -# Review: required -# Source-Hash: d8d1bc5a0f9fa0cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop depth" +# Source-Hash: d8d1bc5a0f9fa0cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopDepth" msgstr "カウンターの奥行き" -# Review: required -# Source-Hash: 10bba4a11996c84b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop height" +# Source-Hash: 10bba4a11996c84b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopHeight" msgstr "カウンターの高さ" -# Review: required -# Source-Hash: 8abdfbc1798d9eaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Create array" +# Source-Hash: 8abdfbc1798d9eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.createArray" msgstr "配列を作成" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cutout" msgstr "切り欠き" -# Review: required -# Source-Hash: 555240ffcca44233 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete {name}" +# Source-Hash: 555240ffcca44233 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.deleteModule" msgstr "{name}を削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 76cd03300faa9b2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Direction" +# Source-Hash: 76cd03300faa9b2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.direction" msgstr "方向" -# Review: required -# Source-Hash: 5ca5884d3ff7edf4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate along run" +# Source-Hash: 5ca5884d3ff7edf4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.duplicateAlongRun" msgstr "列に沿って複製" -# Review: required -# Source-Hash: a834f687290afc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize all resizeable standard base cabinets in this run" +# Source-Hash: a834f687290afc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAll" msgstr "この列のサイズ変更できる標準ベースキャビネットをすべて均等にする" -# Review: required -# Source-Hash: 24da8a2de2c6e345 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The resizeable cabinet widths are already equal" +# Source-Hash: 24da8a2de2c6e345 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAlreadyEqual" msgstr "サイズ変更できるキャビネットの幅はすでに揃っています" -# Review: required -# Source-Hash: c38a4faef2097b73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "At least two standard base cabinets are required" +# Source-Hash: c38a4faef2097b73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNeedsTwo" msgstr "標準ベースキャビネットが 2 つ以上必要です" -# Review: required -# Source-Hash: 61f15ae24419430c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The available run width cannot satisfy the cabinet width limits" +# Source-Hash: 61f15ae24419430c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNoFit" msgstr "列の空き幅ではキャビネットの幅の制限を満たせません" -# Review: required -# Source-Hash: 7617bb3b455dcdda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize widths" +# Source-Hash: 7617bb3b455dcdda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeWidths" msgstr "幅を揃える" -# Review: required -# Source-Hash: ad8c948d883a0c9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Left: {width} mm} other {Right: {width} mm}}" +# Source-Hash: ad8c948d883a0c9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fillerPlanSide" msgstr "{side, select, left {左:{width} mm} other {右:{width} mm}}" -# Review: required -# Source-Hash: 47ccd8c8b04475cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish & align" +# Source-Hash: 47ccd8c8b04475cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishAlign" msgstr "仕上げと整列" -# Review: required -# Source-Hash: 855892e18c74153a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished back" +# Source-Hash: 855892e18c74153a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedBack" msgstr "背面仕上げ" -# Review: required -# Source-Hash: 7ab47aea30c73663 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished exposed ends" +# Source-Hash: 7ab47aea30c73663 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedExposedEnds" msgstr "見える側面の仕上げ" -# Review: required -# Source-Hash: c9356f25caf33eec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fits 50–150 mm gaps at run ends beside perpendicular walls." +# Source-Hash: c9356f25caf33eec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fits50150MmGapsAt" msgstr "直交する壁際の列端にある 50〜150 mm の隙間を埋めます。" -# Review: required -# Source-Hash: b6f541bc28e38d2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fit wall fillers" +# Source-Hash: b6f541bc28e38d2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fitWallFillers" msgstr "壁側フィラーを合わせる" -# Review: required -# Source-Hash: 2af738f010ce28f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fronts" +# Source-Hash: 2af738f010ce28f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fronts" msgstr "前面" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.handles" msgstr "取っ手" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.heightPreset" msgstr "高さプリセット" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.hole" msgstr "穴" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.inset" msgstr "埋め込み" -# Review: required -# Source-Hash: 2997c230c529590d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Island & Bar" +# Source-Hash: 2997c230c529590d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.islandBar" msgstr "アイランドとバー" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.kitchenRun" msgstr "キッチン列" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.knob" msgstr "つまみ" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.left" msgstr "左" -# Review: required -# Source-Hash: 3c88a1840043e15a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments." +# Source-Hash: 3c88a1840043e15a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd" msgstr "ベースの高さ・背面・目地・取っ手を揃え、上部キャビネットを整列します。背の高い家電とカスタム区画はそのまま残します。" -# Review: required -# Source-Hash: b112e31fe44cc94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Metric · 600 mm" +# Source-Hash: b112e31fe44cc94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.metricProfile" msgstr "メートル法 · 600 mm" -# Review: required -# Source-Hash: 4739f6315c5e8680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Module {number}" +# Source-Hash: 4739f6315c5e8680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.moduleNumber" msgstr "モジュール {number}" -# Review: required -# Source-Hash: b8801184e07e4815 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modules" +# Source-Hash: b8801184e07e4815 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.modules" msgstr "モジュール" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.mounting" msgstr "取り付け方法" -# Review: required -# Source-Hash: a3914bd96488175c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No eligible module" +# Source-Hash: a3914bd96488175c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.noEligibleModule" msgstr "対象のモジュールがありません" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.none" msgstr "なし" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.overlay" msgstr "かぶせ" -# Review: required -# Source-Hash: 49fb05dfafd6001d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth height" +# Source-Hash: 49fb05dfafd6001d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.plinthHeight" msgstr "台輪の高さ" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.position" msgstr "位置" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.raisedArch" msgstr "アーチ型" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.revealGap" msgstr "目地幅" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.right" msgstr "右" -# Review: required -# Source-Hash: e022da49ae8b4aef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Seating overhang" +# Source-Hash: e022da49ae8b4aef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.seatingOverhang" msgstr "座席側の張り出し" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.shaker" msgstr "シェーカー" -# Review: required -# Source-Hash: cba29b18ce666b0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared Plinth & Countertop" +# Source-Hash: cba29b18ce666b0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sharedPlinthCountertop" msgstr "共通の台輪とカウンター" -# Review: required -# Source-Hash: f7d577c4064c16ff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show countertop" +# Source-Hash: f7d577c4064c16ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showCountertop" msgstr "カウンターを表示" -# Review: required -# Source-Hash: 5d98e04dfd2e0a83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show plinth" +# Source-Hash: 5d98e04dfd2e0a83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showPlinth" msgstr "台輪を表示" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.slab" msgstr "スラブ" -# Review: required -# Source-Hash: 5bdc0a959203c52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Source module" +# Source-Hash: 5bdc0a959203c52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sourceModule" msgstr "元のモジュール" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.spacing" msgstr "間隔" -# Review: required -# Source-Hash: 40e3105cbe68d163 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard dimensions" +# Source-Hash: 40e3105cbe68d163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.standardDimensions" msgstr "標準寸法" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.style" msgstr "スタイル" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" # Source-Hash: e9d7ad9851d476d9 # Review: required -# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartment" msgstr "{type, select, shelf {棚板} drawer {引き出し} door {扉} oven {オーブン} microwave {電子レンジ} dishwasher {食洗機} sink {シンク} cooktop_gas {ガスコンロ} cooktop_induction {IHコンロ} pull_out_pantry {引き出し式パントリー} fridge_single {冷蔵庫} fridge_double {両開き冷蔵庫} fridge_top_freezer {上段冷凍冷蔵庫} fridge_bottom_freezer {下段冷凍冷蔵庫} hood_pyramid {ピラミッド型フード} hood_curved_glass {曲面ガラスフード} other {{raw}}}" -# Review: required -# Source-Hash: 98582c257f149d44 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} compartment} other {{number} compartments}}" +# Source-Hash: 98582c257f149d44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartments" msgstr "{count, plural, other {{number} 区画}}" -# Review: required -# Source-Hash: 65b1cbba3b92c5b2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Empty" +# Source-Hash: 65b1cbba3b92c5b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryEmpty" msgstr "空" -# Review: required -# Source-Hash: 5db8b868684cf9f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall cabinet" +# Source-Hash: 5db8b868684cf9f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryTall" msgstr "トールキャビネット" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.top" msgstr "上" -# Review: required -# Source-Hash: 88c85283afe0c0bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to finish this kitchen run." +# Source-Hash: 88c85283afe0c0bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.unableToFinish" msgstr "このキッチン列を仕上げられません。" -# Review: required -# Source-Hash: 8f668fb3e1a1696a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use {name} as array source" +# Source-Hash: 8f668fb3e1a1696a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useAsArraySource" msgstr "{name}を配列の元にする" -# Review: required -# Source-Hash: 9308a109d017dcd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use this module as the array source" +# Source-Hash: 9308a109d017dcd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useThisModuleAsTheArray" msgstr "このモジュールを配列の元にする" -# Review: required -# Source-Hash: 1ce253a4d11bfa68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "US · 24 in" +# Source-Hash: 1ce253a4d11bfa68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.usProfile" msgstr "米国 · 24 in" -# Review: required -# Source-Hash: a2d852b2c5629796 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waterfall ends" +# Source-Hash: a2d852b2c5629796 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.waterfallEnds" msgstr "ウォーターフォールエンド" +# English: "An editable module inside a modular cabinet run." # Source-Hash: ffc8d80e438f40b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An editable module inside a modular cabinet run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.description" msgstr "モジュール式キャビネットの列の中にある編集可能なモジュール。" +# English: "Cabinet Module" # Source-Hash: d5c19ec529e26a98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet Module" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.label" msgstr "キャビネットモジュール" -# Source-Hash: 4d6d5e9439f40e9b +# English: "Bottom chord" +# Source-Hash: 25be6d614ec09df9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.bottomChord" +msgstr "下弦材" + +# English: "Ceiling joists" +# Source-Hash: a0c71abc0f146a2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.ceilingJoists" +msgstr "天井根太" + +# English: "Decking" +# Source-Hash: 7d286c87375d417e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.decking" +msgstr "小屋裏床板" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.finish" +msgstr "仕上げ" + +# English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.framing" +msgstr "下地" + +# English: "Joists" +# Source-Hash: c7aa596536ceeecb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.joists" +msgstr "根太" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.layers" +msgstr "層" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.none" +msgstr "なし" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.preset" +msgstr "プリセット" + +# English: "The height is the finished underside; the layers rise from it." +# Source-Hash: 2bc7b44c217cb8fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside" +msgstr "高さは仕上げ下面の位置です。層はそこから上へ重なります。" + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.thickness" +msgstr "厚さ" + +# English: "Truss bottom chords" +# Source-Hash: 2cf52770d658d006 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.trussBottomChords" +msgstr "トラス下弦材" + # English: "A polygon-bounded ceiling surface that hosts ceiling-mounted items." +# Source-Hash: 4d6d5e9439f40e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.description" msgstr "多角形で囲まれた天井面。天井付けのアイテムを取り付けられます。" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.label" msgstr "天井" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.addHole" msgstr "開口を追加" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.area" msgstr "面積" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.auto" msgstr "自動" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.ceiling" msgstr "天井" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.customHeight" msgstr "カスタムの高さ" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.cutOpening" msgstr "開口を切る" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.done" msgstr "完了" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.followsLevel" msgstr "階層に従う" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.height" msgstr "高さ" +# English: "High (3.0m)" # Source-Hash: 703f56131ac45e60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (3.0m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high30m" msgstr "高(3.0m)" +# English: "High (9'0\")" # Source-Hash: 4ed40bdab397fe22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (9'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high90" msgstr "高(9'0\")" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.holes" msgstr "開口" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.info" msgstr "情報" +# English: "Limited by the level to {height} — raise the level height for a taller ceiling." # Source-Hash: 6925a008b1924062 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Limited by the level to {height} — raise the level height for a taller ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.limitedByLevel" msgstr "階層により {height} までに制限されます。天井を高くするには階層の高さを上げてください。" +# English: "Low (2.4m)" # Source-Hash: c7410ff087be3a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (2.4m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low24m" msgstr "低(2.4m)" +# English: "Low (8'0\")" # Source-Hash: a59425761b7c9084 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (8'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low80" msgstr "低(8'0\")" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.move" msgstr "移動" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.noHoles" msgstr "開口なし" +# English: "Standard (2.5m)" # Source-Hash: 24f5fbd158798197 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2.5m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard25m" msgstr "標準(2.5m)" +# English: "Standard (8'6\")" # Source-Hash: 73cf00cf1900c22b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (8'6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard86" msgstr "標準(8'6\")" +# English: "Taller than this level ({available} available). Raise the level height first." # Source-Hash: 065626498678bc5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Taller than this level ({available} available). Raise the level height first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.tallerThanLevel" msgstr "この階層より高すぎます(使用可能 {available})。先に階層の高さを上げてください。" +# English: "Vertical masonry stack on a roof segment." # Source-Hash: ea12ce3c2b3588e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical masonry stack on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.description" msgstr "屋根面上の組積造の煙突。" +# English: "Chimney" # Source-Hash: 95169058fec6db14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.label" msgstr "煙突" -# Review: required -# Source-Hash: dc393661f0038c2d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Above Ridge" +# Source-Hash: dc393661f0038c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.aboveRidge" msgstr "棟上の高さ" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.back" msgstr "戻る" -# Review: required -# Source-Hash: 0d42509b34b05dec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bands" +# Source-Hash: 0d42509b34b05dec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.bands" msgstr "帯" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cap" msgstr "キャップ" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimney" msgstr "煙突" -# Review: required -# Source-Hash: b619f366d2a1dfda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney Type" +# Source-Hash: b619f366d2a1dfda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimneyType" msgstr "煙突の種類" -# Review: required -# Source-Hash: 6786ac75cbe9af57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.corbeled" msgstr "持ち送り" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cornerBevel" msgstr "角の面取り" -# Review: required -# Source-Hash: 8a4beb9a57edc26e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Count" +# Source-Hash: 8a4beb9a57edc26e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.count" msgstr "数" -# Review: required -# Source-Hash: f8ba7d73d3006d57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cricket" msgstr "クリケット" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cutoutOffset" msgstr "開口のオフセット" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.diameter" msgstr "直径" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.double" msgstr "両開き" -# Review: required -# Source-Hash: 150340cd05f4a6dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extent" +# Source-Hash: 150340cd05f4a6dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.extent" msgstr "範囲" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flat" msgstr "陸屋根" -# Review: required -# Source-Hash: f817c09ef0bb6d69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flues" +# Source-Hash: f817c09ef0bb6d69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flues" msgstr "煙道" -# Review: required -# Source-Hash: a3c10560fa3c1612 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Footprint" +# Source-Hash: a3c10560fa3c1612 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.footprint" msgstr "フットプリント" -# Review: required -# Source-Hash: e360f89d3c0ec456 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front" +# Source-Hash: e360f89d3c0ec456 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.front" msgstr "前面" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 5b490bc19dddf201 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hollow Depth" +# Source-Hash: 5b490bc19dddf201 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.hollowDepth" msgstr "くぼみの深さ" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.length" msgstr "長さ" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.none" msgstr "なし" -# Review: required -# Source-Hash: 0f0e13abf9b2bb67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Offset" +# Source-Hash: 0f0e13abf9b2bb67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.offset" msgstr "オフセット" -# Review: required -# Source-Hash: 88540def2ce16ac5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.overhang" msgstr "張り出し" -# Review: required -# Source-Hash: 6ce059aee1386f72 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panels" +# Source-Hash: 6ce059aee1386f72 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.panels" msgstr "パネル" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.position" msgstr "位置" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rectangular" msgstr "長方形" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.round" msgstr "丸" -# Review: required -# Source-Hash: 8c6e3985459dacf9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.shoulder" msgstr "ショルダー" -# Review: required -# Source-Hash: 84b9195c73dd7a67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Side Margin" +# Source-Hash: 84b9195c73dd7a67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sideMargin" msgstr "側面の余白" -# Review: required -# Source-Hash: a05a390ae205fdd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple" +# Source-Hash: a05a390ae205fdd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.simple" msgstr "シンプル" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.single" msgstr "片開き" -# Review: required -# Source-Hash: f6998707123fe72d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sloped" +# Source-Hash: f6998707123fe72d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sloped" msgstr "傾斜" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.spacing" msgstr "間隔" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.square" msgstr "正方形" -# Review: required -# Source-Hash: 2c6b1cf17a973d67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.stepped" msgstr "段状" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.tapered" msgstr "テーパー" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.thickness" msgstr "厚さ" -# Review: required -# Source-Hash: 14b162761ce18fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Offset" +# Source-Hash: 14b162761ce18fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.topOffset" msgstr "上部のオフセット" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.wallThickness" msgstr "壁厚" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.width" msgstr "幅" -# Review: required -# Source-Hash: abff67400a76e5eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brick" +# Source-Hash: abff67400a76e5eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.brick" msgstr "レンガ" -# Review: required -# Source-Hash: f6cbdaa17e989b91 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modern" +# Source-Hash: f6cbdaa17e989b91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.modern" msgstr "モダン" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.round" msgstr "丸" +# English: "A parametric column with configurable cross-section, base, and capital." # Source-Hash: 6a06e628257f3393 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A parametric column with configurable cross-section, base, and capital." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.description" msgstr "断面、柱脚、柱頭を設定できるパラメトリックな柱。" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.label" msgstr "柱" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 14b21685d375581a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "A-Frame" +# Source-Hash: 14b21685d375581a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.aFrame" msgstr "A フレーム" -# Review: required -# Source-Hash: 4007d88108e25c2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply preset..." +# Source-Hash: 4007d88108e25c2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyPreset" msgstr "プリセットを適用…" -# Review: required -# Source-Hash: dbfe7c561383c964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply proportion..." +# Source-Hash: dbfe7c561383c964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyProportion" msgstr "比率を適用…" -# Review: required -# Source-Hash: 1f3fd938f0b26c9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Depth" +# Source-Hash: 1f3fd938f0b26c9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomDepth" msgstr "下部の奥行き" -# Review: required -# Source-Hash: 85576e19cd3da914 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Height" +# Source-Hash: 85576e19cd3da914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomHeight" msgstr "下部の高さ" -# Review: required -# Source-Hash: 4df0bbb04744a810 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Spread" +# Source-Hash: 4df0bbb04744a810 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomSpread" msgstr "下部の開き" -# Review: required -# Source-Hash: 67479fecb980a441 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Step Spread" +# Source-Hash: 67479fecb980a441 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomStepSpread" msgstr "下部段の広がり" -# Review: required -# Source-Hash: 6ea977c42202ca24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Tiers" +# Source-Hash: 6ea977c42202ca24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomTiers" msgstr "下部の段数" -# Review: required -# Source-Hash: 709ca788827f8a94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Width" +# Source-Hash: 709ca788827f8a94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomWidth" msgstr "下部の幅" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.boxFrame" msgstr "ボックスフレーム" -# Review: required -# Source-Hash: 381b1f481f747677 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Depth" +# Source-Hash: 381b1f481f747677 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceDepth" msgstr "ブレースの奥行き" -# Review: required -# Source-Hash: 8509b09b4ecdd6b6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Width" +# Source-Hash: 8509b09b4ecdd6b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceWidth" msgstr "ブレースの幅" -# Review: required -# Source-Hash: 08558dd786f45281 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulge" +# Source-Hash: 08558dd786f45281 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulge" msgstr "ふくらみ" -# Review: required -# Source-Hash: 663b9a9e6a469b02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulged" +# Source-Hash: 663b9a9e6a469b02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulged" msgstr "ふくらみ型" -# Review: required -# Source-Hash: 3530ac1f4046cd22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Column" +# Source-Hash: 3530ac1f4046cd22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.column" msgstr "柱" -# Review: required -# Source-Hash: ca0a856f04460bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Connector Plates" +# Source-Hash: ca0a856f04460bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.connectorPlates" msgstr "接合プレート" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: b963a08f0d540014 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edge Softness" +# Source-Hash: b963a08f0d540014 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.edgeSoftness" msgstr "角の丸み" -# Review: required -# Source-Hash: 82b3db356639ef92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ends" +# Source-Hash: 82b3db356639ef92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ends" msgstr "端部" -# Review: required -# Source-Hash: 646c3cd99df3e285 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Width" +# Source-Hash: 646c3cd99df3e285 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.endWidth" msgstr "端部の幅" -# Review: required -# Source-Hash: 02e229bca2a2b5f1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fork Spread" +# Source-Hash: 02e229bca2a2b5f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.forkSpread" msgstr "分岐の開き" -# Review: required -# Source-Hash: fdebfac05413abcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Heavy" +# Source-Hash: fdebfac05413abcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heavy" msgstr "重厚" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 957fae84ed52a2e3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to." +# Source-Hash: 957fae84ed52a2e3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heightAndFootprintAreControlledBy" msgstr "高さと設置面積は片流れ増築で決まります。ここでは回転と支柱スタイルの変更ができ、サイズは親の片流れ増築で変更します。" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.hourglass" msgstr "砂時計型" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.kBrace" msgstr "K ブレース" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 6dc190abd3f22787 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Width" +# Source-Hash: 6dc190abd3f22787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.neckWidth" msgstr "くびれの幅" -# Review: required -# Source-Hash: 7dba7ed55eeba18f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Bottom" +# Source-Hash: 7dba7ed55eeba18f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noBottom" msgstr "下部なし" -# Review: required -# Source-Hash: 682d71815bb20e94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Top" +# Source-Hash: 682d71815bb20e94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noTop" msgstr "上部なし" -# Review: required -# Source-Hash: 4da8688437f64db8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth Thickness" +# Source-Hash: 4da8688437f64db8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.plinthThickness" msgstr "台座の厚さ" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.portalFrame" msgstr "門型フレーム" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.preset" msgstr "プリセット" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.rectangular" msgstr "長方形" -# Review: required -# Source-Hash: 23b9d290b165cd49 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Pairs" +# Source-Hash: 23b9d290b165cd49 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringPairs" msgstr "リングの対数" -# Review: required -# Source-Hash: 8db82d731a19651e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Spread" +# Source-Hash: 8db82d731a19651e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringSpread" msgstr "リングの間隔" -# Review: required -# Source-Hash: bdb524a15d9e1509 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Thickness" +# Source-Hash: bdb524a15d9e1509 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringThickness" msgstr "リングの厚さ" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.round" msgstr "丸" -# Review: required -# Source-Hash: baa84a2d17095dcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round Band Width" +# Source-Hash: baa84a2d17095dcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundBandWidth" msgstr "丸帯の幅" -# Review: required -# Source-Hash: 5f1f67fb0ca59a6d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Bottom" +# Source-Hash: 5f1f67fb0ca59a6d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedBottom" msgstr "丸い下部" -# Review: required -# Source-Hash: 446816980ed0da27 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Top" +# Source-Hash: 446816980ed0da27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedTop" msgstr "丸い上部" -# Review: required -# Source-Hash: 9e554c26be80f0d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segment Twist" +# Source-Hash: 9e554c26be80f0d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.segmentTwist" msgstr "セグメントのねじれ" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaft" msgstr "シャフト" -# Review: required -# Source-Hash: 9bc3a704a9a96b3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Corner Radius" +# Source-Hash: 9bc3a704a9a96b3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftCornerRadius" msgstr "柱身の角半径" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftWidth" msgstr "柱身の幅" -# Review: required -# Source-Hash: 85ea1d3c8b749680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shape" +# Source-Hash: 85ea1d3c8b749680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shape" msgstr "形状" -# Review: required -# Source-Hash: 49a2ce13016d4073 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Short / Stout" +# Source-Hash: 49a2ce13016d4073 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shortStout" msgstr "短く太い" -# Review: required -# Source-Hash: 4637a9a61638cc66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Block Bottom" +# Source-Hash: 4637a9a61638cc66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleBlockBottom" msgstr "シンプルなブロック下部" -# Review: required -# Source-Hash: 9278f9b4a48208b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Top" +# Source-Hash: 9278f9b4a48208b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleTop" msgstr "シンプルな上部" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.singleStrut" msgstr "単一支柱" -# Review: required -# Source-Hash: d87ce271009ce063 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slender" +# Source-Hash: d87ce271009ce063 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.slender" msgstr "細身" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.square" msgstr "正方形" -# Review: required -# Source-Hash: 2dc84759d8df709d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Plinth Bottom" +# Source-Hash: 2dc84759d8df709d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.squarePlinthBottom" msgstr "角台座の下部" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.standard" msgstr "標準" -# Review: required -# Source-Hash: 01a6393135e0fb10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Bottom" +# Source-Hash: 01a6393135e0fb10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedBottom" msgstr "段付き下部" -# Review: required -# Source-Hash: 3d7e443797d4146e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Top" +# Source-Hash: 3d7e443797d4146e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedTop" msgstr "段付き上部" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.straight" msgstr "直進" -# Review: required -# Source-Hash: 6063ae2877faa35d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Taper" +# Source-Hash: 6063ae2877faa35d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.taper" msgstr "テーパー" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tapered" msgstr "テーパー" -# Review: required -# Source-Hash: 3e7ec47626ee3f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Depth" +# Source-Hash: 3e7ec47626ee3f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topDepth" msgstr "上部の奥行き" -# Review: required -# Source-Hash: 0ca9047731784f56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Height" +# Source-Hash: 0ca9047731784f56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topHeight" msgstr "上部の高さ" -# Review: required -# Source-Hash: 3d8a2be83b5b536a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Spread" +# Source-Hash: 3d8a2be83b5b536a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topSpread" msgstr "上部の開き" -# Review: required -# Source-Hash: f4ae0dc639dbc220 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Step Spread" +# Source-Hash: f4ae0dc639dbc220 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topStepSpread" msgstr "上部段の広がり" -# Review: required -# Source-Hash: 8acc4e843934b086 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Tiers" +# Source-Hash: 8acc4e843934b086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topTiers" msgstr "上部の段数" -# Review: required -# Source-Hash: a50eb6400611a57c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Width" +# Source-Hash: a50eb6400611a57c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topWidth" msgstr "上部の幅" -# Review: required -# Source-Hash: f425799b82e085ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Transform" +# Source-Hash: f425799b82e085ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.transform" msgstr "変形" -# Review: required -# Source-Hash: 952fd1ef187c6646 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle" +# Source-Hash: 952fd1ef187c6646 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.trestle" msgstr "架台" -# Review: required -# Source-Hash: 7430dad59aaf98f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod" +# Source-Hash: 7430dad59aaf98f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tripod" msgstr "三脚" -# Review: required -# Source-Hash: 0db559f22647ec73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Twist Segments" +# Source-Hash: 0db559f22647ec73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.twistSegments" msgstr "ねじれのセグメント数" -# Review: required -# Source-Hash: 8038a34ffebf4932 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical" +# Source-Hash: 8038a34ffebf4932 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vertical" msgstr "垂直" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vSupport" msgstr "V 字支柱" -# Review: required -# Source-Hash: 44dda7136b93c8ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waist" +# Source-Hash: 44dda7136b93c8ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.waist" msgstr "スラブ式" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.width" msgstr "幅" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.xBrace" msgstr "X ブレース" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.yaw" msgstr "ヨー" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ySupport" msgstr "Y 字支柱" +# English: "A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up." +# Source-Hash: 4d3a89de7073d38d # Review: required -# Source-Hash: bf2baf3d7b55f0df -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.aSizeDeckPostStandsNo" +msgstr "{size} のデッキ支柱の高さは最大 {limit} です(IRC Table R507.4、樹種と負担面積による)。確認するか、1 つ上のサイズを使ってください。" + +# English: "Custom" +# Source-Hash: 9e9715d52410c86f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.custom" +msgstr "カスタム" + +# English: "Fit to the beam over it ({height})" +# Source-Hash: 31f74f6b8e7f077e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheBeamOverIt" +msgstr "上の梁に合わせる({height})" + +# English: "Fit to the ceiling plane ({height})" +# Source-Hash: f09c0bf0ea50fc47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheCeilingPlane" +msgstr "天井面に合わせる({height})" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.height" +msgstr "高さ" + +# English: "Keep its shape and style it as a column" +# Source-Hash: b1778491849d76d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.keepItsShapeAndStyleIt" +msgstr "形状を保ったまま柱としてスタイルを設定" + +# English: "Make it a column" +# Source-Hash: 151f47e85cf7cc8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.makeItAColumn" +msgstr "柱にする" + +# English: "Post" +# Source-Hash: 5cc981e79d737694 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.post" +msgstr "支柱" + +# English: "Treated" +# Source-Hash: 64ed304f3773f93d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.treated" +msgstr "防腐処理" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.untreated" +msgstr "未処理" + # English: "A-Frame Support" +# Source-Hash: bf2baf3d7b55f0df +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.aFrameSupport" msgstr "A フレーム支柱" -# Review: required -# Source-Hash: 51cf77bb359cee43 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight Round" +# Source-Hash: 51cf77bb359cee43 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.basicPillar" msgstr "円柱" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.boxFrameSupport" msgstr "ボックスフレーム" -# Review: required -# Source-Hash: 4f22058e862d1f81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Soft Bulged" +# Source-Hash: 4f22058e862d1f81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.bulgedPillar" msgstr "ゆるいふくらみ" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.hourglassPillar" msgstr "砂時計型" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.kBraceSupport" msgstr "K ブレース" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.portalFrameSupport" msgstr "門型フレーム" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.singleStrutSupport" msgstr "単一支柱" -# Review: required -# Source-Hash: 0c13af329b9b57cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Block" +# Source-Hash: 0c13af329b9b57cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.squarePillar" msgstr "角柱" -# Review: required -# Source-Hash: 4c294bad84924665 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered Round" +# Source-Hash: 4c294bad84924665 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.taperedPillar" msgstr "テーパー円柱" -# Review: required -# Source-Hash: 360e405bfd74a379 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle Frame" +# Source-Hash: 360e405bfd74a379 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.trestleSupport" msgstr "架台フレーム" -# Review: required -# Source-Hash: 4589103259b4fc4d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod Support" +# Source-Hash: 4589103259b4fc4d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.tripodSupport" msgstr "三脚支柱" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.vFrameSupport" msgstr "V 字支柱" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.xBraceSupport" msgstr "X ブレース" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.yFrameSupport" msgstr "Y 字支柱" +# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." # Source-Hash: 895766ad1b766c2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.description" msgstr "連動する直線・曲線・円・角度・座標の平面寸法。" +# English: "Construction Dimension" # Source-Hash: 6875b60ee8a7cc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction Dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.label" msgstr "施工寸法" +# English: "Bottom Left" # Source-Hash: 61a6c6b9bc29c4f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomLeft" msgstr "左下" +# English: "Bottom Right" # Source-Hash: c5368accab136fa6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomRight" msgstr "右下" +# English: "Top Left" # Source-Hash: c8f86af3f03af6c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topLeft" msgstr "左上" +# English: "Top Right" # Source-Hash: 0ead7f1ffdbbd892 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topRight" msgstr "右上" +# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." # Source-Hash: a276c9693f01dec7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.description" msgstr "ドームまたはピラミッドの笠と任意の頂華を持つルーバー付き屋根ランタン。" +# English: "Cupola" # Source-Hash: e679dd19f96eba0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.label" msgstr "キューポラ" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: e679dd19f96eba0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cupola" +# Source-Hash: e679dd19f96eba0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.cupola" msgstr "キューポラ" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dome" msgstr "ドーム" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: 1cfe33235b30b1d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.finial" msgstr "フィニアル" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.move" msgstr "移動" -# Review: required -# Source-Hash: b5e0cb318d484d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Finial" +# Source-Hash: b5e0cb318d484d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.noFinial" msgstr "頂華なし" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 47587b825e9a696b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pyramid" +# Source-Hash: 47587b825e9a696b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.pyramid" msgstr "ピラミッド" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.width" msgstr "幅" +# English: "Auto elevator cutout" # Source-Hash: 5d4ccdf13668fab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto elevator cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoElevator" msgstr "エレベーターの自動切り抜き" +# English: "Auto stair cutout" # Source-Hash: 78aa03576e02dda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto stair cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoStair" msgstr "階段の自動切り抜き" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.opening" msgstr "開口" +# English: "Room cutout" # Source-Hash: ce37dbd7a309940b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.room" msgstr "部屋の切り抜き" -# Source-Hash: 6123b20edb9569de +# English: "Swap hinge side" +# Source-Hash: b1757d76048706b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapHingeSide" +msgstr "丁番側を入れ替え" + +# English: "Swap swing side" +# Source-Hash: d8bbb9fdf69726ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapSwingSide" +msgstr "開く向きを入れ替え" + # English: "A door cut into a wall. Animated open/close state." +# Source-Hash: 6123b20edb9569de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.description" msgstr "壁に設けたドア。開閉アニメーション付き。" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.label" msgstr "ドア" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.actions" msgstr "操作" +# English: "+ Add Segment" # Source-Hash: 8337b3213849faca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.addSegment" msgstr "+ セグメントを追加" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.all" msgstr "すべて" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.arch" msgstr "アーチ" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.archHeight" msgstr "アーチの高さ" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.auto" msgstr "自動" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.autoFollowsTheWall" msgstr "自動は壁に合わせます" +# English: "Bar Height" # Source-Hash: 387773c8aa2820a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bar Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.barHeight" msgstr "バーの高さ" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.columns" msgstr "列" +# English: "Content Padding" # Source-Hash: f533584833a4a7bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Content Padding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.contentPadding" msgstr "内側余白" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.cornerRadius" msgstr "角の半径" +# English: "Custom: the segments match no style." # Source-Hash: aa0e8e02212091e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the segments match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.customTheSegmentsMatchNoStyle" msgstr "カスタム:セグメントがどのスタイルとも一致しません。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.delete" msgstr "削除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.depth" msgstr "奥行き" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.dimensions" msgstr "寸法" +# English: "Direction" # Source-Hash: 76cd03300faa9b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.direction" msgstr "方向" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.divider" msgstr "仕切り" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.door" msgstr "ドア" +# English: "Door Closer" # Source-Hash: 4a0aa7f647e6461b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door Closer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.doorCloser" msgstr "ドアクローザー" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.duplicate" msgstr "複製" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.empty" msgstr "空" +# English: "Enable Handle" # Source-Hash: 6982301da2dbe186 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableHandle" msgstr "ハンドルを有効化" +# English: "Enable Threshold" # Source-Hash: 2e96ebf9c6e7fe49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableThreshold" msgstr "敷居を有効化" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.flipSide" msgstr "向きを反転" +# English: "Fold" # Source-Hash: 7e8fdd28f9d54d1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.fold" msgstr "折り" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frame" msgstr "枠" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frameDepthAuto" msgstr "奥行き(自動)" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.garage" msgstr "ガレージ" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.glass" msgstr "ガラス" +# English: "Handle" # Source-Hash: 41483b645efc0c65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handle" msgstr "ハンドル" +# English: "Handle Side" # Source-Hash: b4a496a904640583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handleSide" msgstr "ハンドルの側" +# English: "Hardware" # Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hardware" msgstr "金物" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.height" msgstr "高さ" +# English: "Hinges Side" # Source-Hash: 7c8f079bbefe86c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinges Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hingesSide" msgstr "蝶番の側" +# English: "Horizontal" # Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.horizontal" msgstr "水平" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.individual" msgstr "個別" +# English: "Inset" # Source-Hash: 3db1dea825785a30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inset" msgstr "埋め込み" +# English: "Inward" # Source-Hash: 1f4ad5ad3e1be222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inward" msgstr "内開き" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.left" msgstr "左" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.move" msgstr "移動" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.open" msgstr "開く" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.opening" msgstr "開口" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.openingShape" msgstr "開口の形状" +# English: "Outward" # Source-Hash: ccd8f06b8f8d27c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.outward" msgstr "外開き" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panel" msgstr "パネル" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panels" msgstr "パネル" +# English: "Panic Bar" # Source-Hash: ad56a8ab82ff1a77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panic Bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panicBar" msgstr "パニックバー" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.pocket" msgstr "引き込み戸" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.position" msgstr "位置" +# English: "Rail" # Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rail" msgstr "レール" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rect" msgstr "矩形" +# English: "- Remove" # Source-Hash: c05172113af5a2b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "- Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.remove" msgstr "- 削除" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.revealRadius" msgstr "見込みの半径" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.right" msgstr "右" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rounded" msgstr "角丸" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.segments" msgstr "セグメント" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.sizeIsLimitedToTheWall" msgstr "サイズは開口枠のクリアランスを含めて壁の範囲に制限されます。" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.slide" msgstr "スライド" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.style" msgstr "スタイル" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.swing" msgstr "開き" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.thickness" msgstr "厚さ" +# English: "Threshold" # Source-Hash: f165d6c05be2b651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.threshold" msgstr "敷居" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.topShape" msgstr "上部の形状" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.type" msgstr "種類" +# English: "Vertical" # Source-Hash: 8038a34ffebf4932 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.vertical" msgstr "垂直" +# English: "wall" # Source-Hash: 63d6b766151965f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.wall" msgstr "壁" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.width" msgstr "幅" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.barn" msgstr "納屋ドア" +# English: "Craftsman" # Source-Hash: f747fbe71717772e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.craftsman" msgstr "クラフツマン" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.french" msgstr "フレンチドア" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.glass" msgstr "ガラス" +# English: "Half-louvered" # Source-Hash: 6ab8b98b38eb2e65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.halfLouvered" msgstr "半ルーバー" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.modern" msgstr "モダン" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.panel" msgstr "パネル" +# English: "Paneled glass" # Source-Hash: ac42c328610a0edd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.paneledGlass" msgstr "パネルガラス" +# English: "Shaker" # Source-Hash: 5b68e435b7210876 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.shaker" msgstr "シェーカー" +# English: "Six-panel" # Source-Hash: 9ad6ac42b4152b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.sixPanel" msgstr "6枚パネル" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.barn" msgstr "納屋ドア" +# English: "Double" # Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.double" msgstr "両開き" +# English: "Folding" # Source-Hash: 29ff60e662b789fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.folding" msgstr "折れ戸" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.french" msgstr "フレンチドア" +# English: "Roll-up" # Source-Hash: c86309fefe89699b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roll-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageRollup" msgstr "巻き上げ" +# English: "Sectional" # Source-Hash: fb6a868430547544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sectional" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageSectional" msgstr "セクショナル" +# English: "Tilt-up" # Source-Hash: 0b5eb64b569db837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageTiltup" msgstr "跳ね上げ" +# English: "Hinged" # Source-Hash: e744be60baad6abc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.hinged" msgstr "開き戸" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.pocket" msgstr "引き込み戸" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.sliding" msgstr "引き戸" +# English: "House-shaped protrusion on a roof segment." # Source-Hash: 387e57242c8d110a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "House-shaped protrusion on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.description" msgstr "屋根面の家形の突出部。" +# English: "Dormer" # Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.label" msgstr "ドーマー" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: 6b6f8068bbc4c902 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dormer" msgstr "ドーマー" -# Review: required -# Source-Hash: 6949d667043bee0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dutch" +# Source-Hash: 6949d667043bee0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dutch" msgstr "入母屋" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.flat" msgstr "陸屋根" -# Review: required -# Source-Hash: deaffa095d126dfe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gable" +# Source-Hash: deaffa095d126dfe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gable" msgstr "切妻" -# Review: required -# Source-Hash: 7f53d9ffca39984a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gambrel" msgstr "腰折れ" -# Review: required -# Source-Hash: 9eda0ea8d447066d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hip" +# Source-Hash: 9eda0ea8d447066d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.hip" msgstr "寄棟" -# Review: required -# Source-Hash: 7f7bb0e875af3260 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.mansard" msgstr "マンサード" -# Review: required -# Source-Hash: 53af9efc7eca6594 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Direction" +# Source-Hash: 53af9efc7eca6594 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchDirection" msgstr "勾配の向き" -# Review: required -# Source-Hash: 6c39ef7125494c4a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Rise" +# Source-Hash: 6c39ef7125494c4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchRise" msgstr "勾配の立ち上がり" -# Review: required -# Source-Hash: 96336d4b0820b977 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Back" +# Source-Hash: 96336d4b0820b977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseBack" msgstr "後ろ上がり" -# Review: required -# Source-Hash: ee3d45f8b756d029 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Front" +# Source-Hash: ee3d45f8b756d029 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseFront" msgstr "前上がり" -# Review: required -# Source-Hash: 5e0622f3b09c9326 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Height" +# Source-Hash: 5e0622f3b09c9326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofHeight" msgstr "屋根の高さ" -# Review: required -# Source-Hash: 19ab9527ae7889f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Type" +# Source-Hash: 19ab9527ae7889f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofType" msgstr "屋根の種類" -# Review: required -# Source-Hash: dba6a7b56cf7083f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Section" +# Source-Hash: dba6a7b56cf7083f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.section" msgstr "セクション" -# Review: required -# Source-Hash: c8d0df27faa3feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shed" +# Source-Hash: c8d0df27faa3feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.shed" msgstr "片流れ" - -# Review: required -# Source-Hash: e0f973edce3e4ca8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Wall Height" +# Source-Hash: e0f973edce3e4ca8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.wallHeight" msgstr "壁の高さ" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.width" msgstr "幅" -# Review: required -# Source-Hash: 0b1d1e9a36456a50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows" +# Source-Hash: 0b1d1e9a36456a50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.windows" msgstr "窓" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.actions" msgstr "操作" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.delete" msgstr "削除" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.duplicate" msgstr "複製" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.move" msgstr "移動" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.rotation" msgstr "回転" -# Review: required -# Source-Hash: 7b19d21b9ad868c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Window" +# Source-Hash: 7b19d21b9ad868c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.addWindow" msgstr "窓を追加" -# Review: required -# Source-Hash: 70d7cc5a6422feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit {name}" +# Source-Hash: 70d7cc5a6422feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editNamed" msgstr "{name}を編集" -# Review: required -# Source-Hash: b6648f023e6c83dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit window" +# Source-Hash: b6648f023e6c83dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editWindow" msgstr "窓を編集" -# Review: required -# Source-Hash: 4649baf9753aeb74 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase the dormer width to add another window." +# Source-Hash: 4649baf9753aeb74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd" msgstr "窓をもう 1 つ追加するにはドーマーの幅を広げてください。" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.move" msgstr "移動" -# Review: required -# Source-Hash: c24226954309d83e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move {name}" +# Source-Hash: c24226954309d83e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveNamed" msgstr "{name}を移動" -# Review: required -# Source-Hash: f07faaf0d5e43eef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move window" +# Source-Hash: f07faaf0d5e43eef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveWindow" msgstr "窓を移動" -# Review: required -# Source-Hash: d2816490ea93d363 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No windows" +# Source-Hash: d2816490ea93d363 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.noWindows" msgstr "窓はありません" -# Review: required -# Source-Hash: 5cbb24f8b1ce7fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Window {number}" +# Source-Hash: 5cbb24f8b1ce7fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowNumber" msgstr "窓 {number}" -# Review: required -# Source-Hash: 9ca7d951240907cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows ({length})" +# Source-Hash: 9ca7d951240907cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowsLength" msgstr "窓({length})" -# Review: required -# Source-Hash: f975c4759607806a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{face, select, back {Back} left {Left} right {Right} other {Front}} · {width} × {height} m" +# Source-Hash: f975c4759607806a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowSummary" msgstr "{face, select, back {背面} left {左面} right {右面} other {正面}} · {width} × {height} m" +# English: "Vertical drop pipe from a gutter outlet to the ground." # Source-Hash: b46392d2ea865760 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical drop pipe from a gutter outlet to the ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.description" msgstr "雨樋の排水口から地面までの竪樋。" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/downspout/inspector-editors.tsx #, fuzzy msgid "node.downspout.inspectorEditors.position" msgstr "位置" +# English: "Downspout" # Source-Hash: 1a1ec08a311f1b35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.label" msgstr "竪樋" +# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." # Source-Hash: 29eadb8c84ae9632 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.description" msgstr "ダクトをつなぐエルボ、T、レデューサー、角丸変換。" -# Review: required -# Source-Hash: 7323a0fc9efb077f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap W/H" +# Source-Hash: 7323a0fc9efb077f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWH" msgstr "幅/高さを入れ替え" -# Review: required -# Source-Hash: 064b36c4fc5deb87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap width and height" +# Source-Hash: 064b36c4fc5deb87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWidthAndHeight" msgstr "幅と高さを入れ替え" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.label" msgstr "ダクト継手" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis" msgstr "軸" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis2" msgstr "⌥ 軸" -# Review: required -# Source-Hash: 880b34f6fc0ae854 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hover a duct face that fits the door" +# Source-Hash: 880b34f6fc0ae854 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.hoverADuctFaceThatFits" msgstr "点検口が収まるダクト面にカーソルを合わせてください" -# Review: required -# Source-Hash: 706f5b43a1d4a84a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Place access door" +# Source-Hash: 706f5b43a1d4a84a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.placeAccessDoor" msgstr "点検口を配置" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.rTRotate" msgstr "R/T で回転" +# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." # Source-Hash: 09eb45a9b35d1a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.description" msgstr "空調ダクト — 円形・矩形・扁平楕円断面のポリライン。" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.label" msgstr "ダクト" +# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." # Source-Hash: 33181d0f42421013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.description" msgstr "吹出口 — 給気口、天井ディフューザー、還気グリル。ダクトはそのカラーで終わります。" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.label" msgstr "吹出口" -# Review: required -# Source-Hash: 83c665eafcf43a68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{mount, select, floor {Mount Floor} ceiling {Mount Ceiling} wall {Mount Wall} other {Mount {label}}}" +# Source-Hash: 83c665eafcf43a68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mount" msgstr "{mount, select, floor {取付:床} ceiling {取付:天井} wall {取付:壁} other {取付:{label}}}" -# Review: required -# Source-Hash: f067dd52ea051385 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "M surface" +# Source-Hash: f067dd52ea051385 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Key hint: pressing M switches the mounting surface. #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mSurface" msgstr "M 面" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.rTRotate" msgstr "R/T で回転" -# Review: required -# Source-Hash: 98446855908ee71d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Snapped to duct" +# Source-Hash: 98446855908ee71d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.snappedToDuct" msgstr "ダクトにスナップ" +# English: "A multi-level elevator shaft with configurable openings per level." # Source-Hash: d0afa386e65cd3a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A multi-level elevator shaft with configurable openings per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.description" msgstr "階層ごとに開口を設定できる複数階のエレベーターシャフト。" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.label" msgstr "エレベーター" -# Review: required -# Source-Hash: 7e6d5f138e315bdb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Access" +# Source-Hash: 7e6d5f138e315bdb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.access" msgstr "アクセス" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: fdf7108a011bd94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab" +# Source-Hash: fdf7108a011bd94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cab" msgstr "かご" -# Review: required -# Source-Hash: 2d0ab44f3820f22c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab Height" +# Source-Hash: 2d0ab44f3820f22c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cabHeight" msgstr "かごの高さ" -# Review: required -# Source-Hash: 65f410261609060b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center opening" +# Source-Hash: 65f410261609060b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.centerOpening" msgstr "中央開き" -# Review: required -# Source-Hash: c5433f4d3ea8a8a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Default Floor" +# Source-Hash: c5433f4d3ea8a8a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.defaultFloor" msgstr "既定の階" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 748877ae309c313c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Destination" +# Source-Hash: 748877ae309c313c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.destination" msgstr "接続先" -# Review: required -# Source-Hash: 3bff69bd74544339 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Disabled" +# Source-Hash: 3bff69bd74544339 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.disabled" msgstr "無効" -# Review: required -# Source-Hash: d8baf402fa9c52f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Height" +# Source-Hash: d8baf402fa9c52f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorHeight" msgstr "扉の高さ" -# Review: required -# Source-Hash: 20f60b98de67849b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Doors" +# Source-Hash: 20f60b98de67849b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doors" msgstr "ドア" -# Review: required -# Source-Hash: eb0d4023a513b16c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Time" +# Source-Hash: eb0d4023a513b16c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorTime" msgstr "扉の開閉時間" -# Review: required -# Source-Hash: b3e001acdb1f869d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Type" +# Source-Hash: b3e001acdb1f869d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorType" msgstr "扉の種類" -# Review: required -# Source-Hash: c6203a69fdf44065 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Width" +# Source-Hash: c6203a69fdf44065 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorWidth" msgstr "扉の幅" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: eb3abe5340b26138 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dwell" +# Source-Hash: eb3abe5340b26138 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.dwell" msgstr "停止時間" -# Review: required -# Source-Hash: ab5cbab7be160a1c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Elevator" +# Source-Hash: ab5cbab7be160a1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.elevator" msgstr "エレベーター" -# Review: required -# Source-Hash: 702140636ff3977a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "From" +# Source-Hash: 702140636ff3977a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.from" msgstr "出発階" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glass" msgstr "ガラス" -# Review: required -# Source-Hash: 88810658a2b97bc6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glassFrame" msgstr "ガラス枠" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.motion" msgstr "動き" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 825ac8855353a4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Style" +# Source-Hash: 825ac8855353a4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.openingStyle" msgstr "開き方" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: f9a95d8407fafda4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.segmentedPanel" msgstr "分割パネル" -# Review: required -# Source-Hash: aa7b49b13f96057f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Service" +# Source-Hash: aa7b49b13f96057f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.service" msgstr "サービス" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaft" msgstr "シャフト" -# Review: required -# Source-Hash: 6605ade39317cd42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Depth" +# Source-Hash: 6605ade39317cd42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftDepth" msgstr "昇降路の奥行き" -# Review: required -# Source-Hash: b28db5a3a48fe52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Style" +# Source-Hash: b28db5a3a48fe52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftStyle" msgstr "昇降路のスタイル" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftWidth" msgstr "昇降路の幅" -# Review: required -# Source-Hash: aee3b2bd78c87d0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single left" +# Source-Hash: aee3b2bd78c87d0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleLeft" msgstr "片開き(左)" -# Review: required -# Source-Hash: cc43ffbef4ac7ae0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleRight" msgstr "片開き(右)" -# Review: required -# Source-Hash: ef3e765e692faf4c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solid" msgstr "ソリッド" -# Review: required -# Source-Hash: 8008b6f17089beaf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid panel" +# Source-Hash: 8008b6f17089beaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solidPanel" msgstr "不透明パネル" -# Review: required -# Source-Hash: 3b006be0b1f0493f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Speed" +# Source-Hash: 3b006be0b1f0493f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.speed" msgstr "速度" -# Review: required -# Source-Hash: 6d37a1679fa3708e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop {stopOrder}" +# Source-Hash: 6d37a1679fa3708e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.stopStoporder" msgstr "停止階 {stopOrder}" -# Review: required -# Source-Hash: b3214a20e45a7aed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "To" +# Source-Hash: b3214a20e45a7aed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.to" msgstr "終点" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.wallThickness" msgstr "壁厚" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.width" msgstr "幅" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.yaw" msgstr "ヨー" +# English: "Low curved lens-shaped roof vent with a louvered front." # Source-Hash: 7b22c91cf520e161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low curved lens-shaped roof vent with a louvered front." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.description" msgstr "正面にルーバーのある、低く湾曲したレンズ形の屋根換気口。" +# English: "Eyebrow Vent" # Source-Hash: 654b29319185ac66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.label" msgstr "眉型換気口" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: abc97377800a8333 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back height" +# Source-Hash: abc97377800a8333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.backHeight" msgstr "背面の高さ" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: 654b29319185ac66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Eyebrow Vent" +# Source-Hash: 654b29319185ac66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.eyebrowVent" msgstr "眉型換気口" -# Review: required -# Source-Hash: a2d107a5deaa01f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Half-round" +# Source-Hash: a2d107a5deaa01f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.halfRound" msgstr "半円" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 318192f27a774fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Louvers" +# Source-Hash: 318192f27a774fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.louvers" msgstr "ルーバー" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: e16f85e4bebfefb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.scoop" msgstr "スクープ" -# Review: required -# Source-Hash: ec53d40877e11b62 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slant-box" +# Source-Hash: ec53d40877e11b62 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.slantBox" msgstr "傾斜ボックス" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.width" msgstr "幅" +# English: "A straight or curved fence segment with configurable posts and infill." # Source-Hash: 20032fe9430f610d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved fence segment with configurable posts and infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.description" msgstr "支柱と面材を設定できる直線または曲線のフェンス区間。" -# Review: required -# Source-Hash: 6f84b85097bc1ffc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add point after" +# Source-Hash: 6f84b85097bc1ffc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.addPointAfter" msgstr "後ろに点を追加" -# Review: required -# Source-Hash: 1a15ab3101e2d166 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a slab or shaped surface on this level." +# Source-Hash: 1a15ab3101e2d166 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASlabOrShapedSurface" msgstr "この階のスラブか成形面を選んでください。" -# Review: required -# Source-Hash: 78a5625401384000 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a surface" +# Source-Hash: 78a5625401384000 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASurface" msgstr "面を選択" -# Review: required -# Source-Hash: 2ee121dca87bb64a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner" +# Source-Hash: 2ee121dca87bb64a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.corner" msgstr "コーナー" -# Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curve" +# Source-Hash: f08fbb462e9afc0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curve" msgstr "曲線" -# Review: required -# Source-Hash: a028e08962c388e0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curved" +# Source-Hash: a028e08962c388e0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curved" msgstr "曲線" -# Review: required -# Source-Hash: 81b75058b7843a0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{style, select, picket {Distribution controls pickets in each bay between posts.} horizontal {Distribution controls posts along the full fence.} other {Distribution controls infill pieces between the two end posts.}} Spacing is measured between centers. The available width limits the count so pieces do not overlap." +# Source-Hash: 81b75058b7843a0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.distributionInfo" msgstr "{style, select, picket {配置は支柱間の各スパンのピケットを制御します。} horizontal {配置はフェンス全体の支柱を制御します。} other {配置は両端の支柱間の充填材を制御します。}}間隔は中心間で測ります。重ならないよう、使える幅によって数が制限されます。" -# Review: required -# Source-Hash: fbc8f54b0e3fdeb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit path" +# Source-Hash: fbc8f54b0e3fdeb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.editPath" msgstr "パスを編集" -# Review: required -# Source-Hash: 55686db44529c013 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extend path" +# Source-Hash: 55686db44529c013 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.extendPath" msgstr "パスを延長" -# Review: required -# Source-Hash: 0bf0f27e631aa98d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow highest surface" +# Source-Hash: 0bf0f27e631aa98d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followHighestSurface" msgstr "最も高い面に沿う" -# Review: required -# Source-Hash: e0b381fcb05a65c4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow selected surface" +# Source-Hash: e0b381fcb05a65c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followSelectedSurface" msgstr "選択した面に沿う" -# Review: required -# Source-Hash: c418d97f803af8dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hold starting height" +# Source-Hash: c418d97f803af8dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.holdStartingHeight" msgstr "開始時の高さを保つ" -# Review: required -# Source-Hash: 295ca9336dc1935b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Join" +# Source-Hash: 295ca9336dc1935b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.join" msgstr "結合" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.length" msgstr "長さ" -# Review: required -# Source-Hash: 1fedc8a2f2ea6887 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} point} other {{number} points}} · {length} m" +# Source-Hash: 1fedc8a2f2ea6887 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.pathSummary" msgstr "{count, plural, other {{number} 点}} · {length} m" -# Review: required -# Source-Hash: c96d499e11181429 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket rail projection" +# Source-Hash: c96d499e11181429 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.picketRailProjection" msgstr "ピケットのレール突出" -# Review: required -# Source-Hash: a1880fca8372d06f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Point" +# Source-Hash: a1880fca8372d06f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.point" msgstr "点" -# Review: required -# Source-Hash: 39bc6254b5307f53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove point" +# Source-Hash: 39bc6254b5307f53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.removePoint" msgstr "点を削除" -# Review: required -# Source-Hash: ea56af3a0d7a65e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Set each span to straight or curved, then drag its points and curve handles in either view." +# Source-Hash: ea56af3a0d7a65e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.setEachSpanToStraightOr" msgstr "各スパンを直線か曲線にしてから、どちらのビューでも点と曲線ハンドルをドラッグします。" -# Review: required -# Source-Hash: 354c8bc5c8da5a6e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Smooth" +# Source-Hash: 354c8bc5c8da5a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.smooth" msgstr "スムーズ" -# Review: required -# Source-Hash: 3c700cc1299a150f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Span to point {number}" +# Source-Hash: 3c700cc1299a150f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.spanToPointNumber" msgstr "点 {number} までのスパン" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.straight" msgstr "直進" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surface" msgstr "面" -# Review: required -# Source-Hash: ab2de0428c093018 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface behavior" +# Source-Hash: ab2de0428c093018 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surfaceBehavior" msgstr "面への追従" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.label" msgstr "フェンス" -# Review: required -# Source-Hash: 3e07a9889c1bd7f9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Detach endpoint" +# Source-Hash: 3e07a9889c1bd7f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.detachEndpoint" msgstr "端点を切り離す" -# Review: required -# Source-Hash: 7a3ce9fec6370e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drag endpoint" +# Source-Hash: 7a3ce9fec6370e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.dragEndpoint" msgstr "端点をドラッグ" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.close" msgstr "閉じる" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.open" msgstr "開く" -# Review: required -# Source-Hash: fc4cf2bc034fc547 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Board width" +# Source-Hash: fc4cf2bc034fc547 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.boardWidth" msgstr "板幅" -# Review: required -# Source-Hash: f3b11e5b2dd3b25f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bracing" +# Source-Hash: f3b11e5b2dd3b25f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.bracing" msgstr "筋交い" -# Review: required -# Source-Hash: d09ec3d9565734c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Distance from start" +# Source-Hash: d09ec3d9565734c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.distanceFromStart" msgstr "始点からの距離" -# Review: required -# Source-Hash: dcd803eaf3796298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fence unavailable." +# Source-Hash: dcd803eaf3796298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.fenceUnavailable" msgstr "フェンスを利用できません。" -# Review: required -# Source-Hash: 3519d91b7bda18a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame width" +# Source-Hash: 3519d91b7bda18a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.frameWidth" msgstr "枠の幅" -# Review: required -# Source-Hash: cb18554b2420e700 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate height" +# Source-Hash: cb18554b2420e700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateHeight" msgstr "門扉の高さ" -# Review: required -# Source-Hash: 54098b5ee574737a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate style" +# Source-Hash: 54098b5ee574737a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateStyle" msgstr "門のスタイル" -# Review: required -# Source-Hash: 4fc3e60be28f60d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.groundClearance" msgstr "地面とのクリアランス" -# Review: required -# Source-Hash: 916f2e07bf082795 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinges and latch" +# Source-Hash: 916f2e07bf082795 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingesAndLatch" msgstr "丁番とラッチ" -# Review: required -# Source-Hash: 5bd2e597fbb2a2c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinge side" +# Source-Hash: 5bd2e597fbb2a2c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingeSide" msgstr "丁番側" -# Review: required -# Source-Hash: 8fec7e969045a8e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Horizontal boards" +# Source-Hash: 8fec7e969045a8e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.horizontalBoards" msgstr "横板" -# Review: required -# Source-Hash: f4addad87cc4223e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Infill spacing" +# Source-Hash: f4addad87cc4223e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.infillSpacing" msgstr "充填材の間隔" -# Review: required -# Source-Hash: cdec08b292742b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Jamb posts" +# Source-Hash: cdec08b292742b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.jambPosts" msgstr "門柱" -# Review: required -# Source-Hash: ef88410453ee7414 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Keep the opening inside the fence and clear of other gates or passages." +# Source-Hash: ef88410453ee7414 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.keepTheOpeningInsideTheFence" msgstr "開口はフェンス内に収め、ほかの門扉や通路と重ならないようにしてください。" -# Review: required -# Source-Hash: 67cd0171b226fedb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaf thickness" +# Source-Hash: 67cd0171b226fedb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leafThickness" msgstr "扉の厚さ" -# Review: required -# Source-Hash: c390ba248e629b9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaves" +# Source-Hash: c390ba248e629b9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leaves" msgstr "扉" -# Review: required -# Source-Hash: d7bb934589475512 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left leaf proportion" +# Source-Hash: d7bb934589475512 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leftLeafProportion" msgstr "左扉の比率" -# Review: required -# Source-Hash: aa9fe0ebace8957d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence height" +# Source-Hash: aa9fe0ebace8957d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceHeight" msgstr "フェンスの高さに合わせる" -# Review: required -# Source-Hash: c15c1af69ec5c1eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence style" +# Source-Hash: c15c1af69ec5c1eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceStyle" msgstr "フェンスのスタイルに合わせる" -# Review: required -# Source-Hash: 51b7ab6a0a78d90a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingAngle" msgstr "開き角度" -# Review: required -# Source-Hash: d2e20300c07aa722 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening width" +# Source-Hash: d2e20300c07aa722 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingWidth" msgstr "開口幅" -# Review: required -# Source-Hash: 6b108fb1d0eb8f9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open rails" +# Source-Hash: 6b108fb1d0eb8f9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openRails" msgstr "横桟" -# Review: required -# Source-Hash: d500c4b7827d3390 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cross" +# Source-Hash: d500c4b7827d3390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.cross" msgstr "クロス" -# Review: required -# Source-Hash: 7420c0d2caee1c22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.diagonal" msgstr "斜め" -# Review: required -# Source-Hash: 7308869944020839 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double gate" +# Source-Hash: 7308869944020839 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.doubleGate" msgstr "両開き門" -# Review: required -# Source-Hash: 1f4ad5ad3e1be222 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inward" +# Source-Hash: 1f4ad5ad3e1be222 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.inward" msgstr "内開き" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.left" msgstr "左" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.none" msgstr "なし" -# Review: required -# Source-Hash: ccd8f06b8f8d27c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Outward" +# Source-Hash: ccd8f06b8f8d27c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.outward" msgstr "外開き" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.right" msgstr "右" -# Review: required -# Source-Hash: d327097d9ac1099f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single gate" +# Source-Hash: d327097d9ac1099f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.singleGate" msgstr "片開き門" -# Review: required -# Source-Hash: 9295ddc2805b21e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket" +# Source-Hash: 9295ddc2805b21e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.picket" msgstr "ピケット" -# Review: required -# Source-Hash: 8dd5999b235f60ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Post height" +# Source-Hash: 8dd5999b235f60ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.postHeight" msgstr "支柱の高さ" -# Review: required -# Source-Hash: 90bcd7a8123ec343 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove gate" +# Source-Hash: 90bcd7a8123ec343 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeGate" msgstr "門扉を削除" -# Review: required -# Source-Hash: 7e3505710613543f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove opening" +# Source-Hash: 7e3505710613543f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeOpening" msgstr "開口を削除" -# Review: required -# Source-Hash: af6f8885fe0fa4e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid privacy" +# Source-Hash: af6f8885fe0fa4e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.solidPrivacy" msgstr "目隠し" -# Review: required -# Source-Hash: 1a7048255c3bdfcb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swing direction" +# Source-Hash: 1a7048255c3bdfcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.swingDirection" msgstr "開く向き" -# Review: required -# Source-Hash: eba3df14a2de0812 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical slats" +# Source-Hash: eba3df14a2de0812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.verticalSlats" msgstr "縦板" +# English: "A gate hosted by a fence." # Source-Hash: df61821ed00852d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A gate hosted by a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.description" msgstr "フェンスに取り付けた門。" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.label" msgstr "門" +# English: "An intentional opening in a fence." # Source-Hash: 8f9c927d79796c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An intentional opening in a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.description" msgstr "フェンスに設けた開口。" +# English: "Open Passage" # Source-Hash: ab67bb47e647d594 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.label" msgstr "通路" +# English: "Floor opening" # Source-Hash: dc3adf0c2d50dba6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.floorOpening.label" msgstr "床開口" -# Source-Hash: 2f4fac2b1d39fdb1 +# English: "A concrete strip or pad footing poured under the lowest floor." +# Source-Hash: 0d2952c324c93b87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.description" +msgstr "最下階の床下に打設する、コンクリートの連続フーチングまたは独立フーチング。" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.label" +msgstr "フーチング" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.actions" +msgstr "操作" + +# English: "A grade beam on piles, as the building is." +# Source-Hash: 5cbf600af0f430c1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aGradeBeamOnPilesAs" +msgstr "建物と同じく、杭上の地中梁です。" + +# English: "A pier carries the floor." +# Source-Hash: 4de35a53f73e6d74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPierCarriesTheFloor" +msgstr "ピア(柱状の基礎)が床を支えます。" + +# English: "A pile cap on one pile, as the building is." +# Source-Hash: b07f0dafd314ce61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPileCapOnOnePile" +msgstr "建物と同じく、1 本の杭の上のパイルキャップです。" + +# English: "A stem wall carries the floor." +# Source-Hash: 9b596c6f41bdd6dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aStemWallCarriesTheFloor" +msgstr "立ち上がりが床を支えます。" + +# English: "A thickened slab under the floor slab." +# Source-Hash: edd463abf1ba203f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aThickenedSlabUnderTheFloor" +msgstr "床スラブ下の増し厚スラブです。" + +# English: "At least" +# Source-Hash: 7d978d555a1fba4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.atLeast" +msgstr "最小" + +# English: ", bottom" +# Source-Hash: a6b79a24d92094bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.bottom" +msgstr "、底面" + +# English: "Clear the depth" +# Source-Hash: f09c3e23c581e6e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.clearTheDepth" +msgstr "深さをクリア" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.delete" +msgstr "削除" + +# English: "Depth under grade" +# Source-Hash: 13c7635ae6ebd98b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGrade" +msgstr "根入れ深さ" + +# English: "Depth under grade (building's)" +# Source-Hash: fb5c116d6c004e8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGradeBuildingS" +msgstr "根入れ深さ(建物の設定)" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.footing" +msgstr "フーチング" + +# English: "IRC R403.1 asks a footing at least 12 in wide and 6 in thick." +# Source-Hash: 742775b7550e4d10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.ircR4031AsksAFooting" +msgstr "IRC R403.1 では、フーチングは幅 12 in 以上、厚さ 6 in 以上が必要です。" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.length" +msgstr "長さ" + +# English: "Pad" +# Source-Hash: 862a4fa9c40e14b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.pad" +msgstr "独立" + +# English: "Strip" +# Source-Hash: 168d7c95aa1872fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.strip" +msgstr "連続" + +# English: "the footing’s thickness under the floor reaches this far." +# Source-Hash: 2a4c3ad2bb841f1d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theFootingSThicknessUnderThe" +msgstr "床下のフーチングの厚さでこの深さまで達します。" + +# English: "the grade beam reaches this far." +# Source-Hash: b907c56408d326b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theGradeBeamReachesThisFar" +msgstr "地中梁がこの深さまで達します。" + +# English: "the slab and the footing’s thickness reach this far." +# Source-Hash: 0ec23acc847b60c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theSlabAndTheFootingS" +msgstr "スラブとフーチングの厚さでこの深さまで達します。" + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.thickness" +msgstr "厚さ" + +# English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.top" +msgstr "天端" + +# English: "Use the building's depth" +# Source-Hash: 78d9e3ca09235067 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.useTheBuildingSDepth" +msgstr "建物の根入れ深さを使う" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.width" +msgstr "幅" + +# English: "The outline the foundation is poured to: drawn, or traced from the walls and stretched." +# Source-Hash: 1e069ed68ad21990 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.description" +msgstr "基礎を打設する輪郭。描画するか、壁からなぞって伸縮させます。" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.label" +msgstr "基礎" + # English: "A measurement / reference annotation (linear, area, or arc)." +# Source-Hash: 2f4fac2b1d39fdb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.description" msgstr "計測・参照の注記(直線、面積、円弧)。" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.label" msgstr "ガイド" +# English: "Rain-water channel running along the eave of a roof segment." # Source-Hash: 5402d634f40b2f97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rain-water channel running along the eave of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.description" msgstr "屋根面の軒に沿う雨水の樋。" -# Review: required -# Source-Hash: fc7d4ecf973db135 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Downspout" +# Source-Hash: fc7d4ecf973db135 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.addDownspout" msgstr "縦樋を追加" -# Review: required -# Source-Hash: 9b9929bac2c39bca -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspout {number}" +# Source-Hash: 9b9929bac2c39bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspoutNumber" msgstr "縦樋 {number}" -# Review: required -# Source-Hash: fc34d45725838141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspouts" +# Source-Hash: fc34d45725838141 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspouts" msgstr "縦樋" -# Review: required -# Source-Hash: ac329c74056f0616 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove downspout" +# Source-Hash: ac329c74056f0616 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.removeDownspout" msgstr "縦樋を削除" +# English: "Gutter" # Source-Hash: 14ba981da3627ae2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.label" msgstr "雨樋" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.hole.manual" msgstr "手動" +# English: "Hole {number} (Editing)" # Source-Hash: fffde6fa82e65ffc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number} (Editing)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeEditing" msgstr "開口 {number}(編集中)" +# English: "Hole {number}" # Source-Hash: 0fe446c72451e4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeNumber" msgstr "開口 {number}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: fd26f8df9d087c6b +# English: "{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# Source-Hash: 8eeeb79ff4221939 # Review: required -# English: "{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeSummary" -msgstr "{area} m² · {count, plural, other {{points} 点}} · {source}" +msgstr "{area} {unit} · {count, plural, other {{points} 点}} · {source}" +# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." # Source-Hash: 50e9252d30f3fb4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.description" msgstr "炉、空調機、室外機 — ダクトは給気・還気カラーに接続します。" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.label" msgstr "空調機" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/hvac-equipment/tool.tsx #, fuzzy msgid "node.hvacEquipment.tool.rTRotate" msgstr "R/T で回転" +# English: "Geometry preserved from an imported model when no native Pascal shape exists." # Source-Hash: f24a1089d9256460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry preserved from an imported model when no native Pascal shape exists." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.description" msgstr "Pascal のネイティブ形状がない場合に、インポートしたモデルから保持されるジオメトリ。" +# English: "Imported Mesh" # Source-Hash: 9f9d11f287f722d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Imported Mesh" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.label" msgstr "インポートメッシュ" +# English: "Couldn't rebuild with these values." # Source-Hash: 9cce89f94c673821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Couldn't rebuild with these values." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.couldnTRebuildWithTheseValues" msgstr "この値では再構築できませんでした。" +# English: "Parameters" # Source-Hash: 373b929318436fe3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.parameters" msgstr "パラメーター" +# English: "Rebuilding…" # Source-Hash: 558cb886dbe0babe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuilding" msgstr "再構築中…" +# English: "Rebuilding in Blender…" # Source-Hash: a094c99dc8ad29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding in Blender…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuildingInBlender" msgstr "Blender で再構築中…" +# English: "Reset {param}" # Source-Hash: 1778f93ee143cd95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset {param}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParam" msgstr "{param}をリセット" +# English: "Reset parameters" # Source-Hash: 5a28cf5f2402dd7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParameters" msgstr "パラメーターをリセット" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetToDefault" msgstr "既定に戻す" +# English: "A catalog-backed item (furniture, fixtures, decorations)." # Source-Hash: 63f79b3c467177b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A catalog-backed item (furniture, fixtures, decorations)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.description" msgstr "カタログのアイテム(家具、設備、装飾)。" +# English: "Item" # Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.label" msgstr "アイテム" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.actions" msgstr "操作" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.delete" msgstr "削除" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.dimensions" msgstr "寸法" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.duplicate" msgstr "複製" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.info" msgstr "情報" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.move" msgstr "移動" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.position" msgstr "位置" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rot" msgstr "回転" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rotation" msgstr "回転" +# English: "Scale" # Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale" msgstr "縮尺" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale2" msgstr "縮尺" +# English: "Uniform Scale" # Source-Hash: b0d264950558d50e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uniform Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.uniformScale" msgstr "均等スケール" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.xyz" msgstr "XYZ" +# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." # Source-Hash: a9e2d7b215696e2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.description" msgstr "構造と排水を自動処理する、付属の片流れまたは独立型の片流れ・切妻・バタフライのキャノピー。" +# English: "Canopy" # Source-Hash: 8503bedfbafde9a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.label" msgstr "キャノピー" +# English: "A single floor of a building, holding walls / slabs / ceilings / items." # Source-Hash: 45673462f914d3df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single floor of a building, holding walls / slabs / ceilings / items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.description" msgstr "建物の1つの階。壁・スラブ・天井・アイテムを含みます。" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.label" msgstr "階層" +# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." # Source-Hash: 156bdee8fee5bec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.description" msgstr "冷媒配管 — 室外機と室内コイルを結ぶ銅製の吸入管と液管。" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.label" msgstr "冷媒配管" +# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." # Source-Hash: 2bd4a85bed158269 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.description" msgstr "単独の冷媒液管 — 細い裸銅管。追従モードは既存の冷媒配管に沿います。" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.label" msgstr "液管" -# Review: required -# Source-Hash: 601aaf23ce5a9279 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Click to trace this lineset run" +# Source-Hash: 601aaf23ce5a9279 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.clickToTraceThisLinesetRun" msgstr "クリックしてこの冷媒配管をたどる" -# Review: required -# Source-Hash: 77df017af9260468 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow: hover a lineset" +# Source-Hash: 77df017af9260468 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.followHoverALineset" msgstr "フォロー:冷媒配管にカーソルを合わせる" +# English: "A persistent distance, angle, area, perimeter, or volume annotation." # Source-Hash: ff4e3bb72267859e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A persistent distance, angle, area, perimeter, or volume annotation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.description" msgstr "距離、角度、面積、周長、体積の常設注記。" +# English: "Measurement" # Source-Hash: ec3567fd058bab7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.label" msgstr "計測" +# English: "{kind} {number}" # Source-Hash: d71b38e990539364 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind} {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.numberedName" msgstr "{kind} {number}" +# English: "Currently {value}" # Source-Hash: 09865d8781ef8699 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -24566,1951 +28873,2321 @@ msgstr "{kind} {number}" msgid "node.panel.currently" msgstr "現在 {value}" +# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." # Source-Hash: 7764b50334e9f71d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.description" msgstr "排水継手 — エルボ、45°Y、衛生T。" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.label" msgstr "配管継手" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis" msgstr "軸" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis2" msgstr "⌥ 軸" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.rTRotate" msgstr "R/T で回転" +# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." # Source-Hash: a0bb4762aa66fa3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.description" msgstr "排水・汚水・通気管 — 水平に描くか、S で1フィートあたり¼″の勾配を切り替え。" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.label" msgstr "排水管" +# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." # Source-Hash: 6d0cdd14d0675e05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.description" msgstr "Pトラップ — 排水管の封水。トラップアームは通気管へ伸びます。" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.label" msgstr "トラップ" -# Review: required -# Source-Hash: 73078bc0f5d686fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" Trap" +# Source-Hash: 73078bc0f5d686fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.diameterTrap" msgstr "{diameter}\" トラップ" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.rTRotate" msgstr "R/T で回転" +# English: "Experimental editable recipe" # Source-Hash: 0efe82436b915f6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Experimental editable recipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.description" msgstr "実験的な編集可能レシピ" +# English: "Procedural item" # Source-Hash: c8bbcc888cce7b8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Procedural item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.label" msgstr "プロシージャルアイテム" +# English: "Ventilation strip running along the ridge of a roof segment." # Source-Hash: 356aa0ed69cb8ec5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ventilation strip running along the ridge of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.description" msgstr "屋根面の棟に沿う換気帯。" +# English: "Ridge Vent" # Source-Hash: 54b74080f33c8c4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.label" msgstr "棟換気" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: 18bf817325c1c537 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Caps" +# Source-Hash: 18bf817325c1c537 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.endCaps" msgstr "エンドキャップ" -# Review: required -# Source-Hash: 466f817a0102d4a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flanged" +# Source-Hash: 466f817a0102d4a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.flanged" msgstr "フランジ付き" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.length" msgstr "長さ" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.open" msgstr "開く" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 54b74080f33c8c4e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ridge Vent" +# Source-Hash: 54b74080f33c8c4e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.ridgeVent" msgstr "棟換気" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: e9046e9823b400a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shingled" +# Source-Hash: e9046e9823b400a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.shingled" msgstr "シングル葺き" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.standard" msgstr "標準" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.width" msgstr "幅" +# English: "Arch bays" # Source-Hash: c6d3976b4eb80586 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bays" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.archBays" msgstr "アーチベイ" +# English: "Barrel arch" # Source-Hash: 1cfdd5affedf153c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.barrelArch" msgstr "かまぼこアーチ" +# English: "Flat panes follow a circular arch. Rise is measured from the eaves." # Source-Hash: b3e8929750d18f7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat panes follow a circular arch. Rise is measured from the eaves." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.flatPanesFollowACircularArch" msgstr "平らなガラスが円弧に沿います。ライズは軒から測ります。" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.rise" msgstr "高さ" +# English: "A pitched / hip / gable roof composed of one or more segments." # Source-Hash: 6afeec8de7ed618d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A pitched / hip / gable roof composed of one or more segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.description" msgstr "1つ以上の面で構成される勾配・寄棟・切妻屋根。" +# English: "Arch bay {number}" # Source-Hash: b5be04471fd3fb7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bay {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.archBay" msgstr "アーチベイ {number}" +# English: "Back slope" # Source-Hash: 93381b9024a75a99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.back" msgstr "背面の勾配" +# English: "Left end" # Source-Hash: 08b00107b635e11d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endLeft" msgstr "左端" +# English: "Right end" # Source-Hash: 821734ea5d17a693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endRight" msgstr "右端" +# English: "Front slope" # Source-Hash: 40c9ef62055067e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.front" msgstr "前面の勾配" +# English: "Shed slope" # Source-Hash: 1a88da71d5e5f0d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.slope" msgstr "片流れの勾配" +# English: "Alignment" # Source-Hash: 3bcc542295b4c61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.alignment" msgstr "揃え" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.center" msgstr "中央" +# English: "Choose a layout to edit the grid." # Source-Hash: 512e47607a37af46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a layout to edit the grid." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.chooseALayoutToEditThe" msgstr "レイアウトを選んでグリッドを編集します。" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.empty" msgstr "空" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.end" msgstr "終点" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.fixedSpacing" msgstr "固定間隔" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.glass" msgstr "ガラス" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.layout" msgstr "レイアウト" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maximumSpacing" msgstr "最大間隔" +# English: "Max spacing" # Source-Hash: d10743fce478d9f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Max spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maxSpacing" msgstr "最大間隔" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.mixed" msgstr "混在" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panelCount" msgstr "パネル数" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panels" msgstr "パネル" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.solid" msgstr "ソリッド" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.spacing" msgstr "間隔" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.start" msgstr "始点" +# English: "{title} alignment" # Source-Hash: 095237e9e2df4d9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{title} alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.titleAlignment" msgstr "{title}の揃え" +# English: "Changes apply to all {length} roof segments." # Source-Hash: c98a4e6b28f05e0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Changes apply to all {length} roof segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.changesApplyToAllLengthRoof" msgstr "変更は {length} 個すべての屋根セグメントに適用されます。" +# English: "Choose one roof system to edit settings for all selected segments." # Source-Hash: c5d2ff7fda4787cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose one roof system to edit settings for all selected segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.chooseOneRoofSystemToEdit" msgstr "屋根システムを1つ選ぶと、選択したすべてのセグメントの設定を編集できます。" +# English: "Close the roof ends" # Source-Hash: 80e07a119b63187c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close the roof ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.closeTheRoofEnds" msgstr "屋根の端を閉じる" +# English: "Default panel infill" # Source-Hash: e28dd27d54aaa7ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.defaultPanelInfill" msgstr "パネルの既定の充填" +# English: "Divides each roof face into columns." # Source-Hash: 134d33e2465937e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides each roof face into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesEachRoofFaceIntoColumns" msgstr "各屋根面を列に分割します。" +# English: "Divides the length of each arch bay into columns." # Source-Hash: 896a6e4da3357c39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides the length of each arch bay into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesTheLengthOfEachArch" msgstr "各アーチベイの長さを列に分割します。" +# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." # Source-Hash: be9c810d28d1feb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.endInfillFillsTheSpaceAbove" msgstr "端部の充填は壁の上の空間を埋めます。部屋の壁と天井は別々に編集してください。" +# English: "Frame color" # Source-Hash: 0b8b27dbc57cca44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameColor" msgstr "枠の色" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameDepth" msgstr "枠の奥行き" +# English: "Frame width" # Source-Hash: 3519d91b7bda18a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameWidth" msgstr "枠の幅" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.framing" msgstr "下地" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glass" msgstr "ガラス" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glassTint" msgstr "ガラスの色合い" +# English: "Glazed" # Source-Hash: dd330a94b3f7a65a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazed" msgstr "ガラス張り" +# English: "Glazing is available for gable, straight shed, and barrel roofs." # Source-Hash: e6be52df9197243a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing is available for gable, straight shed, and barrel roofs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazingIsAvailableForGableStraight" msgstr "ガラスは切妻、直線の片流れ、ヴォールト屋根で使えます。" +# English: "Grid across the roof" # Source-Hash: a4e7ef2e31d7220b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid across the roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAcrossTheRoof" msgstr "屋根を横切るグリッド" +# English: "Grid along the length" # Source-Hash: 2f28a3b33fccf56b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheLength" msgstr "長さ方向のグリッド" +# English: "Grid along the slope" # Source-Hash: 1f701893dbf9d72b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheSlope" msgstr "勾配方向のグリッド" +# English: "Grid within each arch bay" # Source-Hash: 20c8dfbadda20e3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid within each arch bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridWithinEachArchBay" msgstr "各アーチベイ内のグリッド" +# English: "Infill" # Source-Hash: 7c339a5fae55681d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.infill" msgstr "充填" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.opacity" msgstr "不透明度" +# English: "Pane thickness" # Source-Hash: 4d412746658f2faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.paneThickness" msgstr "ガラス板の厚さ" +# English: "Roof system" # Source-Hash: 570136e723cb7d99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roofSystem" msgstr "屋根システム" +# English: "Roughness" # Source-Hash: a6a7a36d982b4911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roughness" msgstr "粗さ" +# English: "Spacing is measured on the slope, not its horizontal projection." # Source-Hash: 2fa6fbeda5d45949 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing is measured on the slope, not its horizontal projection." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.spacingIsMeasuredOnTheSlope" msgstr "間隔は水平投影ではなく勾配面に沿って測ります。" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.standard" msgstr "標準" +# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." # Source-Hash: efbe1c4facf561f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.subdividesEachFlatArchBayArch" msgstr "平らな各アーチベイを分割します。曲線方向の分割はアーチベイで設定します。" +# English: "Choose a panel to override its default infill." # Source-Hash: 1497eac9e4f63016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a panel to override its default infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.chooseAPanelToOverrideIts" msgstr "パネルを選んで既定の充填を上書きします。" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.column2" msgstr "柱" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.custom" msgstr "カスタム" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.default" msgstr "標準" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" # Source-Hash: 9ef92ad8ae49ed72 # Review: required -# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.grid" msgstr "{columns, plural, other {{columnCount} 列}} · {rows, plural, other {{rowCount} 行}}" +# English: "Individual panels" # Source-Hash: 11624abbcfdd5506 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.individualPanels" msgstr "個別パネル" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.panelInfill" msgstr "パネルの充填" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.resetToDefault" msgstr "既定に戻す" +# English: "Roof face" # Source-Hash: 6fba44f2bfd8af8e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.roofFace" msgstr "屋根面" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.row2" msgstr "行" +# English: "Selected panel infill" # Source-Hash: 6c2cc2d9cd778998 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.selectedPanelInfill" msgstr "選択パネルの充填" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.label" msgstr "屋根" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.actions" msgstr "操作" +# English: "Add Chimney" # Source-Hash: d60471ce3cd45348 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addChimney" msgstr "煙突を追加" +# English: "Add Cupola" # Source-Hash: 1ca6a1cf9cc6e3ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addCupola" msgstr "キューポラを追加" +# English: "Add Dormer" # Source-Hash: 94dea147eeaac52b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addDormer" msgstr "ドーマーを追加" +# English: "Add Eyebrow Vent" # Source-Hash: 5679acdd64484726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addEyebrowVent" msgstr "アイブロウ換気口を追加" +# English: "Add Gutter" # Source-Hash: 25a141dc0d7c4b44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addGutter" msgstr "雨樋を追加" +# English: "Add Skylight" # Source-Hash: 7230bdc8a2252626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSkylight" msgstr "天窓を追加" +# English: "Add Solar Panel" # Source-Hash: 0f220e2602b7ff24 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSolarPanel" msgstr "ソーラーパネルを追加" +# English: "Add Vent" # Source-Hash: 4d83a2e5a4d9e6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addVent" msgstr "換気口を追加" +# English: "Box" # Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.box" msgstr "ボックス" +# English: "box vent" # Source-Hash: 3bf078fe2560daca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "box vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.boxVent" msgstr "ボックス換気口" +# English: "chimney" # Source-Hash: 4986740251121471 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.chimney" msgstr "煙突" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.custom" msgstr "カスタム" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.delete" msgstr "削除" +# English: "dormer" # Source-Hash: 5169968a4b0c1840 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.dormer" msgstr "ドーマー" +# English: "Draw Segment" # Source-Hash: 72e51c6e5e9c6c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.drawSegment" msgstr "セグメントを描画" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.duplicate" msgstr "複製" -# Source-Hash: 7c4196b9e791c195 +# English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaveOverhang" +msgstr "軒の出" + +# English: "Eaves" +# Source-Hash: 34756c5ea718ccb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaves" +msgstr "軒" + # English: "Elements" +# Source-Hash: 7c4196b9e791c195 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.elements" msgstr "要素" +# English: "Follows walls" # Source-Hash: 6b8ba49aa355fe87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.followsWalls" msgstr "壁に従う" -# Source-Hash: 23b434461c990711 +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.gableOverhang" +msgstr "妻側のけらば" + # English: "gutter" +# Source-Hash: 23b434461c990711 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.gutter" msgstr "雨樋" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.level" msgstr "階層" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.move" msgstr "移動" +# English: "Move to {level}" # Source-Hash: 39661f4827b0f435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToLevel" msgstr "{level}へ移動" +# English: "Move to the roof level" # Source-Hash: c534093ff560a9f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to the roof level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToTheRoofLevel" msgstr "屋根の階層へ移動" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" # Source-Hash: 0939fad1cb1831f0 # Review: required -# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.onStoreyWalls" msgstr "壁と同じ{storey}にあるため、単独表示と分解表示でその階の平面を隠します。{roofs, plural, =0 {} =1 {} other { この階の {roofCount} 個の屋根は一緒に動きます。}}" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.position" msgstr "位置" +# English: "Ridge" # Source-Hash: e6ddc8a624465793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridge" msgstr "棟" +# English: "ridge vent" # Source-Hash: 83112cbc205e9972 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridgeVent" msgstr "棟換気" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.roof" msgstr "屋根" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.rotation" msgstr "回転" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segments" msgstr "セグメント" +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" # Source-Hash: 0c084c517dbc153b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segmentType" msgstr "{roofType, select, hip {寄棟} gable {切妻} shed {片流れ} gambrel {腰折れ} dutch {入母屋} mansard {マンサード} flat {陸屋根} conical {円錐} barrel {ヴォールト} other {屋根}}" +# English: "skylight" # Source-Hash: 0c4c82caccb4775c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.skylight" msgstr "天窓" +# English: "solar panel" # Source-Hash: b9f931508dd6ec70 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "solar panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.solarPanel" msgstr "ソーラーパネル" +# English: "Turbine" # Source-Hash: d2428b22f1757a4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbine" msgstr "タービン" +# English: "turbine vent" # Source-Hash: 86d02a3d3a3a7fa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "turbine vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbineVent" msgstr "タービン換気扇" -# Source-Hash: 99afd827dbe5d647 +# English: "Apply to all planes of this roof" +# Source-Hash: 6df330bcfcaac604 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.applyToAllPlanesOfThis" +msgstr "この屋根のすべての面に適用" + +# English: "Architectural shingle" +# Source-Hash: 7254a3eb47471b8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.architecturalShingle" +msgstr "アーキテクチュラルシングル" + +# English: "Asphalt 3-tab" +# Source-Hash: afd23a6d653689c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.asphalt3Tab" +msgstr "アスファルト 3タブ" + +# English: "Clay tile" +# Source-Hash: 2650e7c436297ef2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.clayTile" +msgstr "粘土瓦" + +# English: "Concrete tile" +# Source-Hash: 92af091c56892f25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.concreteTile" +msgstr "コンクリート瓦" + +# English: "Covering" +# Source-Hash: 75168c15633cbad3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.covering" +msgstr "葺き材" + +# English: "Deck" +# Source-Hash: f3aa561772702108 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.deck" +msgstr "デッキ" + +# English: "Edit layers" +# Source-Hash: 14187d972ce9e1de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.editLayers" +msgstr "層を編集" + +# English: "Finish name" +# Source-Hash: 5aff5f630e4b18c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.finishName" +msgstr "仕上げの名前" + +# English: "Ice and water shield" +# Source-Hash: b9ed3f21665672eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.iceAndWaterShield" +msgstr "粘着式ルーフィング" + +# English: "Insulation name" +# Source-Hash: 6d79431c0092abac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.insulationName" +msgstr "断熱材の名前" + +# English: "Interior finish" +# Source-Hash: e4e9305964238fda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.interiorFinish" +msgstr "内装仕上げ" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.layers" +msgstr "層" + +# English: "#15 felt" +# Source-Hash: 8f343a87f121347e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n15Felt" +msgstr "#15 フェルト" + +# English: "#30 felt" +# Source-Hash: faa54521399dc5b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n30Felt" +msgstr "#30 フェルト" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.none" +msgstr "なし" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.preset" +msgstr "プリセット" + +# English: "Rafters" +# Source-Hash: a9cb232546779779 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.rafters" +msgstr "垂木" + +# English: "Roofing" +# Source-Hash: 5c0d95afa820e706 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofing" +msgstr "屋根材" + +# English: "Roofing name" +# Source-Hash: 75a1bff32810894e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofingName" +msgstr "屋根材の名前" + +# English: "Sheathing" +# Source-Hash: 36a588e44085108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathing" +msgstr "野地板" + +# English: "Sheathing name" +# Source-Hash: abdad84e524e56e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathingName" +msgstr "野地板の名前" + +# English: "Slate" +# Source-Hash: a7b5921026935493 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.slate" +msgstr "天然スレート" + +# English: "Standing-seam metal" +# Source-Hash: c156b8bd7920b129 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.standingSeamMetal" +msgstr "金属立平葺き" + +# English: "Synthetic" +# Source-Hash: f5defb7d8a17154e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.synthetic" +msgstr "合成ルーフィング" + +# English: "Top chord" +# Source-Hash: 472f395a8b6319a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.topChord" +msgstr "上弦材" + +# English: "Trusses" +# Source-Hash: 4dd558e7eeec7acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.trusses" +msgstr "トラス" + +# English: "Underlayment" +# Source-Hash: 3909955f8a920be1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlayment" +msgstr "下葺き材" + +# English: "Underlayment name" +# Source-Hash: 0167cfd2ec25cf5d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlaymentName" +msgstr "下葺き材の名前" + +# English: "Wood shake" +# Source-Hash: 7095fa6e1c927c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.woodShake" +msgstr "木製シェイク" + # English: "A single pitched plane of a parent roof." +# Source-Hash: 99afd827dbe5d647 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.description" msgstr "親屋根の1つの勾配面。" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.label" msgstr "屋根面" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.actions" msgstr "操作" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.angle" msgstr "角度" +# English: "Arc" # Source-Hash: eb8f344a3c137280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.arc" msgstr "円弧" +# English: "Auto gutters" # Source-Hash: 1eb45f83eeaabfad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto gutters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoGutters" msgstr "自動雨樋" +# English: "Auto ridge vent" # Source-Hash: 0986c4933d2b5808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoRidgeVent" msgstr "自動棟換気" +# English: "Barrel" # Source-Hash: 344fe9d407a233a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.barrel" msgstr "ヴォールト" +# English: "Clipped version" # Source-Hash: 6ca418a2ecab5984 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clipped version" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.clippedVersion" msgstr "切り詰め版" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conical" msgstr "円錐" +# English: "Conical Shape" # Source-Hash: a37cfbc4db93cfd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conicalShape" msgstr "円錐形" +# English: "Deck Thick." # Source-Hash: a10f29fcde60eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.deckThick" msgstr "デッキ厚" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.delete" msgstr "削除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.depth" msgstr "奥行き" +# English: "Diameter" # Source-Hash: 016d8e70618916d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.diameter" msgstr "直径" +# English: "Done editing" # Source-Hash: e132bf88b9e4d75e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.doneEditing" msgstr "編集を完了" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.drainage" msgstr "排水" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.duplicate" msgstr "複製" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.dutch" msgstr "入母屋" -# Source-Hash: 5c99a67253bfc02a +# English: "Eave cut" +# Source-Hash: 4c539865731d5d66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.eaveCut" +msgstr "軒先の切り方" + # English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.eaveOverhang" msgstr "軒の出" +# English: "Edit footprint" # Source-Hash: dc90862aafc9d287 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.editFootprint" msgstr "フットプリントを編集" +# English: "End overhang" # Source-Hash: 9532d5e73585ea62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.endOverhang" msgstr "端部の張り出し" +# English: "Fascia" # Source-Hash: 74f4143cd02de4a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fascia" msgstr "鼻隠し" +# English: "Fascia height" # Source-Hash: 45baed9dc8b8047a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fasciaHeight" msgstr "鼻隠しの高さ" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.flat" msgstr "陸屋根" +# English: "Footprint" # Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.footprint" msgstr "フットプリント" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gable" msgstr "切妻" +# English: "Gable overhang" # Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gableOverhang" msgstr "妻側のけらば" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gambrel" msgstr "腰折れ" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.hip" msgstr "寄棟" +# English: "Kink Depth" # Source-Hash: 7ae4179d0abd80f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkDepth" msgstr "折れの奥行き" +# English: "Kink Height" # Source-Hash: e19b2bca70030855 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkHeight" msgstr "折れの高さ" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.length" msgstr "長さ" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.mansard" msgstr "マンサード" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.move" msgstr "移動" +# English: "Pitch" # Source-Hash: 75e3449fc1776d09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.pitch" msgstr "勾配" -# Source-Hash: 9da0cbfad144d737 +# English: "Plumb" +# Source-Hash: 6ae4075a23bb4ec4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumb" +msgstr "垂直" + +# English: "Plumb + level" +# Source-Hash: 16d8ab5937d74bdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumbLevel" +msgstr "垂直 + 水平" + # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.position" msgstr "位置" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.reset" msgstr "リセット" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofSegment" msgstr "屋根面" +# English: "Roof Type" # Source-Hash: 19ab9527ae7889f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofType" msgstr "屋根の種類" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.rotation" msgstr "回転" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shape" msgstr "形状" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shed" msgstr "片流れ" +# English: "Shingle Thick." # Source-Hash: 4d944a2393bbddb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shingleThick" msgstr "シングル厚" +# English: "Span" # Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.span" msgstr "スパン" -# Source-Hash: e90aceedf4a6b183 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.square" +msgstr "直角" + # English: "Start Angle" +# Source-Hash: e90aceedf4a6b183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.startAngle" msgstr "開始角度" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.structure" msgstr "構造" +# English: "Top Rake Length" # Source-Hash: a2db62c74fe2bcee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeLength" msgstr "上部破風の長さ" +# English: "Top Rake Thick." # Source-Hash: 913d9552030ac86a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeThick" msgstr "上部破風の厚さ" +# English: "Trim" # Source-Hash: 3beb5eb35f0c5c17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.trim" msgstr "見切り" +# English: "Waist Height" # Source-Hash: e6c5009c0d31fca1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistHeight" msgstr "腰の高さ" +# English: "Waist Length" # Source-Hash: 4a8caffd24b6ee02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistLength" msgstr "腰の長さ" +# English: "Waist Width" # Source-Hash: 71045af5c81a11e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistWidth" msgstr "腰の幅" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wall" msgstr "壁" +# English: "Wall Height" # Source-Hash: e0f973edce3e4ca8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallHeight" msgstr "壁の高さ" +# English: "Wall Thick." # Source-Hash: d0db6dee5fe2edbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallThick" msgstr "壁厚" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.width" msgstr "幅" +# English: "A captured session with optional mesh, motion, media, and sensor data." # Source-Hash: 6600a850000b98ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A captured session with optional mesh, motion, media, and sensor data." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.description" msgstr "メッシュ、モーション、メディア、センサーデータを任意で含むキャプチャセッション。" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.label" msgstr "キャプチャ" +# English: "Segment {number}" # Source-Hash: 95b5307eefb187c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -26518,7007 +31195,7752 @@ msgstr "キャプチャ" msgid "node.segmentNumber" msgstr "セグメント {number}" +# English: "Separator" # Source-Hash: acb8cb92b3674b50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.separator.label" msgstr "区切り" -# Source-Hash: e7512c891d8da7d8 +# English: "Batt" +# Source-Hash: e34eb5b5bfbfce7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.batt" +msgstr "マット状" + +# English: "Blown" +# Source-Hash: 430cef5fa9bd2787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.blown" +msgstr "吹込み" + +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywall" +msgstr "石膏ボード" + +# English: "Drywall thickness" +# Source-Hash: 4e8f119349fa2dd4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywallThickness" +msgstr "石膏ボードの厚さ" + +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulation" +msgstr "断熱材" + +# English: "Insulation thickness" +# Source-Hash: 4251fc1e9051a7db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulationThickness" +msgstr "断熱材の厚さ" + +# English: "{label} thickness" +# Source-Hash: f567133879529366 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.labelThickness" +msgstr "{label}の厚さ" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.none" +msgstr "なし" + +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.osb" +msgstr "OSB" + +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plaster" +msgstr "漆喰" + +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plywood" +msgstr "合板" + +# English: "Rigid board" +# Source-Hash: 3d153fa2037159f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.rigidBoard" +msgstr "硬質ボード" + +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacing" +msgstr "間隔" + +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. A framing spacing on centre (centre of one member to the next), e.g. 16" o.c. +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacingOC" +msgstr "芯々 {spacing}" + +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.woodPlank" +msgstr "板張り" + # English: "Turn light off" +# Source-Hash: e7512c891d8da7d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOff" msgstr "照明を消す" +# English: "Turn light on" # Source-Hash: d47ec95e14909fe5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turn light on" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOn" msgstr "照明をつける" +# English: "Casing" +# Source-Hash: 18bf48fbbe121ef0 # Review: required -# Source-Hash: ee1c8bc3e27fb550 -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.casing" +msgstr "額縁" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.width" +msgstr "幅" + # English: "Automatic support" +# Source-Hash: ee1c8bc3e27fb550 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.automaticSupport" msgstr "自動サポート" -# Review: required -# Source-Hash: 1353e40320c72a99 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} · segment {segment}" +# Source-Hash: 1353e40320c72a99 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.hangerOnSegment" msgstr "吊り金具 {number} · セグメント {segment}" -# Review: required -# Source-Hash: 737ced90d0454197 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Individual hangers" +# Source-Hash: 737ced90d0454197 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.individualHangers" msgstr "個別の吊り金具" -# Review: required -# Source-Hash: e6b17b09ed91c8c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Missing support" +# Source-Hash: e6b17b09ed91c8c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.missingSupport" msgstr "サポートなし" -# Review: required -# Source-Hash: c895c3c603382ec9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No support within reach. Move the hanger or choose another host." +# Source-Hash: c895c3c603382ec9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.noSupportWithinReachMoveThe" msgstr "届く範囲に支持体がありません。吊り金具を動かすか、別の支持体を選んでください。" -# Review: required -# Source-Hash: 6ce526cb7444be36 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} position in metres" +# Source-Hash: 6ce526cb7444be36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionLabel" msgstr "吊り金具 {number} の位置(メートル)" -# Review: required -# Source-Hash: cbd36c4b82a55539 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position (m)" +# Source-Hash: cbd36c4b82a55539 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionM" msgstr "位置(m)" -# Review: required -# Source-Hash: b629c4f8388fd36f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Positions are measured along each path segment. Changing spacing regenerates the numbered slots." +# Source-Hash: b629c4f8388fd36f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath" msgstr "位置はパスの各セグメントに沿って測ります。間隔を変えると番号付きの位置が作り直されます。" -# Review: required -# Source-Hash: c21cfbdb64e53388 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal support" +# Source-Hash: c21cfbdb64e53388 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.revealSupport" msgstr "サポートを表示" -# Review: required -# Source-Hash: 9d5e4440db18e477 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skip" +# Source-Hash: 9d5e4440db18e477 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.skip" msgstr "スキップ" -# Review: required -# Source-Hash: 1b184f78bf989931 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} support" +# Source-Hash: 1b184f78bf989931 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.supportLabel" msgstr "吊り金具 {number} のサポート" +# English: "About these checks" # Source-Hash: b2d703d283baf64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "About these checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.aboutTheseChecks" msgstr "これらのチェックについて" +# English: "All findings" # Source-Hash: 461cc26706956993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allFindings" msgstr "すべての検出結果" +# English: "All systems" # Source-Hash: 7beeb4c4997f5edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All systems" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allSystems" msgstr "すべてのシステム" +# English: "Connections" # Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.connections" msgstr "接続" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.drainage" msgstr "排水" +# English: "Error" # Source-Hash: fc4ac19649f0ee8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Error" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.error" msgstr "エラー" +# English: "Errors" # Source-Hash: b92720ff88a1bcfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Errors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.errors" msgstr "エラー" +# English: "Filter system checks" # Source-Hash: d0e23bdbe1b380ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Filter system checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.filterSystemChecks" msgstr "システムチェックを絞り込む" +# English: "Hangers" # Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.hangers" msgstr "吊り金具" +# English: "Intersections" # Source-Hash: 8cd416ca57d969f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Intersections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.intersections" msgstr "交差" +# English: "Locate {name} ({id})" # Source-Hash: 18d652275a9a525c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Locate {name} ({id})" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.locate" msgstr "{name}を表示({id})" +# English: "Missing item" # Source-Hash: 88546aa3a5987966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Missing item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.missingItem" msgstr "見つからないアイテム" +# English: "No findings" # Source-Hash: 2452d2922a7ecf4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindings" msgstr "検出結果なし" +# English: "No findings in this category" # Source-Hash: 995442fbfc786768 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings in this category" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindingsInThisCategory" msgstr "このカテゴリーに検出結果はありません" +# English: "Open ends" # Source-Hash: b5c58159b3eef89c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEnds" msgstr "開放端" +# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." # Source-Hash: 3003dacc858d36ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay" msgstr "開放端や分離した分岐は意図的な場合があります。交差チェックはバウンディングボックスで行うため、開口とクリアランスを確認してください。" +# English: "Selected connected system" # Source-Hash: c40c64562b6a1a91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected connected system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedConnectedSystem" msgstr "選択した接続システム" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" # Source-Hash: 68355a98439229e2 # Review: required -# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedRuns" msgstr "選択中:{count, plural, other {{number} 系統}} · {length} m" +# English: "Separate branches" # Source-Hash: e7a352627f8fcd45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate branches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.separateBranches" msgstr "分離した分岐" +# English: "Show all findings" # Source-Hash: 910a5ee73f2ef8a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show all findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.showAllFindings" msgstr "すべての検出結果を表示" +# English: "System checks" # Source-Hash: dda8887833d07390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemChecks" msgstr "システムチェック" +# English: "System finding" # Source-Hash: 4dad0ed68e63d124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System finding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFinding" msgstr "システムの検出結果" +# English: "System findings" # Source-Hash: e37307a519466b38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFindings" msgstr "システムの検出結果" +# English: "Warning" # Source-Hash: 5128de8b5a618339 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warning" msgstr "警告" +# English: "Warnings" # Source-Hash: 7e1647f9ce51509e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warnings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warnings" msgstr "警告" +# English: "A configurable shelving unit. Items host on each row." # Source-Hash: 26aff27b7284fc84 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable shelving unit. Items host on each row." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.description" msgstr "設定可能な棚。各段にアイテムを置けます。" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.label" msgstr "棚" +# English: "The top-level container holding buildings, zones, and the property boundary." # Source-Hash: c52492c73312a922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The top-level container holding buildings, zones, and the property boundary." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.description" msgstr "建物、ゾーン、敷地境界を含む最上位のコンテナ。" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.label" msgstr "敷地" +# English: "Address" # Source-Hash: 3b5a53b6d058a02a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.address" msgstr "住所" +# English: "APN" # Source-Hash: ea03c49930137a0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "APN" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.apn" msgstr "区画番号" +# English: "Auto — most north-facing edge" # Source-Hash: 8519c9ddf5e6b371 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto — most north-facing edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.autoMostNorthFacingEdge" msgstr "自動 — 最も北向きの辺" +# English: "Contour interval (site plan and 3D)" # Source-Hash: bc493d7655b3e3ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Contour interval (site plan and 3D)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.contourIntervalSitePlanAnd3d" msgstr "等高線間隔(配置図と3D)" +# English: "County" # Source-Hash: 99208d226da1315f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "County" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.county" msgstr "郡" +# English: "e.g. RS-60" # Source-Hash: 0830cf0f95ad429d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. RS-60" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGRs60" msgstr "例:RS-60" +# English: "e.g. Tampa LDC §27-156" # Source-Hash: b3e7e118266b5074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. Tampa LDC §27-156" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGTampaLdc27156" msgstr "例:Tampa LDC §27-156" +# English: "every 12 in" # Source-Hash: 187e11889d08a2dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 12 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every12In" msgstr "12 in ごと" +# English: "every 24 in" # Source-Hash: 17ee041f655085f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 24 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every24In" msgstr "24 in ごと" +# English: "every 6 in" # Source-Hash: 40d7f9ddf5efcb7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 6 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every6In" msgstr "6 in ごと" +# English: "Finding…" # Source-Hash: 442a3ce78ae6b462 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.finding" msgstr "検索中…" +# English: "Find parcel" # Source-Hash: 734c1eac014adc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.findParcel" msgstr "区画を検索" +# English: "Front (street) edge" # Source-Hash: ccb70faaf91dbcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front (street) edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.frontStreetEdge" msgstr "前面(道路側)の辺" +# English: "Looking up the parcel and the streets around it…" # Source-Hash: 4232218226616327 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Looking up the parcel and the streets around it…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lookingUpTheParcel" msgstr "区画と周辺の道路を検索中…" +# English: "Lot area" # Source-Hash: c39eb5933468a637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lot area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lotArea" msgstr "敷地面積" +# English: "1200 W Cass St, Tampa, FL" # Source-Hash: c8a874b339ff9696 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "1200 W Cass St, Tampa, FL" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.n1200WCassStTampaFl" msgstr "1200 W Cass St, Tampa, FL" +# English: "none" # Source-Hash: 30a0924a3360be68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "none" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.none" msgstr "なし" +# English: "North rotation" # Source-Hash: dd4e835cb90c5736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "North rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.northRotation" msgstr "北の回転" +# English: "No surveyed lines — contours are drawn from the sculpted ground." # Source-Hash: a4bfb2891c820e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No surveyed lines — contours are drawn from the sculpted ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.noSurveyedLinesContoursAreDrawn" msgstr "測量線はありません。等高線は造形した地面から描かれます。" +# English: "Orientation" # Source-Hash: 1f0d024c0adbf0e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orientation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.orientation" msgstr "向き" +# English: "Parcel" # Source-Hash: 9a3961409e34bf66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcel" msgstr "区画" +# English: "Parcel lookup failed." # Source-Hash: 3aecc563cb9a2c68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel lookup failed." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcelLookupFailed" msgstr "区画の検索に失敗しました。" +# English: "Project look" # Source-Hash: 36696c4a8bede421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.projectLook" msgstr "プロジェクトの外観" +# English: "Save current look" # Source-Hash: 5b50fc78ccdaf7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save current look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveCurrentLook" msgstr "現在の外観を保存" +# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." # Source-Hash: f06b316f421963d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveTheCurrentLightingAndDisplay" msgstr "現在の照明と表示をこのプロジェクトの既定として保存します。個人の表示設定はこのブラウザーに残ります。" +# English: "Setbacks" # Source-Hash: e87df48b61eda8ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Setbacks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.setbacks" msgstr "セットバック" +# English: "Show ground" # Source-Hash: 9ede31abea7ad545 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showGround" msgstr "地面を表示" +# English: "Show the contour lines in 3D" # Source-Hash: 1e16a47e5e10c83e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show the contour lines in 3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showTheContourLinesIn3d" msgstr "等高線を3Dで表示" +# English: "side" # Source-Hash: 464241679edcbdb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.side" msgstr "側面" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.site" msgstr "敷地" +# English: "Source" # Source-Hash: 6165ee130f0082e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.source" msgstr "出典" +# English: "Source / citation" # Source-Hash: 5f3110d075c6a1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source / citation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.sourceCitation" msgstr "出典 / 引用" +# English: "Street address" # Source-Hash: 041cfc610fef830b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Street address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.streetAddress" msgstr "番地" +# English: "survey" # Source-Hash: bdca51c5e6437075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "survey" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.survey" msgstr "測量" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" # Source-Hash: 2177f21713b01a24 # Review: required -# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.surveyedContours" msgstr "{count, plural, other {{lineCount} 本の測量線}}、{interval} ft 間隔({datum})— {source}" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.terrain" msgstr "地形" +# English: "Type an address first." # Source-Hash: 4c8601a0b7aeed3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type an address first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.typeAnAddressFirst" msgstr "先に住所を入力してください。" +# English: "Use project look" # Source-Hash: dfe4cd35b3e0d5f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.useProjectLook" msgstr "プロジェクトの外観を使う" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.zone" msgstr "ゾーン" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.front" msgstr "前面" +# English: "Left (override)" # Source-Hash: ab1354f1aa520a54 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.leftOverride" msgstr "左(上書き)" +# English: "Rear" # Source-Hash: 1a7e8e8f8e9232d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rear" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rear" msgstr "後面" +# English: "Right (override)" # Source-Hash: 8370146b38c30102 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rightOverride" msgstr "右(上書き)" +# English: "Side" # Source-Hash: feefe9c7cb273e79 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.side" msgstr "側面" +# English: "Framed glass opening on a roof segment." # Source-Hash: 67d903d7fe0c69c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framed glass opening on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.description" msgstr "屋根面の枠付きガラス開口。" +# English: "Skylight" # Source-Hash: 358826fbdeaf72b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.label" msgstr "天窓" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: f1113261ad2797de -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along X" +# Source-Hash: f1113261ad2797de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongX" msgstr "X 方向" -# Review: required -# Source-Hash: 5b84b1c644493370 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along Z" +# Source-Hash: 5b84b1c644493370 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongZ" msgstr "Z 方向" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.bottom" msgstr "下" -# Review: required -# Source-Hash: b8ff053666ee326b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.curb" msgstr "立ち上がり" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.cutoutOffset" msgstr "開口のオフセット" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.depth" msgstr "奥行き" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: ce3a6c03765dbbd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.frame" msgstr "枠" -# Review: required -# Source-Hash: 911ea36eb835f3a6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass Thickness" +# Source-Hash: 911ea36eb835f3a6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.glassThickness" msgstr "ガラスの厚さ" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: d56303913961984f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Lantern Height" +# Source-Hash: d56303913961984f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.lanternHeight" msgstr "頂塔の高さ" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.left" msgstr "左" -# Review: required -# Source-Hash: f3e9604e3b85510f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor" +# Source-Hash: f3e9604e3b85510f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motor" msgstr "モーター" -# Review: required -# Source-Hash: a025444e0f500725 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor Housing" +# Source-Hash: a025444e0f500725 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motorHousing" msgstr "モーターハウジング" -# Review: required -# Source-Hash: b2cbc4e2e5545a6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No" +# Source-Hash: b2cbc4e2e5545a6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.no" msgstr "いいえ" -# Review: required -# Source-Hash: a8e0b9fe5cfb4e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Motor" +# Source-Hash: a8e0b9fe5cfb4e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.noMotor" msgstr "モーターなし" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.open" msgstr "開く" -# Review: required -# Source-Hash: 9bfc55f879926fe6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Angle" +# Source-Hash: 9bfc55f879926fe6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.openingAngle" msgstr "開き角度" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.position" msgstr "位置" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.right" msgstr "右" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: 358826fbdeaf72b9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skylight" +# Source-Hash: 358826fbdeaf72b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.skylight" msgstr "天窓" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.thickness" msgstr "厚さ" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.top" msgstr "上" -# Review: required -# Source-Hash: 75991d106eb93dae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Scale" +# Source-Hash: 75991d106eb93dae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.topScale" msgstr "上部の縮尺" -# Review: required -# Source-Hash: 3272a61417949895 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Track Width" +# Source-Hash: 3272a61417949895 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.trackWidth" msgstr "レールの幅" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.type" msgstr "種類" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.width" msgstr "幅" -# Review: required -# Source-Hash: 184fda5c980b9d9c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yes" +# Source-Hash: 184fda5c980b9d9c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.yes" msgstr "はい" -# Review: required -# Source-Hash: 445aacf307de133d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat roof skylight" +# Source-Hash: 445aacf307de133d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.flat" msgstr "陸屋根用天窓" -# Review: required -# Source-Hash: 446176b7c5d8b284 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof lantern" +# Source-Hash: 446176b7c5d8b284 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.lantern" msgstr "頂塔" -# Review: required -# Source-Hash: a6559387f108518f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening skylight" +# Source-Hash: a6559387f108518f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.opening" msgstr "開閉式天窓" -# Review: required -# Source-Hash: 379436bb5a74867a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sliding skylight" +# Source-Hash: 379436bb5a74867a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.sliding" msgstr "スライド式天窓" -# Review: required -# Source-Hash: d26ac61f593fe1bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Walk-on rooflight" +# Source-Hash: d26ac61f593fe1bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.walkOn" msgstr "歩行可能なトップライト" +# English: "A polygon-bounded floor surface that hosts items on top." # Source-Hash: ad8f8036d34f6cea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygon-bounded floor surface that hosts items on top." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.description" msgstr "多角形で囲まれた床面。上にアイテムを置けます。" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.label" msgstr "スラブ" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.addHole" msgstr "開口を追加" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.area" msgstr "面積" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.auto" msgstr "自動" +# English: "Base" # Source-Hash: fadada809e27477c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.base" msgstr "ベース" +# English: "Cut hatch" # Source-Hash: 12195951bba19804 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut hatch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutHatch" msgstr "ハッチを切り抜く" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutOpening" msgstr "開口を切る" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.depth" msgstr "奥行き" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.done" msgstr "完了" +# English: "Elevation" # Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.elevation" msgstr "高さ" +# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." # Source-Hash: 201b720b76d79762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.extendsThePerimeterDownToTerrain" msgstr "外周を地形まで延長します。平面、底面、厚さは変わりません。" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.fixed" msgstr "固定" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.floor" msgstr "階" +# English: "Follows terrain" # Source-Hash: 89b2cfd72a0a7e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.followsTerrain" msgstr "地形に従う" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.foundation" msgstr "基礎" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.holes" msgstr "開口" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.info" msgstr "情報" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.move" msgstr "移動" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.noHoles" msgstr "開口なし" +# English: "Rim" # Source-Hash: c6a29e75bf215488 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.rim" msgstr "縁" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.slab" msgstr "スラブ" +# English: "Standard (2\")" # Source-Hash: fb23c960abf5de23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard2" msgstr "標準(2\")" +# English: "Standard (5cm)" # Source-Hash: 5f865b052c9b40eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (5cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard5cm" msgstr "標準(5cm)" +# English: "Sunken (15cm)" # Source-Hash: cff0e2e72cfab157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken15cm" msgstr "下げ(15cm)" +# English: "Sunken (6\")" # Source-Hash: 8abc2aec4be98fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken6" msgstr "下げ(6\")" +# English: "Surface" # Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.surface" msgstr "面" +# English: "Thick (15cm)" # Source-Hash: 516636d85be919f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick15cm" msgstr "厚(15cm)" +# English: "Thick (6\")" # Source-Hash: c84078f3560da233 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick6" msgstr "厚(6\")" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thickness" msgstr "厚さ" +# English: "Thin (1\")" # Source-Hash: 61c191f6df40285b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (1\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin1" msgstr "薄(1\")" +# English: "Thin (2cm)" # Source-Hash: d345b1f707506fe7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (2cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin2cm" msgstr "薄(2cm)" +# English: "Grid of photovoltaic panels mounted on a roof segment." # Source-Hash: 065ffaaf64ab479d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid of photovoltaic panels mounted on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.description" msgstr "屋根面に設置した太陽光パネルの配列。" +# English: "Solar Panel" # Source-Hash: 4d18446ff8eaa680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.label" msgstr "太陽光パネル" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 2184867946897298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Array" +# Source-Hash: 2184867946897298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.array" msgstr "アレイ" -# Review: required -# Source-Hash: 6eaa0e2a78d44d59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto-fit to roof" +# Source-Hash: 6eaa0e2a78d44d59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.autoFitToRoof" msgstr "屋根に自動フィット" -# Review: required -# Source-Hash: 30edd1c180aac9b4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.columns" msgstr "列" -# Review: required -# Source-Hash: d1c6dd6335337cc1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Custom — dimensions don't match any preset" +# Source-Hash: d1c6dd6335337cc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.customDimensionsDonTMatchAny" msgstr "カスタム — 寸法がどのプリセットとも一致しません" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: fe9356b1d0e0396b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip orientation" +# Source-Hash: fe9356b1d0e0396b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flipOrientation" msgstr "向きを反転" -# Review: required -# Source-Hash: eaaa20f3b603140f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flush" msgstr "面一" -# Review: required -# Source-Hash: fd413477f1a9e5ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameDepth" msgstr "枠の奥行き" -# Review: required -# Source-Hash: 6aa2318fa815e5f2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameThickness" msgstr "枠の厚さ" -# Review: required -# Source-Hash: c5657f721eae3dff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap X" +# Source-Hash: c5657f721eae3dff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapX" msgstr "X 間隔" -# Review: required -# Source-Hash: e6e3b393aa7c0d98 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Y" +# Source-Hash: e6e3b393aa7c0d98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapY" msgstr "Y 間隔" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.mounting" msgstr "取り付け方法" -# Review: required -# Source-Hash: 482469f5a0873f73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel" +# Source-Hash: 482469f5a0873f73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.panel" msgstr "パネル" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.preset" msgstr "プリセット" -# Review: required -# Source-Hash: c913b494c3901a17 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rows" +# Source-Hash: c913b494c3901a17 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.rows" msgstr "行" -# Review: required -# Source-Hash: a4dba2b2675dbba0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Setbacks too large to fit a panel." +# Source-Hash: a4dba2b2675dbba0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.setbacksTooLarge" msgstr "セットバックが大きすぎてパネルを配置できません。" -# Review: required -# Source-Hash: 4d18446ff8eaa680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solar Panel" +# Source-Hash: 4d18446ff8eaa680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.solarPanel" msgstr "太陽光パネル" -# Review: required -# Source-Hash: 0af6194a8b8d8be6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.standoff" msgstr "スタンドオフ" -# Review: required -# Source-Hash: 58cb60c14826f1d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tiltAngle" msgstr "傾斜角度" -# Review: required -# Source-Hash: 787005b26aec58a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilted" +# Source-Hash: 787005b26aec58a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tilted" msgstr "傾斜" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.width" msgstr "幅" -# Review: required -# Source-Hash: cd53f16290b67f7f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compact" +# Source-Hash: cd53f16290b67f7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.compact" msgstr "コンパクト" -# Review: required -# Source-Hash: 317ebe2aa996af70 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frameless" +# Source-Hash: 317ebe2aa996af70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.frameless" msgstr "フレームレス" -# Review: required -# Source-Hash: 46660ad432e19312 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential" +# Source-Hash: 46660ad432e19312 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residential" msgstr "住宅用" -# Review: required -# Source-Hash: b33386b8fe3725fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential Large" +# Source-Hash: b33386b8fe3725fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residentialLarge" msgstr "住宅用(大)" +# English: "Player or camera origin within a level. One per level." # Source-Hash: 638f17c80f424dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Player or camera origin within a level. One per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.description" msgstr "階層内のプレイヤーまたはカメラの起点。階層ごとに1つ。" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.label" msgstr "スポーン地点" +# English: "Body" # Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.body" msgstr "本体" +# English: "Center stringer" # Source-Hash: 29b9fdb127a54435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center stringer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.centerStringer" msgstr "中央の桁" +# English: "Closed risers" # Source-Hash: b2b2383577835a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closed risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.closedRisers" msgstr "蹴込み板あり" -# Source-Hash: 6f6b188bff3c0223 +# English: "From structure" +# Source-Hash: 49a25acbaf33ee3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/construction-controls.tsx +#, fuzzy +msgid "node.stair.constructionControls.fromStructure" +msgstr "構造から" + # English: "Inherit" +# Source-Hash: 6f6b188bff3c0223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.inherit" msgstr "継承" +# English: "Nosing" # Source-Hash: ce25dc4a23f3ae10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nosing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.nosing" msgstr "段鼻" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.open" msgstr "開く" +# English: "Original" # Source-Hash: 7717e0464ad1e7df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.original" msgstr "元のまま" +# English: "Riser thickness" # Source-Hash: 8a6f6c6b0cca7460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.riserThickness" msgstr "蹴込み板の厚さ" +# English: "Side stringers" # Source-Hash: 2fe058966023b542 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side stringers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.sideStringers" msgstr "側桁" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.solid" msgstr "ソリッド" +# English: "Stringer depth" # Source-Hash: 4c1489839e306865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerDepth" msgstr "桁の高さ" +# English: "Stringer width" # Source-Hash: 17e1a09c422d3660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerWidth" msgstr "桁の幅" +# English: "Tread finish" # Source-Hash: 146f8e61ff310dc5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadFinish" msgstr "踏板の仕上げ" +# English: "Tread thickness" # Source-Hash: 9089f91f3fbbf4cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadThickness" msgstr "踏板の厚さ" +# English: "Waist" # Source-Hash: 44dda7136b93c8ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waist" msgstr "スラブ式" +# English: "Waist thickness" # Source-Hash: dfd6d2ff7a94f22e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waistThickness" msgstr "段板の厚さ" +# English: "A stair composed of one or more flights with configurable treads, risers, railings." # Source-Hash: 8535b75637105412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A stair composed of one or more flights with configurable treads, risers, railings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.description" msgstr "1つ以上の段部分で構成され、踏板・蹴込み・手すりを設定できる階段。" +# English: "The flight chain does not reach the declared destination." # Source-Hash: c24b098693e1006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The flight chain does not reach the declared destination." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.arrivalMismatch" msgstr "階段の連なりが指定の接続先に届いていません。" +# English: "Two risers plus going is outside the 0.60–0.65 m design range." # Source-Hash: 124ad1cd6bb00013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Two risers plus going is outside the 0.60–0.65 m design range." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.comfortTarget" msgstr "蹴上げ2つと踏面の合計が設計範囲 0.60–0.65 m を外れています。" +# English: "Going is below the design target." # Source-Hash: 7452fcd8370bef34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Going is below the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.goingTarget" msgstr "踏面が設計目標を下回っています。" +# English: "Overhead surfaces leave less than the target headroom." # Source-Hash: 449683eb727091d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhead surfaces leave less than the target headroom." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomTarget" msgstr "上部の面による頭上空間が目標を下回っています。" +# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." # Source-Hash: 9e9357408f6a0d08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomUnresolved" msgstr "詳細な頭上空間を評価できませんでした。段数と面クエリの上限を確認してください。" +# English: "Flight dimensions and integer riser count must be positive." # Source-Hash: 40fd019d72ff3835 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight dimensions and integer riser count must be positive." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidFlight" msgstr "階段の寸法と整数の段数は正の値である必要があります。" +# English: "The destination must be above the stair base." # Source-Hash: 3d4b6f859bd8de50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The destination must be above the stair base." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidRise" msgstr "接続先は階段の基部より上である必要があります。" +# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." # Source-Hash: 5f196b22e2c8296a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.landingOverlap" msgstr "踊り場の奥行きが歩行線上で一周を超えています。表示される踊り場は一周に制限されます。" +# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." # Source-Hash: 0122ec92b951a114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.narrowWinderEnd" msgstr "回り段の狭い側が 0.10 m 未満です。階段の用途を確認してください。" +# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." # Source-Hash: a664dc7f3b014af8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.nonuniformRisers" msgstr "階段の蹴上げがそろっていません。均一化の修正は計画中の設計の場合にのみ使ってください。" +# English: "A landing has a stored rise; inspect its transition before repair." # Source-Hash: cd140b519d37b01e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A landing has a stored rise; inspect its transition before repair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.raisedLanding" msgstr "踊り場に高さが保存されています。修正前に接続部を確認してください。" +# English: "Riser height exceeds the design target." # Source-Hash: a94cfa7b2a21cf14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser height exceeds the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.riserTarget" msgstr "蹴上げが設計目標を超えています。" +# English: "Stair" # Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.label" msgstr "階段" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.straight" msgstr "直進" +# English: "{layout} turn" # Source-Hash: 1cda16cb49e6fb93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{layout} turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.turn" msgstr "{layout} 折り返し" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.actions" msgstr "操作" +# English: "Add flight" # Source-Hash: 15eaf25f306541f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addFlight" msgstr "階段を追加" +# English: "Add landing" # Source-Hash: 1fa37c346bffb8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addLanding" msgstr "踊り場を追加" +# English: "Apply {label} layout" # Source-Hash: 9ccbd6c1785f1e73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply {label} layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.applyLabelLayout" msgstr "{label}レイアウトを適用" +# English: "Auto Cutout" # Source-Hash: 7eb42ea0fc442b8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto Cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.autoCutout" msgstr "自動切り抜き" +# English: "Balusters" # Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.balusters" msgstr "手すり子" +# English: "Boards" # Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.boards" msgstr "板" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.both" msgstr "両方" +# English: "Cable" # Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.cable" msgstr "ケーブル" +# English: "Center Column" # Source-Hash: 11d6eec5d485488d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.centerColumn" msgstr "中央の柱" +# English: "Clockwise" # Source-Hash: 5fbbf4ad24befa7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.clockwise" msgstr "時計回り" +# English: "Connected to {surface}. Move the stair to connect to another edge." # Source-Hash: 31028ca51b49018b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to {surface}. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedTo" msgstr "{surface}に接続されています。階段を移動すると別の辺に接続できます。" +# English: "Connected to a surface. Move the stair to connect to another edge." # Source-Hash: 33133921e270509f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to a surface. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedToSurface" msgstr "面に接続されています。階段を移動すると別の辺に接続できます。" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.construction" msgstr "構造" +# English: "Counterclockwise" # Source-Hash: 5e3e3ab7d91c2c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Counterclockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.counterclockwise" msgstr "反時計回り" +# English: "Curved" # Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.curved" msgstr "曲線" +# English: "Custom rise" # Source-Hash: 0bd4232a56d25366 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.customRise" msgstr "カスタムの蹴上げ" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.deck" msgstr "デッキ" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.delete" msgstr "削除" +# English: "Design targets" # Source-Hash: 1c34e5cc37add163 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Design targets" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.designTargets" msgstr "設計目標" +# English: "Destination" # Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.destination" msgstr "接続先" +# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." # Source-Hash: 1fa48e82752d1505 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.dragNearADeckPatioConcrete" msgstr "デッキ、パティオ、コンクリートスラブ、踊り場の端の近くにドラッグして階段を合わせます。" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.duplicate" msgstr "複製" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalAngle" msgstr "等角" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalGoing" msgstr "踏面を均等に" +# English: "Fit risers and going" # Source-Hash: 6ff728d5058c7e31 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit risers and going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitRisersAndGoing" msgstr "蹴上げと踏面を合わせる" +# English: "Fit To Floor" # Source-Hash: d60a4c7995bd2184 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit To Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitToFloor" msgstr "床に合わせる" +# English: "Fit uniform risers" # Source-Hash: 0638614580661eb0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit uniform risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitUniformRisers" msgstr "均一な蹴上げに合わせる" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" # Source-Hash: 4d365dbc1fb92b90 # Review: required -# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.flightMeasurements" msgstr "階段 {number}:{count, plural, other {{riserCount} 段}} · 蹴上げ {riser} · 踏面 {going} · {slope}" +# English: "Floors, ceilings and stairs only." # Source-Hash: 6e946bd1acd94637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floors, ceilings and stairs only." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.floorsCeilingsAndStairsOnly" msgstr "床、天井、階段のみ。" +# English: "Follows deck" # Source-Hash: fa10ea42944107a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsDeck" msgstr "デッキに追従" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsLevel" msgstr "階層に従う" +# English: "From Level" # Source-Hash: b7fc627a965bc016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fromLevel" msgstr "開始階層" +# English: "Geometry" # Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.geometry" msgstr "ジオメトリ" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.glass" msgstr "ガラス" +# English: "Headroom:" # Source-Hash: 55adba80c39500f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Headroom:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.headroom" msgstr "頭上空間:" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.height" msgstr "高さ" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerGap" msgstr "内側の隙間" +# English: "Inner Radius" # Source-Hash: 8b54356df7ae40bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerRadius" msgstr "内半径" +# English: "Integrated" # Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.integrated" msgstr "一体型" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.landing" msgstr "踊り場" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.layout" msgstr "レイアウト" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.left" msgstr "左" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.leftTurn" msgstr "左折れ" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.measurements" msgstr "計測" +# English: "Metal" # Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.metal" msgstr "金属" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.move" msgstr "移動" +# English: "Moving this stair keeps its current rise, steps, and run." # Source-Hash: a4f1cc34aae4db27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Moving this stair keeps its current rise, steps, and run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.movingThisStairKeepsItsCurrent" msgstr "この階段を移動しても現在の高さ、段数、水平長は変わりません。" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.none" msgstr "なし" +# English: "No overhead surface" # Source-Hash: 47c858b3022b3ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No overhead surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.noOverheadSurface" msgstr "上部に面がありません" +# English: "Not evaluated" # Source-Hash: 17f1933d93cf655c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not evaluated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.notEvaluated" msgstr "未評価" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.opening" msgstr "開口" +# English: "Opening Offset" # Source-Hash: 599220556fc4bbfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.openingOffset" msgstr "開口のオフセット" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.position" msgstr "位置" +# English: "Post & rail" # Source-Hash: f522323372a7a59e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post & rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.postRail" msgstr "支柱とレール" +# English: "Railing" # Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.railing" msgstr "手すり" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.right" msgstr "右" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rightTurn" msgstr "右折れ" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rise" msgstr "高さ" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rotation" msgstr "回転" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segments" msgstr "セグメント" +# English: "{type, select, landing {Landing} other {Stair}}" # Source-Hash: 0b9a0bef102b75bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{type, select, landing {Landing} other {Stair}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segmentType" msgstr "{type, select, landing {踊り場} other {階段}}" +# English: "Sizing" # Source-Hash: 87b926bbf34a78ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sizing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sizing" msgstr "サイズ設定" +# English: "Snap to nearby surfaces" # Source-Hash: 8a0f4562198c266f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap to nearby surfaces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.snapToNearbySurfaces" msgstr "近くの面にスナップ" +# English: "Spiral" # Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.spiral" msgstr "らせん" +# English: "Staircase" # Source-Hash: f8d4f66145e1eba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.staircase" msgstr "階段" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.steps" msgstr "段" +# English: "Step Supports" # Source-Hash: 8846dc96332c5181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step Supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.stepSupports" msgstr "段の支持材" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.straight" msgstr "直進" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.style" msgstr "スタイル" +# English: "Surface connection" # Source-Hash: cde3878287069df2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.surfaceConnection" msgstr "面との接続" +# English: "Sweep" # Source-Hash: 4e0f8b363503efff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sweep" msgstr "スイープ" +# English: "Target going" # Source-Hash: 4c294d88d712855a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetGoing" msgstr "目標の踏面" +# English: "Target headroom" # Source-Hash: 74cfba22cf2e6eb8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetHeadroom" msgstr "目標の頭上空間" +# English: "Target max riser" # Source-Hash: 9d05958a265a6726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target max riser" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMaxRiser" msgstr "目標の最大蹴上げ" +# English: "Target minimum going" # Source-Hash: d10322b16178ce37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target minimum going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMinimumGoing" msgstr "目標の最小踏面" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.thickness" msgstr "厚さ" +# English: "To" # Source-Hash: b3214a20e45a7aed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.to" msgstr "終点" +# English: "Top Landing" # Source-Hash: 846fc58043454194 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.topLanding" msgstr "上部の踊り場" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.type" msgstr "種類" +# English: "Unavailable" # Source-Hash: b4c2f503b19443c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.unavailable" msgstr "利用不可" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.walkingLineOffset" msgstr "歩行線のオフセット" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.width" msgstr "幅" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.winder" msgstr "回り段" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.both" msgstr "両方" +# English: "Bottom sloped extension" # Source-Hash: fe633d20f198d7e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom sloped extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.bottomExtension" msgstr "下部の傾斜延長" +# English: "Continuous" # Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.continuous" msgstr "連続" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.floor" msgstr "階" +# English: "Handrail diameter" # Source-Hash: 644d2d9e9c3dd31f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailDiameter" msgstr "手すりの径" +# English: "Handrail height" # Source-Hash: 5f3f2be1e572a066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailHeight" msgstr "手すりの高さ" +# English: "Handrail inset" # Source-Hash: cb3239847f068a1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailInset" msgstr "手すりの内側オフセット" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.left" msgstr "左" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.none" msgstr "なし" +# English: "No return" # Source-Hash: e6ac8d7c9639c351 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.noReturn" msgstr "返しなし" +# English: "Original layout" # Source-Hash: 5688b2003002d03a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.originalLayout" msgstr "元のレイアウト" +# English: "Post-facing" # Source-Hash: d0d904fbd052d7b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post-facing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.postFacing" msgstr "支柱側" +# English: "Return length" # Source-Hash: fc4d8c6f156b0430 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.returnLength" msgstr "返しの長さ" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.right" msgstr "右" +# English: "Separate handrail" # Source-Hash: e1f8354a95b5060a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate handrail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.separateHandrail" msgstr "独立した手すり" +# English: "Set the length to reach the wall or post." # Source-Hash: 005f0197ec3bc95a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set the length to reach the wall or post." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.setTheLengthToReachThe" msgstr "壁または支柱に届く長さに設定します。" +# English: "Top horizontal extension" # Source-Hash: bef713f3ec96f758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top horizontal extension" -#: packages/nodes/src/stair/railing-controls.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/railing-controls.tsx +#, fuzzy +msgid "node.stair.railingControls.topExtension" +msgstr "上部の水平延長" + +# English: "Wall-facing" +# Source-Hash: bdad79929de6b142 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/railing-controls.tsx +#, fuzzy +msgid "node.stair.railingControls.wallFacing" +msgstr "壁側" + +# English: "A solid poured flight on a footing; risers closed, no stringers." +# Source-Hash: 1709ce71eba750ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.aSolidPouredFlightOnA" +msgstr "フーチングの上に一体で打設した階段です。蹴込みは閉じ、桁はありません。" + +# English: "Auto ({autoCount})" +# Source-Hash: 56be143b76f65392 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoAutocount" +msgstr "自動({autoCount})" + +# English: "Auto (closed)" +# Source-Hash: d7820aaac08e4fcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoClosed" +msgstr "自動(あり)" + +# English: "Auto (open)" +# Source-Hash: c7cbd493df719e71 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoOpen" +msgstr "自動(なし)" + +# English: "Auto (pressure-treated)" +# Source-Hash: be21258d52930d5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoPressureTreated" +msgstr "自動(加圧防腐処理)" + +# English: "Auto ({treads})" +# Source-Hash: 1828540d95a5b5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoTreads" +msgstr "自動({treads})" + +# English: "Auto (untreated)" +# Source-Hash: 43c1949fa53685b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoUntreated" +msgstr "自動(未処理)" + +# English: "Closed (1x8 boards)" +# Source-Hash: d0a3ca3ce29ae833 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.closed1x8Boards" +msgstr "あり(1x8 板)" + +# English: "Concrete" +# Source-Hash: 824471a3691b51db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.concrete" +msgstr "コンクリート" + +# English: "{count, plural, one {# steel channel} other {# steel channels}}" +# Source-Hash: dcb2dd54c3301068 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannels" +msgstr "{count, plural, other {溝形鋼 # 本}}" + +# English: "{count, plural, one {# steel channel at {spacing} o.c.} other {# steel channels at {spacing} o.c.}}" +# Source-Hash: fbf12088b828a103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannelsAtSpacing" +msgstr "{count, plural, other {溝形鋼 # 本(芯々 {spacing})}}" + +# English: "{count, plural, one {# stringer} other {# stringers}}" +# Source-Hash: 6f0f06f0531c7e96 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringers" +msgstr "{count, plural, other {桁 # 本}}" + +# English: "{count, plural, one {# stringer at {spacing} o.c.} other {# stringers at {spacing} o.c.}}" +# Source-Hash: cfdb3f3fc95ea940 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringersAtSpacing" +msgstr "{count, plural, other {桁 # 本(芯々 {spacing})}}" + +# English: "Cut" +# Source-Hash: fa4b97d3aa600bcd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.cut" +msgstr "ささら桁" + +# English: "Flight {number}: {layout}" +# Source-Hash: ba4f3c1346b3e7af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.flightLayout" +msgstr "階段 {number}:{layout}" + +# English: "Housed sides" +# Source-Hash: 4cbb34fdf4c1e107 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.housedSides" +msgstr "側桁(彫り込み)" + +# English: "{inches} in o.c." +# Source-Hash: e3ddff241f642295 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.inchesInOC" +msgstr "芯々 {inches} in" + +# English: "{layout} (auto)" +# Source-Hash: 542ca0bee99a1533 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. The stringer count was chosen automatically, not set by hand. +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.layoutAuto" +msgstr "{layout}(自動)" + +# English: "Lumber" +# Source-Hash: 73dcfae611575500 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.lumber" +msgstr "木材" + +# English: "Max spacing" +# Source-Hash: d10743fce478d9f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.maxSpacing" +msgstr "最大間隔" + +# English: "Nosing" +# Source-Hash: ce25dc4a23f3ae10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.nosing" +msgstr "段鼻" + +# English: "One {stock}" +# Source-Hash: ba534b07476bc06d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.oneStock" +msgstr "{stock} 1 枚" + +# English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.open" +msgstr "なし" + +# English: "Original body" +# Source-Hash: 20f023962f848e14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.originalBody" +msgstr "元の本体" + +# English: "Plywood + finish" +# Source-Hash: ac658d76a942652f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.plywoodFinish" +msgstr "合板 + 仕上げ" + +# English: "Pressure-treated" +# Source-Hash: 86fda85cdc89fd27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.pressureTreated" +msgstr "加圧防腐処理" + +# English: "Risers" +# Source-Hash: d903bba423ddb755 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.risers" +msgstr "蹴込み板" + +# English: "Steel stringers" +# Source-Hash: ff0400a550f24adf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.steelStringers" +msgstr "鋼製の桁" + +# English: "Stringer count" +# Source-Hash: 50d4cac7f3002cd2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerCount" +msgstr "桁の本数" + +# English: "Stringers" +# Source-Hash: 71733e5b479af2a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringers" +msgstr "桁" + +# English: "Stringer type" +# Source-Hash: 191196b94d1c86f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerType" +msgstr "桁の種類" + +# English: "Structure" +# Source-Hash: a20c89205a4448d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.structure" +msgstr "構造" + +# English: "Treads" +# Source-Hash: 869e3785611a0858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.treads" +msgstr "踏板" + +# English: "Two {stock}" +# Source-Hash: e1e6555b827b4620 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.twoStock" +msgstr "{stock} 2 枚" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx #, fuzzy -msgid "node.stair.railingControls.topExtension" -msgstr "上部の水平延長" +msgid "node.stair.structureControls.untreated" +msgstr "未処理" -# Source-Hash: bdad79929de6b142 +# English: "Wood stringers" +# Source-Hash: 401bae5ffee978fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall-facing" -#: packages/nodes/src/stair/railing-controls.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx #, fuzzy -msgid "node.stair.railingControls.wallFacing" -msgstr "壁側" +msgid "node.stair.structureControls.woodStringers" +msgstr "木製の桁" +# English: "A single flight of a parent stair." # Source-Hash: ce0b6361eb77e93c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single flight of a parent stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.description" msgstr "親階段の1つの段部分。" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.label" msgstr "階段の段部分" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.actions" msgstr "操作" +# English: "Attachment" # Source-Hash: 551b04c03b07595e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.attachment" msgstr "接続" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.delete" msgstr "削除" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.dimensions" msgstr "寸法" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.duplicate" msgstr "複製" +# English: "Editing switches the stair to Custom rise" # Source-Hash: ad063fb27f370c28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Editing switches the stair to Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.editingSwitchesTheStairToCustom" msgstr "編集すると階段はカスタムの蹴上げに切り替わります" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalAngle" msgstr "等角" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalGoing" msgstr "踏面を均等に" +# English: "Fill to floor" # Source-Hash: 66f1018b50c2e426 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.fillToFloor" msgstr "床まで埋める" +# English: "Flight" # Source-Hash: deff4254bb163b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flight" msgstr "階段" +# English: "Flight shape" # Source-Hash: d6da828cd0c5e8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flightShape" msgstr "階段の形状" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.front" msgstr "前面" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.height" msgstr "高さ" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.innerGap" msgstr "内側の隙間" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.landing" msgstr "踊り場" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.left" msgstr "左" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.leftTurn" msgstr "左折れ" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.length" msgstr "長さ" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.move" msgstr "移動" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.position" msgstr "位置" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.right" msgstr "右" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rightTurn" msgstr "右折れ" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rotation" msgstr "回転" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.stairSegment" msgstr "階段の段部分" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.steps" msgstr "段" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.straight" msgstr "直進" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.structure" msgstr "構造" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.thickness" msgstr "厚さ" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.type" msgstr "種類" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.walkingLineOffset" msgstr "歩行線のオフセット" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.width" msgstr "幅" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.winder" msgstr "回り段" +# English: "Persistent construction grid axis with identification bubbles." # Source-Hash: 5434a3189441c913 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Persistent construction grid axis with identification bubbles." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.description" msgstr "符号付きバブルのある常設の通り芯。" +# English: "Structural Grid" # Source-Hash: 0cfe636f7fdd9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.label" msgstr "構造通り芯" +# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." # Source-Hash: 36b714210f25c284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.description" msgstr "屋根勾配用の風力で回るタービン排気口。" +# English: "Turbine Vent" # Source-Hash: 4a855abb962f5e7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.label" msgstr "タービン換気扇" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: c65f02bcdc3231d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.cylinder" msgstr "円柱" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.delete" msgstr "削除" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.diameter" msgstr "直径" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.dimensions" msgstr "寸法" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.duplicate" msgstr "複製" -# Review: required -# Source-Hash: 2d4223f80dc3945c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Globe" +# Source-Hash: 2d4223f80dc3945c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.globe" msgstr "グローブ" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.height" msgstr "高さ" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.motion" msgstr "動き" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.move" msgstr "移動" -# Review: required -# Source-Hash: 6faf9532dcbfe4bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Height" +# Source-Hash: 6faf9532dcbfe4bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.neckHeight" msgstr "首の高さ" -# Review: required -# Source-Hash: 1550d72740e66f77 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pause" +# Source-Hash: 1550d72740e66f77 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.pause" msgstr "一時停止" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.play" msgstr "再生" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.rotation" msgstr "回転" -# Review: required -# Source-Hash: bf553d40ce68834d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spin Speed" +# Source-Hash: bf553d40ce68834d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.spinSpeed" msgstr "回転速度" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.style" msgstr "スタイル" -# Review: required -# Source-Hash: 4a855abb962f5e7a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Turbine Vent" +# Source-Hash: 4a855abb962f5e7a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.turbineVent" msgstr "タービン換気扇" -# Review: required -# Source-Hash: 80cef0dce71e121f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vanes" +# Source-Hash: 80cef0dce71e121f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.vanes" msgstr "羽根" +# English: "A named group of zones forming one apartment, hotel room or commercial lot." # Source-Hash: e3d2aaaf5c72bc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A named group of zones forming one apartment, hotel room or commercial lot." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.description" msgstr "1つの住戸、客室、テナント区画を構成する名前付きゾーンのグループ。" +# English: "Apartment" # Source-Hash: 40e5a51d0d205fcf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apartment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.apartment" msgstr "アパート" +# English: "Commercial" # Source-Hash: ad041d9634dd55ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Commercial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.commercial" msgstr "商業" +# English: "Common" # Source-Hash: 862f30cb2d49f812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Common" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.common" msgstr "共用" +# English: "Hotel room" # Source-Hash: cd7e1bc42d47a105 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hotel room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.hotelRoom" msgstr "ホテル客室" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.label" msgstr "住戸" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberRoom" msgstr "部屋" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberZone" msgstr "ゾーン" +# English: "Members sit on levels that are not adjacent." # Source-Hash: e25d5b5630b9d8df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members sit on levels that are not adjacent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.nonAdjacentLevels" msgstr "メンバーが隣接していない階層にあります。" +# English: "No zones assigned yet." # Source-Hash: 5ebeb440e0e64d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones assigned yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.noZonesAssigned" msgstr "ゾーンはまだ割り当てられていません。" +# English: "Remove {name} from unit" # Source-Hash: 07731fa851c75d8c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove {name} from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeMember" msgstr "{name}をユニットから外す" +# English: "Remove zone from unit" # Source-Hash: 389839734c8350b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove zone from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeZone" msgstr "ゾーンをユニットから外す" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.actions" msgstr "操作" +# English: "Color" # Source-Hash: fe18958cedd627d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.color" msgstr "色" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.delete" msgstr "削除" +# English: "Focus" # Source-Hash: b80dc93401f953a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focus" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.focus" msgstr "フォーカス" +# English: "Gross area" # Source-Hash: 1218205b3bb34f1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gross area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.grossArea" msgstr "総面積" +# English: "Kind" # Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kind" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.kind" msgstr "種類" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.levels" msgstr "階層" +# English: "Members" # Source-Hash: 6bbc51529519435c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.members" msgstr "メンバー" +# English: "Name" # Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.name" msgstr "名前" +# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." # Source-Hash: 54b25f70c7b63446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.noRoomsYetPickThisUnit" msgstr "部屋はまだありません。部屋のパネルからこのユニットを選ぶか、フォーカス中にゾーンを描いてください。" +# English: "Report" # Source-Hash: 85d640066187108d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Report" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.report" msgstr "レポート" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.unit" msgstr "住戸" -# Source-Hash: d53242481ff46157 +# English: "Dimensions to the exterior faces (outside size). Click for both" +# Source-Hash: e04a35319a83ad7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheExteriorFacesOutside" +msgstr "外面までの寸法(外形寸法)。クリックで両方に切り替え" + +# English: "Dimensions to the interior and exterior faces. Click for interior only" +# Source-Hash: 41c6c329ecb15086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorAndExterior" +msgstr "内面と外面までの寸法。クリックで内法のみに切り替え" + +# English: "Dimensions to the interior faces (room size). Click for exterior only" +# Source-Hash: 57524fee08ddde73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorFacesRoom" +msgstr "内面までの寸法(部屋の大きさ)。クリックで外法のみに切り替え" + +# English: "ID" +# Source-Hash: 81ef3a25ccb6eaf7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: interior dimension, measured to the interior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.id" +msgstr "内法" + +# English: "ID+OD" +# Source-Hash: e846b1fcc616fb6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: ID is the interior dimension, OD the outside dimension. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.idOd" +msgstr "内法+外法" + # English: "Merge walls" +# Source-Hash: d53242481ff46157 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.mergeWalls" msgstr "壁を結合" -# Source-Hash: d7ead4708d50965f +# English: "OD" +# Source-Hash: fdaa3b3faa1844fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: outside dimension, measured to the exterior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.od" +msgstr "外法" + # English: "Split wall" +# Source-Hash: d7ead4708d50965f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.splitWall" msgstr "壁を分割" -# Source-Hash: 96a6f084d095ea36 +# English: "These walls cannot be merged." +# Source-Hash: 2f8c3189a0b3972b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.theseWallsCannotBeMerged" +msgstr "これらの壁は結合できません。" + # English: "Column {column}, row {row}: {type, select, glass {glass} solid {solid} other {empty}}" +# Source-Hash: 96a6f084d095ea36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.cell" msgstr "{column} 列 {row} 行:{type, select, glass {ガラス} solid {パネル} other {空}}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" # Source-Hash: 904e9b269b68c670 # Review: required -# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.columnSpacing" msgstr "{count, plural, other {{number} 列}} · グリッド間隔 {spacing}" +# English: "No panels" # Source-Hash: 8b2b2f11fca9f077 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.noPanels" msgstr "パネルなし" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" # Source-Hash: 9f5d9775377b666e # Review: required -# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.rowSpacing" msgstr "{count, plural, other {{number} 行}} · グリッド間隔 {spacing}" +# English: "Apply tint" # Source-Hash: 90638d5a19a4f0a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.applyTint" msgstr "色合いを適用" +# English: "Border / opening frame" # Source-Hash: a7d624ec0f879829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Border / opening frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.borderOpeningFrame" msgstr "縁 / 開口枠" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottom" msgstr "下" +# English: "Bottom row" # Source-Hash: 5503fb89c40cefeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottomRow" msgstr "最下段" +# English: "Cancel tint" # Source-Hash: 1928d9e7a16360d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.cancelTint" msgstr "色合いを取り消す" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.center" msgstr "中央" +# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." # Source-Hash: 9f4c033fa80119db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.chooseACellOrEnterIts" msgstr "セルを選ぶか、列と行を入力してください。矢印キーでセル間を移動します。列は壁の始点から、行は下から数えます。" +# English: "Clear hidden overrides" # Source-Hash: bbeadcebc59c923f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear hidden overrides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.clearHiddenOverrides" msgstr "非表示の上書きをクリア" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.column" msgstr "柱" +# English: "Column layout" # Source-Hash: 711a6426e336877a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnLayout" msgstr "列のレイアウト" +# English: "Column {column}, row {row}" # Source-Hash: 59b2ab5fdf54c2e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column {column}, row {row}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnRow" msgstr "{column} 列 {row} 行" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.construction" msgstr "構造" +# English: "Continuous mullions and transoms assembled along the wall." # Source-Hash: 59d2bec8b9ae1025 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous mullions and transoms assembled along the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong" msgstr "壁に沿って組み立てた連続する方立と無目。" +# English: "Curtain wall panel selection" # Source-Hash: bbc5e5f3f3b99232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall panel selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.curtainWallPanelSelection" msgstr "カーテンウォールのパネル選択" +# English: "Custom infill" # Source-Hash: 9fd79cf417eab9ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.customInfill" msgstr "カスタム充填" +# English: "Default panel" # Source-Hash: 097ed751fa47e5fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.defaultPanel" msgstr "既定のパネル" +# English: "Edit individual panels" # Source-Hash: 0a0c98ae42f519ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.editIndividualPanels" msgstr "パネルを個別に編集" +# English: "Effective thickness: {thickness}, limited by frame depth." # Source-Hash: 8690f82f64806e4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Effective thickness: {thickness}, limited by frame depth." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.effectiveThicknessLimited" msgstr "有効厚さ:{thickness}(枠の奥行きで制限)。" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.empty" msgstr "空" +# English: "Enable panel editing" # Source-Hash: c6e7792839d09c69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable panel editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.enablePanelEditing" msgstr "パネル編集を有効化" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.end" msgstr "終点" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fixedSpacing" msgstr "固定間隔" +# English: "Following wall defaults" # Source-Hash: 1c851bde836b3b57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Following wall defaults" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.followingWallDefaults" msgstr "壁の既定値に従う" +# English: "Frame dimensions" # Source-Hash: 49be990b89741501 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.frameDimensions" msgstr "枠の寸法" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.framing" msgstr "下地" +# English: "Fully capped" # Source-Hash: 221f418784836f11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fully capped" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fullyCapped" msgstr "完全に覆う" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glass" msgstr "ガラス" +# English: "Glass appearance" # Source-Hash: a17c63662aa4404d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearance" msgstr "ガラスの外観" +# English: "Glass appearance is controlled by the paint tool." # Source-Hash: f59c4938a2116a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance is controlled by the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearanceIsControlledByThe" msgstr "ガラスの外観はペイントツールで設定します。" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassTint" msgstr "ガラスの色合い" +# English: "Glazing gap" # Source-Hash: 7660a6aa7e7cd365 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glazingGap" msgstr "ガラスの隙間" +# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." # Source-Hash: 6e739c8a698efaab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.gridLimitedTo32PanelsPer" msgstr "グリッドは各軸 32 パネルまでです。この壁に合わせて間隔が広がります。" +# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" # Source-Hash: 1c63255b675bbcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.hiddenCustomPanelCount" msgstr "{length, plural, other {{number} 個のカスタムパネルがこのグリッドの外にあります。グリッドを広げると戻ります。}}" +# English: "Horizontal caps" # Source-Hash: 84af317e818c10cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalCaps" msgstr "水平キャップ" +# English: "Horizontal frame width" # Source-Hash: 7a71f6b25c5dc718 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalFrameWidth" msgstr "水平枠の幅" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.layout" msgstr "レイアウト" +# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." # Source-Hash: fc37833ef19c8feb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness" msgstr "粗さが低いと滑らか、高いとレンダリングでマットな表面になります。" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.maximumSpacing" msgstr "最大間隔" +# English: "Module joint width" # Source-Hash: 1452f51ec5dfc6ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Module joint width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.moduleJointWidth" msgstr "モジュール目地の幅" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.none" msgstr "なし" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opacity" msgstr "不透明度" +# English: "Opaque band" # Source-Hash: 0f9ba67daa6b4625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque band" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBand" msgstr "不透明帯" +# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." # Source-Hash: fdf533d6592eff60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow" msgstr "不透明帯は1行全体を不透明パネルに置き換えます。仕上げはペイントツールで適用します。" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelCount" msgstr "パネル数" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelInfill" msgstr "パネルの充填" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panels" msgstr "パネル" +# English: "Panel thickness" # Source-Hash: d24fe036670f54a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelThickness" msgstr "パネルの厚さ" +# English: "Reset this panel" # Source-Hash: a780f13a732bab88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset this panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.resetThisPanel" msgstr "このパネルをリセット" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.row2" msgstr "行" +# English: "Row layout" # Source-Hash: 0e18ec19d0e4ea93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.rowLayout" msgstr "行のレイアウト" +# English: "Separate framed modules with joints between units." # Source-Hash: dfb58bbd8932811d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate framed modules with joints between units." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween" msgstr "ユニット間に目地のある独立した枠付きモジュール。" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.solid" msgstr "ソリッド" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.spacing" msgstr "間隔" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.start" msgstr "始点" +# English: "Stick-built" # Source-Hash: d3d738ccbc7103ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stick-built" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.stickBuilt" msgstr "現場組み" +# English: "Structural glazing" # Source-Hash: fbdb9260ee302d3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural glazing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.structuralGlazing" msgstr "構造ガラス" +# English: "Surface roughness" # Source-Hash: 393d5c5ec4e7014c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.surfaceRoughness" msgstr "表面の粗さ" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.top" msgstr "上" +# English: "Top row" # Source-Hash: 51aa1c35a1761178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.topRow" msgstr "最上段" +# English: "Uncapped members sit behind the glazing." # Source-Hash: 1181faa315f26c83 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncapped members sit behind the glazing." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing" msgstr "キャップなしの部材はガラスの裏側にあります。" +# English: "Unitized" # Source-Hash: e68eeb4033268842 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unitized" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.unitized" msgstr "ユニット式" +# English: "Use glass settings instead" # Source-Hash: 355b469b8a9c42f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use glass settings instead" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useGlassSettingsInstead" msgstr "代わりにガラスの設定を使う" +# English: "Use the Door or Window tool to add an opening through the wall." # Source-Hash: 9d75fad413e547e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use the Door or Window tool to add an opening through the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useTheDoorOrWindowTool" msgstr "ドアまたは窓ツールで壁に開口を追加します。" +# English: "Vertical caps" # Source-Hash: c1fa8c24203a5dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalCaps" msgstr "垂直キャップ" +# English: "Vertical frame width" # Source-Hash: 0560516882b93f0d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalFrameWidth" msgstr "垂直枠の幅" +# English: "Wall system" # Source-Hash: 4b903041c38cdd72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.wallSystem" msgstr "壁システム" +# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." # Source-Hash: 6c753f6234766116 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.description" msgstr "直線または曲線の壁。ドア、窓、差し掛け、壁付けアイテムを取り付けられます。" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.label" msgstr "壁" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.actions" msgstr "操作" +# English: "Assembly" # Source-Hash: 67d2ec01cdce2bdc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.assembly" msgstr "構成" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.auto" msgstr "自動" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.both" msgstr "両方" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.bottom" msgstr "下" +# English: "Brick veneer" # Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brick veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.brickVeneer" msgstr "レンガ張り" +# English: "Chair rail" # Source-Hash: 32d0bfee628c42a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chair rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.chairRail" msgstr "チェアレール" +# English: "CMU" # Source-Hash: a85f39db8f92f8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "CMU" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cmu" msgstr "CMU" +# English: "Colonial" # Source-Hash: f76e125a47ed0612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Colonial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.colonial" msgstr "コロニアル" +# English: "Cove" # Source-Hash: a4962c0f39d629ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cove" msgstr "コーブ" +# English: "Craft" # Source-Hash: e5d425095ade6f06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.craft" msgstr "クラフト" +# English: "Crown molding" # Source-Hash: fa8a6b7f2f4eee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crown molding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.crownMolding" msgstr "廻り縁" +# English: "Curtain wall" # Source-Hash: 9736e0a78294e68d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curtainWall" msgstr "カーテンウォール" +# English: "Curve" # Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curve" msgstr "曲線" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.custom" msgstr "カスタム" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.customHeight" msgstr "カスタムの高さ" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.depth" msgstr "奥行き" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.dimensions" msgstr "寸法" +# English: "Drywall" # Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drywall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.drywall" msgstr "石膏ボード" +# English: "Extends downward to meet the terrain. Height and top stay unchanged." # Source-Hash: d6ba089e1ca40cdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends downward to meet the terrain. Height and top stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.extendsDownwardToMeetTheTerrain" msgstr "下方向に地形まで延長します。高さと上面は変わりません。" +# English: "Exterior" # Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exterior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.exterior" msgstr "外部" +# English: "Fiber cement" # Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fiber cement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fiberCement" msgstr "繊維セメント" +# English: "Fill to terrain" # Source-Hash: a6be391d580de5cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fillToTerrain" msgstr "地形まで埋める" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.flat" msgstr "陸屋根" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.followsLevel" msgstr "階層に従う" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.frameDepth" msgstr "枠の奥行き" -# Source-Hash: c7e72beff9fe4b37 +# English: "Framer's default" +# Source-Hash: cb22c8e4b0d92234 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.framersDefault" +msgstr "施工者の標準" + # English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.framing" msgstr "下地" +# English: "Gypsum" # Source-Hash: eda78e8a76f068d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gypsum" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.gypsum" msgstr "石膏" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.height" msgstr "高さ" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.hideCode" msgstr "コードを隠す" +# English: "ICF" # Source-Hash: 0c6dcbbbda6b4dbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ICF" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.icf" msgstr "ICF" +# English: "Interior" # Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Interior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.interior" msgstr "内部" +# English: "Lap siding" # Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lap siding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.lapSiding" msgstr "下見板張り" +# English: "Layered" # Source-Hash: 297b21e90e8c25f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layered" msgstr "多層" +# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." # Source-Hash: bf279d0f8f1e3b78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layerThicknessesFollowThe2021Irc" msgstr "層の厚さは 2021 IRC の構成データに基づきます。作図の補助であり構造計算ではありません。所管官庁に確認してください。" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.length" msgstr "長さ" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.loading" msgstr "読み込み中…" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.modern" msgstr "モダン" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.none" msgstr "なし" +# English: "None (single layer)" # Source-Hash: c7300ca33851fba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None (single layer)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.noneSingleLayer" msgstr "なし(単層)" +# English: "Offset" # Source-Hash: 0f0e13abf9b2bb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.offset" msgstr "オフセット" +# English: "Ogee" # Source-Hash: 04b3006bd4e6e207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ogee" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.ogee" msgstr "オジー" +# English: "OSB" # Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "OSB" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.osb" msgstr "OSB" +# English: "Partition: the interior finish is applied to both faces." # Source-Hash: 1243161deca7d470 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partition: the interior finish is applied to both faces." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.partitionTheInteriorFinishIsApplied" msgstr "間仕切り:内装仕上げが両面に適用されます。" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.picture" msgstr "額縁" +# English: "Plaster" # Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plaster" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plaster" msgstr "漆喰" +# English: "Plywood" # Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plywood" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plywood" msgstr "合板" +# English: "Preset" # Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.preset" msgstr "プリセット" -# Source-Hash: 62bf2677b9ec30fc +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.railing" +msgstr "手すり" + # English: "Reference" +# Source-Hash: 62bf2677b9ec30fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.reference" msgstr "参照" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.remove" msgstr "削除" +# English: "Round" # Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.round" msgstr "丸" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.scriptedFinish" msgstr "スクリプトの仕上げ" +# English: "Sheathing" # Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheathing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sheathing" msgstr "下張り" +# English: "Shoe" # Source-Hash: 6ae9aa7061d8c617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.shoe" msgstr "シュー" +# English: "Side A" # Source-Hash: 136762ac1bc96e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side A" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideA" msgstr "A面" +# English: "Side B" # Source-Hash: 8b9cb16850549c35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side B" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideB" msgstr "B面" +# English: "Side {face}" # Source-Hash: e6a27f6f8cd489b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side {face}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideFace" msgstr "{face}面" +# English: "Skirting" # Source-Hash: d9d55c5883a53789 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skirting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.skirting" msgstr "幅木" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.standard" msgstr "標準" +# English: "Steel studs" # Source-Hash: da3e3dd077163ba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steel studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.steelStuds" msgstr "鋼製下地" +# English: "Step" # Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.step" msgstr "段" +# English: "Stone veneer" # Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stone veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stoneVeneer" msgstr "石張り" +# English: "Stucco" # Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stucco" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stucco" msgstr "スタッコ" -# Source-Hash: fdbb503f0941bd56 +# English: "Studs" +# Source-Hash: 4fce02ee3f69bf99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.studs" +msgstr "スタッド" + # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thickness" msgstr "厚さ" +# English: "{thickness} · from assembly" # Source-Hash: 00cb7b58f3a10140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{thickness} · from assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thicknessFromAssembly" msgstr "{thickness} · 構成から" +# English: "This project" +# Source-Hash: 9eef4c34dfbc970b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.thisProject" +msgstr "このプロジェクト" + +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.top" msgstr "上" +# English: "Total thickness" # Source-Hash: 3ee03b0e9ae1be4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Total thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.totalThickness" msgstr "総厚" +# English: "Unverified: {presetNote}" # Source-Hash: ea7e8bbd87ce4152 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unverified: {presetNote}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.unverifiedPresetnote" msgstr "未検証:{presetNote}" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.viewCode" msgstr "コードを表示" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wall" msgstr "壁" +# English: "Wall type" # Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wallType" msgstr "壁の種類" +# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." # Source-Hash: e97a456d667465f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.whichFaceIsOutsideIsUndetermined" msgstr "どちらの面が外側か判定できません(両側とも部屋が検出されません)。外側の層は B 面に描かれます。" +# English: "Wood studs" # Source-Hash: 31e404a4a4870faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wood studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.woodStuds" msgstr "木製下地" -# Source-Hash: 5211341ed18f33f6 +# English: "A guard's openings must not pass a 4 in sphere (IRC R312.1.3)." +# Source-Hash: ece946252401f4ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.aGuardSOpeningsMustNot" +msgstr "転落防止柵の隙間は、直径 4 in の球が通らないようにします(IRC R312.1.3)。" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.height" +msgstr "高さ" + +# English: "Infill" +# Source-Hash: 7c339a5fae55681d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.infill" +msgstr "手すり子・面材" + +# English: "2x6 cap" +# Source-Hash: d814a47c4eccee3f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n2x6Cap" +msgstr "2x6 笠木" + +# English: "36 in" +# Source-Hash: 137b45940b723a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n36In" +msgstr "36 in" + +# English: "42 in" +# Source-Hash: ebf854ac4dd4daaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n42In" +msgstr "42 in" + +# English: "Posts" +# Source-Hash: ede386e1d510ebfb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.posts" +msgstr "支柱" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.railing" +msgstr "手すり" + +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.style" +msgstr "スタイル" + # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.center" msgstr "中央" +# English: "Inside face" # Source-Hash: 09576259041494f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.inside" msgstr "内側の面" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.left" msgstr "左" +# English: "Outside face" # Source-Hash: 697001376b872e74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.outside" msgstr "外側の面" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.right" msgstr "右" +# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" # Source-Hash: eac63d14f275db3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.hide" msgstr "{trim, select, skirting {幅木を隠す} crown {廻り縁を隠す} other {チェアレールを隠す}}" +# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" # Source-Hash: 8db070a4247db221 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.show" msgstr "{trim, select, skirting {幅木を表示} crown {廻り縁を表示} other {チェアレールを表示}}" +# English: "A window cut into a wall. Animated open/close for opening windows." # Source-Hash: ea6a51e66eb0c0dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A window cut into a wall. Animated open/close for opening windows." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.description" msgstr "壁に設けた窓。開閉する窓はアニメーション付き。" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.label" msgstr "窓" +# English: "Raise" # Source-Hash: 011e4de244050a78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.raise" msgstr "上げ下げ" +# English: "Slats" # Source-Hash: 19ca2434d4f7eb87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slats" msgstr "スラット" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slide" msgstr "スライド" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.swing" msgstr "開き" +# English: "Tilt" # Source-Hash: 1615637fe68e188a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.tilt" msgstr "傾斜" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.actions" msgstr "操作" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.all" msgstr "すべて" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.arch" msgstr "アーチ" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.archHeight" msgstr "アーチの高さ" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.auto" msgstr "自動" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.autoFollowsTheWall" msgstr "自動は壁に合わせます" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.columns" msgstr "列" +# English: "Col Widths" # Source-Hash: 2f417371c399ad67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Col Widths" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.colWidths" msgstr "列幅" +# English: "Corner" # Source-Hash: 2ee121dca87bb64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.corner" msgstr "コーナー" +# English: "Corner post" # Source-Hash: 04e3425c5ea29359 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerPost" msgstr "コーナーポスト" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerRadius" msgstr "角の半径" +# English: "Custom: the panes match no style." # Source-Hash: 67d10b97eff316c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the panes match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.customThePanesMatchNoStyle" msgstr "カスタム:ガラス割りがどのスタイルとも一致しません。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.delete" msgstr "削除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.depth" msgstr "奥行き" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.dimensions" msgstr "寸法" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.divider" msgstr "仕切り" +# English: "Down" # Source-Hash: 72c65752a1d1bbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.down" msgstr "下" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.duplicate" msgstr "複製" +# English: "Enable Sill" # Source-Hash: 8497c4373d5d18ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.enableSill" msgstr "窓台を有効化" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.flipSide" msgstr "向きを反転" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frame" msgstr "枠" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frameDepthAuto" msgstr "奥行き(自動)" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.french" msgstr "フレンチドア" +# English: "Fused glass" # Source-Hash: a6362b937fffe999 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fused glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.fusedGlass" msgstr "フュージングガラス" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.grid" msgstr "グリッド" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.height" msgstr "高さ" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.individual" msgstr "個別" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.left" msgstr "左" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.move" msgstr "移動" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.opening" msgstr "開口" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.openingShape" msgstr "開口の形状" +# English: "Other side" # Source-Hash: cf40395b71bedcca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.otherSide" msgstr "反対側" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.position" msgstr "位置" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rect" msgstr "矩形" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.revealRadius" msgstr "見込みの半径" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.right" msgstr "右" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rounded" msgstr "角丸" +# English: "Row Heights" # Source-Hash: fb811056b862b6d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row Heights" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rowHeights" msgstr "行の高さ" +# English: "Rows" # Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rows" msgstr "行" +# English: "Sill" # Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sill" msgstr "窓台" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.single" msgstr "片開き" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sizeIsLimitedToTheWall" msgstr "サイズは開口枠のクリアランスを含めて壁の範囲に制限されます。" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.style" msgstr "スタイル" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thickness" msgstr "厚さ" +# English: "This side" # Source-Hash: 79ff1ae7250c1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thisSide" msgstr "こちら側" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.topShape" msgstr "上部の形状" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.type" msgstr "種類" +# English: "Unwrap" # Source-Hash: c29767f86bd9df1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.unwrap" msgstr "解除" +# English: "Up" # Source-Hash: 1d732503a54a532f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.up" msgstr "上" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.width" msgstr "幅" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.window" msgstr "窓" +# English: "Window Type" # Source-Hash: f59cd625e6082651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.windowType" msgstr "窓の種類" +# English: "Wraps the corner with {partner}" # Source-Hash: 7bf8c4e2b90b3021 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with {partner}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithNamed" msgstr "{partner}とコーナーを回ります" +# English: "Wraps the corner with its partner" # Source-Hash: 8f1f8eff5fbe90e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with its partner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithPartner" msgstr "対の窓とコーナーを回ります" +# English: "Wrap the corner" # Source-Hash: cccf75b8c8dc5ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapTheCorner" msgstr "コーナーを回る" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.casement" msgstr "開き窓" +# English: "Double-hung" # Source-Hash: e06fc5a432b74a5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.doubleHung" msgstr "上げ下げ(両)" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.grid" msgstr "グリッド" +# English: "Horizontal bands" # Source-Hash: fbb293fe8aee84b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.horizontalBands" msgstr "横帯" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.picture" msgstr "額縁" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.single" msgstr "片開き" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.sliding" msgstr "引き戸" +# English: "Tall grid" # Source-Hash: 0c88aea2bd130193 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tallGrid" msgstr "縦長グリッド" +# English: "Transom" # Source-Hash: 9628d0cc155e29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.transom" msgstr "欄間" +# English: "Triple-hung" # Source-Hash: 305f3aba543b1962 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tripleHung" msgstr "上げ下げ(3枚)" +# English: "Wide grid" # Source-Hash: 8af77effb76bc6e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.wideGrid" msgstr "横長グリッド" +# English: "Awning" # Source-Hash: 50393830365aaf2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.awning" msgstr "すべり出し窓" +# English: "Bay" # Source-Hash: 8447ee52258e16e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bay" msgstr "ベイウィンドウ" +# English: "Bow" # Source-Hash: e828c89bd0ba1ea4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bow" msgstr "ボウウィンドウ" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.casement" msgstr "開き窓" +# English: "Double Hung" # Source-Hash: b0b737fc5cb1d32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.doubleHung" msgstr "上げ下げ窓(両)" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.fixed" msgstr "固定" +# English: "Hopper" # Source-Hash: 04a40f27911c3192 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hopper" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.hopper" msgstr "内倒し窓" +# English: "Louvered" # Source-Hash: 5746be19ba2a53be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.louvered" msgstr "ルーバー窓" +# English: "Single Hung" # Source-Hash: de6ffb1cf676f270 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.singleHung" msgstr "上げ下げ窓(片)" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.sliding" msgstr "引き戸" +# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." # Source-Hash: 4a4d97a4101793b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.description" msgstr "TSL グラデーション素材を使う多角形の敷地ゾーン(芝生、水面、舗装)。" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.label" msgstr "ゾーン" +# English: "Enclosed room" # Source-Hash: e9a6d2e71b97e757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enclosed room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.enclosedRoom" msgstr "囲まれた部屋" +# English: "Floor surface" # Source-Hash: 3660922f0a8f1d3c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.floorSurface" msgstr "床面" +# English: "Footprint only" # Source-Hash: 88c8703c4ff21ee1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint only" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.footprintOnly" msgstr "フットプリントのみ" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.none" msgstr "なし" +# English: "Not proven" # Source-Hash: a9756a3da570c3dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not proven" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.notProven" msgstr "未確認" +# English: "Room quantities" # Source-Hash: ff97d005a536cf58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.roomQuantities" msgstr "部屋の数量" +# English: "Top view with zone edge dimensions" # Source-Hash: f520e6a06fbeb803 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top view with zone edge dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions" msgstr "ゾーンの辺の寸法付き平面図" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.unit" msgstr "住戸" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.volume" msgstr "体積" +# English: "Wall surface" # Source-Hash: a7941c60a3b6998a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.wallSurface" msgstr "壁面" +# English: "Zone boundary unavailable" # Source-Hash: b8b0dbdff53a83ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone boundary unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneBoundaryUnavailable" msgstr "ゾーンの境界が利用できません" +# English: "Zone quantities" # Source-Hash: 6bfb4c6f4d7b0ff2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneQuantities" msgstr "ゾーンの数量" +# English: "Orbit left" # Source-Hash: 8bf25afa3cb8baea # Review: human -# English: "Orbit left" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitLeft" msgstr "左に回転" +# English: "Orbit right" # Source-Hash: b9cc64c1d1bb0d46 # Review: human -# English: "Orbit right" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitRight" msgstr "右に回転" +# English: "Top view" # Source-Hash: 1ab1eb3a14f4b0e0 # Review: human -# English: "Top view" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.topView" msgstr "上面図" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.off" msgstr "オフ" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: human -# English: "On" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.on" msgstr "オン" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: human -# English: "Camera" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.camera" msgstr "カメラ" +# English: "Colored" # Source-Hash: d3d478fa6c6d7ddc # Review: human -# English: "Colored" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colored" msgstr "カラー表示" +# English: "Colors" # Source-Hash: 82b74c4f5ef43336 # Review: human -# English: "Colors" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colors" msgstr "カラー" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.off" msgstr "オフ" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: human -# English: "No edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.offDetail" msgstr "エッジ線を表示しない" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: human -# English: "Soft" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.soft" msgstr "ソフト" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: human -# English: "Faint outline of major creases" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.softDetail" msgstr "主要な稜線を薄く表示" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: human -# English: "Strong" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strong" msgstr "強調" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: human -# English: "Crisp, opaque edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strongDetail" msgstr "エッジ線をくっきり表示" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: human -# English: "Edges" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.title" msgstr "エッジ" +# English: "Monochrome" # Source-Hash: ecde2f7c26914be3 # Review: human -# English: "Monochrome" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.monochrome" msgstr "モノクロ" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: human -# English: "Orthographic" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.orthographic" msgstr "平行投影" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: human -# English: "Perspective" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.perspective" msgstr "透視投影" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: human -# English: "Rendered" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.rendered" msgstr "レンダリング" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: human -# English: "Full ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.renderedDetail" msgstr "アンビエントオクルージョンを完全表示" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: human -# English: "Solid" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solid" msgstr "ソリッド" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: human -# English: "Flat and fast — no ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solidDetail" msgstr "軽量表示(アンビエントオクルージョンなし)" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: human -# English: "Render" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.title" msgstr "レンダリング" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: human -# English: "Shadows" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.shadows" msgstr "影" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: human -# English: "Display settings" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.title" msgstr "表示設定" +# English: "Align view to north" # Source-Hash: 2bdf83801d4a40af # Review: human -# English: "Align view to north" #: packages/editor/src/components/viewer/floorplan-compass-button.tsx msgid "viewer.floorplan.alignNorth" msgstr "北を上に表示" +# English: "No floor plans are available for this scene." # Source-Hash: 286a757d8b52bb02 # Review: human -# English: "No floor plans are available for this scene." #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.empty" msgstr "このシーンには表示できる平面図がありません。" +# English: "Fit floor plan" # Source-Hash: bf63c679ab94dbcd # Review: human -# English: "Fit floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.fit" msgstr "全体を表示" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: human -# English: "Floor" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floor" msgstr "階" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: human -# English: "Floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floorPlan" msgstr "平面図" +# English: "{floor} 2D view" # Source-Hash: 28b255b7bf5f1084 # Review: human -# English: "{floor} 2D view" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.viewLabel" msgstr "{floor}の2Dビュー" +# English: "Zoom in" # Source-Hash: d17804412f8dd081 # Review: human -# English: "Zoom in" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomIn" msgstr "拡大" +# English: "Zoom out" # Source-Hash: 23b012a283b2c14a # Review: human -# English: "Zoom out" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomOut" msgstr "縮小" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: human -# English: "Back" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.back" msgstr "戻る" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: human -# English: "Levels" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.levels" msgstr "階層" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.rooms" msgstr "部屋" +# English: "Selection" # Source-Hash: 170b2049ef0f9131 # Review: human -# English: "Selection" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.selection" msgstr "選択" +# English: "Units" # Source-Hash: b404dded35241274 # Review: human -# English: "Units" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.units" msgstr "住戸" +# English: "Untitled" # Source-Hash: 6547a4c0c368042a # Review: human -# English: "Untitled" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.untitled" msgstr "名称未設定" +# English: "Zones" # Source-Hash: 665db49a26f81425 # Review: human -# English: "Zones" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.zones" msgstr "ゾーン" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: human -# English: "Exploded" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.exploded" msgstr "分解" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: human -# English: "Levels: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.label" msgstr "階層:{mode}" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: human -# English: "Manual" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.manual" msgstr "手動" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: human -# English: "Solo" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.solo" msgstr "単独" +# English: "Stacked" # Source-Hash: c203c84eee18c0f6 # Review: human -# English: "Stacked" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.stacked" msgstr "積層" +# English: "Viewer layout" # Source-Hash: 9dd21ca47cffa3b8 # Review: human -# English: "Viewer layout" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.layout" msgstr "ビュー配置" +# English: "Split" # Source-Hash: e4cd64b1680ac0d2 # Review: human -# English: "Split" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.split" msgstr "分割" +# English: "Blueprint" # Source-Hash: f14b89d713d2528a # Review: human -# English: "Blueprint" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.blueprint" msgstr "青図" +# English: "Mediterranean" # Source-Hash: 142bcb2841f423c5 # Review: human -# English: "Mediterranean" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.mediterranean" msgstr "地中海" +# English: "Night" # Source-Hash: b399942ec5d7e1ba # Review: human -# English: "Night" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.night" msgstr "夜景" +# English: "Overcast" # Source-Hash: f512d1b947b8ae5a # Review: human -# English: "Overcast" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.overcast" msgstr "曇天" +# English: "Paper" # Source-Hash: 583542b3bc837192 # Review: human -# English: "Paper" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.paper" msgstr "ペーパー" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: human -# English: "Studio" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.studio" msgstr "スタジオ" +# English: "Sunset" # Source-Hash: efe94d3ec8d8c44e # Review: human -# English: "Sunset" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.sunset" msgstr "夕景" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: human -# English: "Scene theme" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.theme.title" msgstr "シーンテーマ" +# English: "Twilight" # Source-Hash: 4c10ea0aa0e2ea41 # Review: human -# English: "Twilight" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.twilight" msgstr "薄暮" +# English: "Verdant" # Source-Hash: f9592fb0672740c0 # Review: human -# English: "Verdant" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.verdant" msgstr "緑景" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: human -# English: "Guides" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.guides" msgstr "ガイド" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: human -# English: "Scans" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.scans" msgstr "3Dスキャン" +# English: "Visibility" # Source-Hash: 2cefbb554eadbb6e # Review: human -# English: "Visibility" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.title" msgstr "表示" +# English: "Walkthrough: {state}" # Source-Hash: 31afd9dee47ba943 # Review: human -# English: "Walkthrough: {state}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walkthrough.label" msgstr "ウォークスルー:{state}" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: human -# English: "Cutaway" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.cutaway" msgstr "カット表示" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: human -# English: "Full height" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.fullHeight" msgstr "全高" +# English: "Walls: {mode}" # Source-Hash: 32f3d058cf2526d2 # Review: human -# English: "Walls: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.label" msgstr "壁:{mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: human -# English: "Low" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.low" msgstr "低く表示" diff --git a/packages/editor/src/i18n/locales/ja/messages.ts b/packages/editor/src/i18n/locales/ja/messages.ts index c213b9a5f3..87e5e64b50 100644 --- a/packages/editor/src/i18n/locales/ja/messages.ts +++ b/packages/editor/src/i18n/locales/ja/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"地下1階\"],\"core.level.basementNumber\":[\"地下\",[\"number\"],\"階\"],\"core.level.floorNumber\":[[\"number\"],\"階\"],\"core.level.groundFloor\":[\"1階\"],\"editor.buildPanel.advanced\":[\"詳細\"],\"editor.buildPanel.rooms.hint\":[\"壁 · 床 · 天井\"],\"editor.buildPanel.rooms.idle\":[\"形状を選んで開始します。部屋ごとに壁・床・天井が作成されます。\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\"最後に始点をクリックして閉じます。L字型や斜めの部屋に使用します。\"],\"editor.buildPanel.rooms.polygon.label\":[\"多角形\"],\"editor.buildPanel.rooms.polygon.lead\":[\"各頂点をクリックし、\"],\"editor.buildPanel.rooms.polygon.title\":[\"多角形の部屋\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"グリッド上で\",[\"lead\"],\"します。壁・床・天井をまとめて作成します。\"],\"editor.buildPanel.rooms.rectangle.label\":[\"長方形\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"対角の2点をクリック\"],\"editor.buildPanel.rooms.rectangle.title\":[\"長方形の部屋\"],\"editor.buildPanel.rooms.title\":[\"部屋\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\"輪郭を閉じると部屋になり、開いたままにすると間仕切りになります。\"],\"editor.buildPanel.rooms.walls.label\":[\"壁\"],\"editor.buildPanel.rooms.walls.lead\":[\"壁を1面ずつ描画します。\"],\"editor.buildPanel.rooms.walls.title\":[\"壁\"],\"viewer.camera.orbitLeft\":[\"左に回転\"],\"viewer.camera.orbitRight\":[\"右に回転\"],\"viewer.camera.topView\":[\"上面図\"],\"viewer.common.off\":[\"オフ\"],\"viewer.common.on\":[\"オン\"],\"viewer.display.camera\":[\"カメラ\"],\"viewer.display.colored\":[\"カラー表示\"],\"viewer.display.colors\":[\"カラー\"],\"viewer.display.edges.off\":[\"オフ\"],\"viewer.display.edges.offDetail\":[\"エッジ線を表示しない\"],\"viewer.display.edges.soft\":[\"ソフト\"],\"viewer.display.edges.softDetail\":[\"主要な稜線を薄く表示\"],\"viewer.display.edges.strong\":[\"強調\"],\"viewer.display.edges.strongDetail\":[\"エッジ線をくっきり表示\"],\"viewer.display.edges.title\":[\"エッジ\"],\"viewer.display.monochrome\":[\"モノクロ\"],\"viewer.display.orthographic\":[\"平行投影\"],\"viewer.display.perspective\":[\"透視投影\"],\"viewer.display.render.rendered\":[\"レンダリング\"],\"viewer.display.render.renderedDetail\":[\"アンビエントオクルージョンを完全表示\"],\"viewer.display.render.solid\":[\"ソリッド\"],\"viewer.display.render.solidDetail\":[\"軽量表示(アンビエントオクルージョンなし)\"],\"viewer.display.render.title\":[\"レンダリング\"],\"viewer.display.shadows\":[\"影\"],\"viewer.display.title\":[\"表示設定\"],\"viewer.floorplan.alignNorth\":[\"北を上に表示\"],\"viewer.floorplan.empty\":[\"このシーンには表示できる平面図がありません。\"],\"viewer.floorplan.fit\":[\"全体を表示\"],\"viewer.floorplan.floor\":[\"階\"],\"viewer.floorplan.floorPlan\":[\"平面図\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\"の2Dビュー\"],\"viewer.floorplan.zoomIn\":[\"拡大\"],\"viewer.floorplan.zoomOut\":[\"縮小\"],\"viewer.header.back\":[\"戻る\"],\"viewer.header.levels\":[\"階層\"],\"viewer.header.rooms\":[\"部屋\"],\"viewer.header.selection\":[\"選択\"],\"viewer.header.units\":[\"住戸\"],\"viewer.header.untitled\":[\"名称未設定\"],\"viewer.header.zones\":[\"ゾーン\"],\"viewer.levels.exploded\":[\"分解\"],\"viewer.levels.label\":[\"階層:\",[\"mode\"]],\"viewer.levels.manual\":[\"手動\"],\"viewer.levels.solo\":[\"単独\"],\"viewer.levels.stacked\":[\"積層\"],\"viewer.stage.layout\":[\"ビュー配置\"],\"viewer.stage.split\":[\"分割\"],\"viewer.theme.blueprint\":[\"青図\"],\"viewer.theme.mediterranean\":[\"地中海\"],\"viewer.theme.night\":[\"夜景\"],\"viewer.theme.overcast\":[\"曇天\"],\"viewer.theme.paper\":[\"ペーパー\"],\"viewer.theme.studio\":[\"スタジオ\"],\"viewer.theme.sunset\":[\"夕景\"],\"viewer.theme.title\":[\"シーンテーマ\"],\"viewer.theme.twilight\":[\"薄暮\"],\"viewer.theme.verdant\":[\"緑景\"],\"viewer.visibility.guides\":[\"ガイド\"],\"viewer.visibility.scans\":[\"3Dスキャン\"],\"viewer.visibility.title\":[\"表示\"],\"viewer.walkthrough.label\":[\"ウォークスルー:\",[\"state\"]],\"viewer.walls.cutaway\":[\"カット表示\"],\"viewer.walls.fullHeight\":[\"全高\"],\"viewer.walls.label\":[\"壁:\",[\"mode\"]],\"viewer.walls.low\":[\"低く表示\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"地下1階\"],\"core.level.basementNumber\":[\"地下\",[\"number\"],\"階\"],\"core.level.floorNumber\":[[\"number\"],\"階\"],\"core.level.groundFloor\":[\"1階\"],\"editor.buildPanel.advanced\":[\"詳細\"],\"editor.buildPanel.rooms.hint\":[\"壁 · 床 · 天井\"],\"editor.buildPanel.rooms.idle\":[\"形状を選んで開始します。部屋ごとに壁・床・天井が作成されます。\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\"最後に始点をクリックして閉じます。L字型や斜めの部屋に使用します。\"],\"editor.buildPanel.rooms.polygon.label\":[\"多角形\"],\"editor.buildPanel.rooms.polygon.lead\":[\"各頂点をクリックし、\"],\"editor.buildPanel.rooms.polygon.title\":[\"多角形の部屋\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"グリッド上で\",[\"lead\"],\"します。壁・床・天井をまとめて作成します。\"],\"editor.buildPanel.rooms.rectangle.label\":[\"長方形\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"対角の2点をクリック\"],\"editor.buildPanel.rooms.rectangle.title\":[\"長方形の部屋\"],\"editor.buildPanel.rooms.title\":[\"部屋\"],\"editor.buildPanel.rooms.walls.label\":[\"壁\"],\"editor.buildPanel.rooms.walls.title\":[\"壁\"],\"viewer.camera.orbitLeft\":[\"左に回転\"],\"viewer.camera.orbitRight\":[\"右に回転\"],\"viewer.camera.topView\":[\"上面図\"],\"viewer.common.off\":[\"オフ\"],\"viewer.common.on\":[\"オン\"],\"viewer.display.camera\":[\"カメラ\"],\"viewer.display.colored\":[\"カラー表示\"],\"viewer.display.colors\":[\"カラー\"],\"viewer.display.edges.off\":[\"オフ\"],\"viewer.display.edges.offDetail\":[\"エッジ線を表示しない\"],\"viewer.display.edges.soft\":[\"ソフト\"],\"viewer.display.edges.softDetail\":[\"主要な稜線を薄く表示\"],\"viewer.display.edges.strong\":[\"強調\"],\"viewer.display.edges.strongDetail\":[\"エッジ線をくっきり表示\"],\"viewer.display.edges.title\":[\"エッジ\"],\"viewer.display.monochrome\":[\"モノクロ\"],\"viewer.display.orthographic\":[\"平行投影\"],\"viewer.display.perspective\":[\"透視投影\"],\"viewer.display.render.rendered\":[\"レンダリング\"],\"viewer.display.render.renderedDetail\":[\"アンビエントオクルージョンを完全表示\"],\"viewer.display.render.solid\":[\"ソリッド\"],\"viewer.display.render.solidDetail\":[\"軽量表示(アンビエントオクルージョンなし)\"],\"viewer.display.render.title\":[\"レンダリング\"],\"viewer.display.shadows\":[\"影\"],\"viewer.display.title\":[\"表示設定\"],\"viewer.floorplan.alignNorth\":[\"北を上に表示\"],\"viewer.floorplan.empty\":[\"このシーンには表示できる平面図がありません。\"],\"viewer.floorplan.fit\":[\"全体を表示\"],\"viewer.floorplan.floor\":[\"階\"],\"viewer.floorplan.floorPlan\":[\"平面図\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\"の2Dビュー\"],\"viewer.floorplan.zoomIn\":[\"拡大\"],\"viewer.floorplan.zoomOut\":[\"縮小\"],\"viewer.header.back\":[\"戻る\"],\"viewer.header.levels\":[\"階層\"],\"viewer.header.rooms\":[\"部屋\"],\"viewer.header.selection\":[\"選択\"],\"viewer.header.units\":[\"住戸\"],\"viewer.header.untitled\":[\"名称未設定\"],\"viewer.header.zones\":[\"ゾーン\"],\"viewer.levels.exploded\":[\"分解\"],\"viewer.levels.label\":[\"階層:\",[\"mode\"]],\"viewer.levels.manual\":[\"手動\"],\"viewer.levels.solo\":[\"単独\"],\"viewer.levels.stacked\":[\"積層\"],\"viewer.stage.layout\":[\"ビュー配置\"],\"viewer.stage.split\":[\"分割\"],\"viewer.theme.blueprint\":[\"青図\"],\"viewer.theme.mediterranean\":[\"地中海\"],\"viewer.theme.night\":[\"夜景\"],\"viewer.theme.overcast\":[\"曇天\"],\"viewer.theme.paper\":[\"ペーパー\"],\"viewer.theme.studio\":[\"スタジオ\"],\"viewer.theme.sunset\":[\"夕景\"],\"viewer.theme.title\":[\"シーンテーマ\"],\"viewer.theme.twilight\":[\"薄暮\"],\"viewer.theme.verdant\":[\"緑景\"],\"viewer.visibility.guides\":[\"ガイド\"],\"viewer.visibility.scans\":[\"3Dスキャン\"],\"viewer.visibility.title\":[\"表示\"],\"viewer.walkthrough.label\":[\"ウォークスルー:\",[\"state\"]],\"viewer.walls.cutaway\":[\"カット表示\"],\"viewer.walls.fullHeight\":[\"全高\"],\"viewer.walls.label\":[\"壁:\",[\"mode\"]],\"viewer.walls.low\":[\"低く表示\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/i18n/locales/zh-CN/messages.po b/packages/editor/src/i18n/locales/zh-CN/messages.po index 825e4445d1..5b9b79c359 100644 --- a/packages/editor/src/i18n/locales/zh-CN/messages.po +++ b/packages/editor/src/i18n/locales/zh-CN/messages.po @@ -13,24551 +13,28858 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" +# English: "Pascal home" # Source-Hash: 8ffb1c5066e53a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pascal home" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/app-home-link.tsx #, fuzzy msgid "app.appShell.pascalHome" msgstr "Pascal 首页" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addGate" msgstr "添加大门" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addOpenPassage" msgstr "添加通道" +# English: "Add to rooms" # Source-Hash: 9a4d56454dc89966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.addToRooms" msgstr "添加到房间" +# English: "← All build tools" # Source-Hash: a17210912addc85f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "← All build tools" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.allBuildTools" msgstr "← 所有建造工具" +# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." # Source-Hash: 6b63404967e1d5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose Gate or Open Passage, then click its position on any fence. Find placed gates and openings under their fence in the scene graph." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.chooseGateOrOpenPassageThen" msgstr "选择大门或通道,然后在任意围栏上点击其位置。已放置的大门和开口位于场景图中所属围栏下。" +# English: "Click a lineset to lay the line beside it." # Source-Hash: 8fd051e1e1a6c1a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a lineset to lay the line beside it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.clickALinesetToLayThe" msgstr "点击冷媒管组,在其旁边布线。" +# English: "Double gate" # Source-Hash: 7308869944020839 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.doubleGate" msgstr "双开大门" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.featuresExtensions" msgstr "构件与扩建" +# English: "Fence features" # Source-Hash: b18788f1d889c1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence features" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.fenceFeatures" msgstr "围栏构件" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.followLineset" msgstr "沿冷媒管组" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gate" msgstr "大门" +# English: "Gate style" # Source-Hash: 54098b5ee574737a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.gateStyle" msgstr "大门样式" +# English: "Horizontal boards" # Source-Hash: 8fec7e969045a8e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.horizontalBoards" msgstr "横向板条" +# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." # Source-Hash: 09197c4fea516277 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a fence to preview. Click to place. Esc cancels. Leave room between openings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.hoverAFenceToPreviewClick" msgstr "悬停在围栏上预览,点击放置,按 Esc 取消。开口之间请留出间距。" +# English: "Leaves" # Source-Hash: c390ba248e629b9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leaves" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.leaves" msgstr "门扇" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.liquidLine" msgstr "液管" +# English: "Match fence style" # Source-Hash: c15c1af69ec5c1eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match fence style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.matchFenceStyle" msgstr "匹配围栏样式" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.mep" msgstr "机电" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.off" msgstr "关" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.on" msgstr "开" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.opening" msgstr "开口" +# English: "Opening width (m)" # Source-Hash: 9514d3c022e68808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening width (m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openingWidthM" msgstr "开口宽度(米)" +# English: "Open rails" # Source-Hash: 6b108fb1d0eb8f9d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rails" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.openRails" msgstr "开放栏杆" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.outdoor" msgstr "室外" +# English: "Picket" # Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.picket" msgstr "尖桩" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roofType" msgstr "屋顶类型" +# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." # Source-Hash: 0f9daa442776a317 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms already create their floor and ceiling. Use these for platforms and one-off structure." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.roomsAlreadyCreateTheirFloorAnd" msgstr "房间会自动创建地面和天花板。这些工具用于平台和单独的结构。" +# English: "Search build tools and kitchen items" # Source-Hash: fc79fd0c7730de76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search build tools and kitchen items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchBuildToolsAndKitchenItems" msgstr "搜索建造工具和厨房物品" +# English: "Search tools, fridge, sink…" # Source-Hash: dc1901f276edca6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search tools, fridge, sink…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.searchToolsFridgeSink" msgstr "搜索工具、冰箱、水槽…" +# English: "Select a curved wall to match its radius and arc." # Source-Hash: 9558db89023d4f05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a curved wall to match its radius and arc." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.selectACurvedWallToMatch" msgstr "选择一面曲面墙体以匹配其半径和弧度。" +# English: "Single gate" # Source-Hash: d327097d9ac1099f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.singleGate" msgstr "单开大门" +# English: "Slab, ceiling, column…" # Source-Hash: 99acd5128c51c8a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab, ceiling, column…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.slabCeilingColumn" msgstr "楼板、天花板、柱…" +# English: "Solid privacy" # Source-Hash: af6f8885fe0fa4e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid privacy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.solidPrivacy" msgstr "实心遮挡" +# English: "Trace a line alongside an existing lineset (F)." # Source-Hash: e2b1a3bb9630b33b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace a line alongside an existing lineset (F)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.traceALineAlongsideAnExisting" msgstr "沿现有冷媒管组布线 (F)。" +# English: "Vertical slats" # Source-Hash: eba3df14a2de0812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/build-tab.tsx #, fuzzy msgid "app.buildTab.verticalSlats" msgstr "竖向板条" +# English: "All projects" # Source-Hash: 2ee62be78ba76f53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All projects" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.allProjects" msgstr "所有项目" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.preview" msgstr "预览" +# English: "Project name" # Source-Hash: 9f2cbc9214c8f7b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.projectName" msgstr "项目名称" +# English: "Rename project" # Source-Hash: 150e143a84240eee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.renameProject" msgstr "重命名项目" +# English: "Share" # Source-Hash: 331e1584943b09b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/editor-navbar.tsx #, fuzzy msgid "app.editorNavbar.share" msgstr "分享" +# English: "Add {label}" # Source-Hash: 4b2f676fb4c9ec63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addLabel" msgstr "添加{label}" +# English: "Add to kitchen" # Source-Hash: 53f98cc505aacbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.addToKitchen" msgstr "添加到厨房" +# English: "Choose an item, then click to place it. Appliances include their cabinetry." # Source-Hash: 923a2e81dcc77852 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose an item, then click to place it. Appliances include their cabinetry." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.chooseAnItemThenClickTo" msgstr "选择物品,然后点击放置。电器包含配套柜体。" +# English: "Find in this project · {length}" # Source-Hash: 44156a2c888e8a4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in this project · {length}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findInThisProjectLength" msgstr "在本项目中查找 · {length}" +# English: "Find {name}" # Source-Hash: aeda65db67edac21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find {name}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.findName" msgstr "查找{name}" +# English: "Kitchen · Add" # Source-Hash: c1d847411ba09cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · Add" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupAdd" msgstr "厨房 · 添加" +# English: "Kitchen · In this project" # Source-Hash: bb3f4f8d3407c409 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen · In this project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.groupInThisProject" msgstr "厨房 · 本项目中" +# English: "Kitchen cabinet" # Source-Hash: 25ffad31086309f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.kitchenCabinet" msgstr "厨房橱柜" +# English: "No matching kitchen items placed yet." # Source-Hash: a5af7817a7e8947d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No matching kitchen items placed yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.noMatchingKitchenItemsPlacedYet" msgstr "尚未放置匹配的厨房物品。" +# English: "Select and show in scene" # Source-Hash: 640155f99c25773b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select and show in scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/kitchen-browser.tsx #, fuzzy msgid "app.kitchenBrowser.selectAndShowInScene" msgstr "选中并在场景中显示" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.ceiling" msgstr "天花板" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.column" msgstr "柱" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.door" msgstr "门" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.duct" msgstr "风管" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.dwvPipe" msgstr "排水管" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.elevator" msgstr "电梯" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.fence" msgstr "围栏" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.hvacUnit" msgstr "暖通设备" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.kitchen" msgstr "厨房" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.lineset" msgstr "冷媒管组" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.liquidLine" msgstr "液管" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.mep" msgstr "机电" -# Source-Hash: 161e5386b939e1bc +# English: "Post" +# Source-Hash: 5cc981e79d737694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/lib/build-palette.ts +#, fuzzy +msgid "app.lib.buildPalette.post" +msgstr "立柱" + # English: "Register" +# Source-Hash: 161e5386b939e1bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.register" msgstr "风口" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.roof" msgstr "屋顶" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.shelf" msgstr "置物架" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.slab" msgstr "楼板" +# English: "Spawn point" # Source-Hash: 95e8af5d2b3ca73d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.spawnPoint" msgstr "出生点" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.stairs" msgstr "楼梯" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrace" msgstr "露台" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.terrain" msgstr "地形" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.wall" msgstr "墙体" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-palette.ts #, fuzzy msgid "app.lib.buildPalette.window" msgstr "窗" +# English: "Add Fitting" # Source-Hash: d40c50013043284f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addFitting" msgstr "添加管件" +# English: "Add Gate" # Source-Hash: 534540df302975a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addGate" msgstr "添加大门" +# English: "Add Open Passage" # Source-Hash: 8b9110c4e72f2db2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addOpenPassage" msgstr "添加通道" +# English: "Add Trap" # Source-Hash: 7136b5836de6e69f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.addTrap" msgstr "添加存水弯" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.build" msgstr "建造" +# English: "Create from" # Source-Hash: a23831beeb35ad3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Create from" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.createFrom" msgstr "创建来源" +# English: "Draw the roof footprint with two corner clicks." # Source-Hash: 8146c309bd8326f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the roof footprint with two corner clicks." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.drawTheRoofFootprintWithTwo" msgstr "点击两个角点绘制屋顶轮廓。" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.duct" msgstr "风管" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.dwvPipe" msgstr "排水管" +# English: "Features & extensions" # Source-Hash: 281d63a4b3e2de4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Features & extensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.featuresExtensions" msgstr "构件与扩建" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.fence" msgstr "围栏" +# English: "Follow lineset" # Source-Hash: 62803100bd6e7cc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.followLineset" msgstr "沿冷媒管组" +# English: "Gates & openings" # Source-Hash: c80972ec078ac551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gates & openings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.gatesOpenings" msgstr "大门与开口" +# English: "Hover a room to preview its boundary, then click to place." # Source-Hash: 3720347aa16ee777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a room to preview its boundary, then click to place." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.hoverARoomToPreviewIts" msgstr "悬停在房间上预览其边界,然后点击放置。" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.kitchen" msgstr "厨房" +# English: "MEP" # Source-Hash: 95057eef5cd69340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "MEP" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.mep" msgstr "机电" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.modularCabinet" msgstr "组合橱柜" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.off" msgstr "关" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.on" msgstr "开" +# English: "Painting" # Source-Hash: 3fafdc69e7756f19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Painting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.painting" msgstr "涂装" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roof" msgstr "屋顶" +# English: "Roof type" # Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.roofType" msgstr "屋顶类型" +# English: "{count, plural, one {# tool} other {# tools}}" # Source-Hash: 0c35bee851cc57a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# tool} other {# tools}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-panel-model.ts #, fuzzy msgid "app.lib.buildPanelModel.toolCount" msgstr "{count, plural, other {# 个工具}}" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.conical" msgstr "圆锥" +# English: "Draw" # Source-Hash: b5c56dd8c68de309 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.draw" msgstr "绘制" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.dutch" msgstr "荷兰式" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.flat" msgstr "平屋顶" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gable" msgstr "双坡" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.gambrel" msgstr "复折式" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.hip" msgstr "四坡" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.mansard" msgstr "孟莎式" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.room" msgstr "房间" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.shed" msgstr "单坡" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/lib/build-tab-state.ts #, fuzzy msgid "app.lib.buildTabState.wall" msgstr "墙体" +# English: "Another session saved first — refresh?" # Source-Hash: 8d17e7556ce142da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Another session saved first — refresh?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.anotherSessionSavedFirstRefresh" msgstr "另一个会话已先保存——要刷新吗?" +# English: "Build" # Source-Hash: 8f4b2028ad75465d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Build" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.build" msgstr "建造" +# English: "Dismiss" # Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.dismiss" msgstr "关闭" +# English: "Items" # Source-Hash: daebc0755d239c99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Items" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.items" msgstr "物品" +# English: "Paint" # Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.paint" msgstr "涂装" +# English: "Reload" # Source-Hash: 854555777b69a4b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.reload" msgstr "重新加载" +# English: "Scene" # Source-Hash: 8dc78e215a97d9fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.scene" msgstr "场景" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.settings" msgstr "设置" +# English: "Your changes haven't been saved. Reload to pick up the latest version." # Source-Hash: ee255fa26d076dcc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Your changes haven't been saved. Reload to pick up the latest version." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/scene-loader.tsx #, fuzzy msgid "app.sceneLoader.yourChangesHavenTBeenSaved" msgstr "你的更改尚未保存。重新加载以获取最新版本。" +# English: "Anyone with the link can view this project. They cannot edit it." # Source-Hash: 703e1b4b9b715d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Anyone with the link can view this project. They cannot edit it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.anyoneWithTheLinkCanView" msgstr "任何拥有链接的人都可以查看此项目,但无法编辑。" +# English: "Copied" # Source-Hash: 07e04484867c17d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copied" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copied" msgstr "已复制" +# English: "Copy" # Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Copy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.copy" msgstr "复制" +# English: "Could not read the sharing state." # Source-Hash: 8ff50e1a9962a4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not read the sharing state." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotReadTheSharingState" msgstr "无法读取分享状态。" +# English: "Could not turn off the public link." # Source-Hash: 791e281338a51483 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn off the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOffThePublic" msgstr "无法关闭公开链接。" +# English: "Could not turn on the public link." # Source-Hash: 2c5ddecfd652ba63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Could not turn on the public link." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.couldNotTurnOnThePublic" msgstr "无法开启公开链接。" +# English: "Embed code" # Source-Hash: 15e49d4da8765318 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedCode" msgstr "嵌入代码" +# English: "Embed link" # Source-Hash: d4523c2d303f99b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Embed link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.embedLink" msgstr "嵌入链接" +# English: "Hide controls" # Source-Hash: 252f2cbf5e1609a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.hideControls" msgstr "隐藏控件" +# English: "Link" # Source-Hash: ba77c0610a63bb2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.link" msgstr "链接" +# English: "Only the 3D view, no header or toolbar." # Source-Hash: a2560430aaec31d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Only the 3D view, no header or toolbar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.onlyThe3dViewNoHeader" msgstr "仅显示 3D 视图,不显示标题栏或工具栏。" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.open" msgstr "打开" +# English: "Public link" # Source-Hash: b34faaa5378056c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Public link" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.publicLink" msgstr "公开链接" +# English: "Share project" # Source-Hash: 7525e8007fcab09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Share project" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/share-panel.tsx #, fuzzy msgid "app.sharePanel.shareProject" msgstr "分享项目" +# English: "Automatic dimensions" # Source-Hash: 3d3f438d5a49eba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.automaticDimensions" msgstr "自动尺寸" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.camera" msgstr "相机" -# Source-Hash: e6bce525c47ba3ac +# English: "Ceilings" +# Source-Hash: a03ae72f209e32db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: apps/editor/components/viewer-toolbar.tsx +#, fuzzy +msgid "app.viewerToolbar.ceilings" +msgstr "天花板" + # English: "Clean plan; dimensions appear with selection" +# Source-Hash: e6bce525c47ba3ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cleanPlanDimensionsAppearWithSelection" msgstr "简洁平面;选中时显示尺寸" +# English: "Collapse sidebar" # Source-Hash: 61ce356577cfb04a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.collapseSidebar" msgstr "收起侧边栏" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crisp, opaque edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.crispOpaqueEdgeLines" msgstr "清晰、不透明的边线" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cutaway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.cutaway" msgstr "剖切" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.default" msgstr "默认" +# English: "Display" # Source-Hash: a313661ef9dd0a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.display" msgstr "显示" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Display settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.displaySettings" msgstr "显示设置" +# English: "Door/window marks" # Source-Hash: 0286962ab8e9cfcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door/window marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.doorWindowMarks" msgstr "门窗标记" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edges" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.edges" msgstr "边线" +# English: "Enter VR" # Source-Hash: d86126c5d16c6238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.enterVr" msgstr "进入 VR" +# English: "Exit VR" # Source-Hash: eb99fb0b382e58cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit VR" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exitVr" msgstr "退出 VR" +# English: "Expand sidebar" # Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expandSidebar" msgstr "展开侧边栏" +# English: "Expert" # Source-Hash: 08c178ee733ff009 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.expert" msgstr "专家" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exploded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.exploded" msgstr "分解" +# English: "Face of stud" # Source-Hash: c6f865a366ef5755 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face of stud" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faceOfStud" msgstr "龙骨面" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Faint outline of major creases" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.faintOutlineOfMajorCreases" msgstr "主要折边的淡轮廓" +# English: "Feet & inches" # Source-Hash: 08ecd5878b08139d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Feet & inches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.feetInches" msgstr "英尺和英寸" +# English: "Finished faces" # Source-Hash: 43b8017fa821c46c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finished faces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.finishedFaces" msgstr "完成面" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat and fast — no ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.flatAndFastNoAmbientOcclusion" msgstr "平面且快速 — 无环境光遮蔽" +# English: "Floor plan annotations" # Source-Hash: 060a718cdcff61b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanAnnotations" msgstr "平面图标注" +# English: "Floor plan mode" # Source-Hash: 483171f79dc32ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.floorPlanMode" msgstr "平面图模式" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full ambient occlusion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullAmbientOcclusion" msgstr "完整环境光遮蔽" +# English: "Full documentation and annotation controls" # Source-Hash: 91fd3544d9ce16bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full documentation and annotation controls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullDocumentationAndAnnotationControls" msgstr "完整的图档与标注控件" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullHeight" msgstr "全高" +# English: "Full wall thickness" # Source-Hash: c9838bc98cb35125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Full wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.fullWallThickness" msgstr "墙体全厚" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.grid" msgstr "网格" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels: {mode}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.levelsMode" msgstr "楼层:{mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.low" msgstr "矮墙" +# English: "Magnetic snap" # Source-Hash: 32f9a404c0bdce10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Magnetic snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.magneticSnap" msgstr "磁性吸附" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manual" msgstr "手动" +# English: "Manual dimensions" # Source-Hash: 2fb69122dd996c0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.manualDimensions" msgstr "手动尺寸" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.measurements" msgstr "测量" +# English: "Meters" # Source-Hash: 0a39470be3b84536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Meters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.meters" msgstr "米" +# English: "Millimeters" # Source-Hash: 83cce6c521e71d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Millimeters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.millimeters" msgstr "毫米" +# English: "3D measurements" # Source-Hash: b784527bb90b9c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.n3dMeasurements" msgstr "3D 测量" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No edge lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.noEdgeLines" msgstr "无边线" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.off" msgstr "关" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.on" msgstr "开" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orthographic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.orthographic" msgstr "正交" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perspective" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.perspective" msgstr "透视" +# English: "Preview" # Source-Hash: 20226b68d5467072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.preview" msgstr "预览" +# English: "Preview mode" # Source-Hash: 34c20b72a42ca618 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preview mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.previewMode" msgstr "预览模式" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.render" msgstr "渲染" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rendered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.rendered" msgstr "渲染" +# English: "Room labels" # Source-Hash: 4959d5538efc595c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.roomLabels" msgstr "房间标签" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scene theme" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.sceneTheme" msgstr "场景主题" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shadows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.shadows" msgstr "阴影" +# English: "Single wall axis" # Source-Hash: 59dc62417b44931d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single wall axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.singleWallAxis" msgstr "单一墙体轴线" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Soft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.soft" msgstr "柔和" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solid" msgstr "实体" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solo" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.solo" msgstr "单独" +# English: "Stack" # Source-Hash: 481b7608ce12139b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stack" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stack" msgstr "堆叠" +# English: "Stair annotations" # Source-Hash: 61ad94ca68b0458e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair annotations" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.stairAnnotations" msgstr "楼梯标注" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strong" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.strong" msgstr "强烈" +# English: "Structural core face" # Source-Hash: e1be031e67d95382 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural core face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralCoreFace" msgstr "结构核心面" +# English: "Structural grids & column centers" # Source-Hash: efc50964d7b0ec47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural grids & column centers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.structuralGridsColumnCenters" msgstr "结构轴网与柱中心" +# English: "Translucent" # Source-Hash: 5ce4f6b80cd30f86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Translucent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.translucent" msgstr "半透明" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.units" msgstr "单位" +# English: "Walkthrough" # Source-Hash: faf51e28b748e0d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walkthrough" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.walkthrough" msgstr "漫游" +# English: "Wall centerline" # Source-Hash: 62281975e51da7e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall centerline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallCenterline" msgstr "墙体中心线" +# English: "Wall dimensions" # Source-Hash: ff145d7003118ec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallDimensions" msgstr "墙体尺寸" +# English: "Walls: {label}" # Source-Hash: 13969d1d6e310aa4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls: {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: apps/editor/components/viewer-toolbar.tsx #, fuzzy msgid "app.viewerToolbar.wallsLabel" msgstr "墙体:{label}" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: human -# English: "Basement" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basement" msgstr "地下层" +# English: "Basement {number}" # Source-Hash: 14927085ee91ba05 # Review: human -# English: "Basement {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.basementNumber" msgstr "地下 {number} 层" +# English: "Floor {number}" # Source-Hash: e59074a7b9b3f56e # Review: human -# English: "Floor {number}" #: packages/editor/src/i18n/level-name.ts msgid "core.level.floorNumber" msgstr "{number} 层" +# English: "Ground floor" # Source-Hash: cde3efc9b7ddfa41 # Review: human -# English: "Ground floor" #: packages/editor/src/i18n/level-name.ts msgid "core.level.groundFloor" msgstr "首层" +# English: "Orbit Left" # Source-Hash: fdd8b3366021a1cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitLeft" msgstr "向左环绕" +# English: "Orbit Right" # Source-Hash: 4a56359f284a3b17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orbit Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.orbitRight" msgstr "向右环绕" +# English: "Top View" # Source-Hash: f5c12d738240f889 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top View" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/camera-actions.tsx #, fuzzy msgid "editor.actionMenu.cameraActions.topView" msgstr "顶视图" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.delete" msgstr "删除" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/control-modes.tsx #, fuzzy msgid "editor.actionMenu.controlModes.select" msgstr "选择" +# English: "Resume following" # Source-Hash: d9a257653600d5a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resume following" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-pill.tsx #, fuzzy msgid "editor.actionMenu.followPascalPill.resumeFollowing" msgstr "恢复跟随" +# English: "Follow Pascal: Off" # Source-Hash: 4f073f8051cca3ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.off" msgstr "跟随 Pascal:关" +# English: "Follow Pascal: On" # Source-Hash: 328ea04bb8cd12f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow Pascal: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/follow-pascal-toggle.tsx #, fuzzy msgid "editor.actionMenu.followPascalToggle.on" msgstr "跟随 Pascal:开" +# English: "Appliance" # Source-Hash: 7f4dcb90defe1ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appliance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.appliance" msgstr "电器" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.bathroom" msgstr "浴室" +# English: "Furniture" # Source-Hash: ed68e72f5353712b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furniture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.furniture" msgstr "家具" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.kitchen" msgstr "厨房" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/furnish-tools.tsx #, fuzzy msgid "editor.actionMenu.furnishTools.outdoor" msgstr "室外" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angle" msgstr "角度" +# English: "Angular dimension" # Source-Hash: 622f566fe1e3d8e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angular dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.angularDimension" msgstr "角度尺寸" +# English: "Arc length" # Source-Hash: 7e3b0d541355305e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.arcLength" msgstr "弧长" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.area" msgstr "面积" +# English: "Center mark" # Source-Hash: 88c7750ed51d47a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center mark" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.centerMark" msgstr "中心标记" +# English: "Chord dimension" # Source-Hash: 634912dabb092872 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chord dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.chordDimension" msgstr "弦长尺寸" +# English: "Continuous dimension" # Source-Hash: 3cb090120253f68f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.continuousDimension" msgstr "连续尺寸" +# English: "Coordinate dimensions" # Source-Hash: fb91b1d43b8362d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coordinate dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.coordinateDimensions" msgstr "坐标尺寸" +# English: "Diameter dimension" # Source-Hash: 530cb7ea461e8298 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.diameterDimension" msgstr "直径尺寸" +# English: "Distance" # Source-Hash: fe7ddb094aa8ff4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Distance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.distance" msgstr "距离" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.floorPlan" msgstr "平面图" +# English: "Linear dimension" # Source-Hash: a73539fc472e2e12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Linear dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.linearDimension" msgstr "线性尺寸" +# English: "Measurement options" # Source-Hash: 6bba239f244c3d28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementOptions" msgstr "测量选项" +# English: "Measurement type" # Source-Hash: 2cf5ca9b834ce8d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measurementType" msgstr "测量类型" +# English: "Measure: {selectedLabel}" # Source-Hash: 0431574a0e6d69d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measure: {selectedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.measureSelectedlabel" msgstr "测量:{selectedLabel}" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.perimeter" msgstr "周长" +# English: "Radius dimension" # Source-Hash: 6c9acc0589bdd4d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Radius dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.radiusDimension" msgstr "半径尺寸" +# English: "Smart" # Source-Hash: 01b0015e4e176124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.smart" msgstr "智能" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/measurement-control.tsx #, fuzzy msgid "editor.actionMenu.measurementControl.volume" msgstr "体积" +# English: "Point and ask" # Source-Hash: 64f3eb5e1936906d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAsk" msgstr "指点提问" +# English: "Point and ask (C)" # Source-Hash: d86d31e83f6f0888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask (C)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskC" msgstr "指点提问 (C)" +# English: "Point and ask works in the 3D view" # Source-Hash: 65622d2ef53c1f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Point and ask works in the 3D view" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/point-ask-button.tsx #, fuzzy msgid "editor.actionMenu.pointAskButton.pointAndAskWorksInThe" msgstr "指点提问仅在 3D 视图中可用" -# Source-Hash: 4275c05172abd7fb +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.beam" +msgstr "梁" + # English: "Ceiling" +# Source-Hash: 4275c05172abd7fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ceiling" msgstr "天花板" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.column" msgstr "柱" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.door" msgstr "门" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.duct" msgstr "风管" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.ductFitting" msgstr "风管配件" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.dwvPipe" msgstr "排水管" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.elevator" msgstr "电梯" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.fence" msgstr "围栏" -# Source-Hash: 7f4d93d5f2083844 +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.footing" +msgstr "基脚" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/structure-tools.tsx +#, fuzzy +msgid "editor.actionMenu.structureTools.foundation" +msgstr "基础" + # English: "Gable Roof" +# Source-Hash: 7f4d93d5f2083844 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.gableRoof" msgstr "双坡屋顶" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.hvacUnit" msgstr "暖通设备" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.lineset" msgstr "冷媒管组" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.liquidLine" msgstr "液管" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.pipeFitting" msgstr "管件" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.register" msgstr "风口" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.shelf" msgstr "置物架" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.slab" msgstr "楼板" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.spawnPoint" msgstr "出生点" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.stairs" msgstr "楼梯" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.trap" msgstr "存水弯" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.wall" msgstr "墙体" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.window" msgstr "窗" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/structure-tools.tsx #, fuzzy msgid "editor.actionMenu.structureTools.zone" msgstr "区域" -# Source-Hash: cdf3f12939ba108c +# English: "Dashed outlines of the neighbouring levels’ walls, to line walls up and check a roof against the walls under it." +# Source-Hash: a309ae5ee641f072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.dashedOutlinesOfTheNeighbouringLevels" +msgstr "以虚线显示相邻楼层的墙体轮廓,便于对齐墙体,并对照下方墙体检查屋顶。" + # English: "Delete guide image" +# Source-Hash: cdf3f12939ba108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteGuideImage" msgstr "删除参考图" +# English: "Delete scan" # Source-Hash: 6add06abbafa8289 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete scan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.deleteScan" msgstr "删除扫描模型" +# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" # Source-Hash: 6f5e541cb5fb502f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# guide image on this level} other {# guide images on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideCount" msgstr "{count, plural, other {本楼层有 # 张参考图}}" +# English: "Guide image {number}" # Source-Hash: 8a832a2f33757bf8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageNumber" msgstr "参考图 {number}" +# English: "Guide images" # Source-Hash: 7f3aaccfe8d1af1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImages" msgstr "参考图" +# English: "Guide image settings" # Source-Hash: 9b1de02505434d74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guideImageSettings" msgstr "参考图设置" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guides" msgstr "参考图" +# English: "Guides: Hidden" # Source-Hash: ccfb05c78d47d6f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesHidden" msgstr "参考图:隐藏" +# English: "Guides: Visible" # Source-Hash: 6d7ca85c4f389991 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guides: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.guidesVisible" msgstr "参考图:显示" +# English: "Hide guide images" # Source-Hash: 5f6264ac469f57c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideGuideImages" msgstr "隐藏参考图" +# English: "Hide reference floor" # Source-Hash: cae234c23229c80b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideReferenceFloor" msgstr "隐藏参考楼层" +# English: "Hide scans" # Source-Hash: 469a8416de876e0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.hideScans" msgstr "隐藏扫描模型" +# English: "{count} below" # Source-Hash: 873620c284a5651f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count} below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.levelsBelow" msgstr "下方 {count} 层" -# Source-Hash: 22a59ae72d8c9504 +# English: "No level above" +# Source-Hash: 6661c6766fdd1a7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelAbove" +msgstr "上方无楼层" + +# English: "No level below" +# Source-Hash: e34d9ec372c823ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.noLevelBelow" +msgstr "下方无楼层" + # English: "No lower floor available." +# Source-Hash: 22a59ae72d8c9504 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.noLowerFloorAvailable" msgstr "没有可用的下层楼层。" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.opacity" msgstr "不透明度" +# English: "Reference floor" # Source-Hash: 8bbd9bc90ac70a4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloor" msgstr "参考楼层" +# English: "Reference floor: {selectedLevelName}" # Source-Hash: 45493691a23c7690 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor: {selectedLevelName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSelectedlevelname" msgstr "参考楼层:{selectedLevelName}" +# English: "Reference floor settings" # Source-Hash: 7178c7b5a4581367 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference floor settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceFloorSettings" msgstr "参考楼层设置" +# English: "References" # Source-Hash: a52848021f822f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.references" msgstr "参考资料" +# English: "Upload GLB meshes as scan references or blueprint images as guide references." # Source-Hash: e59c9eec7dbd6ba1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload GLB meshes as scan references or blueprint images as guide references." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesEmpty" msgstr "上传 GLB 网格作为扫描参考,或上传图纸图片作为参考图。" +# English: "Reference settings" # Source-Hash: 3e73d06c3e96efba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referenceSettings" msgstr "参考资料设置" +# English: "References: Hidden" # Source-Hash: aa0df2c2f00672e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesHidden" msgstr "参考资料:隐藏" +# English: "References: Visible" # Source-Hash: 9976708fc92bfa91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "References: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.referencesVisible" msgstr "参考资料:显示" +# English: "Riser diagram" # Source-Hash: 320a5f1ec012900b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser diagram" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.riserDiagram" msgstr "立管图" +# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" # Source-Hash: 357b3d3eaf878a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# scan on this level} other {# scans on this level}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanCount" msgstr "{count, plural, other {本楼层有 # 个扫描模型}}" +# English: "Scan {number}" # Source-Hash: f09f12547484c888 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanNumber" msgstr "扫描模型 {number}" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scans" msgstr "扫描模型" +# English: "Scan settings" # Source-Hash: c9c52015095997e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scan settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scanSettings" msgstr "扫描模型设置" +# English: "Scans: Hidden" # Source-Hash: 60abc9f1a8aec65c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Hidden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansHidden" msgstr "扫描模型:隐藏" +# English: "Scans: Visible" # Source-Hash: 2d2ff119a7dd7911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scans: Visible" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.scansVisible" msgstr "扫描模型:显示" +# English: "Show guide images" # Source-Hash: 8211234b205fd9a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show guide images" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showGuideImages" msgstr "显示参考图" +# English: "Show reference floor" # Source-Hash: 7786cae6acb04cfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show reference floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showReferenceFloor" msgstr "显示参考楼层" +# English: "Show scans" # Source-Hash: 6066f0b6cff8a910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show scans" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.showScans" msgstr "显示扫描模型" -# Source-Hash: def6dff836767bda +# English: "Show walls below and above" +# Source-Hash: c100768fc929ccf4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.showWallsBelowAndAbove" +msgstr "显示上下层墙体" + # English: "Upload scan or guide image" +# Source-Hash: def6dff836767bda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/action-menu/view-toggles.tsx #, fuzzy msgid "editor.actionMenu.viewToggles.uploadScanOrGuideImage" msgstr "上传扫描模型或参考图" -# Source-Hash: 6aee0962fc18c0f1 +# English: "Walls above" +# Source-Hash: e9ac75feeb58a5be +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsAbove" +msgstr "上层墙体" + +# English: "Walls below" +# Source-Hash: 790a0b5c6dbf0e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelow" +msgstr "下层墙体" + +# English: "Walls below and above" +# Source-Hash: e74e28937511bce9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAbove" +msgstr "上下层墙体" + +# English: "Walls below and above settings" +# Source-Hash: 7c7bd2a996dfd5ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveSettings" +msgstr "上下层墙体设置" + +# English: "Walls below and above: shown" +# Source-Hash: 43bc80f62aeab442 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/action-menu/view-toggles.tsx +#, fuzzy +msgid "editor.actionMenu.viewToggles.wallsBelowAndAboveShown" +msgstr "上下层墙体:已显示" + # English: "Back to home" +# Source-Hash: 6aee0962fc18c0f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.backToHome" msgstr "返回首页" +# English: "Advanced" # Source-Hash: f571cb2d06ae01ae # Review: human -# English: "Advanced" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.advanced" msgstr "高级" +# English: "{lead} every wall takes the exterior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: 7afac05841af7b0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.caption" +msgstr "{lead}无论落在何处,每面墙体都采用外墙类型。按 Esc 或双击结束连续绘制。" + +# English: "Exterior wall" +# Source-Hash: 0a7c3607e2011f54 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.label" +msgstr "外墙" + +# English: "Exterior walls: every wall drawn takes the exterior type and cladding" +# Source-Hash: 76fd5dab80b9d731 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.exteriorWall.title" +msgstr "外墙:绘制的每面墙体都采用外墙类型和外饰面" + +# English: "walls · floor · ceiling" # Source-Hash: fb40866c2d6db5fb # Review: human -# English: "walls · floor · ceiling" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.hint" msgstr "墙体 · 楼板 · 天花板" +# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." # Source-Hash: 271039bd919dc4c1 # Review: human -# English: "Pick a shape to start. Every room brings its own walls, floor and ceiling." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.idle" msgstr "选择一种形状开始绘制。每个房间都会同时创建墙体、楼板和天花板。" +# English: "{lead} every wall takes the interior type, wherever it lands. Esc or double-click ends the run." +# Source-Hash: f1fddcf6d6b5c32b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.caption" +msgstr "{lead}无论落在何处,每面墙体都采用内墙类型。按 Esc 或双击结束连续绘制。" + +# English: "Interior wall" +# Source-Hash: b9e25ba9781c9848 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.label" +msgstr "内墙" + +# English: "Interior walls: every wall drawn takes the interior type and paint" +# Source-Hash: 9e4a942fd52a06c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.interiorWall.title" +msgstr "内墙:绘制的每面墙体都采用内墙类型和涂料" + +# English: "{lead} every line is an invisible wall: rooms close on it, nothing is built. Closed off outside the house, a room becomes a porch. Esc or double-click ends the run." +# Source-Hash: 3f519112d88b3ba6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.caption" +msgstr "{lead}每条线都是一道隐形墙:房间可在其上闭合,但不建造任何构件。在房屋外围合后,房间即成为门廊。按 Esc 或双击结束连续绘制。" + +# English: "Invisible wall" +# Source-Hash: 5b0317bd807d560e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.label" +msgstr "隐形墙" + +# English: "Invisible walls: divide a room or close off a porch without building a wall. Rooms close on them; they never cut a wall." +# Source-Hash: 72fbf62dac3603b5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.invisibleWall.title" +msgstr "隐形墙:无需建造墙体即可分隔房间或围合门廊。房间可在其上闭合;它们不会切断墙体。" + +# English: "{lead} then the first one again to close. For L-shapes and angled rooms." # Source-Hash: ae2800b6c9e2b6ed # Review: human -# English: "{lead} then the first one again to close. For L-shapes and angled rooms." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.caption" msgstr "{lead}然后再次点击起点闭合。适用于 L 形和斜角房间。" +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: human -# English: "Polygon" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.label" msgstr "多边形" +# English: "Click each corner," # Source-Hash: 345e3aca3879b5c5 # Review: human -# English: "Click each corner," #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.lead" msgstr "依次点击各个角点," +# English: "Polygon room" # Source-Hash: 8ec6152e139b0e3f # Review: human -# English: "Polygon room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.polygon.title" msgstr "多边形房间" +# English: "{lead} every line is a railing: a guard rooms close on, with no studs, siding or footing. Place a door on it for an opening. Esc or double-click ends the run." +# Source-Hash: 18773160bb84f069 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.caption" +msgstr "{lead}每条线都是一道栏杆:房间可在其上闭合的护栏,没有龙骨、壁板或基脚。在其上放置门即可形成开口。按 Esc 或双击结束连续绘制。" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.label" +msgstr "栏杆" + +# English: "Railings: a 36 in guard drawn like walls, posts at most 6 ft apart. Rooms close on it and it takes openings; style and height in the wall panel." +# Source-Hash: 161fbdfe5f09b81a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy +msgid "editor.buildPanel.rooms.railing.title" +msgstr "栏杆:像墙体一样绘制的 36 in 护栏,立柱间距不超过 6 ft。房间可在其上闭合,也可在其上开口;样式和高度在墙体面板中设置。" + +# English: "{lead} on the grid. Walls, floor and ceiling are created together." # Source-Hash: 9b46295eedcbedae # Review: human -# English: "{lead} on the grid. Walls, floor and ceiling are created together." #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.caption" msgstr "在网格上{lead}。墙体、楼板和天花板将一并创建。" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: human -# English: "Rectangle" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.label" msgstr "矩形" +# English: "Click two corners" # Source-Hash: 060faa48e299d7c9 # Review: human -# English: "Click two corners" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.lead" msgstr "点击两个对角点" +# English: "Rectangle room" # Source-Hash: 83a45603ac7f41e8 # Review: human -# English: "Rectangle room" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.rectangle.title" msgstr "矩形房间" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.title" msgstr "房间" -# Source-Hash: 8a21a03c4acf396b -# Review: human -# English: "{lead} Close a loop and it becomes a room; leave it open for partitions." +# English: "{lead} every wall starts where the last ended. Esc or double-click ends the run; close a loop and it becomes a room." +# Source-Hash: ee04555fa3ead992 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.caption" -msgstr "{lead}闭合轮廓后会形成房间;保持开口可绘制隔墙。" +msgstr "{lead}每面墙体都从上一面的终点开始。按 Esc 或双击结束连续绘制;闭合成环即形成房间。" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.label" msgstr "墙体" -# Source-Hash: 9843edd68426fa2d -# Review: human -# English: "Draw one wall at a time." +# English: "Click each corner;" +# Source-Hash: ab5c2c822d968ca2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/build-panel/build-panel.tsx +#, fuzzy msgid "editor.buildPanel.rooms.walls.lead" -msgstr "逐面绘制墙体。" +msgstr "依次点击各个转角;" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: human -# English: "Walls" #: packages/editor/src/components/ui/build-panel/build-panel.tsx msgid "editor.buildPanel.rooms.walls.title" msgstr "墙体" -# Source-Hash: 1f3650da23603008 +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraControlsHint" -msgstr "相机操作提示" +msgid "editor.buildPanel.wallTypeControls.default" +msgstr "默认" -# Source-Hash: 101b6a54c377049a +# English: "Delete from this project" +# Source-Hash: 1dba2aacc55e0bb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pan" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.pan" -msgstr "平移" +msgid "editor.buildPanel.wallTypeControls.deleteFromThisProject" +msgstr "从本项目中删除" -# Source-Hash: 306d409f8a1cf480 +# English: "Duplicate as a new type…" +# Source-Hash: 58a1b780e877ddc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.rotate" -msgstr "旋转" +msgid "editor.buildPanel.wallTypeControls.duplicateAsANewType" +msgstr "复制为新类型…" -# Source-Hash: 663cecc316eb91b8 +# English: "Edit layers…" +# Source-Hash: 43e7aad9a5f75b6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zoom" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.cameraHint.zoom" -msgstr "缩放" +msgid "editor.buildPanel.wallTypeControls.editLayers" +msgstr "编辑构造层…" -# Source-Hash: 34b7ca7ff46c44a9 +# English: "Edit or duplicate {label}" +# Source-Hash: 640331cc307a8421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label}: {finish}" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.ariaLabel" -msgstr "{label}:{finish}" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateLabel" +msgstr "编辑或复制{label}" -# Source-Hash: 4ca0e0e73229540c +# English: "Edit or duplicate this wall type" +# Source-Hash: 8f1ea5a4c16ba810 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" -#: packages/editor/src/components/ui/controls/finish-swatch.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.controls.finishSwatch.default" -msgstr "默认" +msgid "editor.buildPanel.wallTypeControls.editOrDuplicateThisWallType" +msgstr "编辑或复制此墙体类型" -# Source-Hash: a2bd204e93e90598 +# English: "New wall type…" +# Source-Hash: 273fcb71b4f9272f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismiss" -msgstr "关闭" +msgid "editor.buildPanel.wallTypeControls.newWallType" +msgstr "新建墙体类型…" -# Source-Hash: 9222f970dce6cc60 +# English: "Presets stay as cited. Duplicate one to change its layers." +# Source-Hash: 079e4005e0093122 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dismiss camera controls hint" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-controls.tsx #, fuzzy -msgid "editor.dismissCameraControlsHint" -msgstr "关闭相机操作提示" +msgid "editor.buildPanel.wallTypeControls.presetsStayAsCitedDuplicateOne" +msgstr "预设保持引用原样。复制后即可更改其构造层。" -# Source-Hash: aeac665749d3101d +# English: "Add" +# Source-Hash: b071c943d8da1500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand sidebar" -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.expandSidebar" -msgstr "展开侧边栏" +msgid "editor.buildPanel.wallTypeEditorDialog.add" +msgstr "添加" -# Source-Hash: dd86efcab72fc5c7 +# English: "Air space" +# Source-Hash: fcc69acc0fd92288 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis" -msgstr "轴" +msgid "editor.buildPanel.wallTypeEditorDialog.airSpace" +msgstr "空气层" -# Source-Hash: 5232a826549f915e +# English: "A wall type holds at most {limit, plural, one {# layer} other {# layers}}." +# Source-Hash: 46fb2832f979d656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⌥ axis" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.axis2" -msgstr "⌥ 轴" +msgid "editor.buildPanel.wallTypeEditorDialog.aWallTypeHoldsAtMost" +msgstr "一种墙体类型最多包含 {limit, plural, other {# 层}}。" -# Source-Hash: 7001600001844c92 +# English: "Brick veneer" +# Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "⚠ no equipment" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.noEquipment" -msgstr "⚠ 无设备" +msgid "editor.buildPanel.wallTypeEditorDialog.brickVeneer" +msgstr "砖饰面" -# Source-Hash: eae0194029f96edb +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# register} other {# registers}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.registerCount" -msgstr "{count, plural, other {# 个风口}}" +msgid "editor.buildPanel.wallTypeEditorDialog.cancel" +msgstr "取消" -# Source-Hash: 8fb180251a321cef +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "R/T rotate" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.rTRotate" -msgstr "R/T 旋转" +msgid "editor.buildPanel.wallTypeEditorDialog.drywall" +msgstr "石膏板" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 6fb660d4eabe8d6a +# English: "Duplicate wall type" +# Source-Hash: 1f2e15ce909adf0c # Review: required -# English: "{count, plural, one {{number} run} other {{number} runs}}" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.runCount" -msgstr "{count, plural, other {{number} 段}}" +msgid "editor.buildPanel.wallTypeEditorDialog.duplicateWallType" +msgstr "复制墙体类型" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Edit wall type" +# Source-Hash: 832628bc67f36778 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" -#: packages/editor/src/components/editor/floating-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingActionMenu.system" -msgstr "系统" +msgid "editor.buildPanel.wallTypeEditorDialog.editWallType" +msgstr "编辑墙体类型" -# Source-Hash: f0e76aea883d4e48 +# English: "Every wall type has one framing core" +# Source-Hash: 7d3d13d418b64f1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a floor under the roof" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" -msgstr "在屋顶下添加楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.everyWallTypeHasOneFraming" +msgstr "每种墙体类型都有一个龙骨核心层" -# Source-Hash: a982ff9b075af829 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level above" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelAbove" -msgstr "在上方添加楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.fiberCement" +msgstr "纤维水泥" -# Source-Hash: 0db7d221df08fa6b +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add level below" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.addLevelBelow" -msgstr "在下方添加楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.finish" +msgstr "饰面" -# Source-Hash: c0774c185060fc13 +# English: "Framing core" +# Source-Hash: 23306d290f066b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.cancel" -msgstr "取消" +msgid "editor.buildPanel.wallTypeEditorDialog.framingCore" +msgstr "龙骨核心层" -# Source-Hash: b26de372b4b0eaac +# English: "{fullReason} Remove one to add another." +# Source-Hash: b50fa488c2bfb502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.delete" -msgstr "删除" +msgid "editor.buildPanel.wallTypeEditorDialog.fullreasonRemoveOneToAddAnother" +msgstr "{fullReason}移除一层后才能再添加。" -# Source-Hash: 8e29375893d63408 +# English: "Gypsum sheathing" +# Source-Hash: e3e25fbbf14e97fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevel" -msgstr "删除楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.gypsumSheathing" +msgstr "石膏覆面板" -# Source-Hash: c3bc69ec52022250 +# English: "Inside" +# Source-Hash: 8a7ff5011275abbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.deleteLevelConfirm" -msgstr "确定要删除{levelName}吗?此楼层上的所有墙体、楼板和对象都将被永久移除。" +msgid "editor.buildPanel.wallTypeEditorDialog.inside" +msgstr "内侧" -# Source-Hash: 5b2826ddac95cd0c +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to reorder" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.dragToReorder" -msgstr "拖动以重新排序" +msgid "editor.buildPanel.wallTypeEditorDialog.insulation" +msgstr "保温层" -# Source-Hash: 933177338f5a03ed +# English: "Lap siding" +# Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateLevel" -msgstr "复制楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.lapSiding" +msgstr "搭接壁板" -# Source-Hash: 9614c6825a7c67bf +# English: "Layer" +# Source-Hash: d7ee1cefa20d0e32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.duplicateWithOptions" -msgstr "带选项复制..." +msgid "editor.buildPanel.wallTypeEditorDialog.layer" +msgstr "构造层" -# Source-Hash: 5ec91bb339806413 +# English: "Layer role" +# Source-Hash: bd00fff893ace820 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit unit focus" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.exitUnitFocus" -msgstr "退出单元聚焦" +msgid "editor.buildPanel.wallTypeEditorDialog.layerRole" +msgstr "层功能" -# Source-Hash: a88c3f8bed58b4fa +# English: "Lining" +# Source-Hash: e5536deeaabfacec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused unit has zones here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" -msgstr "聚焦的单元在此有区域" +msgid "editor.buildPanel.wallTypeEditorDialog.lining" +msgstr "衬层" -# Source-Hash: 655055a310fb5d13 +# English: "Lists under Exterior: it has a cladding side." +# Source-Hash: d4e37e79879efa23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} over grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorOverGrade" -msgstr "首层高出地坪 {distance}。将首层设为高出地坪 8 英寸" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderExteriorItHasA" +msgstr "归入“外墙”:有外饰面一侧。" -# Source-Hash: 77548faae4869eec +# English: "Lists under Interior: both faces match." +# Source-Hash: d455f88eb0e9e68e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground floor stands {distance} under grade. Seat the ground floor 8 in over grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundFloorUnderGrade" -msgstr "首层低于地坪 {distance}。将首层设为高出地坪 8 英寸" +msgid "editor.buildPanel.wallTypeEditorDialog.listsUnderInteriorBothFacesMatch" +msgstr "归入“内墙”:两面相同。" -# Source-Hash: e3763ae2dc62c03f +# English: "List the layers from the outside in, around one framing core. Walls already drawn keep their layers; walls drawn from now on use the saved type." +# Source-Hash: 5454746105949d05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground line: the levels under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" -msgstr "地坪线:其下方的楼层位于地坪以下" +msgid "editor.buildPanel.wallTypeEditorDialog.listTheLayersFromTheOutside" +msgstr "从外向内列出各构造层,围绕一个龙骨核心层。已绘制的墙体保留原有构造层;此后绘制的墙体使用保存的类型。" -# Source-Hash: e12035eb47cc61cc +# English: "Material" +# Source-Hash: 964b86ae1474ff98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground · {offset}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.groundOffset" -msgstr "地坪 · {offset}" +msgid "editor.buildPanel.wallTypeEditorDialog.material" +msgstr "材料" -# Source-Hash: 5661c11383217661 +# English: "Mineral wool board" +# Source-Hash: 75d79fb9a2e8fcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insert level here" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.insertLevelHere" -msgstr "在此插入楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.mineralWoolBoard" +msgstr "岩棉板" -# Source-Hash: 0215b36d5a08597a +# English: "Move in" +# Source-Hash: 78acc1bad880ce0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level height" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.levelHeight" -msgstr "楼层高度" +msgid "editor.buildPanel.wallTypeEditorDialog.moveIn" +msgstr "向内移动" -# Source-Hash: 91af436e81bb0875 +# English: "Move out" +# Source-Hash: 2de6ee30b4e608bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paste copied selection" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.pasteCopiedSelection" -msgstr "粘贴复制的选择" +msgid "editor.buildPanel.wallTypeEditorDialog.moveOut" +msgstr "向外移动" -# Source-Hash: 68bc52f53240cbec +# English: "Name" +# Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reorder {levelName}" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.reorderLevelname" -msgstr "重新排序{levelName}" +msgid "editor.buildPanel.wallTypeEditorDialog.name" +msgstr "名称" -# Source-Hash: 625d53de72ebc2ef +# English: "New wall type" +# Source-Hash: 4b3edeb00b32607e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seat the building so this floor stands at grade; the floors under it are below grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" -msgstr "调整建筑使此层位于地坪;其下方各层位于地坪以下" +msgid "editor.buildPanel.wallTypeEditorDialog.newWallType" +msgstr "新建墙体类型" -# Source-Hash: 7ac9c00895c30d76 +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set as ground floor" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.setAsGroundFloor" -msgstr "设为首层" +msgid "editor.buildPanel.wallTypeEditorDialog.osb" +msgstr "定向刨花板" -# Source-Hash: d6b381247066a7aa +# English: "Outside" +# Source-Hash: dc4fe6cb415ed882 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" -msgstr "无法删除首层楼层" +msgid "editor.buildPanel.wallTypeEditorDialog.outside" +msgstr "外侧" -# Source-Hash: b1122051dce0d7a1 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor stands at grade" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" -msgstr "此层位于地坪" +msgid "editor.buildPanel.wallTypeEditorDialog.plaster" +msgstr "灰泥" -# Source-Hash: 062cc0f1afef0c52 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" -#: packages/editor/src/components/ui/floating-level-selector.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floatingLevelSelector.unit" -msgstr "单元" +msgid "editor.buildPanel.wallTypeEditorDialog.plywood" +msgstr "胶合板" -# Source-Hash: 5871b7c20478f4a1 +# English: "Remove layer" +# Source-Hash: ca36e9fd7fba1fce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor area" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.area" -msgstr "楼面区域" +msgid "editor.buildPanel.wallTypeEditorDialog.removeLayer" +msgstr "移除构造层" -# Source-Hash: 8b2e1435272aa86a +# English: "Rigid foam" +# Source-Hash: 7d8f351c35f89b94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{room} floor" -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.room" -msgstr "{room}楼板" +msgid "editor.buildPanel.wallTypeEditorDialog.rigidFoam" +msgstr "硬质泡沫板" -# Source-Hash: 4731183fec06cbdf +# English: "Save wall type" +# Source-Hash: e8bc984965d5753d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shared floor" -#: packages/editor/src/components/ui/panels/room-construction-rows.tsx -#: packages/editor/src/i18n/floor-footprint-name.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.floorFootprint.shared" -msgstr "共用楼板" +msgid "editor.buildPanel.wallTypeEditorDialog.saveWallType" +msgstr "保存墙体类型" -# Source-Hash: c0774c185060fc13 +# English: "Sheathing" +# Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.cancel" -msgstr "取消" +msgid "editor.buildPanel.wallTypeEditorDialog.sheathing" +msgstr "覆面板" -# Source-Hash: 4c6ca3441c74da9e +# English: "Stone veneer" +# Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place building" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.placeBuilding" -msgstr "放置建筑" +msgid "editor.buildPanel.wallTypeEditorDialog.stoneVeneer" +msgstr "石材饰面" -# Source-Hash: 306d409f8a1cf480 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/building-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.buildingHelper.rotate" -msgstr "旋转" +msgid "editor.buildPanel.wallTypeEditorDialog.stucco" +msgstr "灰泥饰面" -# Source-Hash: 832470d3aeee82fd +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel placement" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.cancelPlacement" -msgstr "取消放置" +msgid "editor.buildPanel.wallTypeEditorDialog.thickness" +msgstr "厚度" -# Source-Hash: e374fcaee33c3e2c +# English: "Total {thickness}" +# Source-Hash: 72e31eecaeb87447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation — click or press C to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" -msgstr "连续放置 — 点击或按 C 切换" +msgid "editor.buildPanel.wallTypeEditorDialog.totalThickness" +msgstr "总计 {thickness}" -# Source-Hash: b108c7dc2ec2b9de +# English: "Wall type" +# Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuation: {label}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/wall-type-editor-dialog.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.continuationLabel" -msgstr "连续放置:{label}" +msgid "editor.buildPanel.wallTypeEditorDialog.wallType" +msgstr "墙体类型" -# Source-Hash: 729d87891cb7dbb1 +# English: "Brick" +# Source-Hash: abff67400a76e5eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag to draw fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" -msgstr "拖动绘制围栏" +msgid "editor.buildPanel.wallTypes.brick" +msgstr "砖" -# Source-Hash: b52e874718af1916 +# English: "Cladding" +# Source-Hash: ecca209317a2d1cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScope" -msgstr "擦除:{scope}" +msgid "editor.buildPanel.wallTypes.cladding" +msgstr "外饰面" -# Source-Hash: 61f7494abc319e66 +# English: "Drawing: Exterior walls." +# Source-Hash: 5c53959decce5dae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" -msgstr "擦除范围 — 点击或按 Shift 切换" +msgid "editor.buildPanel.wallTypes.drawingExterior" +msgstr "正在绘制:外墙。" -# Source-Hash: 59993c163d68cc53 +# English: "Drawing: Interior walls." +# Source-Hash: 5206b8695c329b06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" -msgstr "擦除范围:{scope}" +msgid "editor.buildPanel.wallTypes.drawingInterior" +msgstr "正在绘制:内墙。" -# Source-Hash: a4bdcbc6cab772bf +# English: "Drywall paint" +# Source-Hash: 8a542e796ac8edf0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence type — click or press T to switch between straight, curved and freehand" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" -msgstr "围栏类型 — 点击或按 T 在直线、曲线和手绘之间切换" +msgid "editor.buildPanel.wallTypes.drywallPaint" +msgstr "石膏板涂料" -# Source-Hash: 2a9ed258727078ac +# English: "Exterior" +# Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fewer shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" -msgstr "收起快捷键" +msgid "editor.buildPanel.wallTypes.exterior" +msgstr "外墙" -# Source-Hash: 698fa6e78af4d4b7 +# English: "Fiber cement" +# Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish curve (or double-click)" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" -msgstr "完成曲线(或双击)" +msgid "editor.buildPanel.wallTypes.fiberCement" +msgstr "纤维水泥" -# Source-Hash: 09f6d79edf5cfd16 +# English: "{drawing} Every wall takes the exterior type until you pick a shape or leave the tool." +# Source-Hash: 5badd399f078d485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridLength" -msgstr "网格:{length}" +msgid "editor.buildPanel.wallTypes.forcedExterior" +msgstr "{drawing}在选择形状或退出工具之前,每面墙体都采用外墙类型。" -# Source-Hash: c493b562040c4328 +# English: "{drawing} Every wall takes the interior type until you pick a shape or leave the tool." +# Source-Hash: de2c329c8d03a385 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step — click or tap Ctrl to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" -msgstr "网格步长 — 点击或轻按 Ctrl 切换" +msgid "editor.buildPanel.wallTypes.forcedInterior" +msgstr "{drawing}在选择形状或退出工具之前,每面墙体都采用内墙类型。" -# Source-Hash: d36a8c93d81b3a36 +# English: "New walls use the exterior type and cladding; walls drawn inside a room use the interior type. Every drywall face gets the paint." +# Source-Hash: b70a5d0948632955 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid step: {length}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.gridStepLength" -msgstr "网格步长:{length}" +msgid "editor.buildPanel.wallTypes.hint" +msgstr "新墙体采用外墙类型和外饰面;在房间内绘制的墙体采用内墙类型。所有石膏板面都刷该涂料。" -# Source-Hash: abea993ede7daa7e +# English: "Interior" +# Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a painted surface to erase" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" -msgstr "悬停在已涂装的表面上以擦除" +msgid "editor.buildPanel.wallTypes.interior" +msgstr "内墙" -# Source-Hash: fc1ed0ddc0cfcef5 +# English: "Plain wall" +# Source-Hash: 3744d42a0e4ee161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" -msgstr "悬停在表面上以涂装" +msgid "editor.buildPanel.wallTypes.plainWall" +msgstr "普通墙体" -# Source-Hash: 63dec5de4a337015 +# English: "Siding" +# Source-Hash: 8884a1d20caae02f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a surface to pick its material" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" -msgstr "悬停在表面上以拾取其材质" +msgid "editor.buildPanel.wallTypes.siding" +msgstr "壁板" -# Source-Hash: 7ad6dc1b1f03f970 +# English: "Stone" +# Source-Hash: 44c6466e6907a0b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More shortcuts" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.moreShortcuts" -msgstr "更多快捷键" +msgid "editor.buildPanel.wallTypes.stone" +msgstr "石材" -# Source-Hash: 476b8f5f9ee542e6 +# English: "Stucco" +# Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/build-panel/build-panel.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScope" -msgstr "涂装:{scope}" +msgid "editor.buildPanel.wallTypes.stucco" +msgstr "灰泥饰面" -# Source-Hash: 9c09ce4c34122277 +# English: "Camera controls hint" +# Source-Hash: 1f3650da23603008 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" -msgstr "涂装范围 — 点击或按 Shift 切换" +msgid "editor.cameraControlsHint" +msgstr "相机操作提示" -# Source-Hash: 7890aa81687f6654 +# English: "Pan" +# Source-Hash: 101b6a54c377049a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint scope: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" -msgstr "涂装范围:{scope}" +msgid "editor.cameraHint.pan" +msgstr "平移" -# Source-Hash: a75b3abc0784b12b +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick: {scope}" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.pickScope" -msgstr "拾取:{scope}" +msgid "editor.cameraHint.rotate" +msgstr "旋转" -# Source-Hash: 69c1044e6ea87ccf +# English: "Zoom" +# Source-Hash: 663cecc316eb91b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gate on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" -msgstr "在围栏上放置大门" +msgid "editor.cameraHint.zoom" +msgstr "缩放" -# Source-Hash: 3754255b8affc7a0 +# English: "{label}: {finish}" +# Source-Hash: 34b7ca7ff46c44a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place passage on a fence" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" -msgstr "在围栏上放置通道" +msgid "editor.controls.finishSwatch.ariaLabel" +msgstr "{label}:{finish}" -# Source-Hash: a8328c8964e947fa +# English: "Default" +# Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a material to paint" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/controls/finish-swatch.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" -msgstr "选择要涂装的材质" +msgid "editor.controls.finishSwatch.default" +msgstr "默认" -# Source-Hash: 6f95d7116aea5738 +# English: "Dismiss" +# Source-Hash: a2bd204e93e90598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Angles" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingAngles" -msgstr "吸附:角度" +msgid "editor.dismiss" +msgstr "关闭" -# Source-Hash: 2d6d8d85d2ebed47 +# English: "Dismiss camera controls hint" +# Source-Hash: 9222f970dce6cc60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Grid" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingGrid" -msgstr "吸附:网格" +msgid "editor.dismissCameraControlsHint" +msgstr "关闭相机操作提示" -# Source-Hash: 9096c5f9d8ce0f7c +# English: "Click to edit the foundation’s outline" +# Source-Hash: e314809b6907d9ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Lines" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor-2d/floorplan-foundation-layer.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingLines" -msgstr "吸附:线条" +msgid "editor.editor2d.floorplanFoundationLayer.clickToEditTheFoundationS" +msgstr "点击编辑基础轮廓" -# Source-Hash: b663afbf0835cf32 +# English: "Expand sidebar" +# Source-Hash: aeac665749d3101d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping mode — click or press Shift to cycle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" -msgstr "吸附模式 — 点击或按 Shift 切换" +msgid "editor.expandSidebar" +msgstr "展开侧边栏" -# Source-Hash: d00e984fc88f71aa +# English: "Axis" +# Source-Hash: dd86efcab72fc5c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snapping: Off" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.snappingOff" -msgstr "吸附:关" +msgid "editor.floatingActionMenu.axis" +msgstr "轴" -# Source-Hash: 6be9d0d312200761 +# English: "⌥ axis" +# Source-Hash: 5232a826549f915e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight continuation is unavailable for curved or freehand fences" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" -msgstr "曲线或手绘围栏无法直线连续" +msgid "editor.floatingActionMenu.axis2" +msgstr "⌥ 轴" -# Source-Hash: 0d56b1f0a0493302 +# English: "⚠ no equipment" +# Source-Hash: 7001600001844c92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Continuous" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightContinuous" -msgstr "直线:连续" +msgid "editor.floatingActionMenu.noEquipment" +msgstr "⚠ 无设备" -# Source-Hash: c034e85d3e7fd27e +# English: "{count, plural, one {# register} other {# registers}}" +# Source-Hash: eae0194029f96edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight fence continuation — click or press C to toggle" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" -msgstr "直线围栏连续 — 点击或按 C 切换" +msgid "editor.floatingActionMenu.registerCount" +msgstr "{count, plural, other {# 个风口}}" -# Source-Hash: e3f679cdb4a3d603 +# English: "R/T rotate" +# Source-Hash: 8fb180251a321cef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight: Single" -#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.contextualHelperPanel.straightSingle" -msgstr "直线:单段" +msgid "editor.floatingActionMenu.rTRotate" +msgstr "R/T 旋转" -# Source-Hash: 9a56d90d461b4bca +# English: "{count, plural, one {{number} run} other {{number} runs}}" +# Source-Hash: 6fb660d4eabe8d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPoint" -msgstr "添加点" +msgid "editor.floatingActionMenu.runCount" +msgstr "{count, plural, other {{number} 段}}" -# Source-Hash: c3dc3918c823a5e6 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point · click the first to close" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/floating-action-menu.tsx #, fuzzy -msgid "editor.helpers.helperManager.addPointClickTheFirstTo" -msgstr "添加点 · 点击第一个点闭合" +msgid "editor.floatingActionMenu.system" +msgstr "系统" -# Source-Hash: d8cf72d482d56cd7 +# English: "Add a floor under the roof" +# Source-Hash: f0e76aea883d4e48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brush size" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.brushSize" -msgstr "笔刷大小" +msgid "editor.floatingLevelSelector.addAFloorUnderTheRoof" +msgstr "在屋顶下添加楼层" -# Source-Hash: c0774c185060fc13 +# English: "Add level above" +# Source-Hash: a982ff9b075af829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancel" -msgstr "取消" +msgid "editor.floatingLevelSelector.addLevelAbove" +msgstr "在上方添加楼层" -# Source-Hash: 104fdab7934ec66c +# English: "Add level below" +# Source-Hash: 0db7d221df08fa6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel picking" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelPicking" -msgstr "取消拾取" +msgid "editor.floatingLevelSelector.addLevelBelow" +msgstr "在下方添加楼层" -# Source-Hash: c318cf0ce70b8977 +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel stroke" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.cancelStroke" -msgstr "取消笔画" +msgid "editor.floatingLevelSelector.cancel" +msgstr "取消" -# Source-Hash: 16552c2580bbe8b8 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.done" -msgstr "完成" +msgid "editor.floatingLevelSelector.delete" +msgstr "删除" -# Source-Hash: e297db9ca0ce6d31 +# English: "Delete level" +# Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the mezzanine" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheMezzanine" -msgstr "绘制夹层" +msgid "editor.floatingLevelSelector.deleteLevel" +msgstr "删除楼层" -# Source-Hash: c8be604718a7a9fd +# English: "Are you sure you want to delete {levelName}? All walls, floors, and objects on this level will be permanently removed." +# Source-Hash: c3bc69ec52022250 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the opening" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheOpening" -msgstr "绘制开口" +msgid "editor.floatingLevelSelector.deleteLevelConfirm" +msgstr "确定要删除{levelName}吗?此楼层上的所有墙体、楼板和对象都将被永久移除。" -# Source-Hash: 231386ae6d534163 +# English: "Drag to reorder" +# Source-Hash: 5b2826ddac95cd0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw the terrace" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.drawTheTerrace" -msgstr "绘制露台" +msgid "editor.floatingLevelSelector.dragToReorder" +msgstr "拖动以重新排序" -# Source-Hash: e092e803eb60af69 +# English: "Duplicate level" +# Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit smart measure" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.exitSmartMeasure" -msgstr "退出智能测量" +msgid "editor.floatingLevelSelector.duplicateLevel" +msgstr "复制楼层" -# Source-Hash: b3907da0ad234f10 +# English: "Duplicate with options..." +# Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finish" -msgstr "完成" +msgid "editor.floatingLevelSelector.duplicateWithOptions" +msgstr "带选项复制..." -# Source-Hash: f74bd937ae2e3a40 +# English: "Exit unit focus" +# Source-Hash: 5ec91bb339806413 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish at the edge" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.finishAtTheEdge" -msgstr "在边缘处完成" +msgid "editor.floatingLevelSelector.exitUnitFocus" +msgstr "退出单元聚焦" -# Source-Hash: 21029cf1c45697cc +# English: "Focused unit has zones here" +# Source-Hash: a88c3f8bed58b4fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMove" -msgstr "自由移动" +msgid "editor.floatingLevelSelector.focusedUnitHasZonesHere" +msgstr "聚焦的单元在此有区域" -# Source-Hash: 3247d8689bed6ff6 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Free move · slide doors and windows" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" -msgstr "自由移动 · 滑动门窗" +msgid "editor.floatingLevelSelector.foundation" +msgstr "基础" -# Source-Hash: b5ad765b43e75a5c +# English: "Basement" +# Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to pick a material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.holdToPickAMaterial" -msgstr "按住以拾取材质" +msgid "editor.floatingLevelSelector.foundationBadgeBasement" +msgstr "地下室" -# Source-Hash: 46b53c5449c98964 +# English: "Basement walls cut into the ground" +# Source-Hash: 96878e51aa1f4aac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inspect surface dimensions" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.inspectSurfaceDimensions" -msgstr "查看表面尺寸" +msgid "editor.floatingLevelSelector.foundationBadgeBasementTitle" +msgstr "地下室墙体嵌入地下" -# Source-Hash: 63f1f7006ae14177 +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint with this material" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.paintWithThisMaterial" -msgstr "使用此材质涂装" +msgid "editor.floatingLevelSelector.foundationBadgeOff" +msgstr "关闭" -# Source-Hash: 188f55cc5afbe34f +# English: "No foundation drawn: click to set one up" +# Source-Hash: 44761dadfc126cbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick target height" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pickTargetHeight" -msgstr "拾取目标高度" +msgid "editor.floatingLevelSelector.foundationBadgeOffTitle" +msgstr "未绘制基础:点击进行设置" -# Source-Hash: 2880fc9752eababa +# English: "Piles" +# Source-Hash: f4031d60a7899a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin measurement lens" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pinMeasurementLens" -msgstr "固定测量放大镜" +msgid "editor.floatingLevelSelector.foundationBadgePiles" +msgstr "桩基" -# Source-Hash: 722741535b346e09 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.place" -msgstr "放置" +msgid "editor.floatingLevelSelector.foundationBadgePilesTitle" +msgstr "桩上的基础梁" -# Source-Hash: 82010a7fd6350c93 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place inside the room" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.placeInsideTheRoom" -msgstr "放置在房间内" +msgid "editor.floatingLevelSelector.foundationBadgeRaised" +msgstr "架空" -# Source-Hash: 8b97f4503edb27b9 +# English: "Raised floor on a stem wall" +# Source-Hash: 69980d5b1f49e272 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Press anywhere, drag to the opposite corner" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" -msgstr "在任意位置按下,拖到对角" +msgid "editor.floatingLevelSelector.foundationBadgeRaisedTitle" +msgstr "基础短墙上的架空楼板" -# Source-Hash: 786f0b1c81b2a6c0 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise or lower" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.raiseOrLower" -msgstr "升高或降低" +msgid "editor.floatingLevelSelector.foundationBadgeSlab" +msgstr "楼板" -# Source-Hash: b033f46ae3a0cbb7 +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove a painted part, or reset a surface" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" -msgstr "移除涂装部分或重置表面" +msgid "editor.floatingLevelSelector.foundationBadgeSlabTitle" +msgstr "落地楼板" -# Source-Hash: 0dc6d121791696f7 +# English: "The ground floor stands {distance} over grade. {action}" +# Source-Hash: 6a00e82fda812178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last point" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.removeLastPoint" -msgstr "移除最后一个点" +msgid "editor.floatingLevelSelector.groundFloorOverGrade" +msgstr "首层高出地坪 {distance}。{action}" -# Source-Hash: f85bdac0a1d16603 +# English: "The ground floor stands {distance} under grade. {action}" +# Source-Hash: 61f400e77494df9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.resize" -msgstr "调整大小" +msgid "editor.floatingLevelSelector.groundFloorUnderGrade" +msgstr "首层低于地坪 {distance}。{action}" -# Source-Hash: 8a665ecaeffa9308 +# English: "Ground line: the levels under it are below grade" +# Source-Hash: e3763ae2dc62c03f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotate45" -msgstr "旋转 ±45°" +msgid "editor.floatingLevelSelector.groundLineTheLevelsUnderIt" +msgstr "地坪线:其下方的楼层位于地坪以下" -# Source-Hash: 6099ee99f8c38700 +# English: "Ground · {offset}" +# Source-Hash: e12035eb47cc61cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.rotateTheSelection45" -msgstr "将所选旋转 ±45°" +msgid "editor.floatingLevelSelector.groundOffset" +msgstr "地坪 · {offset}" -# Source-Hash: c93ae898b4d59f6f +# English: "Insert level here" +# Source-Hash: 5661c11383217661 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set one corner, then the opposite" -#: packages/editor/src/components/ui/helpers/helper-manager.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" -msgstr "设置一个角,再设置对角" +msgid "editor.floatingLevelSelector.insertLevelHere" +msgstr "在此插入楼层" -# Source-Hash: c0774c185060fc13 +# English: "Level height" +# Source-Hash: 0215b36d5a08597a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.cancel" -msgstr "取消" +msgid "editor.floatingLevelSelector.levelHeight" +msgstr "楼层高度" -# Source-Hash: ed7f3d8417a3e074 +# English: "Paste copied selection" +# Source-Hash: 91af436e81bb0875 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.forcePlace" -msgstr "强制放置" +msgid "editor.floatingLevelSelector.pasteCopiedSelection" +msgstr "粘贴复制的选择" -# Source-Hash: 722741535b346e09 +# English: "Reorder {levelName}" +# Source-Hash: 68bc52f53240cbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.place" -msgstr "放置" +msgid "editor.floatingLevelSelector.reorderLevelname" +msgstr "重新排序{levelName}" -# Source-Hash: 306d409f8a1cf480 +# English: "Seat the building so this floor stands at grade; the floors under it are below grade" +# Source-Hash: 625d53de72ebc2ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/components/ui/helpers/item-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.itemHelper.rotate" -msgstr "旋转" +msgid "editor.floatingLevelSelector.seatTheBuildingSoThisFloor" +msgstr "调整建筑使此层位于地坪;其下方各层位于地坪以下" -# Source-Hash: e9dfc2a178bca5f7 +# English: "Set as ground floor" +# Source-Hash: 7ac9c00895c30d76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guided constraints bypassed" -#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" -msgstr "已绕过引导约束" +msgid "editor.floatingLevelSelector.setAsGroundFloor" +msgstr "设为首层" -# Source-Hash: c69865ec5d9d1ac9 +# English: "Show the foundation" +# Source-Hash: 46b0734342ab499f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.angle.g10zaw" -msgstr "角度" +msgid "editor.floatingLevelSelector.showTheFoundation" +msgstr "显示基础" -# Source-Hash: 9c562983f6fed685 +# English: "The ground level cannot be deleted" +# Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arm length m" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.armLengthM.1jclqgw" -msgstr "臂长 m" +msgid "editor.floatingLevelSelector.theGroundLevelCannotBeDeleted" +msgstr "无法删除首层楼层" -# Source-Hash: 38461bc364321749 +# English: "This floor stands at grade" +# Source-Hash: b1122051dce0d7a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment side" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.attachmentSide.6n9s5v" -msgstr "连接侧" +msgid "editor.floatingLevelSelector.thisFloorStandsAtGrade" +msgstr "此层位于地坪" -# Source-Hash: 441124187e7da2fc +# English: "Unit" +# Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx #, fuzzy -msgid "editor.inspector.field.autoHangers.10554ka" +msgid "editor.floatingLevelSelector.unit" +msgstr "单元" + +# English: "Floor area" +# Source-Hash: 5871b7c20478f4a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.area" +msgstr "楼面区域" + +# English: "{room} floor" +# Source-Hash: 8b2e1435272aa86a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.room" +msgstr "{room}楼板" + +# English: "Shared floor" +# Source-Hash: 4731183fec06cbdf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#: packages/editor/src/i18n/floor-footprint-name.ts +#, fuzzy +msgid "editor.floorFootprint.shared" +msgstr "共用楼板" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.cancel" +msgstr "取消" + +# English: "Place building" +# Source-Hash: 4c6ca3441c74da9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.placeBuilding" +msgstr "放置建筑" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/building-helper.tsx +#, fuzzy +msgid "editor.helpers.buildingHelper.rotate" +msgstr "旋转" + +# English: "Cancel placement" +# Source-Hash: 832470d3aeee82fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.cancelPlacement" +msgstr "取消放置" + +# English: "Continuation — click or press C to cycle" +# Source-Hash: e374fcaee33c3e2c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationClickOrPressCTo" +msgstr "连续放置 — 点击或按 C 切换" + +# English: "Continuation: {label}" +# Source-Hash: b108c7dc2ec2b9de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.continuationLabel" +msgstr "连续放置:{label}" + +# English: "Drag to draw fence" +# Source-Hash: 729d87891cb7dbb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.dragToDrawFence" +msgstr "拖动绘制围栏" + +# English: "Erase: {scope}" +# Source-Hash: b52e874718af1916 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScope" +msgstr "擦除:{scope}" + +# English: "Erase scope — click or press Shift to cycle" +# Source-Hash: 61f7494abc319e66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeClickOrPressShift" +msgstr "擦除范围 — 点击或按 Shift 切换" + +# English: "Erase scope: {scope}" +# Source-Hash: 59993c163d68cc53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.eraseScopeLabel" +msgstr "擦除范围:{scope}" + +# English: "Fence type — click or press T to switch between straight, curved and freehand" +# Source-Hash: a4bdcbc6cab772bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fenceTypeClickOrPressT" +msgstr "围栏类型 — 点击或按 T 在直线、曲线和手绘之间切换" + +# English: "Fewer shortcuts" +# Source-Hash: 2a9ed258727078ac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.fewerShortcuts" +msgstr "收起快捷键" + +# English: "Finish curve (or double-click)" +# Source-Hash: 698fa6e78af4d4b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.finishCurveOrDoubleClick" +msgstr "完成曲线(或双击)" + +# English: "Grid: {length}" +# Source-Hash: 09f6d79edf5cfd16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridLength" +msgstr "网格:{length}" + +# English: "Grid step — click or tap Ctrl to cycle" +# Source-Hash: c493b562040c4328 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepClickOrTapCtrl" +msgstr "网格步长 — 点击或轻按 Ctrl 切换" + +# English: "Grid step: {length}" +# Source-Hash: d36a8c93d81b3a36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.gridStepLength" +msgstr "网格步长:{length}" + +# English: "Hover a painted surface to erase" +# Source-Hash: abea993ede7daa7e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverAPaintedSurfaceToErase" +msgstr "悬停在已涂装的表面上以擦除" + +# English: "Hover a surface to paint" +# Source-Hash: fc1ed0ddc0cfcef5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPaint" +msgstr "悬停在表面上以涂装" + +# English: "Hover a surface to pick its material" +# Source-Hash: 63dec5de4a337015 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.hoverASurfaceToPickIts" +msgstr "悬停在表面上以拾取其材质" + +# English: "More shortcuts" +# Source-Hash: 7ad6dc1b1f03f970 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.moreShortcuts" +msgstr "更多快捷键" + +# English: "Paint: {scope}" +# Source-Hash: 476b8f5f9ee542e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScope" +msgstr "涂装:{scope}" + +# English: "Paint scope — click or press Shift to cycle" +# Source-Hash: 9c09ce4c34122277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeClickOrPressShift" +msgstr "涂装范围 — 点击或按 Shift 切换" + +# English: "Paint scope: {scope}" +# Source-Hash: 7890aa81687f6654 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.paintScopeLabel" +msgstr "涂装范围:{scope}" + +# English: "Pick: {scope}" +# Source-Hash: a75b3abc0784b12b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.pickScope" +msgstr "拾取:{scope}" + +# English: "Place gate on a fence" +# Source-Hash: 69c1044e6ea87ccf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placeGateOnAFence" +msgstr "在围栏上放置大门" + +# English: "Place passage on a fence" +# Source-Hash: 3754255b8affc7a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.placePassageOnAFence" +msgstr "在围栏上放置通道" + +# English: "Select a material to paint" +# Source-Hash: a8328c8964e947fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.selectAMaterialToPaint" +msgstr "选择要涂装的材质" + +# English: "Snapping: Angles" +# Source-Hash: 6f95d7116aea5738 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingAngles" +msgstr "吸附:角度" + +# English: "Snapping: Grid" +# Source-Hash: 2d6d8d85d2ebed47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingGrid" +msgstr "吸附:网格" + +# English: "Snapping: Lines" +# Source-Hash: 9096c5f9d8ce0f7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingLines" +msgstr "吸附:线条" + +# English: "Snapping mode — click or press Shift to cycle" +# Source-Hash: b663afbf0835cf32 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingModeClickOrPressShift" +msgstr "吸附模式 — 点击或按 Shift 切换" + +# English: "Snapping: Off" +# Source-Hash: d00e984fc88f71aa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.snappingOff" +msgstr "吸附:关" + +# English: "Straight continuation is unavailable for curved or freehand fences" +# Source-Hash: 6be9d0d312200761 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuationIsUnavailableForCurved" +msgstr "曲线或手绘围栏无法直线连续" + +# English: "Straight: Continuous" +# Source-Hash: 0d56b1f0a0493302 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightContinuous" +msgstr "直线:连续" + +# English: "Straight fence continuation — click or press C to toggle" +# Source-Hash: c034e85d3e7fd27e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightFenceContinuationClickOrPress" +msgstr "直线围栏连续 — 点击或按 C 切换" + +# English: "Straight: Single" +# Source-Hash: e3f679cdb4a3d603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/contextual-helper-panel.tsx +#, fuzzy +msgid "editor.helpers.contextualHelperPanel.straightSingle" +msgstr "直线:单段" + +# English: "Add point" +# Source-Hash: 9a56d90d461b4bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPoint" +msgstr "添加点" + +# English: "Add point · click the first to close" +# Source-Hash: c3dc3918c823a5e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.addPointClickTheFirstTo" +msgstr "添加点 · 点击第一个点闭合" + +# English: "Brush size" +# Source-Hash: d8cf72d482d56cd7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.brushSize" +msgstr "笔刷大小" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancel" +msgstr "取消" + +# English: "Cancel picking" +# Source-Hash: 104fdab7934ec66c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelPicking" +msgstr "取消拾取" + +# English: "Cancel stroke" +# Source-Hash: c318cf0ce70b8977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.cancelStroke" +msgstr "取消笔画" + +# English: "Done" +# Source-Hash: 16552c2580bbe8b8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.done" +msgstr "完成" + +# English: "Draw the mezzanine" +# Source-Hash: e297db9ca0ce6d31 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheMezzanine" +msgstr "绘制夹层" + +# English: "Draw the opening" +# Source-Hash: c8be604718a7a9fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheOpening" +msgstr "绘制开口" + +# English: "Draw the terrace" +# Source-Hash: 231386ae6d534163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.drawTheTerrace" +msgstr "绘制露台" + +# English: "Exit smart measure" +# Source-Hash: e092e803eb60af69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.exitSmartMeasure" +msgstr "退出智能测量" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finish" +msgstr "完成" + +# English: "Finish at the edge" +# Source-Hash: f74bd937ae2e3a40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.finishAtTheEdge" +msgstr "在边缘处完成" + +# English: "Free move" +# Source-Hash: 21029cf1c45697cc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMove" +msgstr "自由移动" + +# English: "Free move · slide doors and windows" +# Source-Hash: 3247d8689bed6ff6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.freeMoveSlideDoorsAndWindows" +msgstr "自由移动 · 滑动门窗" + +# English: "Hold to pick a material" +# Source-Hash: b5ad765b43e75a5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.holdToPickAMaterial" +msgstr "按住以拾取材质" + +# English: "Inspect surface dimensions" +# Source-Hash: 46b53c5449c98964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.inspectSurfaceDimensions" +msgstr "查看表面尺寸" + +# English: "Paint with this material" +# Source-Hash: 63f1f7006ae14177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.paintWithThisMaterial" +msgstr "使用此材质涂装" + +# English: "Pick target height" +# Source-Hash: 188f55cc5afbe34f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pickTargetHeight" +msgstr "拾取目标高度" + +# English: "Pin measurement lens" +# Source-Hash: 2880fc9752eababa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pinMeasurementLens" +msgstr "固定测量放大镜" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.place" +msgstr "放置" + +# English: "Place inside the room" +# Source-Hash: 82010a7fd6350c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.placeInsideTheRoom" +msgstr "放置在房间内" + +# English: "Press anywhere, drag to the opposite corner" +# Source-Hash: 8b97f4503edb27b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.pressAnywhereDragToTheOpposite" +msgstr "在任意位置按下,拖到对角" + +# English: "Raise or lower" +# Source-Hash: 786f0b1c81b2a6c0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.raiseOrLower" +msgstr "升高或降低" + +# English: "Remove a painted part, or reset a surface" +# Source-Hash: b033f46ae3a0cbb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeAPaintedPartOrReset" +msgstr "移除涂装部分或重置表面" + +# English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.removeLastPoint" +msgstr "移除最后一个点" + +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.resize" +msgstr "调整大小" + +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotate45" +msgstr "旋转 ±45°" + +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.rotateTheSelection45" +msgstr "将所选旋转 ±45°" + +# English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/helper-manager.tsx +#, fuzzy +msgid "editor.helpers.helperManager.setOneCornerThenTheOpposite" +msgstr "设置一个角,再设置对角" + +# English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.cancel" +msgstr "取消" + +# English: "Force place" +# Source-Hash: ed7f3d8417a3e074 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.forcePlace" +msgstr "强制放置" + +# English: "Place" +# Source-Hash: 722741535b346e09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.place" +msgstr "放置" + +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/item-helper.tsx +#, fuzzy +msgid "editor.helpers.itemHelper.rotate" +msgstr "旋转" + +# English: "Guided constraints bypassed" +# Source-Hash: e9dfc2a178bca5f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/helpers/registered-tool-helper.tsx +#, fuzzy +msgid "editor.helpers.registeredToolHelper.guidedConstraintsBypassed" +msgstr "已绕过引导约束" + +# English: "Angle" +# Source-Hash: c69865ec5d9d1ac9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.angle.g10zaw" +msgstr "角度" + +# English: "Arm length m" +# Source-Hash: 9c562983f6fed685 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.armLengthM.1jclqgw" +msgstr "臂长 m" + +# English: "Attachment side" +# Source-Hash: 38461bc364321749 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.attachmentSide.6n9s5v" +msgstr "连接侧" + +# English: "Auto hangers" +# Source-Hash: 441124187e7da2fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoHangers.10554ka" msgstr "自动吊架" -# Source-Hash: e20033e4b0045f1c +# English: "Auto miter corners" +# Source-Hash: e20033e4b0045f1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoMiterCorners.zxr5lt" +msgstr "自动斜接转角" + +# English: "Auto span" +# Source-Hash: 92f3a8b63963daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.autoSpan.1gas9r6" +msgstr "自动跨度" + +# English: "Back ratio" +# Source-Hash: 8b2dfb9b9137fda0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.backRatio.1ughd99" +msgstr "后部比例" + +# English: "Base height" +# Source-Hash: 75dfae8d9f03fa0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseHeight.1hs4zdf" +msgstr "底座高度" + +# English: "Base style" +# Source-Hash: 1cc2da12e8463e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.baseStyle.24vty5" +msgstr "底座样式" + +# English: "Beam height" +# Source-Hash: f9d95c6df06b6223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamHeight.1kq0oh1" +msgstr "梁高" + +# English: "Beam width" +# Source-Hash: a133f8d23b1274dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.beamWidth.1q1mzbe" +msgstr "梁宽" + +# English: "Body shape" +# Source-Hash: b98c0d0189526335 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bodyShape.arvkuw" +msgstr "主体形状" + +# English: "Bracket style" +# Source-Hash: 2251ecab18467631 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.bracketStyle.1vvoptm" +msgstr "支架样式" + +# English: "Branch angle" +# Source-Hash: 5e9d4a4fcea481ca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.branchAngle.v56zme" +msgstr "分支角度" + +# English: "Cab height" +# Source-Hash: dec709f1f9c714ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cabHeight.1yn34mk" +msgstr "轿厢高度" + +# English: "Canopy form" +# Source-Hash: 87863f8366e1c88a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.canopyForm.1czzggp" +msgstr "雨棚形式" + +# English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cap.s0thej" +msgstr "顶盖" + +# English: "Cap overhang" +# Source-Hash: 2f2e49598ec8188e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capOverhang.kxxz9j" +msgstr "顶盖出挑" + +# English: "Cap shape" +# Source-Hash: 8523c086e95b83e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capShape.17mbxhu" +msgstr "顶盖形状" + +# English: "Cap thickness" +# Source-Hash: acd98600a518dd95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.capThickness.u835q9" +msgstr "顶盖厚度" + +# English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.carcassHeight.1aks0zy" +msgstr "柜体高度" + +# English: "Cleanout style" +# Source-Hash: 827709caa79db37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cleanoutStyle.127zz6z" +msgstr "清扫口样式" + +# English: "Collar diameter" +# Source-Hash: a549dc98e2a1bd07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarDiameter.1d5k28r" +msgstr "套环直径" + +# English: "Collar height" +# Source-Hash: aa0232fc6f3eeadc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarHeight.1dqwsrf" +msgstr "套环高度" + +# English: "Collar shape" +# Source-Hash: cbd8079c3f540a4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarShape.1gacv11" +msgstr "套环形状" + +# English: "Collar width" +# Source-Hash: 557cde7f95616454 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.collarWidth.qvc9ik" +msgstr "套环宽度" + +# English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.columns.1sy73ks" +msgstr "列" + +# English: "Conical full circle" +# Source-Hash: cd5b2f70924b7c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" +msgstr "锥形整圆" + +# English: "Connection mode" +# Source-Hash: dc90f3abb935e7e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionMode.1nty478" +msgstr "连接方式" + +# English: "Connection offset" +# Source-Hash: 1a9a49d7135d4f15 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.connectionOffset.2m70s8" +msgstr "连接偏移" + +# English: "Corner bevel" +# Source-Hash: 69be301fcb8030e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cornerBevel.16iniho" +msgstr "转角倒角" + +# English: "Covering type" +# Source-Hash: b997a96a9dc7e450 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.coveringType.95yd3k" +msgstr "覆盖层类型" + +# English: "Cricket height" +# Source-Hash: 03b33160d8baa966 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketHeight.pok31n" +msgstr "泛水坡高度" + +# English: "Cricket length" +# Source-Hash: 21552eee05ccb5d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketLength.i2aht8" +msgstr "泛水坡长度" + +# English: "Cricket side" +# Source-Hash: c13b707bfdeac855 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketSide.1xnwil7" +msgstr "泛水坡侧" + +# English: "Cricket style" +# Source-Hash: 4ee7089fa1b3e63b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.cricketStyle.10j0lat" +msgstr "泛水坡样式" + +# English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curb.5xi2ol" +msgstr "路缘" + +# English: "Curb height" +# Source-Hash: 8da648df210dbc0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curbHeight.1yn97sg" +msgstr "路缘高度" + +# English: "Curve offset" +# Source-Hash: 0c01b9848f5d9673 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.curveOffset.l4xhvr" +msgstr "弧度偏移" + +# English: "Damper angle" +# Source-Hash: 69179f4caa384585 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.damperAngle.h5fxn7" +msgstr "风阀角度" + +# English: "Deck thickness" +# Source-Hash: 15cff91231e806a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.deckThickness.4jmqze" +msgstr "平台厚度" + +# English: "Default level id" +# Source-Hash: eff1e7dc0901b9d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.defaultLevelId.xmrjzp" +msgstr "默认楼层 ID" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.depth.1lu11u2" +msgstr "深度" + +# English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter.s3robe" +msgstr "直径" + +# English: "Diameter2" +# Source-Hash: dab4100e83e4e6b3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.diameter2.pm6cl4" +msgstr "直径 2" + +# English: "Door duration ms" +# Source-Hash: 5747145349432396 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorDurationMs.13nx909" +msgstr "门动作时长 ms" + +# English: "Door height" +# Source-Hash: 6f4aca26196c221f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorHeight.92r9k4" +msgstr "门高" + +# English: "Door panel style" +# Source-Hash: 990ae81752236e3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorPanelStyle.2qog4q" +msgstr "门板样式" + +# English: "Door style" +# Source-Hash: c94e002123bb1199 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorStyle.gerpos" +msgstr "门样式" + +# English: "Door width" +# Source-Hash: 91aeb04b0b7b605e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.doorWidth.sy732l" +msgstr "门宽" + +# English: "Downspout enabled" +# Source-Hash: baad1238f70f79ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutEnabled.1gn00nv" +msgstr "启用落水管" + +# English: "Downspout position" +# Source-Hash: e288531858c4c44d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.downspoutPosition.fxcsop" +msgstr "落水管位置" + +# English: "Duct material" +# Source-Hash: 861d28317325cf61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ductMaterial.i8eonq" +msgstr "风管材料" + +# English: "Dutch gablet rake" +# Source-Hash: ad8ab4ae38c3299c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchGabletRake.6paydp" +msgstr "歇山山花檐口" + +# English: "Dutch hip height ratio" +# Source-Hash: 913d25cf2c5ff179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" +msgstr "歇山高度比" + +# English: "Dutch hip width ratio" +# Source-Hash: f4a80233d49f2b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" +msgstr "歇山宽度比" + +# English: "Dutch top rake thickness" +# Source-Hash: 92021b0e37f9a25a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" +msgstr "歇山顶部檐口厚度" + +# English: "Dutch waist length ratio" +# Source-Hash: d0a2ec13d71ce659 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" +msgstr "歇山腰部长度比" + +# English: "Dwell ms" +# Source-Hash: 993396ccca2cd1d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.dwellMs.1lkk0o5" +msgstr "停留 ms" + +# English: "Edge inset" +# Source-Hash: 9cf550eb019bca1b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.edgeInset.1obeimx" +msgstr "边缘内缩" + +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.elevation.4jbp44" +msgstr "标高" + +# English: "End cap left" +# Source-Hash: 1fc91958e30dbeaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapLeft.19jel4v" +msgstr "左端盖" + +# English: "End cap right" +# Source-Hash: 4c9ae62e0ed70635 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCapRight.lhse9c" +msgstr "右端盖" + +# English: "End caps" +# Source-Hash: a9cba06250228a55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endCaps.4fsb5b" +msgstr "端盖" + +# English: "End post" +# Source-Hash: ebad74353b5a7610 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.endPost.1guwsk0" +msgstr "端柱" + +# English: "Equipment type" +# Source-Hash: ecbe36df4238439e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.equipmentType.1mc2ywt" +msgstr "设备类型" + +# English: "Fill to floor" +# Source-Hash: 66f1018b50c2e426 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fillToFloor.1oppnkf" +msgstr "填充至地面" + +# English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.finial.1ertf4s" +msgstr "尖顶饰" + +# English: "Fitting type" +# Source-Hash: 28cd69e506709f16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fittingType.18mz17s" +msgstr "管件类型" + +# English: "Flashing height" +# Source-Hash: 3c1aec5e6fb180cf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingHeight.3e8nn6" +msgstr "泛水高度" + +# English: "Flashing projection" +# Source-Hash: 327ba762101db94d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flashingProjection.hhf0fk" +msgstr "泛水出挑" + +# English: "Flue count" +# Source-Hash: 9473e2ab53370b40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueCount.j3t2ci" +msgstr "烟道数量" + +# English: "Flue diameter" +# Source-Hash: fd0b65425ed3df8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueDiameter.cjhubg" +msgstr "烟道直径" + +# English: "Flue height" +# Source-Hash: 07ebe5e3780c624b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueHeight.1dhc4y4" +msgstr "烟道高度" + +# English: "Flue shape" +# Source-Hash: ff8319792830d591 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueShape.1ealiy4" +msgstr "烟道形状" + +# English: "Flue spacing" +# Source-Hash: 5acf77854a0030ec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.flueSpacing.exhzco" +msgstr "烟道间距" + +# English: "Footing style" +# Source-Hash: 0a64209c0edacaa1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.footingStyle.e3btg6" +msgstr "基脚样式" + +# English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameDepth.1934a5n" +msgstr "框架深度" + +# English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.frameThickness.1agxzd8" +msgstr "框架厚度" + +# English: "Framing strategy" +# Source-Hash: 9a3495a1e3181b56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.framingStrategy.3jx9qe" +msgstr "龙骨方式" + +# English: "From level id" +# Source-Hash: 49f329aab535fd3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.fromLevelId.15ieejs" +msgstr "起始楼层 ID" + +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gableOverhang.2z0obw" +msgstr "山墙出挑" + +# English: "Gambrel lower height ratio" +# Source-Hash: 517caa8af48afb22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" +msgstr "复折下部高度比" + +# English: "Gambrel lower width ratio" +# Source-Hash: 03ecb8f3b625d49d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" +msgstr "复折下部宽度比" + +# English: "Gap x" +# Source-Hash: f3ca5555e8c54375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapX.wjxa7" +msgstr "间隙 x" + +# English: "Gap y" +# Source-Hash: 2736f7c5dfc7d917 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gapY.mkbl8" +msgstr "间隙 y" + +# English: "Glass thickness" +# Source-Hash: 5d03ca50d712339b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.glassThickness.s97b0x" +msgstr "玻璃厚度" + +# English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.groundClearance.4os7f0" +msgstr "离地间隙" + +# English: "Guard infill" +# Source-Hash: 6a22a09b67309545 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.guardInfill.16heiig" +msgstr "护栏填充" + +# English: "Gutter enabled" +# Source-Hash: 98dfdac04af3c762 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterEnabled.1oxiltf" +msgstr "启用檐沟" + +# English: "Gutter profile" +# Source-Hash: 4bdc3cdf0fe0ac3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterProfile.1ngzqi3" +msgstr "檐沟型材" + +# English: "Gutter size" +# Source-Hash: 76555670cba9bb13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.gutterSize.jgavtz" +msgstr "檐沟尺寸" + +# English: "Hanger max reach" +# Source-Hash: c14d3aa4a98efa98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerMaxReach.1869epv" +msgstr "吊架最大跨度" + +# English: "Hanger spacing" +# Source-Hash: dfbc357fb556559b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerSpacing.i2b5d7" +msgstr "吊架间距" + +# English: "Hanger style" +# Source-Hash: 3ce5e50c572644ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hangerStyle.l7v7gb" +msgstr "吊架样式" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height.7puwki" +msgstr "高度" + +# English: "Height2" +# Source-Hash: d0b836103c795726 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.height2.7b3jlc" +msgstr "高度 2" + +# English: "Height above ridge" +# Source-Hash: 681fd794e601ace5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.heightAboveRidge.1k99brk" +msgstr "高出屋脊" + +# English: "High edge height" +# Source-Hash: b4905e5ec62a170f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highEdgeHeight.1s7v90t" +msgstr "高边高度" + +# English: "High overhang" +# Source-Hash: c136d5cd7f3ce04a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highOverhang.67w55d" +msgstr "高侧出挑" + +# English: "High side mode" +# Source-Hash: 8b4cd537cc650dac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.highSideMode.1rc1r5j" +msgstr "高侧模式" + +# English: "Hood overhang" +# Source-Hash: c38e4e73ed9779f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.hoodOverhang.1q1hcin" +msgstr "罩出挑" + +# English: "Infill placement" +# Source-Hash: 9884ee52a0e84c0d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.infillPlacement.11dxzpo" +msgstr "填充位置" + +# English: "Inlet shape" +# Source-Hash: 37fde9f9290ddc59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.inletShape.ypayh2" +msgstr "进口形状" + +# English: "Inner radius" +# Source-Hash: d840917007332b94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.innerRadius.1fkmg0j" +msgstr "内半径" + +# English: "Insulated" +# Source-Hash: c8ce389856a90ae2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulated.iwziey" +msgstr "保温" + +# English: "Insulation r" +# Source-Hash: 043a7a10b6c91f4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.insulationR.663ufx" +msgstr "保温 R" + +# English: "Lantern height" +# Source-Hash: fe661655ba41b709 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternHeight.oz7zpm" +msgstr "灯笼高度" + +# English: "Lantern top scale" +# Source-Hash: ab22022e7d5597af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lanternTopScale.hkmpp2" +msgstr "灯笼顶部比例" + +# English: "Ledger depth" +# Source-Hash: 17f854a76ebc033a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerDepth.7av8k9" +msgstr "托梁深度" + +# English: "Ledger height" +# Source-Hash: 547d3a8a9e3e24f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerHeight.zo2oif" +msgstr "托梁高度" + +# English: "Ledger vertical offset" +# Source-Hash: 88603f884243c81e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" +msgstr "托梁垂直偏移" + +# English: "Left end condition" +# Source-Hash: 6519f495202b830d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftEndCondition.1syqlaw" +msgstr "左端条件" + +# English: "Left overhang" +# Source-Hash: d041b8217c356977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.leftOverhang.1ydl9ma" +msgstr "左侧出挑" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.length.1kpdcfp" +msgstr "长度" + +# English: "Liquid diameter" +# Source-Hash: d6deaf0ceb9f43af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.liquidDiameter.w3pk7c" +msgstr "液管直径" + +# English: "Louver count" +# Source-Hash: 45aca762426392fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.louverCount.lhw2gr" +msgstr "百叶数量" + +# English: "Low beam inset" +# Source-Hash: c645a86c55eb0075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowBeamInset.1g7yw5l" +msgstr "低梁内缩" + +# English: "Low edge height" +# Source-Hash: 7e7b896682c78c5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowEdgeHeight.47mae1" +msgstr "低边高度" + +# English: "Low overhang" +# Source-Hash: 9deb775fc64854b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.lowOverhang.t4nz1h" +msgstr "低侧出挑" + +# English: "Mansard steep height ratio" +# Source-Hash: 9aa621d270fe64b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" +msgstr "孟莎陡坡高度比" + +# English: "Mansard steep width ratio" +# Source-Hash: 7cb03b27c632949b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" +msgstr "孟莎陡坡宽度比" + +# English: "Match host roof material" +# Source-Hash: 95772435857a7424 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" +msgstr "匹配主屋顶材质" + +# English: "Match host roof structure" +# Source-Hash: dc22618e31654bde +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.matchHostRoofStructure.bojht3" +msgstr "匹配主屋顶结构" + +# English: "Mount" +# Source-Hash: 06b36a09a84fa8b1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mount.h80tay" +msgstr "安装" + +# English: "Mounting type" +# Source-Hash: a288700f96d2b362 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.mountingType.1xvin1e" +msgstr "安装类型" + +# English: "Neck height" +# Source-Hash: 3087bc9b219af9ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.neckHeight.19ym8n1" +msgstr "颈部高度" + +# English: "Opacity" +# Source-Hash: 1c0b8a5503c5daeb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.opacity.1pmpfd2" +msgstr "不透明度" + +# English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingAngle.brhi7c" +msgstr "开启角度" + +# English: "Opening offset" +# Source-Hash: 1d714b576a057902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingOffset.wpoqt6" +msgstr "开洞偏移" + +# English: "Opening side" +# Source-Hash: 55e5629865efd03d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.openingSide.18l2iwe" +msgstr "开启侧" + +# English: "Operation state" +# Source-Hash: 9824d357692ecb8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.operationState.1lwrz07" +msgstr "开启状态" + +# English: "Outlet shape" +# Source-Hash: 4f1cbc14dcfb8f3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.outletShape.lgpqhx" +msgstr "出口形状" + +# English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.overhang.b5ljpv" +msgstr "出挑" + +# English: "Panel height" +# Source-Hash: 50d7e5e370667f63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelHeight.1kf31vk" +msgstr "面板高度" + +# English: "Panel width" +# Source-Hash: 068d9e0adabc4578 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.panelWidth.946581" +msgstr "面板宽度" + +# English: "Pattern alignment" +# Source-Hash: aca94a3adc0b0f01 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternAlignment.jd1ht0" +msgstr "图案对齐" + +# English: "Pattern count" +# Source-Hash: 9e9f9408ec2c8f0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternCount.1r37pve" +msgstr "图案数量" + +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternDistribution.fmam51" +msgstr "图案分布" + +# English: "Pattern remainder" +# Source-Hash: 63f833f219b9a592 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.patternRemainder.i47p5y" +msgstr "图案余量" + +# English: "Picket profile" +# Source-Hash: e0b8f766411329ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketProfile.1xgzuyc" +msgstr "栅条型材" + +# English: "Picket rail count" +# Source-Hash: dfac7d68ea16e8a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketRailCount.atgt6w" +msgstr "栅条横杆数量" + +# English: "Picket spacing" +# Source-Hash: cd2652254a81d288 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketSpacing.p8d98w" +msgstr "栅条间距" + +# English: "Picket top" +# Source-Hash: 462f6cb03b23d773 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTop.1tskplu" +msgstr "栅条顶部" + +# English: "Picket top clearance" +# Source-Hash: 0a4dbbe1cf592326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketTopClearance.16eq1k0" +msgstr "栅条顶部间隙" + +# English: "Picket variation" +# Source-Hash: c99ccea380ca41bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketVariation.f7vl1c" +msgstr "栅条变化" + +# English: "Picket width" +# Source-Hash: 798382373625fecd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.picketWidth.3fr3rh" +msgstr "栅条宽度" + +# English: "Pipe material" +# Source-Hash: 48ce215c3be034c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pipeMaterial.1ggc2wy" +msgstr "管材" + +# English: "Pitch" +# Source-Hash: 75e3449fc1776d09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.pitch.xe6ekx" +msgstr "坡度" + +# English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.position.1quewx6" +msgstr "位置" + +# English: "Post bracing" +# Source-Hash: d090baf9f08c2280 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postBracing.crq8nn" +msgstr "立柱支撑" + +# English: "Post cap" +# Source-Hash: 6467b580eed1d333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCap.1xcvxrv" +msgstr "立柱帽" + +# English: "Post count" +# Source-Hash: e1d91cdd16cc6593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postCount.22r56c" +msgstr "立柱数量" + +# English: "Post depth" +# Source-Hash: d6d8b8653cb43624 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postDepth.1ko6j1m" +msgstr "立柱深度" + +# English: "Post inset" +# Source-Hash: dd071e84e259ee48 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postInset.le36li" +msgstr "立柱内缩" + +# English: "Post layout mode" +# Source-Hash: 88c5d586822e58f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postLayoutMode.16jre62" +msgstr "立柱布置方式" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSize.1jodxek" +msgstr "立柱尺寸" + +# English: "Post spacing" +# Source-Hash: c8e8490c957e01f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postSpacing.voppfi" +msgstr "立柱间距" + +# English: "Post through" +# Source-Hash: b98cbe8478508d41 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postThrough.1abgu4a" +msgstr "立柱贯穿" + +# English: "Post width" +# Source-Hash: d1492df1fbb22c29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.postWidth.rk26fz" +msgstr "立柱宽度" + +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.profile.1kwlci6" +msgstr "轮廓" + +# English: "Projection" +# Source-Hash: d01cd36d3939abab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.projection.jhtig0" +msgstr "出挑" + +# English: "Purlin height" +# Source-Hash: 800a7a9159519b46 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinHeight.eo9cjm" +msgstr "檩条高度" + +# English: "Purlin spacing" +# Source-Hash: da4d0d12715d6c33 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinSpacing.4gbzb2" +msgstr "檩条间距" + +# English: "Purlin width" +# Source-Hash: b4774bcbdf599812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.inspector.field.purlinWidth.r6tu3j" +msgstr "檩条宽度" + +# English: "Rafter end inset" +# Source-Hash: 2a4dd54f8a8c15ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto miter corners" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoMiterCorners.zxr5lt" -msgstr "自动斜接转角" +msgid "editor.inspector.field.rafterEndInset.14c0wy5" +msgstr "椽端内缩" -# Source-Hash: 92f3a8b63963daeb +# English: "Rafter height" +# Source-Hash: 5a80a11b97c0e043 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.autoSpan.1gas9r6" -msgstr "自动跨度" +msgid "editor.inspector.field.rafterHeight.1e1n99w" +msgstr "椽高" -# Source-Hash: 8b2dfb9b9137fda0 +# English: "Rafter spacing" +# Source-Hash: b6348d554660af5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.backRatio.1ughd99" -msgstr "后部比例" +msgid "editor.inspector.field.rafterSpacing.19subc0" +msgstr "椽距" -# Source-Hash: 75dfae8d9f03fa0e +# English: "Rafter width" +# Source-Hash: 112c447946c3beaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseHeight.1hs4zdf" -msgstr "底座高度" +msgid "editor.inspector.field.rafterWidth.eksj4t" +msgstr "椽宽" -# Source-Hash: 1cc2da12e8463e85 +# English: "Railing height" +# Source-Hash: 7020d720876a2106 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.baseStyle.24vty5" -msgstr "底座样式" +msgid "editor.inspector.field.railingHeight.6wf4bm" +msgstr "栏杆高度" -# Source-Hash: f9d95c6df06b6223 +# English: "Railing mode" +# Source-Hash: 3e89ce193885b5b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamHeight.1kq0oh1" -msgstr "梁高" +msgid "editor.inspector.field.railingMode.ov7xpu" +msgstr "栏杆模式" -# Source-Hash: a133f8d23b1274dc +# English: "Railing style" +# Source-Hash: 174342bb31a86afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Beam width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.beamWidth.1q1mzbe" -msgstr "梁宽" +msgid "editor.inspector.field.railingStyle.1nyd2u" +msgstr "栏杆样式" -# Source-Hash: b98c0d0189526335 +# English: "Resize lock" +# Source-Hash: a44eedc895b95474 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bodyShape.arvkuw" -msgstr "主体形状" +msgid "editor.inspector.field.resizeLock.b2hbr0" +msgstr "锁定比例" -# Source-Hash: 2251ecab18467631 +# English: "Return diameter" +# Source-Hash: edd319917231def8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bracket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.bracketStyle.1vvoptm" -msgstr "支架样式" +msgid "editor.inspector.field.returnDiameter.166ozhe" +msgstr "回风直径" -# Source-Hash: 5e9d4a4fcea481ca +# English: "Return height" +# Source-Hash: dafa151b183daa77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Branch angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.branchAngle.v56zme" -msgstr "分支角度" +msgid "editor.inspector.field.returnHeight.b9sq6y" +msgstr "回风高度" -# Source-Hash: dec709f1f9c714ee +# English: "Return shape" +# Source-Hash: cd55fd029ad2803b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cabHeight.1yn34mk" -msgstr "轿厢高度" +msgid "editor.inspector.field.returnShape.1e3a27i" +msgstr "回风形状" -# Source-Hash: 87863f8366e1c88a +# English: "Return width" +# Source-Hash: 6a7c411667124177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy form" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.canopyForm.1czzggp" -msgstr "雨棚形式" +msgid "editor.inspector.field.returnWidth.z9r3h3" +msgstr "回风宽度" -# Source-Hash: d52b53241789ee63 +# English: "Right end condition" +# Source-Hash: 460bd11bfc2e5697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cap.s0thej" -msgstr "顶盖" +msgid "editor.inspector.field.rightEndCondition.tjlw3l" +msgstr "右端条件" -# Source-Hash: 2f2e49598ec8188e +# English: "Right overhang" +# Source-Hash: 1ed35f8d27ae5583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capOverhang.kxxz9j" -msgstr "顶盖出挑" +msgid "editor.inspector.field.rightOverhang.yqbeft" +msgstr "右侧出挑" -# Source-Hash: 8523c086e95b83e2 +# English: "Roof height" +# Source-Hash: 39c02aa2d97a62ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capShape.17mbxhu" -msgstr "顶盖形状" +msgid "editor.inspector.field.roofHeight.1uhy6qy" +msgstr "屋顶高度" -# Source-Hash: acd98600a518dd95 +# English: "Roof style" +# Source-Hash: d62f8833d77ca8a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.capThickness.u835q9" -msgstr "顶盖厚度" +msgid "editor.inspector.field.roofStyle.pawqfy" +msgstr "屋顶样式" -# Source-Hash: 7af3b69b4b5aeb19 +# English: "Roof thickness" +# Source-Hash: 0ddd561a4126b826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carcass height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.carcassHeight.1aks0zy" -msgstr "柜体高度" +msgid "editor.inspector.field.roofThickness.1xlr3rx" +msgstr "屋顶厚度" -# Source-Hash: 827709caa79db37f +# English: "Roof type" +# Source-Hash: 3a08ede9e51d5395 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cleanoutStyle.127zz6z" -msgstr "清扫口样式" +msgid "editor.inspector.field.roofType.1m9kv5h" +msgstr "屋顶类型" -# Source-Hash: a549dc98e2a1bd07 +# English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarDiameter.1d5k28r" -msgstr "套环直径" +msgid "editor.inspector.field.rotation.ly2rj" +msgstr "旋转" -# Source-Hash: aa0232fc6f3eeadc +# English: "Rows" +# Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarHeight.1dqwsrf" -msgstr "套环高度" +msgid "editor.inspector.field.rows.1ba1neg" +msgstr "行" -# Source-Hash: cbd8079c3f540a4c +# English: "Scale" +# Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarShape.1gacv11" -msgstr "套环形状" +msgid "editor.inspector.field.scale.b6rpxt" +msgstr "比例" -# Source-Hash: 557cde7f95616454 +# English: "Seam detail" +# Source-Hash: adb6face4bac5245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.collarWidth.qvc9ik" -msgstr "套环宽度" +msgid "editor.inspector.field.seamDetail.197za48" +msgstr "接缝细节" -# Source-Hash: 30edd1c180aac9b4 +# English: "Segment type" +# Source-Hash: 4b2aed9d56cc4dee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.columns.1sy73ks" -msgstr "列" +msgid "editor.inspector.field.segmentType.l667sy" +msgstr "分段类型" -# Source-Hash: cd5b2f70924b7c2d +# English: "Shaft style" +# Source-Hash: a7794a81141b4de4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical full circle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.conicalFullCircle.1pgkpz5" -msgstr "锥形整圆" +msgid "editor.inspector.field.shaftStyle.1i5ampa" +msgstr "井道样式" -# Source-Hash: dc90f3abb935e7e2 +# English: "Shaft wall thickness" +# Source-Hash: 8eb6438543b18cdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionMode.1nty478" -msgstr "连接方式" +msgid "editor.inspector.field.shaftWallThickness.bduslr" +msgstr "井道墙厚" -# Source-Hash: 1a9a49d7135d4f15 +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.connectionOffset.2m70s8" -msgstr "连接偏移" +msgid "editor.inspector.field.shape.iq9t52" +msgstr "形状" -# Source-Hash: 69be301fcb8030e2 +# English: "Shape2" +# Source-Hash: 44a8ab89c854cad1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner bevel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cornerBevel.16iniho" -msgstr "转角倒角" +msgid "editor.inspector.field.shape2.fi0nx8" +msgstr "形状 2" -# Source-Hash: b997a96a9dc7e450 +# English: "Shed high side" +# Source-Hash: fcee9712879570ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.coveringType.95yd3k" -msgstr "覆盖层类型" +msgid "editor.inspector.field.shedHighSide.bzzddg" +msgstr "单坡高侧" -# Source-Hash: 03b33160d8baa966 +# English: "Shingle thickness" +# Source-Hash: f5a931cd6e97e5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketHeight.pok31n" -msgstr "泛水坡高度" +msgid "editor.inspector.field.shingleThickness.1hn5wxr" +msgstr "瓦片厚度" -# Source-Hash: 21552eee05ccb5d6 +# English: "Shoulder extent" +# Source-Hash: b14990b3e4222dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketLength.i2aht8" -msgstr "泛水坡长度" +msgid "editor.inspector.field.shoulderExtent.11j8uiv" +msgstr "肩部范围" -# Source-Hash: c13b707bfdeac855 +# English: "Shoulder height" +# Source-Hash: 505de343ec5010e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketSide.1xnwil7" -msgstr "泛水坡侧" +msgid "editor.inspector.field.shoulderHeight.xyqn6g" +msgstr "肩部高度" -# Source-Hash: 4ee7089fa1b3e63b +# English: "Shoulder style" +# Source-Hash: 58d48c01b7ec1a40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.cricketStyle.10j0lat" -msgstr "泛水坡样式" +msgid "editor.inspector.field.shoulderStyle.1hkcv2w" +msgstr "肩部样式" -# Source-Hash: b8ff053666ee326b +# English: "Show center column" +# Source-Hash: af84db1026bf2e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curb.5xi2ol" -msgstr "路缘" +msgid "editor.inspector.field.showCenterColumn.1uggg45" +msgstr "显示中柱" -# Source-Hash: 8da648df210dbc0d +# English: "Show infill" +# Source-Hash: 80214dc086ebc1b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curbHeight.1yn97sg" -msgstr "路缘高度" +msgid "editor.inspector.field.showInfill.33rv7u" +msgstr "显示填充" -# Source-Hash: 0c01b9848f5d9673 +# English: "Show step supports" +# Source-Hash: f02d6cdfb4c6ad08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.curveOffset.l4xhvr" -msgstr "弧度偏移" +msgid "editor.inspector.field.showStepSupports.1kg8h8m" +msgstr "显示踏步支撑" -# Source-Hash: 69179f4caa384585 +# English: "Side flashing" +# Source-Hash: 5d1147c774718b76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.damperAngle.h5fxn7" -msgstr "风阀角度" +msgid "editor.inspector.field.sideFlashing.cv744o" +msgstr "侧面泛水" -# Source-Hash: 15cff91231e806a4 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.deckThickness.4jmqze" -msgstr "平台厚度" +msgid "editor.inspector.field.size.1a4x3zw" +msgstr "尺寸" -# Source-Hash: eff1e7dc0901b9d0 +# English: "Skylight type" +# Source-Hash: fbdbc6c97c75b030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.defaultLevelId.xmrjzp" -msgstr "默认楼层 ID" +msgid "editor.inspector.field.skylightType.1a5vj3i" +msgstr "天窗类型" -# Source-Hash: 99ac4d03c3061f3a +# English: "Slab opening mode" +# Source-Hash: c9a6561cb3c3da82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.depth.1lu11u2" -msgstr "深度" +msgid "editor.inspector.field.slabOpeningMode.g4f730" +msgstr "楼板开洞方式" -# Source-Hash: 016d8e70618916d4 +# English: "Slat gap" +# Source-Hash: ef0efc845ccdba78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter.s3robe" -msgstr "直径" +msgid "editor.inspector.field.slatGap.slmk93" +msgstr "叶片间隙" -# Source-Hash: dab4100e83e4e6b3 +# English: "Slide direction" +# Source-Hash: 4daab7a2ce2ecef3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.diameter2.pm6cl4" -msgstr "直径 2" +msgid "editor.inspector.field.slideDirection.1527q13" +msgstr "推拉方向" -# Source-Hash: 5747145349432396 +# English: "Span" +# Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door duration ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorDurationMs.13nx909" -msgstr "门动作时长 ms" +msgid "editor.inspector.field.span.1bmmuw1" +msgstr "跨度" -# Source-Hash: 6f4aca26196c221f +# English: "Speed" +# Source-Hash: 3b006be0b1f0493f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorHeight.92r9k4" -msgstr "门高" +msgid "editor.inspector.field.speed.2w2xwg" +msgstr "速度" -# Source-Hash: 990ae81752236e3a +# English: "Spin speed" +# Source-Hash: c8cfb8336a240cb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door panel style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorPanelStyle.2qog4q" -msgstr "门板样式" +msgid "editor.inspector.field.spinSpeed.1ozx746" +msgstr "旋转速度" -# Source-Hash: c94e002123bb1199 +# English: "Stair type" +# Source-Hash: c9f54c3b258961c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorStyle.gerpos" -msgstr "门样式" +msgid "editor.inspector.field.stairType.1u05e02" +msgstr "楼梯类型" -# Source-Hash: 91aeb04b0b7b605e +# English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.doorWidth.sy732l" -msgstr "门宽" +msgid "editor.inspector.field.standoff.4wv2g0" +msgstr "支脚" -# Source-Hash: baad1238f70f79ea +# English: "Standoff height" +# Source-Hash: 0464d4efa1e80c62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutEnabled.1gn00nv" -msgstr "启用落水管" +msgid "editor.inspector.field.standoffHeight.1qdnff" +msgstr "支脚高度" -# Source-Hash: e288531858c4c44d +# English: "Start post" +# Source-Hash: 85da28b0b26b8c2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.downspoutPosition.fxcsop" -msgstr "落水管位置" +msgid "editor.inspector.field.startPost.1ood9kv" +msgstr "起始柱" -# Source-Hash: 861d28317325cf61 +# English: "Step count" +# Source-Hash: d5dc1ba8cca3193d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ductMaterial.i8eonq" -msgstr "风管材料" +msgid "editor.inspector.field.stepCount.nzorrw" +msgstr "踏步数" -# Source-Hash: ad8ab4ae38c3299c +# English: "Strap spacing" +# Source-Hash: cadbb9cadd6a5e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch gablet rake" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchGabletRake.6paydp" -msgstr "歇山山花檐口" +msgid "editor.inspector.field.strapSpacing.1cyt3dc" +msgstr "吊带间距" -# Source-Hash: 913d25cf2c5ff179 +# English: "Strap style" +# Source-Hash: c017cd56b3ae5865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipHeightRatio.1etnwgg" -msgstr "歇山高度比" +msgid "editor.inspector.field.strapStyle.fh1oy4" +msgstr "吊带样式" -# Source-Hash: f4a80233d49f2b20 +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch hip width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchHipWidthRatio.1dopfvb" -msgstr "歇山宽度比" +msgid "editor.inspector.field.style.nf3vcm" +msgstr "样式" -# Source-Hash: 92021b0e37f9a25a +# English: "Suction diameter" +# Source-Hash: 7b3f016989c631a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch top rake thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchTopRakeThickness.1w9khxt" -msgstr "歇山顶部檐口厚度" +msgid "editor.inspector.field.suctionDiameter.nutxtj" +msgstr "吸气管直径" -# Source-Hash: d0a2ec13d71ce659 +# English: "Supply diameter" +# Source-Hash: 3f1e0fd30e5b1f39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch waist length ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dutchWaistLengthRatio.agtoxw" -msgstr "歇山腰部长度比" +msgid "editor.inspector.field.supplyDiameter.h5ldh5" +msgstr "送风直径" -# Source-Hash: 993396ccca2cd1d5 +# English: "Supply height" +# Source-Hash: 05c9b608538e181d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dwell ms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.dwellMs.1lkk0o5" -msgstr "停留 ms" +msgid "editor.inspector.field.supplyHeight.zy6ru1" +msgstr "送风高度" -# Source-Hash: 9cf550eb019bca1b +# English: "Supply shape" +# Source-Hash: 3c9400722baa474d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edge inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.edgeInset.1obeimx" -msgstr "边缘内缩" +msgid "editor.inspector.field.supplyShape.k39wnb" +msgstr "送风形状" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Supply width" +# Source-Hash: af7619025438f995 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.elevation.4jbp44" -msgstr "标高" +msgid "editor.inspector.field.supplyWidth.g66axi" +msgstr "送风宽度" -# Source-Hash: 1fc91958e30dbeaf +# English: "Support offset" +# Source-Hash: 617e198cf3f214d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapLeft.19jel4v" -msgstr "左端盖" +msgid "editor.inspector.field.supportOffset.1w4cocr" +msgstr "支撑偏移" -# Source-Hash: 4c9ae62e0ed70635 +# English: "Sweep angle" +# Source-Hash: 2686abc093f3dfb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCapRight.lhse9c" -msgstr "右端盖" +msgid "editor.inspector.field.sweepAngle.ucmmlw" +msgstr "扫掠角度" -# Source-Hash: a9cba06250228a55 +# English: "System" +# Source-Hash: 88e6c2e1c344b2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endCaps.4fsb5b" -msgstr "端盖" +msgid "editor.inspector.field.system.13qbhrw" +msgstr "系统" -# Source-Hash: ebad74353b5a7610 +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.endPost.1guwsk0" -msgstr "端柱" +msgid "editor.inspector.field.terminal.1501knl" +msgstr "风口" -# Source-Hash: ecbe36df4238439e +# English: "Terminal type" +# Source-Hash: de1777c521046f7d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.equipmentType.1mc2ywt" -msgstr "设备类型" +msgid "editor.inspector.field.terminalType.1ne8lgb" +msgstr "风口类型" -# Source-Hash: 66f1018b50c2e426 +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fillToFloor.1oppnkf" -msgstr "填充至地面" +msgid "editor.inspector.field.thickness.1l8hu45" +msgstr "厚度" -# Source-Hash: 1cfe33235b30b1d8 +# English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.finial.1ertf4s" -msgstr "尖顶饰" +msgid "editor.inspector.field.tiltAngle.1312yw1" +msgstr "倾斜角度" -# Source-Hash: 28cd69e506709f16 +# English: "To level id" +# Source-Hash: 7057972ff4c78dc7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fittingType.18mz17s" -msgstr "管件类型" +msgid "editor.inspector.field.toLevelId.8i5qx7" +msgstr "目标楼层 ID" -# Source-Hash: 3c1aec5e6fb180cf +# English: "Top landing depth" +# Source-Hash: fa8fc3d5c612e774 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingHeight.3e8nn6" -msgstr "泛水高度" +msgid "editor.inspector.field.topLandingDepth.ki7gk2" +msgstr "顶部平台深度" -# Source-Hash: 327ba762101db94d +# English: "Top landing mode" +# Source-Hash: 6e4a0b98089ec2cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flashing projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flashingProjection.hhf0fk" -msgstr "泛水出挑" +msgid "editor.inspector.field.topLandingMode.zk0h7e" +msgstr "顶部平台方式" -# Source-Hash: 9473e2ab53370b40 +# English: "Top rail height" +# Source-Hash: c182bb3c8a701085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueCount.j3t2ci" -msgstr "烟道数量" +msgid "editor.inspector.field.topRailHeight.fscisv" +msgstr "顶部横杆高度" -# Source-Hash: fd0b65425ed3df8b +# English: "Transition mode" +# Source-Hash: 8c5502f1d8087b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueDiameter.cjhubg" -msgstr "烟道直径" +msgid "editor.inspector.field.transitionMode.f3evgt" +msgstr "过渡方式" -# Source-Hash: 07ebe5e3780c624b +# English: "Transition width" +# Source-Hash: 96eb5997f0aa2dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueHeight.1dhc4y4" -msgstr "烟道高度" +msgid "editor.inspector.field.transitionWidth.rtgz3m" +msgstr "过渡宽度" -# Source-Hash: ff8319792830d591 +# English: "Vane count" +# Source-Hash: 812d27902509339f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueShape.1ealiy4" -msgstr "烟道形状" +msgid "editor.inspector.field.vaneCount.55vtye" +msgstr "叶片数" -# Source-Hash: 5acf77854a0030ec +# English: "Wall height" +# Source-Hash: 1e7dcd717c8ac239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flue spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.flueSpacing.exhzco" -msgstr "烟道间距" +msgid "editor.inspector.field.wallHeight.18vmh5c" +msgstr "墙高" -# Source-Hash: 0a64209c0edacaa1 +# English: "Wall shell" +# Source-Hash: f14bdaed6101896a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.footingStyle.e3btg6" -msgstr "基脚样式" +msgid "editor.inspector.field.wallShell.vqsyhv" +msgstr "墙体外壳" -# Source-Hash: fd413477f1a9e5ed +# English: "Wall skirt height" +# Source-Hash: 801268585c9a679b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameDepth.1934a5n" -msgstr "框架深度" +msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" +msgstr "墙裙高度" -# Source-Hash: 6aa2318fa815e5f2 +# English: "Wall thickness" +# Source-Hash: 6b377d0ba1565477 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.frameThickness.1agxzd8" -msgstr "框架厚度" +msgid "editor.inspector.field.wallThickness.17wn4iv" +msgstr "墙厚" -# Source-Hash: 9a3495a1e3181b56 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing strategy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.framingStrategy.3jx9qe" -msgstr "龙骨方式" +msgid "editor.inspector.field.width.gfxz9b" +msgstr "宽度" -# Source-Hash: 49f329aab535fd3a +# English: "Width2" +# Source-Hash: de05070474f589b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.fromLevelId.15ieejs" -msgstr "起始楼层 ID" +msgid "editor.inspector.field.width2.1nqawvr" +msgstr "宽度 2" -# Source-Hash: dbd062efc7f4fd7f +# English: "With back" +# Source-Hash: 6199b641888a83fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gableOverhang.2z0obw" -msgstr "山墙出挑" +msgid "editor.inspector.field.withBack.1ay8nus" +msgstr "带背板" -# Source-Hash: 517caa8af48afb22 +# English: "With bottom" +# Source-Hash: 699934d8b9571b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerHeightRatio.wqq7lg" -msgstr "复折下部高度比" +msgid "editor.inspector.field.withBottom.1x1z1vo" +msgstr "带底板" -# Source-Hash: 03ecb8f3b625d49d +# English: "With sides" +# Source-Hash: 03987b9aadaab50f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel lower width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gambrelLowerWidthRatio.uvyb1v" -msgstr "复折下部宽度比" +msgid "editor.inspector.field.withSides.uq066x" +msgstr "带侧板" -# Source-Hash: f3ca5555e8c54375 +# English: "Advanced" +# Source-Hash: f571cb2d06ae01ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap x" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapX.wjxa7" -msgstr "间隙 x" +msgid "editor.inspector.group.advanced.qwfkor" +msgstr "高级" -# Source-Hash: 2736f7c5dfc7d917 +# English: "Air" +# Source-Hash: a886619d15bae47e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gap y" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gapY.mkbl8" -msgstr "间隙 y" +msgid "editor.inspector.group.air.15jotdj" +msgstr "空气" -# Source-Hash: 5d03ca50d712339b +# English: "Appearance" +# Source-Hash: b69fc1f8128958fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.glassThickness.s97b0x" -msgstr "玻璃厚度" +msgid "editor.inspector.group.appearance.dli9wf" +msgstr "外观" -# Source-Hash: 4fc3e60be28f60d5 +# English: "Body" +# Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.groundClearance.4os7f0" -msgstr "离地间隙" +msgid "editor.inspector.group.body.ign4wl" +msgstr "主体" -# Source-Hash: 6a22a09b67309545 +# English: "Cab" +# Source-Hash: fdf7108a011bd94b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.guardInfill.16heiig" -msgstr "护栏填充" +msgid "editor.inspector.group.cab.o4z1ox" +msgstr "轿厢" -# Source-Hash: 98dfdac04af3c762 +# English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter enabled" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterEnabled.1oxiltf" -msgstr "启用檐沟" +msgid "editor.inspector.group.cabinet.rzvymr" +msgstr "柜体" -# Source-Hash: 4bdc3cdf0fe0ac3c +# English: "Cap" +# Source-Hash: d52b53241789ee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterProfile.1ngzqi3" -msgstr "檐沟型材" +msgid "editor.inspector.group.cap.s0thej" +msgstr "顶盖" -# Source-Hash: 76555670cba9bb13 +# English: "Collar" +# Source-Hash: 93a545688c917e69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.gutterSize.jgavtz" -msgstr "檐沟尺寸" +msgid "editor.inspector.group.collar.1am2iqo" +msgstr "套环" -# Source-Hash: c14d3aa4a98efa98 +# English: "Connection" +# Source-Hash: 7a9c55b1c5a6e61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger max reach" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerMaxReach.1869epv" -msgstr "吊架最大跨度" +msgid "editor.inspector.group.connection.2r1h4p" +msgstr "连接" -# Source-Hash: dfbc357fb556559b +# English: "Connections" +# Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerSpacing.i2b5d7" -msgstr "吊架间距" +msgid "editor.inspector.group.connections.ai4w8u" +msgstr "连接" -# Source-Hash: 3ce5e50c572644ed +# English: "Construction" +# Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hanger style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hangerStyle.l7v7gb" -msgstr "吊架样式" +msgid "editor.inspector.group.construction.j6p9kc" +msgstr "构造" -# Source-Hash: 646ef0fbcbc58edf +# English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height.7puwki" -msgstr "高度" +msgid "editor.inspector.group.cricket.6iwg8g" +msgstr "泛水坡" -# Source-Hash: d0b836103c795726 +# English: "Curb" +# Source-Hash: b8ff053666ee326b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.height2.7b3jlc" -msgstr "高度 2" +msgid "editor.inspector.group.curb.5xi2ol" +msgstr "路缘" -# Source-Hash: 681fd794e601ace5 +# English: "Curve points" +# Source-Hash: 3b9c59e996dcc9c5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height above ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.heightAboveRidge.1k99brk" -msgstr "高出屋脊" +msgid "editor.inspector.group.curvePoints.ztdcnn" +msgstr "曲线点" -# Source-Hash: b4905e5ec62a170f +# English: "Dimensions" +# Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highEdgeHeight.1s7v90t" -msgstr "高边高度" +msgid "editor.inspector.group.dimensions.pewa8g" +msgstr "尺寸" -# Source-Hash: c136d5cd7f3ce04a +# English: "Doors" +# Source-Hash: 20f60b98de67849b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highOverhang.67w55d" -msgstr "高侧出挑" +msgid "editor.inspector.group.doors.18w2kpa" +msgstr "门" -# Source-Hash: 8b4cd537cc650dac +# English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High side mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.highSideMode.1rc1r5j" -msgstr "高侧模式" +msgid "editor.inspector.group.dormer.1fjs5tq" +msgstr "老虎窗" -# Source-Hash: c38e4e73ed9779f4 +# English: "Dormer roof" +# Source-Hash: cda262793b4cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hood overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.hoodOverhang.1q1hcin" -msgstr "罩出挑" +msgid "editor.inspector.group.dormerRoof.u9uy2g" +msgstr "老虎窗屋顶" -# Source-Hash: 9884ee52a0e84c0d +# English: "Drainage" +# Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.infillPlacement.11dxzpo" -msgstr "填充位置" +msgid "editor.inspector.group.drainage.7easy0" +msgstr "排水" -# Source-Hash: 37fde9f9290ddc59 +# English: "Elevation" +# Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.inletShape.ypayh2" -msgstr "进口形状" +msgid "editor.inspector.group.elevation.4jbp44" +msgstr "标高" -# Source-Hash: d840917007332b94 +# English: "End caps" +# Source-Hash: a9cba06250228a55 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.innerRadius.1fkmg0j" -msgstr "内半径" +msgid "editor.inspector.group.endCaps.4fsb5b" +msgstr "端盖" -# Source-Hash: c8ce389856a90ae2 +# English: "Equipment" +# Source-Hash: 228f2206862e41cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulated.iwziey" -msgstr "保温" +msgid "editor.inspector.group.equipment.1xtq0ev" +msgstr "设备" -# Source-Hash: 043a7a10b6c91f4f +# English: "Face" +# Source-Hash: a19276baa3688cbf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation r" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.insulationR.663ufx" -msgstr "保温 R" +msgid "editor.inspector.group.face.1531fr0" +msgstr "面" -# Source-Hash: fe661655ba41b709 +# English: "Fitting" +# Source-Hash: fb34ee7c59cb84ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternHeight.oz7zpm" -msgstr "灯笼高度" +msgid "editor.inspector.group.fitting.171z1r4" +msgstr "管件" -# Source-Hash: ab22022e7d5597af +# English: "Flues" +# Source-Hash: f817c09ef0bb6d69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern top scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lanternTopScale.hkmpp2" -msgstr "灯笼顶部比例" +msgid "editor.inspector.group.flues.nq6pye" +msgstr "烟道" -# Source-Hash: 17f854a76ebc033a +# English: "Footprint" +# Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerDepth.7av8k9" -msgstr "托梁深度" +msgid "editor.inspector.group.footprint.g8q2pg" +msgstr "基底" -# Source-Hash: 547d3a8a9e3e24f5 +# English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerHeight.zo2oif" -msgstr "托梁高度" +msgid "editor.inspector.group.frame.q7q9fq" +msgstr "框架" -# Source-Hash: 88603f884243c81e +# English: "Geometry" +# Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ledger vertical offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.ledgerVerticalOffset.1athbgt" -msgstr "托梁垂直偏移" +msgid "editor.inspector.group.geometry.a21os1" +msgstr "几何" -# Source-Hash: 6519f495202b830d +# English: "Grid" +# Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftEndCondition.1syqlaw" -msgstr "左端条件" +msgid "editor.inspector.group.grid.d8gwxd" +msgstr "网格" -# Source-Hash: d041b8217c356977 +# English: "Hangers" +# Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.leftOverhang.1ydl9ma" -msgstr "左侧出挑" +msgid "editor.inspector.group.hangers.agnfzn" +msgstr "吊架" -# Source-Hash: 643687bc3adaac76 +# English: "Hardware" +# Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.length.1kpdcfp" -msgstr "长度" +msgid "editor.inspector.group.hardware.1nygwm1" +msgstr "五金件" -# Source-Hash: d6deaf0ceb9f43af +# English: "Hung wall" +# Source-Hash: 2a47557eb4a5f766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.liquidDiameter.w3pk7c" -msgstr "液管直径" +msgid "editor.inspector.group.hungWall.1ftr1gz" +msgstr "悬挂墙" -# Source-Hash: 45aca762426392fa +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louver count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.inspector.field.louverCount.lhw2gr" -msgstr "百叶数量" +#, fuzzy +msgid "editor.inspector.group.insulation.1kyo5e7" +msgstr "保温层" -# Source-Hash: c645a86c55eb0075 +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low beam inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowBeamInset.1g7yw5l" -msgstr "低梁内缩" +msgid "editor.inspector.group.lantern.1h7o2xb" +msgstr "灯笼" -# Source-Hash: 7e7b896682c78c5b +# English: "Levels" +# Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low edge height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowEdgeHeight.47mae1" -msgstr "低边高度" +msgid "editor.inspector.group.levels.1s6yaru" +msgstr "楼层" -# Source-Hash: 9deb775fc64854b2 +# English: "Line" +# Source-Hash: 1a849e52a46d5087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.lowOverhang.t4nz1h" -msgstr "低侧出挑" +msgid "editor.inspector.group.line.166mv5z" +msgstr "线" -# Source-Hash: 9aa621d270fe64b1 +# English: "Lines" +# Source-Hash: 9ab4f7a738ebee13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep height ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepHeightRatio.1hbb8rq" -msgstr "孟莎陡坡高度比" +msgid "editor.inspector.group.lines.12bghr0" +msgstr "线" -# Source-Hash: 7cb03b27c632949b +# English: "Motion" +# Source-Hash: c0ff16d77ebc9593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard steep width ratio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mansardSteepWidthRatio.6xk6vd" -msgstr "孟莎陡坡宽度比" +msgid "editor.inspector.group.motion.1be7sjr" +msgstr "运动" -# Source-Hash: 95772435857a7424 +# English: "Mounting" +# Source-Hash: 81d365ccef73068c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofMaterial.s56zwj" -msgstr "匹配主屋顶材质" +msgid "editor.inspector.group.mounting.feutd2" +msgstr "安装方式" -# Source-Hash: dc22618e31654bde +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Match host roof structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.matchHostRoofStructure.bojht3" -msgstr "匹配主屋顶结构" +msgid "editor.inspector.group.opening.dfet3" +msgstr "开口" -# Source-Hash: 06b36a09a84fa8b1 +# English: "Panel dimensions" +# Source-Hash: f46314c54627858d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mount.h80tay" -msgstr "安装" +msgid "editor.inspector.group.panelDimensions.b59qe6" +msgstr "面板尺寸" -# Source-Hash: a288700f96d2b362 +# English: "Pattern distribution" +# Source-Hash: 4712c449e9a42d3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.mountingType.1xvin1e" -msgstr "安装类型" +msgid "editor.inspector.group.patternDistribution.fmam51" +msgstr "图案分布" -# Source-Hash: 3087bc9b219af9ef +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Neck height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.neckHeight.19ym8n1" -msgstr "颈部高度" +msgid "editor.inspector.group.placement.13lcvts" +msgstr "放置" -# Source-Hash: 1c0b8a5503c5daeb +# English: "Position" +# Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.opacity.1pmpfd2" -msgstr "不透明度" +msgid "editor.inspector.group.position.1quewx6" +msgstr "位置" -# Source-Hash: 51b7ab6a0a78d90a +# English: "Profile" +# Source-Hash: 96ad4bd0f7baa40e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingAngle.brhi7c" -msgstr "开启角度" +msgid "editor.inspector.group.profile.1kwlci6" +msgstr "轮廓" -# Source-Hash: 1d714b576a057902 +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingOffset.wpoqt6" -msgstr "开洞偏移" +msgid "editor.inspector.group.railing.19a5bnr" +msgstr "栏杆" -# Source-Hash: 55e5629865efd03d +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.openingSide.18l2iwe" -msgstr "开启侧" +msgid "editor.inspector.group.return.1klrjrz" +msgstr "回风" -# Source-Hash: 9824d357692ecb8a +# English: "Settings" +# Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Operation state" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.operationState.1lwrz07" -msgstr "开启状态" +msgid "editor.inspector.group.settings.ktdb8o" +msgstr "设置" -# Source-Hash: 4f1cbc14dcfb8f3b +# English: "Shaft" +# Source-Hash: 4fc63ad21de30103 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outlet shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.outletShape.lgpqhx" -msgstr "出口形状" +msgid "editor.inspector.group.shaft.mad9cv" +msgstr "井道" -# Source-Hash: 88540def2ce16ac5 +# English: "Shape" +# Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.overhang.b5ljpv" -msgstr "出挑" +msgid "editor.inspector.group.shape.iq9t52" +msgstr "形状" -# Source-Hash: 50d7e5e370667f63 +# English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelHeight.1kf31vk" -msgstr "面板高度" +msgid "editor.inspector.group.shoulder.1nyfx25" +msgstr "肩部" -# Source-Hash: 068d9e0adabc4578 +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.panelWidth.946581" -msgstr "面板宽度" +msgid "editor.inspector.group.size.1a4x3zw" +msgstr "尺寸" -# Source-Hash: aca94a3adc0b0f01 +# English: "Structure" +# Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternAlignment.jd1ht0" -msgstr "图案对齐" +msgid "editor.inspector.group.structure.gezdra" +msgstr "结构" -# Source-Hash: 9e9f9408ec2c8f0b +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternCount.1r37pve" -msgstr "图案数量" +msgid "editor.inspector.group.style.nf3vcm" +msgstr "样式" -# Source-Hash: 4712c449e9a42d3b +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternDistribution.fmam51" -msgstr "图案分布" +msgid "editor.inspector.group.supply.1oy2zwu" +msgstr "送风" -# Source-Hash: 63f833f219b9a592 +# English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern remainder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.patternRemainder.i47p5y" -msgstr "图案余量" +msgid "editor.inspector.group.surface.wtamqk" +msgstr "表面" -# Source-Hash: e0b8f766411329ff +# English: "Terminal" +# Source-Hash: ccb3afc48bd9a284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketProfile.1xgzuyc" -msgstr "栅条型材" +msgid "editor.inspector.group.terminal.1501knl" +msgstr "风口" -# Source-Hash: dfac7d68ea16e8a8 +# English: "Topology" +# Source-Hash: 399fc3e62f491f08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket rail count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketRailCount.atgt6w" -msgstr "栅条横杆数量" +msgid "editor.inspector.group.topology.18t8dyc" +msgstr "拓扑" -# Source-Hash: cd2652254a81d288 +# English: "Transform" +# Source-Hash: f425799b82e085ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketSpacing.p8d98w" -msgstr "栅条间距" +msgid "editor.inspector.group.transform.1c55ayj" +msgstr "变换" -# Source-Hash: 462f6cb03b23d773 +# English: "Trap" +# Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTop.1tskplu" -msgstr "栅条顶部" +msgid "editor.inspector.group.trap.1qc7vhe" +msgstr "存水弯" -# Source-Hash: 0a4dbbe1cf592326 +# English: "Type" +# Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket top clearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketTopClearance.16eq1k0" -msgstr "栅条顶部间隙" +msgid "editor.inspector.group.type.1m2zofh" +msgstr "类型" -# Source-Hash: c99ccea380ca41bf +# English: "Abs" +# Source-Hash: c8fd17853839516f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket variation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketVariation.f7vl1c" -msgstr "栅条变化" +msgid "editor.inspector.option.abs.132mz8b" +msgstr "ABS" -# Source-Hash: 798382373625fecd +# English: "Access panel" +# Source-Hash: fd9eb1a16cb6b996 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.picketWidth.3fr3rh" -msgstr "栅条宽度" +msgid "editor.inspector.option.accessPanel.13sptb5" +msgstr "检修口" -# Source-Hash: 48ce215c3be034c7 +# English: "Air handler" +# Source-Hash: db8064add8c27a5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe material" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pipeMaterial.1ggc2wy" -msgstr "管材" +msgid "editor.inspector.option.airHandler.1crnyg3" +msgstr "空气处理机" -# Source-Hash: 75e3449fc1776d09 +# English: "Alternating" +# Source-Hash: d8234782848be927 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.pitch.xe6ekx" -msgstr "坡度" +msgid "editor.inspector.option.alternating.5jashi" +msgstr "交替" -# Source-Hash: 9da0cbfad144d737 +# English: "Arched" +# Source-Hash: 63cbda7b09662368 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.position.1quewx6" -msgstr "位置" +msgid "editor.inspector.option.arched.14188w0" +msgstr "拱形" -# Source-Hash: d090baf9f08c2280 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post bracing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postBracing.crq8nn" -msgstr "立柱支撑" +msgid "editor.inspector.option.auto.540b06" +msgstr "自动" -# Source-Hash: 6467b580eed1d333 +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCap.1xcvxrv" -msgstr "立柱帽" +msgid "editor.inspector.option.automatic.r122ro" +msgstr "自动" -# Source-Hash: e1d91cdd16cc6593 +# English: "Back" +# Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postCount.22r56c" -msgstr "立柱数量" +msgid "editor.inspector.option.back.1hzmxtu" +msgstr "返回" -# Source-Hash: d6d8b8653cb43624 +# English: "Balusters" +# Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postDepth.1ko6j1m" -msgstr "立柱深度" +msgid "editor.inspector.option.balusters.1j3w6ng" +msgstr "栏杆柱" -# Source-Hash: dd071e84e259ee48 +# English: "Band" +# Source-Hash: f211248ec8619e4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postInset.le36li" -msgstr "立柱内缩" +msgid "editor.inspector.option.band.160umau" +msgstr "腰线" -# Source-Hash: 88c5d586822e58f0 +# English: "Base plate" +# Source-Hash: 8f9ff05c6a3b08a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post layout mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postLayoutMode.16jre62" -msgstr "立柱布置方式" +msgid "editor.inspector.option.basePlate.17fcg8k" +msgstr "底板" -# Source-Hash: faa36308c1ecb2a9 +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSize.1jodxek" -msgstr "立柱尺寸" +msgid "editor.inspector.option.boards.u14hig" +msgstr "木板" -# Source-Hash: c8e8490c957e01f3 +# English: "Bookshelf" +# Source-Hash: c0fee00bdfb09d37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postSpacing.voppfi" -msgstr "立柱间距" +msgid "editor.inspector.option.bookshelf.ls0zqk" +msgstr "书架" -# Source-Hash: b98cbe8478508d41 +# English: "Both" +# Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post through" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postThrough.1abgu4a" -msgstr "立柱贯穿" +msgid "editor.inspector.option.both.mkyb42" +msgstr "两侧" -# Source-Hash: d1492df1fbb22c29 +# English: "Bottom" +# Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.postWidth.rk26fz" -msgstr "立柱宽度" +msgid "editor.inspector.option.bottom.9mpruy" +msgstr "底部" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.profile.1kwlci6" -msgstr "轮廓" +msgid "editor.inspector.option.box.1nbjcuq" +msgstr "盒形" -# Source-Hash: d01cd36d3939abab +# English: "Break" +# Source-Hash: 2c2815df4f724abe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Projection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.projection.jhtig0" -msgstr "出挑" +msgid "editor.inspector.option.break.17spnvc" +msgstr "断开" -# Source-Hash: 800a7a9159519b46 +# English: "Butterfly" +# Source-Hash: 0d5a25303d3d8058 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinHeight.eo9cjm" -msgstr "檩条高度" +msgid "editor.inspector.option.butterfly.pvv0g4" +msgstr "蝶形" -# Source-Hash: da4d0d12715d6c33 +# English: "Cable" +# Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinSpacing.4gbzb2" -msgstr "檩条间距" +msgid "editor.inspector.option.cable.fp50zq" +msgstr "钢索" -# Source-Hash: b4774bcbdf599812 +# English: "Cast iron" +# Source-Hash: 69129e514347672a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlin width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.purlinWidth.r6tu3j" -msgstr "檩条宽度" +msgid "editor.inspector.option.castIron.1q2z4qk" +msgstr "铸铁" -# Source-Hash: 2a4dd54f8a8c15ef +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter end inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterEndInset.14c0wy5" -msgstr "椽端内缩" +msgid "editor.inspector.option.ceiling.1t2zaqo" +msgstr "天花板" -# Source-Hash: 5a80a11b97c0e043 +# English: "Center" +# Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterHeight.1e1n99w" -msgstr "椽高" +msgid "editor.inspector.option.center.kzdxno" +msgstr "居中" -# Source-Hash: b6348d554660af5b +# English: "Center opening" +# Source-Hash: 65f410261609060b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterSpacing.19subc0" -msgstr "椽距" +msgid "editor.inspector.option.centerOpening.bn1rvw" +msgstr "中开" -# Source-Hash: 112c447946c3beaa +# English: "Cleanout" +# Source-Hash: cf5f9756cc866f3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafter width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rafterWidth.eksj4t" -msgstr "椽宽" +msgid "editor.inspector.option.cleanout.lww8bm" +msgstr "清扫口" -# Source-Hash: 7020d720876a2106 +# English: "Concrete pad" +# Source-Hash: e71d1d949f1d1b41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingHeight.6wf4bm" -msgstr "栏杆高度" +msgid "editor.inspector.option.concretePad.m1zdij" +msgstr "混凝土垫层" -# Source-Hash: 3e89ce193885b5b4 +# English: "Condenser" +# Source-Hash: a3804c4f5e6aefbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingMode.ov7xpu" -msgstr "栏杆模式" +msgid "editor.inspector.option.condenser.a74nps" +msgstr "冷凝器" -# Source-Hash: 174342bb31a86afc +# English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.railingStyle.1nyd2u" -msgstr "栏杆样式" +msgid "editor.inspector.option.corbeled.bovs4v" +msgstr "叠涩" -# Source-Hash: a44eedc895b95474 +# English: "Count" +# Source-Hash: 8a4beb9a57edc26e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.resizeLock.b2hbr0" -msgstr "锁定比例" +msgid "editor.inspector.option.count.1qoi4r8" +msgstr "数量" -# Source-Hash: edd319917231def8 +# English: "Coupling" +# Source-Hash: db1f8f141714b363 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnDiameter.166ozhe" -msgstr "回风直径" +msgid "editor.inspector.option.coupling.111aap2" +msgstr "管接头" -# Source-Hash: dafa151b183daa77 +# English: "Covering specific" +# Source-Hash: 7a9a92d2e59a3bbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnHeight.b9sq6y" -msgstr "回风高度" +msgid "editor.inspector.option.coveringSpecific.1h9fo8w" +msgstr "覆盖层细节" -# Source-Hash: cd55fd029ad2803b +# English: "Cross" +# Source-Hash: d500c4b7827d3390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnShape.1e3a27i" -msgstr "回风形状" +msgid "editor.inspector.option.cross.1elubzv" +msgstr "十字" -# Source-Hash: 6a7c411667124177 +# English: "Cubby" +# Source-Hash: 7ca7f00b3ba531f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.returnWidth.z9r3h3" -msgstr "回风宽度" +msgid "editor.inspector.option.cubby.ivq4qc" +msgstr "储物格" -# Source-Hash: 460bd11bfc2e5697 +# English: "Curved" +# Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end condition" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightEndCondition.tjlw3l" -msgstr "右端条件" +msgid "editor.inspector.option.curved.6noz1s" +msgstr "弧形" -# Source-Hash: 1ed35f8d27ae5583 +# English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rightOverhang.yqbeft" -msgstr "右侧出挑" +msgid "editor.inspector.option.cylinder.eol6vj" +msgstr "圆柱" -# Source-Hash: 39c02aa2d97a62ba +# English: "Damper" +# Source-Hash: 53f224e2828d5ab7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofHeight.1uhy6qy" -msgstr "屋顶高度" +msgid "editor.inspector.option.damper.wgri80" +msgstr "风阀" -# Source-Hash: d62f8833d77ca8a1 +# English: "Destination" +# Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofStyle.pawqfy" -msgstr "屋顶样式" +msgid "editor.inspector.option.destination.gfzm1r" +msgstr "目标" -# Source-Hash: 0ddd561a4126b826 +# English: "Diffuser" +# Source-Hash: bfb3561e28d92a39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofThickness.1xlr3rx" -msgstr "屋顶厚度" +msgid "editor.inspector.option.diffuser.1igj0jh" +msgstr "散流器" -# Source-Hash: 3a08ede9e51d5395 +# English: "Dog ear" +# Source-Hash: 6cff941e10ff22b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.roofType.1m9kv5h" -msgstr "屋顶类型" +msgid "editor.inspector.option.dogEar.xhtvjt" +msgstr "切角" -# Source-Hash: 01989595ee7c1dc0 +# English: "Dome" +# Source-Hash: de47e23eb81efe02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rotation.ly2rj" -msgstr "旋转" +msgid "editor.inspector.option.dome.1c87e56" +msgstr "穹顶" -# Source-Hash: c913b494c3901a17 +# English: "Double" +# Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.rows.1ba1neg" -msgstr "行" +msgid "editor.inspector.option.double.13h5354" +msgstr "双扇" -# Source-Hash: 8210f376c0d29acc +# English: "Duct board" +# Source-Hash: 72a84a9762461745 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.scale.b6rpxt" -msgstr "比例" +msgid "editor.inspector.option.ductBoard.lc3o4t" +msgstr "风管板" -# Source-Hash: adb6face4bac5245 +# English: "Dutch" +# Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Seam detail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.seamDetail.197za48" -msgstr "接缝细节" +msgid "editor.inspector.option.dutch.1djx429" +msgstr "荷兰式" -# Source-Hash: 4b2aed9d56cc4dee +# English: "Elbow" +# Source-Hash: e6637bddef6aabd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.segmentType.l667sy" -msgstr "分段类型" +msgid "editor.inspector.option.elbow.1flup74" +msgstr "弯头" -# Source-Hash: a7794a81141b4de4 +# English: "End" +# Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftStyle.1i5ampa" -msgstr "井道样式" +msgid "editor.inspector.option.end.1llf32i" +msgstr "终点" -# Source-Hash: 8eb6438543b18cdb +# English: "End cap" +# Source-Hash: ada9fc2eec2c7d61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shaftWallThickness.bduslr" -msgstr "井道墙厚" +msgid "editor.inspector.option.endCap.1p4v4na" +msgstr "端盖" -# Source-Hash: 85ea1d3c8b749680 +# English: "Equal fit" +# Source-Hash: 73916b8395208874 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape.iq9t52" -msgstr "形状" +msgid "editor.inspector.option.equalFit.11vw52c" +msgstr "均匀适配" -# Source-Hash: 44a8ab89c854cad1 +# English: "Fixed count" +# Source-Hash: 83579a6e420df2ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shape2.fi0nx8" -msgstr "形状 2" +msgid "editor.inspector.option.fixedCount.1o3dznu" +msgstr "固定数量" -# Source-Hash: fcee9712879570ae +# English: "Fixed spacing" +# Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed high side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shedHighSide.bzzddg" -msgstr "单坡高侧" +msgid "editor.inspector.option.fixedSpacing.hl4dr4" +msgstr "固定间距" -# Source-Hash: f5a931cd6e97e5e2 +# English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shingleThickness.1hn5wxr" -msgstr "瓦片厚度" +msgid "editor.inspector.option.flat.ly0bei" +msgstr "平屋顶" -# Source-Hash: b14990b3e4222dd3 +# English: "Flex" +# Source-Hash: a9cad0badcc16693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder extent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderExtent.11j8uiv" -msgstr "肩部范围" +msgid "editor.inspector.option.flex.ktpamq" +msgstr "软管" -# Source-Hash: 505de343ec5010e6 +# English: "Floating" +# Source-Hash: 7843c5142b7d066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderHeight.xyqn6g" -msgstr "肩部高度" +msgid "editor.inspector.option.floating.1oadpln" +msgstr "悬浮" -# Source-Hash: 58d48c01b7ec1a40 +# English: "Floor" +# Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.shoulderStyle.1hkcv2w" -msgstr "肩部样式" +msgid "editor.inspector.option.floor.q9caf1" +msgstr "楼层" -# Source-Hash: af84db1026bf2e6c +# English: "Flush" +# Source-Hash: eaaa20f3b603140f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show center column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showCenterColumn.1uggg45" -msgstr "显示中柱" +msgid "editor.inspector.option.flush.olwwu5" +msgstr "齐平" -# Source-Hash: 80214dc086ebc1b1 +# English: "Front" +# Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showInfill.33rv7u" -msgstr "显示填充" +msgid "editor.inspector.option.front.uhugl4" +msgstr "前" -# Source-Hash: f02d6cdfb4c6ad08 +# English: "Furnace" +# Source-Hash: bccfc0565157dae6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show step supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.showStepSupports.1kg8h8m" -msgstr "显示踏步支撑" +msgid "editor.inspector.option.furnace.w0mzpf" +msgstr "暖风炉" -# Source-Hash: 5d1147c774718b76 +# English: "Gable" +# Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side flashing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sideFlashing.cv744o" -msgstr "侧面泛水" +msgid "editor.inspector.option.gable.1ekyx6" +msgstr "双坡" -# Source-Hash: 60bb3338e0ad558c +# English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.size.1a4x3zw" -msgstr "尺寸" +msgid "editor.inspector.option.gambrel.1yo90d9" +msgstr "复折式" -# Source-Hash: fbdbc6c97c75b030 +# English: "Generic" +# Source-Hash: 3c4900155c96b139 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.skylightType.1a5vj3i" -msgstr "天窗类型" +msgid "editor.inspector.option.generic.1074oey" +msgstr "通用" -# Source-Hash: c9a6561cb3c3da82 +# English: "Glass" +# Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab opening mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slabOpeningMode.g4f730" -msgstr "楼板开洞方式" +msgid "editor.inspector.option.glass.163tc5n" +msgstr "玻璃" -# Source-Hash: ef0efc845ccdba78 +# English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slatGap.slmk93" -msgstr "叶片间隙" +msgid "editor.inspector.option.glassFrame.jp9s3m" +msgstr "玻璃框" -# Source-Hash: 4daab7a2ce2ecef3 +# English: "Globe" +# Source-Hash: 2d4223f80dc3945c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.slideDirection.1527q13" -msgstr "推拉方向" +msgid "editor.inspector.option.globe.1avkqoy" +msgstr "球形" -# Source-Hash: 93b34a5b546664c4 +# English: "Grounded" +# Source-Hash: 398af4f45ba60c85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.span.1bmmuw1" -msgstr "跨度" +msgid "editor.inspector.option.grounded.jwoq73" +msgstr "落地" -# Source-Hash: 3b006be0b1f0493f +# English: "Guard" +# Source-Hash: 4972a0e68d69be58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.speed.2w2xwg" -msgstr "速度" +msgid "editor.inspector.option.guard.1m575se" +msgstr "护栏" -# Source-Hash: c8cfb8336a240cb1 +# English: "Half round" +# Source-Hash: 67be98efb58a7767 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spin speed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.spinSpeed.1ozx746" -msgstr "旋转速度" +msgid "editor.inspector.option.halfRound.m5hob0" +msgstr "半圆" -# Source-Hash: c9f54c3b258961c7 +# English: "Hidden" +# Source-Hash: 229199467e9cf2db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stairType.1u05e02" -msgstr "楼梯类型" +msgid "editor.inspector.option.hidden.gp48rd" +msgstr "隐藏" -# Source-Hash: 0af6194a8b8d8be6 +# English: "Hip" +# Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoff.4wv2g0" -msgstr "支脚" +msgid "editor.inspector.option.hip.r0jnjy" +msgstr "四坡" -# Source-Hash: 0464d4efa1e80c62 +# English: "Horizontal" +# Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standoff height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.standoffHeight.1qdnff" -msgstr "支脚高度" +msgid "editor.inspector.option.horizontal.tyapah" +msgstr "水平" -# Source-Hash: 85da28b0b26b8c2a +# English: "Include" +# Source-Hash: 6a9b4142ab179557 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.startPost.1ood9kv" -msgstr "起始柱" +msgid "editor.inspector.option.include.1a27vnr" +msgstr "包含" -# Source-Hash: d5dc1ba8cca3193d +# English: "Independent high beam" +# Source-Hash: 8061b426b9d1b5fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.stepCount.nzorrw" -msgstr "踏步数" +msgid "editor.inspector.option.independentHighBeam.1tn0zvy" +msgstr "独立高梁" -# Source-Hash: cadbb9cadd6a5e6f +# English: "Industrial" +# Source-Hash: 1dc686f2068f976b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapSpacing.1cyt3dc" -msgstr "吊带间距" +msgid "editor.inspector.option.industrial.8sd9u" +msgstr "工业风" -# Source-Hash: c017cd56b3ae5865 +# English: "Inline" +# Source-Hash: 0c9b1ec8947ee177 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.strapStyle.fh1oy4" -msgstr "吊带样式" +msgid "editor.inspector.option.inline.1uh05lg" +msgstr "直线" -# Source-Hash: 1a79a485d91f3f28 +# English: "Integrated" +# Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.style.nf3vcm" -msgstr "样式" +msgid "editor.inspector.option.integrated.1xd9lv0" +msgstr "一体式" -# Source-Hash: 7b3f016989c631a7 +# English: "Joined" +# Source-Hash: bb197b9219f32603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Suction diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.suctionDiameter.nutxtj" -msgstr "吸气管直径" +msgid "editor.inspector.option.joined.1x2o8ds" +msgstr "已合并" -# Source-Hash: 3f1e0fd30e5b1f39 +# English: "Kickout" +# Source-Hash: 0299cc0ee05d17fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyDiameter.h5ldh5" -msgstr "送风直径" +msgid "editor.inspector.option.kickout.vt8s6p" +msgstr "导水板" -# Source-Hash: 05c9b608538e181d +# English: "Knee" +# Source-Hash: a0712c8461bb0758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyHeight.zy6ru1" -msgstr "送风高度" +msgid "editor.inspector.option.knee.186hcui" +msgstr "膝墙" -# Source-Hash: 3c9400722baa474d +# English: "K style" +# Source-Hash: 083febe9876348ec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyShape.k39wnb" -msgstr "送风形状" +msgid "editor.inspector.option.kStyle.m8kfwz" +msgstr "K 型" -# Source-Hash: af7619025438f995 +# English: "Landing" +# Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supplyWidth.g66axi" -msgstr "送风宽度" +msgid "editor.inspector.option.landing.tyqarg" +msgstr "平台" -# Source-Hash: 617e198cf3f214d3 +# English: "Lantern" +# Source-Hash: d4c6c2495c233bb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Support offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.supportOffset.1w4cocr" -msgstr "支撑偏移" +msgid "editor.inspector.option.lantern.1h7o2xb" +msgstr "灯笼" -# Source-Hash: 2686abc093f3dfb1 +# English: "Leave" +# Source-Hash: 9477f663422698ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.sweepAngle.ucmmlw" -msgstr "扫掠角度" +msgid "editor.inspector.option.leave.1xf0sbk" +msgstr "离开" -# Source-Hash: 88e6c2e1c344b2fc +# English: "Left" +# Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.system.13qbhrw" -msgstr "系统" +msgid "editor.inspector.option.left.14n06mo" +msgstr "左" -# Source-Hash: ccb3afc48bd9a284 +# English: "Level" +# Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminal.1501knl" -msgstr "风口" +msgid "editor.inspector.option.level.i4lhtp" +msgstr "楼层" -# Source-Hash: de1777c521046f7d +# English: "Low profile" +# Source-Hash: a281a83a9fef6b88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.terminalType.1ne8lgb" -msgstr "风口类型" +msgid "editor.inspector.option.lowProfile.1h9lgzg" +msgstr "低矮型" -# Source-Hash: fdbb503f0941bd56 +# English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.thickness.1l8hu45" -msgstr "厚度" +msgid "editor.inspector.option.mansard.dwzck9" +msgstr "孟莎式" -# Source-Hash: 58cb60c14826f1d0 +# English: "Manual" +# Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.tiltAngle.1312yw1" -msgstr "倾斜角度" +msgid "editor.inspector.option.manual.1wu7r43" +msgstr "手动" -# Source-Hash: 7057972ff4c78dc7 +# English: "Maximum spacing" +# Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To level id" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.toLevelId.8i5qx7" -msgstr "目标楼层 ID" +msgid "editor.inspector.option.maximumSpacing.n1fzl4" +msgstr "最大间距" -# Source-Hash: fa8fc3d5c612e774 +# English: "Metal" +# Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingDepth.ki7gk2" -msgstr "顶部平台深度" +msgid "editor.inspector.option.metal.1az9fgc" +msgstr "金属" -# Source-Hash: 6e4a0b98089ec2cd +# English: "Metal panel" +# Source-Hash: c6185ecb219a9435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top landing mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topLandingMode.zk0h7e" -msgstr "顶部平台方式" +msgid "editor.inspector.option.metalPanel.1xdz476" +msgstr "金属板" -# Source-Hash: c182bb3c8a701085 +# English: "Minimal" +# Source-Hash: 2fd92342f3986843 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top rail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.topRailHeight.fscisv" -msgstr "顶部横杆高度" +msgid "editor.inspector.option.minimal.fwmiwg" +msgstr "极简" -# Source-Hash: 8c5502f1d8087b7b +# English: "Mono" +# Source-Hash: 5d8a5912056f83ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionMode.f3evgt" -msgstr "过渡方式" +msgid "editor.inspector.option.mono.oszvd6" +msgstr "单坡" -# Source-Hash: 96eb5997f0aa2dde +# English: "None" +# Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.transitionWidth.rtgz3m" -msgstr "过渡宽度" +msgid "editor.inspector.option.none.deku7v" +msgstr "无" -# Source-Hash: 812d27902509339f +# English: "Omit" +# Source-Hash: 7b6e4a6b86ba5112 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vane count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.vaneCount.55vtye" -msgstr "叶片数" +msgid "editor.inspector.option.omit.okcs7w" +msgstr "省略" -# Source-Hash: 1e7dcd717c8ac239 +# English: "Open" +# Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallHeight.18vmh5c" -msgstr "墙高" +msgid "editor.inspector.option.open.n6hn1l" +msgstr "打开" -# Source-Hash: f14bdaed6101896a +# English: "Opening" +# Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallShell.vqsyhv" -msgstr "墙体外壳" +msgid "editor.inspector.option.opening.dfet3" +msgstr "开口" -# Source-Hash: 801268585c9a679b +# English: "Open rack" +# Source-Hash: cc72b9bda6b30b5c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall skirt height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallSkirtHeight.1uea2ad" -msgstr "墙裙高度" +msgid "editor.inspector.option.openRack.mvt1x0" +msgstr "开放式架" -# Source-Hash: 6b377d0ba1565477 +# English: "Oval" +# Source-Hash: ed6d7b3dd9d5fc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.wallThickness.17wn4iv" -msgstr "墙厚" +msgid "editor.inspector.option.oval.1jiqzxx" +msgstr "椭圆" -# Source-Hash: 3a0ccd6c46877a87 +# English: "Picket" +# Source-Hash: 9295ddc2805b21e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width.gfxz9b" -msgstr "宽度" +msgid "editor.inspector.option.picket.zyeoy1" +msgstr "尖桩" -# Source-Hash: de05070474f589b4 +# English: "Pointed" +# Source-Hash: 180ffb8b75c977aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width2" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.width2.1nqawvr" -msgstr "宽度 2" +msgid "editor.inspector.option.pointed.effedk" +msgstr "尖头" -# Source-Hash: 6199b641888a83fa +# English: "Post and rail" +# Source-Hash: fca7919fb0c0669c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBack.1ay8nus" -msgstr "带背板" +msgid "editor.inspector.option.postAndRail.lajk2u" +msgstr "立柱与横杆" -# Source-Hash: 699934d8b9571b64 +# English: "Preserve high edge" +# Source-Hash: a78b28a428b27e20 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withBottom.1x1z1vo" -msgstr "带底板" +msgid "editor.inspector.option.preserveHighEdge.y6ywf0" +msgstr "保持高边" -# Source-Hash: 03987b9aadaab50f +# English: "Preserve low edge" +# Source-Hash: 325c6f53f6dc7634 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "With sides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.field.withSides.uq066x" -msgstr "带侧板" +msgid "editor.inspector.option.preserveLowEdge.196m5pk" +msgstr "保持低边" -# Source-Hash: f571cb2d06ae01ae +# English: "Preserve pitch" +# Source-Hash: 95c0b860d22a62cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Advanced" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.advanced.qwfkor" -msgstr "高级" +msgid "editor.inspector.option.preservePitch.1uq973r" +msgstr "保持坡度" -# Source-Hash: a886619d15bae47e +# English: "Privacy" +# Source-Hash: d4e11841abf22593 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.air.15jotdj" -msgstr "空气" +msgid "editor.inspector.option.privacy.1th4hrb" +msgstr "私密" -# Source-Hash: b69fc1f8128958fc +# English: "Purlins" +# Source-Hash: 1a8c46c7712a83b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.appearance.dli9wf" -msgstr "外观" +msgid "editor.inspector.option.purlins.od124c" +msgstr "檩条" -# Source-Hash: faaa4f6b40001667 +# English: "Pvc" +# Source-Hash: a9650f6f94de9247 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.body.ign4wl" -msgstr "主体" +msgid "editor.inspector.option.pvc.1oc4u2w" +msgstr "PVC" -# Source-Hash: fdf7108a011bd94b +# English: "Pyramid" +# Source-Hash: 47587b825e9a696b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cab.o4z1ox" -msgstr "轿厢" +msgid "editor.inspector.option.pyramid.17ucsqv" +msgstr "金字塔形" -# Source-Hash: 5737c4567e0f6863 +# English: "Rafters" +# Source-Hash: a9cb232546779779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cabinet.rzvymr" -msgstr "柜体" +msgid "editor.inspector.option.rafters.16tzbby" +msgstr "椽子" -# Source-Hash: d52b53241789ee63 +# English: "Rail" +# Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cap.s0thej" -msgstr "顶盖" +msgid "editor.inspector.option.rail.39zqc9" +msgstr "横杆" -# Source-Hash: 93a545688c917e69 +# English: "Raised" +# Source-Hash: bd50bd18e682db41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.collar.1am2iqo" -msgstr "套环" +msgid "editor.inspector.option.raised.4kyx9h" +msgstr "抬高" -# Source-Hash: 7a9c55b1c5a6e61d +# English: "Rect" +# Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connection.2r1h4p" -msgstr "连接" +msgid "editor.inspector.option.rect.tpfvaf" +msgstr "矩形" -# Source-Hash: d613df280aa13b42 +# English: "Reducer" +# Source-Hash: 68206f70689b2340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.connections.ai4w8u" -msgstr "连接" +msgid "editor.inspector.option.reducer.18byt67" +msgstr "异径管" -# Source-Hash: ce1429556f57d7ee +# English: "Return" +# Source-Hash: 2e0aa7113b021c67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.construction.j6p9kc" -msgstr "构造" +msgid "editor.inspector.option.return.1klrjrz" +msgstr "回风" -# Source-Hash: f8ba7d73d3006d57 +# English: "Return grille" +# Source-Hash: 63b39b1681f74fe4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cricket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.cricket.6iwg8g" -msgstr "泛水坡" +msgid "editor.inspector.option.returnGrille.r28ncm" +msgstr "回风格栅" -# Source-Hash: b8ff053666ee326b +# English: "Right" +# Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curb" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curb.5xi2ol" -msgstr "路缘" +msgid "editor.inspector.option.right.8humpx" +msgstr "右" -# Source-Hash: 3b9c59e996dcc9c5 +# English: "Round" +# Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve points" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.curvePoints.ztdcnn" -msgstr "曲线点" +msgid "editor.inspector.option.round.1vx0wyj" +msgstr "圆形" -# Source-Hash: 12df69f42beb0643 +# English: "Rounded" +# Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dimensions.pewa8g" -msgstr "尺寸" +msgid "editor.inspector.option.rounded.199nwpm" +msgstr "圆角" -# Source-Hash: 20f60b98de67849b +# English: "Sanitary tee" +# Source-Hash: dc67a1cdb2482f23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Doors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.doors.18w2kpa" -msgstr "门" +msgid "editor.inspector.option.sanitaryTee.oro3d4" +msgstr "卫生三通" -# Source-Hash: 6b6f8068bbc4c902 +# English: "Scalloped" +# Source-Hash: beb3fec22c287185 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormer.1fjs5tq" -msgstr "老虎窗" +msgid "editor.inspector.option.scalloped.1eltba0" +msgstr "扇贝形" -# Source-Hash: cda262793b4cc754 +# English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.dormerRoof.u9uy2g" -msgstr "老虎窗屋顶" +msgid "editor.inspector.option.scoop.qy5zdv" +msgstr "勺形" -# Source-Hash: 00a4416e0a33d856 +# English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.drainage.7easy0" -msgstr "排水" +msgid "editor.inspector.option.segmentedPanel.5r3hwl" +msgstr "分段面板" -# Source-Hash: 8a3bc5111b9e97f4 +# English: "Shed" +# Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.elevation.4jbp44" -msgstr "标高" +msgid "editor.inspector.option.shed.6t994v" +msgstr "单坡" -# Source-Hash: a9cba06250228a55 +# English: "Sheet metal" +# Source-Hash: 7fbc26cd497cfd12 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.endCaps.4fsb5b" -msgstr "端盖" +msgid "editor.inspector.option.sheetMetal.l164lv" +msgstr "金属板" -# Source-Hash: 228f2206862e41cf +# English: "Shingle" +# Source-Hash: 26e2c261223cbb46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equipment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.equipment.1xtq0ev" -msgstr "设备" +msgid "editor.inspector.option.shingle.pfmynh" +msgstr "瓦片" -# Source-Hash: a19276baa3688cbf +# English: "Shingled" +# Source-Hash: e9046e9823b400a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.face.1531fr0" -msgstr "面" +msgid "editor.inspector.option.shingled.1nm9inf" +msgstr "瓦片铺设" -# Source-Hash: fb34ee7c59cb84ac +# English: "Simple" +# Source-Hash: a05a390ae205fdd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.fitting.171z1r4" -msgstr "管件" +msgid "editor.inspector.option.simple.12mfu73" +msgstr "简单" -# Source-Hash: f817c09ef0bb6d69 +# English: "Single" +# Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flues" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.flues.nq6pye" -msgstr "烟道" +msgid "editor.inspector.option.single.1uzxurt" +msgstr "单扇" -# Source-Hash: a3c10560fa3c1612 +# English: "Single left" +# Source-Hash: aee3b2bd78c87d0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.footprint.g8q2pg" -msgstr "基底" +msgid "editor.inspector.option.singleLeft.1atdh02" +msgstr "单扇左开" -# Source-Hash: ce3a6c03765dbbd8 +# English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.frame.q7q9fq" -msgstr "框架" +msgid "editor.inspector.option.singleRight.1axbu67" +msgstr "单扇右开" -# Source-Hash: 523b9912e283b644 +# English: "Slant box" +# Source-Hash: 7672d2764445c110 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.geometry.a21os1" -msgstr "几何" +msgid "editor.inspector.option.slantBox.1ta99vy" +msgstr "斜面盒" -# Source-Hash: 7e4a0a38f694be6b +# English: "Slat" +# Source-Hash: 405e07a8cc2c88e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.grid.d8gwxd" -msgstr "网格" +msgid "editor.inspector.option.slat.1p9njbz" +msgstr "叶片" -# Source-Hash: 30d8dba84c42040e +# English: "Sliding" +# Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hangers.agnfzn" -msgstr "吊架" +msgid "editor.inspector.option.sliding.cd0jox" +msgstr "推拉门" -# Source-Hash: 8c1bc3a078056ff7 +# English: "Slope" +# Source-Hash: e6d82e606b8c9301 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hardware.1nygwm1" -msgstr "五金件" +msgid "editor.inspector.option.slope.1bn2qw" +msgstr "坡度" -# Source-Hash: 2a47557eb4a5f766 +# English: "Sloped" +# Source-Hash: f6998707123fe72d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hung wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.hungWall.1ftr1gz" -msgstr "悬挂墙" +msgid "editor.inspector.option.sloped.lo4sh0" +msgstr "倾斜" -# Source-Hash: 7d6ed24c02ab0c5c +# English: "Solid" +# Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Insulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.insulation.1kyo5e7" -msgstr "保温层" +msgid "editor.inspector.option.solid.o4uy6s" +msgstr "实体" -# Source-Hash: d4c6c2495c233bb5 +# English: "Solid panel" +# Source-Hash: 8008b6f17089beaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lantern.1h7o2xb" -msgstr "灯笼" +msgid "editor.inspector.option.solidPanel.2yaw62" +msgstr "实板" -# Source-Hash: cc3d601d59e2e26c +# English: "Spiral" +# Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.levels.1s6yaru" -msgstr "楼层" +msgid "editor.inspector.option.spiral.91hcl4" +msgstr "螺旋" -# Source-Hash: 1a849e52a46d5087 +# English: "Splash" +# Source-Hash: beef1cf79388b77b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.line.166mv5z" -msgstr "线" +msgid "editor.inspector.option.splash.1uke4cm" +msgstr "溅水板" -# Source-Hash: 9ab4f7a738ebee13 +# English: "Spread" +# Source-Hash: 324677a79ab15e22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lines" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.lines.12bghr0" -msgstr "线" +msgid "editor.inspector.option.spread.1g1zysu" +msgstr "展开" -# Source-Hash: c0ff16d77ebc9593 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Motion" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.motion.1be7sjr" -msgstr "运动" +msgid "editor.inspector.option.square.1xl1lk6" +msgstr "方形" -# Source-Hash: 81d365ccef73068c +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mounting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.mounting.feutd2" -msgstr "安装方式" +msgid "editor.inspector.option.stair.1xf86e" +msgstr "楼梯" -# Source-Hash: e04298381837252f +# English: "Standard" +# Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.opening.dfet3" -msgstr "开口" +msgid "editor.inspector.option.standard.vuco" +msgstr "标准" -# Source-Hash: f46314c54627858d +# English: "Start" +# Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.panelDimensions.b59qe6" -msgstr "面板尺寸" +msgid "editor.inspector.option.start.30xvgf" +msgstr "起点" -# Source-Hash: 4712c449e9a42d3b +# English: "Step" +# Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pattern distribution" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.patternDistribution.fmam51" -msgstr "图案分布" +msgid "editor.inspector.option.step.jtn9kf" +msgstr "台阶" -# Source-Hash: b3be684ac62fbf53 +# English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.placement.13lcvts" -msgstr "放置" +msgid "editor.inspector.option.stepped.naxzh0" +msgstr "阶梯式" -# Source-Hash: 9da0cbfad144d737 +# English: "Straight" +# Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.position.1quewx6" -msgstr "位置" +msgid "editor.inspector.option.straight.lxqbfr" +msgstr "直跑" -# Source-Hash: 96ad4bd0f7baa40e +# English: "Strap" +# Source-Hash: b9b45ac5ba1daeee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Profile" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.profile.1kwlci6" -msgstr "轮廓" +msgid "editor.inspector.option.strap.14q0ps9" +msgstr "吊带" -# Source-Hash: 240ed9c77627b9a1 +# English: "Supply" +# Source-Hash: b45f382a67d6c0eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.railing.19a5bnr" -msgstr "栏杆" +msgid "editor.inspector.option.supply.1oy2zwu" +msgstr "送风" -# Source-Hash: 2e0aa7113b021c67 +# English: "Supply register" +# Source-Hash: 00e7a2b3b2aa36ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.return.1klrjrz" -msgstr "回风" +msgid "editor.inspector.option.supplyRegister.1lzdbbn" +msgstr "送风口" -# Source-Hash: 83909f82446ee9c3 +# English: "Tapered" +# Source-Hash: f86f1f22230596b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.settings.ktdb8o" -msgstr "设置" +msgid "editor.inspector.option.tapered.3y5blo" +msgstr "锥形" -# Source-Hash: 4fc63ad21de30103 +# English: "Target spacing" +# Source-Hash: 5fed23c2217b83fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shaft.mad9cv" -msgstr "井道" +msgid "editor.inspector.option.targetSpacing.14uibc9" +msgstr "目标间距" -# Source-Hash: 85ea1d3c8b749680 +# English: "Tee" +# Source-Hash: f3ceed7f867f090f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shape.iq9t52" -msgstr "形状" +msgid "editor.inspector.option.tee.7d6tjp" +msgstr "三通" -# Source-Hash: 8c6e3985459dacf9 +# English: "Tilted" +# Source-Hash: 787005b26aec58a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoulder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.shoulder.1nyfx25" -msgstr "肩部" +msgid "editor.inspector.option.tilted.6n0u5j" +msgstr "倾斜" -# Source-Hash: 60bb3338e0ad558c +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.size.1a4x3zw" -msgstr "尺寸" +msgid "editor.inspector.option.top.2nypcs" +msgstr "顶部" -# Source-Hash: a20c89205a4448d2 +# English: "Transition" +# Source-Hash: e6657000554dfe53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.structure.gezdra" -msgstr "结构" +msgid "editor.inspector.option.transition.1rziz9e" +msgstr "过渡" -# Source-Hash: 1a79a485d91f3f28 +# English: "Vent" +# Source-Hash: 04bc0909a133a7e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.style.nf3vcm" -msgstr "样式" +msgid "editor.inspector.option.vent.xda5j2" +msgstr "通风口" -# Source-Hash: b45f382a67d6c0eb +# English: "Walk on" +# Source-Hash: 7eee9aff0dd520b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.supply.1oy2zwu" -msgstr "送风" +msgid "editor.inspector.option.walkOn.1n5sg9l" +msgstr "行走面" -# Source-Hash: 78a454b7ac7a3cd9 +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.surface.wtamqk" -msgstr "表面" +msgid "editor.inspector.option.wall.blj585" +msgstr "墙体" -# Source-Hash: ccb3afc48bd9a284 +# English: "Wall abutment" +# Source-Hash: f3578f7177ad0054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terminal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.terminal.1501knl" -msgstr "风口" +msgid "editor.inspector.option.wallAbutment.lza941" +msgstr "靠墙" -# Source-Hash: 399fc3e62f491f08 +# English: "Wall ledger" +# Source-Hash: 8dcf182f91ba3245 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Topology" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.topology.18t8dyc" -msgstr "拓扑" +msgid "editor.inspector.option.wallLedger.5m8ana" +msgstr "墙上托梁" -# Source-Hash: f425799b82e085ed +# English: "Wall shelf" +# Source-Hash: 0adda30be5f17975 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transform" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.transform.1c55ayj" -msgstr "变换" +msgid "editor.inspector.option.wallShelf.yiozfl" +msgstr "墙面搁板" -# Source-Hash: b778a315e82065a0 +# English: "Waste" +# Source-Hash: 29d8590334492601 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.trap.1qc7vhe" -msgstr "存水弯" +msgid "editor.inspector.option.waste.3xrp79" +msgstr "排污" -# Source-Hash: c44f7ab8bbd24858 +# English: "Wye" +# Source-Hash: 00249876bb37079f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy -msgid "editor.inspector.group.type.1m2zofh" -msgstr "类型" +msgid "editor.inspector.option.wye.14vupc8" +msgstr "Y 形三通" -# Source-Hash: c8fd17853839516f +# English: "Collapse" +# Source-Hash: 179b025e25f9a091 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Abs" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.option.abs.132mz8b" -msgstr "ABS" +msgid "editor.layers.collapse" +msgstr "折叠" -# Source-Hash: fd9eb1a16cb6b996 +# English: "Ceiling ({area})" +# Source-Hash: dbeae4c43f49327f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Access panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.accessPanel.13sptb5" -msgstr "检修口" +msgid "editor.layers.defaultName.ceiling" +msgstr "天花板({area})" -# Source-Hash: db8064add8c27a5c +# English: "Collection" +# Source-Hash: d29165c9d4bfc3cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Air handler" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.airHandler.1crnyg3" -msgstr "空气处理机" +msgid "editor.layers.defaultName.collection" +msgstr "集合" -# Source-Hash: d8234782848be927 +# English: "Flight ({width}×{length}m)" +# Source-Hash: 8f536b859be662a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alternating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.alternating.5jashi" -msgstr "交替" +msgid "editor.layers.defaultName.flight" +msgstr "梯段({width}×{length}m)" -# Source-Hash: 63cbda7b09662368 +# English: "Landing ({width}×{length}m)" +# Source-Hash: 46dcbf4548374073 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arched" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.arched.14188w0" -msgstr "拱形" +msgid "editor.layers.defaultName.landing" +msgstr "平台({width}×{length}m)" -# Source-Hash: 05b65242d8c48791 +# English: "Node" +# Source-Hash: 3ea44453fcd08a6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.auto.540b06" -msgstr "自动" +msgid "editor.layers.defaultName.node" +msgstr "节点" -# Source-Hash: d18036b75b2c3e4f +# English: "Roof ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 93f3a04e15b6fb49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Automatic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.automatic.r122ro" -msgstr "自动" +msgid "editor.layers.defaultName.roof" +msgstr "屋顶({count, plural, other {# 个分段}})" -# Source-Hash: 7ceade8f25b58fe9 +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" +# Source-Hash: a730fab01c172704 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.back.1hzmxtu" -msgstr "返回" +msgid "editor.layers.defaultName.roofSegment" +msgstr "{roofType, select, hip {四坡} gable {双坡} shed {单坡} gambrel {复折} dutch {歇山} mansard {孟莎} flat {平屋顶} conical {锥形} barrel {筒形} other {屋顶}}({width}×{depth}m)" -# Source-Hash: 3eda29328f91485d +# English: "Room ({area})" +# Source-Hash: 54efcb0756c2f2c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.balusters.1j3w6ng" -msgstr "栏杆柱" +msgid "editor.layers.defaultName.room" +msgstr "房间({area})" -# Source-Hash: f211248ec8619e4c +# English: "Slab ({area})" +# Source-Hash: e38a4b9986a9d21a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Band" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.band.160umau" -msgstr "腰线" +msgid "editor.layers.defaultName.slab" +msgstr "楼板({area})" -# Source-Hash: 8f9ff05c6a3b08a6 +# English: "Staircase ({count, plural, one {# segment} other {# segments}})" +# Source-Hash: 57398365d4b9066f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base plate" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.basePlate.17fcg8k" -msgstr "底板" +msgid "editor.layers.defaultName.staircase" +msgstr "楼梯({count, plural, other {# 个分段}})" -# Source-Hash: 19ae2360207ecf5a +# English: "Zone ({area})" +# Source-Hash: a17f8fc29aae896e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts #, fuzzy -msgid "editor.inspector.option.boards.u14hig" -msgstr "木板" +msgid "editor.layers.defaultName.zone" +msgstr "区域({area})" -# Source-Hash: c0fee00bdfb09d37 +# English: "Expand" +# Source-Hash: 5cabcabb8bc14809 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bookshelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx #, fuzzy -msgid "editor.inspector.option.bookshelf.ls0zqk" -msgstr "书架" +msgid "editor.layers.expand" +msgstr "展开" -# Source-Hash: 69578b7dd7723fa1 +# English: "{levelName} options" +# Source-Hash: 6731e9dd94e8b268 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/floating-level-selector.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.both.mkyb42" -msgstr "两侧" +msgid "editor.layers.levelOptions" +msgstr "{levelName}选项" -# Source-Hash: b3a163b453896bef +# English: "Rename" +# Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx #, fuzzy -msgid "editor.inspector.option.bottom.9mpruy" -msgstr "底部" +msgid "editor.layers.rename" +msgstr "重命名" -# Source-Hash: e9a89a5e4ac8cd61 +# English: "{role, select, room {Room colour} other {Zone colour}}" +# Source-Hash: 21eafa019ad62fea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy -msgid "editor.inspector.option.box.1nbjcuq" -msgstr "盒形" +msgid "editor.layers.spaceColour" +msgstr "{role, select, room {房间颜色} other {区域颜色}}" -# Source-Hash: 2c2815df4f724abe +# English: "Unit colour" +# Source-Hash: f5de042416a2c04f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Break" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy -msgid "editor.inspector.option.break.17spnvc" -msgstr "断开" +msgid "editor.layers.unitColour" +msgstr "单元颜色" -# Source-Hash: 0d5a25303d3d8058 +# English: "Cancel" +# Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Butterfly" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.butterfly.pvv0g4" -msgstr "蝶形" +msgid "editor.levelDuplicateDialog.cancel" +msgstr "取消" -# Source-Hash: 5e2429ced8144ab4 +# English: "Choose what to copy from {levelName}." +# Source-Hash: 07c11e7e4d4681d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cable.fp50zq" -msgstr "钢索" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" +msgstr "选择要从{levelName}复制的内容。" -# Source-Hash: 69129e514347672a +# English: "Choose what to copy from this level." +# Source-Hash: 9500d4378a319532 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cast iron" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.castIron.1q2z4qk" -msgstr "铸铁" +msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" +msgstr "选择要从此楼层复制的内容。" -# Source-Hash: 4275c05172abd7fb +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.ceiling.1t2zaqo" -msgstr "天花板" +msgid "editor.levelDuplicateDialog.duplicate" +msgstr "复制" -# Source-Hash: 5211341ed18f33f6 +# English: "Duplicate Level" +# Source-Hash: a56e3eb43e60b45c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.center.kzdxno" -msgstr "居中" +msgid "editor.levelDuplicateDialog.duplicateLevel" +msgstr "复制楼层" -# Source-Hash: 65f410261609060b +# English: "Everything" +# Source-Hash: e4c6ab1ea473a082 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.centerOpening.bn1rvw" -msgstr "中开" +msgid "editor.levelDuplicateDialog.everything" +msgstr "全部" -# Source-Hash: cf5f9756cc866f3e +# English: "Structure, finishes, and placed items, without guide references." +# Source-Hash: ac3f119e560388ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cleanout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.cleanout.lww8bm" -msgstr "清扫口" +msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" +msgstr "结构、饰面和已放置的物品,不含参考底图。" -# Source-Hash: e71d1d949f1d1b41 +# English: "Structure + furniture" +# Source-Hash: acbdf968391d5273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Concrete pad" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.concretePad.m1zdij" -msgstr "混凝土垫层" +msgid "editor.levelDuplicateDialog.structureFurniture" +msgstr "结构 + 家具" -# Source-Hash: a3804c4f5e6aefbc +# English: "Structure + materials" +# Source-Hash: ce682864a140fe75 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Condenser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.condenser.a74nps" -msgstr "冷凝器" +msgid "editor.levelDuplicateDialog.structureMaterials" +msgstr "结构 + 材质" -# Source-Hash: 6786ac75cbe9af57 +# English: "Structure, materials, furniture, and references." +# Source-Hash: 5ee44d53847c5ea2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corbeled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.corbeled.bovs4v" -msgstr "叠涩" +msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" +msgstr "结构、材质、家具和参考。" -# Source-Hash: 8a4beb9a57edc26e +# English: "Structure only" +# Source-Hash: 95aa1b43dbdb3f02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.count.1qoi4r8" -msgstr "数量" +msgid "editor.levelDuplicateDialog.structureOnly" +msgstr "仅结构" -# Source-Hash: db1f8f141714b363 +# English: "Structure with the current material and finish assignments." +# Source-Hash: 2c53b46c9ca9fe2f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Coupling" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.coupling.111aap2" -msgstr "管接头" +msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" +msgstr "结构及当前的材质和饰面分配。" -# Source-Hash: 7a9a92d2e59a3bbc +# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." +# Source-Hash: 3350e9008eaba15d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Covering specific" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-duplicate-dialog.tsx #, fuzzy -msgid "editor.inspector.option.coveringSpecific.1h9fo8w" -msgstr "覆盖层细节" +msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" +msgstr "墙体、楼板、屋顶、楼梯、窗和门,不含饰面。" -# Source-Hash: d500c4b7827d3390 +# English: "Copy" +# Source-Hash: 4f60d1e142affa9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cross" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cross.1elubzv" -msgstr "十字" +msgid "editor.levelWallCopyPrompt.copy" +msgstr "复制" -# Source-Hash: 7ca7f00b3ba531f4 +# English: "Copy exterior walls from the level {direction}?" +# Source-Hash: 8ccaf4c98e335cc8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cubby" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cubby.ivq4qc" -msgstr "储物格" +msgid "editor.levelWallCopyPrompt.copyExteriorWallsFromTheLevel" +msgstr "要从{direction, select, below {下方} above {上方} other {{direction}}}楼层复制外墙吗?" -# Source-Hash: a028e08962c388e0 +# English: "No thanks" +# Source-Hash: f6ad0e813d2945bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.curved.6noz1s" -msgstr "弧形" +msgid "editor.levelWallCopyPrompt.noThanks" +msgstr "不用了" -# Source-Hash: c65f02bcdc3231d6 +# English: "{sourceName}’s outside walls, in place, so this level lines up with it." +# Source-Hash: d2aecc3c47120ad5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cylinder" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/level-wall-copy-prompt.tsx #, fuzzy -msgid "editor.inspector.option.cylinder.eol6vj" -msgstr "圆柱" +msgid "editor.levelWallCopyPrompt.sourcenameSOutsideWallsInPlace" +msgstr "原位复制{sourceName}的外墙,使此楼层与其对齐。" -# Source-Hash: 53f224e2828d5ab7 +# English: "Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies." +# Source-Hash: b6317f13da65f81e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Damper" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.damper.wgri80" -msgstr "风阀" +msgid "editor.lib.buildingFoundation.basementExcavation" +msgstr "在高侧向地下开挖 {depth},开挖范围以墙体外表面为界;房屋四周地面保持原状。" -# Source-Hash: 748877ae309c313c +# English: "The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 16c97d232e4c05dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.destination.gfzm1r" -msgstr "目标" +msgid "editor.lib.buildingFoundation.basementTopOverTheGround" +msgstr "地下室墙顶高出地面高侧 {distance};基础设定为高出 {asked}。" -# Source-Hash: bfb3561e28d92a39 +# English: "The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 9b781eb9749c9af3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diffuser" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.diffuser.1igj0jh" -msgstr "散流器" +msgid "editor.lib.buildingFoundation.basementTopUnderTheGround" +msgstr "地下室墙顶低于地面高侧 {distance};基础设定为高出 {asked}。" -# Source-Hash: 6cff941e10ff22b3 +# English: "Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1)." +# Source-Hash: 6903234208cb7eb7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dog ear" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dogEar.xhtvjt" -msgstr "切角" +msgid "editor.lib.buildingFoundation.crawlSpaceDugOut" +msgstr "架空层:最多开挖 {dug},以保证搁栅下净空 {clearance}(IRC R317.1)。" -# Source-Hash: de47e23eb81efe02 +# English: "Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall." +# Source-Hash: fee27efc9440d33c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dome" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dome.1c87e56" -msgstr "穹顶" +msgid "editor.lib.buildingFoundation.crippleWalls" +msgstr "短墙露出地面超过 {stem} 处,短墙降至高出地坪 {least}:其上设 {cripples, plural, other {# 道短柱墙}},最高 {tallest}。" -# Source-Hash: 3cd71355956a1821 +# English: "The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it." +# Source-Hash: d9f9c3b0c3978923 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.double.13h5354" -msgstr "双扇" +msgid "editor.lib.buildingFoundation.daylightWalls" +msgstr "地面在房屋范围内落差 {fall}:下坡一侧的墙体最多露出地面 {exposed}。" -# Source-Hash: 72a84a9762461745 +# English: "That is {fill} of fill. A raised floor on a stem wall suits a floor this high." +# Source-Hash: a62040ee19eb7321 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct board" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.ductBoard.lc3o4t" -msgstr "风管板" +msgid "editor.lib.buildingFoundation.deepFill" +msgstr "这需要填方 {fill}。楼面这么高,更适合采用基础短墙上的架空楼板。" -# Source-Hash: 6949d667043bee0b +# English: "That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope." +# Source-Hash: 7f8966c9c28a5d64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.dutch.1djx429" -msgstr "荷兰式" +msgid "editor.lib.buildingFoundation.deepFillOnASlope" +msgstr "低侧需要填方 {fill}。此坡度更适合采用基础短墙上的架空楼板。" -# Source-Hash: e6637bddef6aabd6 +# English: "The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: a2434c0de9dae5e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.elbow.1flup74" -msgstr "弯头" +msgid "editor.lib.buildingFoundation.floorOverTheGround" +msgstr "楼面高出地面高侧 {distance};基础设定为高出 {asked}。" -# Source-Hash: 135874d189176d13 +# English: "The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it." +# Source-Hash: 6dcf21da285e3910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.end.1llf32i" -msgstr "终点" +msgid "editor.lib.buildingFoundation.floorUnderTheGround" +msgstr "楼面低于地面高侧 {distance};基础设定为高出 {asked}。" -# Source-Hash: ada9fc2eec2c7d61 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: b4e4be1da470f91e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End cap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.endCap.1p4v4na" -msgstr "端盖" +msgid "editor.lib.buildingFoundation.footingSteps" +msgstr "地面在房屋范围内落差 {fall}:基脚沿最陡一侧跌级 {steps, plural, other {# 次}},每级 {riser}。" -# Source-Hash: 73916b8395208874 +# English: "The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: bea524db96134378 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal fit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.equalFit.11vw52c" -msgstr "均匀适配" +msgid "editor.lib.buildingFoundation.footingStepsUpTo" +msgstr "地面在房屋范围内落差 {fall}:基脚沿最陡一侧跌级 {steps, plural, other {# 次}},每级最高 {riser}。" -# Source-Hash: 83579a6e420df2ec +# English: "The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1))." +# Source-Hash: 31192a74aa068c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed count" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedCount.1o3dznu" -msgstr "固定数量" +msgid "editor.lib.buildingFoundation.footingTooNarrow" +msgstr "基脚宽 {width}:在 1500 psf 地基土上,{storeys, plural, other {# 层建筑要求}} {asked}(IRC Table R403.1(1))。" -# Source-Hash: 4fb65101d0b1a222 +# English: "The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: c9f4ef98e9cd7d9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.fixedSpacing.hl4dr4" -msgstr "固定间距" +msgid "editor.lib.buildingFoundation.footingTooShallow" +msgstr "基脚底面低于地坪 {depth}:应至少保持 {least},或深至冻土线(IRC R403.1.4)。" -# Source-Hash: 9cab4d4c95f63cdd +# English: "The ground is held {least} under the slab’s top on every side: cut {cut}." +# Source-Hash: 8e9be841759f6e6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flat.ly0bei" -msgstr "平屋顶" +msgid "editor.lib.buildingFoundation.gradingCut" +msgstr "四周地面均保持低于楼板顶 {least}:挖方 {cut}。" -# Source-Hash: a9cad0badcc16693 +# English: "The ground is held {least} under the slab’s top on every side: cut {cut} on the high side." +# Source-Hash: 779f127732e5589b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flex" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flex.ktpamq" -msgstr "软管" +msgid "editor.lib.buildingFoundation.gradingCutOnASlope" +msgstr "四周地面均保持低于楼板顶 {least}:高侧挖方 {cut}。" -# Source-Hash: 7843c5142b7d066d +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}." +# Source-Hash: 954523c1f6ed230c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floating" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floating.1oadpln" -msgstr "悬浮" +msgid "editor.lib.buildingFoundation.gradingFilled" +msgstr "四周地面均保持低于楼板顶 {least}:填方 {fill}。" -# Source-Hash: d7148fa065bc709b +# English: "The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}." +# Source-Hash: b55f69797190bfee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.floor.q9caf1" -msgstr "楼层" +msgid "editor.lib.buildingFoundation.gradingFilledAndCut" +msgstr "四周地面均保持低于楼板顶 {least}:填方 {fill},挖方 {cut}。" -# Source-Hash: eaaa20f3b603140f +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side." +# Source-Hash: bae5f3ae42970e8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flush" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.flush.olwwu5" -msgstr "齐平" +msgid "editor.lib.buildingFoundation.gradingFilledAndCutOnASlope" +msgstr "四周地面均保持低于楼板顶 {least}:低侧最多填方 {fill},高侧挖方 {cut}。" -# Source-Hash: e360f89d3c0ec456 +# English: "The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side." +# Source-Hash: 4a06302380f3f85d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.front.uhugl4" -msgstr "前" +msgid "editor.lib.buildingFoundation.gradingFilledOnASlope" +msgstr "四周地面均保持低于楼板顶 {least}:低侧最多填方 {fill}。" -# Source-Hash: bccfc0565157dae6 +# English: "The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less." +# Source-Hash: 64931230719b598b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.furnace.w0mzpf" -msgstr "暖风炉" +msgid "editor.lib.buildingFoundation.groundCutRoundTheStem" +msgstr "高侧地面挖方 {cut},使基础短墙顶高出地面 {least}(IRC R317.1)。抬高楼面可减少挖方。" -# Source-Hash: deaffa095d126dfe +# English: "Raise it on a stem wall" +# Source-Hash: 3613643e58e4b6d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gable.1ekyx6" -msgstr "双坡" +msgid "editor.lib.buildingFoundation.raiseItOnAStemWall" +msgstr "改为基础短墙架空" -# Source-Hash: 7f53d9ffca39984a +# English: "Raise the floor" +# Source-Hash: f69b48072be7f6ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.gambrel.1yo90d9" -msgstr "复折式" +msgid "editor.lib.buildingFoundation.raiseTheFloor" +msgstr "抬高楼面" -# Source-Hash: 3c4900155c96b139 +# English: "Seat the basement wall’s top {asked} over the high side of the ground" +# Source-Hash: c1e5c4d64f7952a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Generic" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.generic.1074oey" -msgstr "通用" +msgid "editor.lib.buildingFoundation.seatBasementWallTop" +msgstr "将地下室墙顶设为高出地面高侧 {asked}" -# Source-Hash: 8ab91094cf650204 +# English: "Seat the floor’s top {asked} over the high side of the ground" +# Source-Hash: 3f82cd72a6654fdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glass.163tc5n" -msgstr "玻璃" +msgid "editor.lib.buildingFoundation.seatFloorTop" +msgstr "将楼面顶设为高出地面高侧 {asked}" -# Source-Hash: 88810658a2b97bc6 +# English: "Seat the ground floor {asked} over grade" +# Source-Hash: b4bbe36aae57a0d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass frame" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.glassFrame.jp9s3m" -msgstr "玻璃框" +msgid "editor.lib.buildingFoundation.seatGroundFloor" +msgstr "将首层设为高出地坪 {asked}" -# Source-Hash: 2d4223f80dc3945c +# English: "Seat it at {asked}" +# Source-Hash: bc5256a4749abb35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Globe" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.globe.1avkqoy" -msgstr "球形" +msgid "editor.lib.buildingFoundation.seatItAt" +msgstr "设为 {asked}" -# Source-Hash: 398af4f45ba60c85 +# English: "Seat the raised floor’s top {asked} over the high side of the ground" +# Source-Hash: 2becc7a7c8c48a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.grounded.jwoq73" -msgstr "落地" +msgid "editor.lib.buildingFoundation.seatRaisedFloorTop" +msgstr "将架空楼板顶设为高出地面高侧 {asked}" -# Source-Hash: 4972a0e68d69be58 +# English: "Seat the slab’s top {asked} over the high side of the ground" +# Source-Hash: 88e009e2eb3ec2a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.guard.1m575se" -msgstr "护栏" +msgid "editor.lib.buildingFoundation.seatSlabTop" +msgstr "将楼板顶设为高出地面高侧 {asked}" -# Source-Hash: 67be98efb58a7767 +# English: "Set it {height}" +# Source-Hash: ccb9193f87264256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.halfRound.m5hob0" -msgstr "半圆" +msgid "editor.lib.buildingFoundation.setIt" +msgstr "设为 {height}" -# Source-Hash: 229199467e9cf2db +# English: "Set it {depth} deep" +# Source-Hash: e004c8ca1e6b6117 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hidden" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hidden.gp48rd" -msgstr "隐藏" +msgid "editor.lib.buildingFoundation.setItDeep" +msgstr "设为深 {depth}" -# Source-Hash: 9eda0ea8d447066d +# English: "The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1)." +# Source-Hash: e884fcda7ee630ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.hip.r0jnjy" -msgstr "四坡" +msgid "editor.lib.buildingFoundation.sillTooNearTheGround" +msgstr "地梁板和楼面框架高出地面 {height}:应至少距地面 {least}(IRC R317.1)。" -# Source-Hash: ac9c79f08ef130a1 +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step." +# Source-Hash: fe154a985943c7ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.horizontal.tyapah" -msgstr "水平" +msgid "editor.lib.buildingFoundation.slabEdgeSteps" +msgstr "地面在房屋范围内落差 {fall}:板边沿最陡一侧跌级 {steps, plural, other {# 次}},每级 {riser}。" -# Source-Hash: 6a9b4142ab179557 +# English: "The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step." +# Source-Hash: ee469b2069a20e5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.include.1a27vnr" -msgstr "包含" +msgid "editor.lib.buildingFoundation.slabEdgeStepsUpTo" +msgstr "地面在房屋范围内落差 {fall}:板边沿最陡一侧跌级 {steps, plural, other {# 次}},每级最高 {riser}。" -# Source-Hash: 8061b426b9d1b5fa +# English: "The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4)." +# Source-Hash: e45d256982a22d6e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Independent high beam" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.independentHighBeam.1tn0zvy" -msgstr "独立高梁" +msgid "editor.lib.buildingFoundation.slabEdgeTooShallow" +msgstr "板边底面低于地坪 {depth}:应至少保持 {least},或深至冻土线(IRC R403.1.4)。" -# Source-Hash: 1dc686f2068f976b +# English: "Widen it to {width}" +# Source-Hash: 70fa99a8b7e7c762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Industrial" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/building-foundation.ts #, fuzzy -msgid "editor.inspector.option.industrial.8sd9u" -msgstr "工业风" +msgid "editor.lib.buildingFoundation.widenItTo" +msgstr "加宽至 {width}" -# Source-Hash: 0c9b1ec8947ee177 +# English: "Add or remove objects from the selection" +# Source-Hash: 395fd0821b203517 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inline" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.inline.1uh05lg" -msgstr "直线" +msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" +msgstr "在选择中添加或移除对象" -# Source-Hash: 88d5b9ecb242c650 +# English: "Adjust one side’s corner radius" +# Source-Hash: 8428cb0af9c6d6a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.integrated.1xd9lv0" -msgstr "一体式" +msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" +msgstr "调整一侧的圆角半径" -# Source-Hash: bb197b9219f32603 +# English: "Clear the selection (or click outside)" +# Source-Hash: d63b45c195713993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Joined" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.joined.1x2o8ds" -msgstr "已合并" +msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" +msgstr "清除选择(或点击外部)" -# Source-Hash: 0299cc0ee05d17fc +# English: "Click a handle dot to show move arrows" +# Source-Hash: 40b1afbeadbd6213 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kickout" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kickout.vt8s6p" -msgstr "导水板" +msgid "editor.lib.contextualHelp.clickAHandleDotToShow" +msgstr "点击控制点以显示移动箭头" -# Source-Hash: a0712c8461bb0758 +# English: "Click or drag the selection to move it as one" +# Source-Hash: 0b82a35c71549580 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Knee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.knee.186hcui" -msgstr "膝墙" +msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" +msgstr "点击或拖动选择以整体移动" -# Source-Hash: 083febe9876348ec +# English: "Click the handle dot to show move + rotate handles" +# Source-Hash: 73a761f393f69cb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "K style" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.kStyle.m8kfwz" -msgstr "K 型" +msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" +msgstr "点击控制点以显示移动和旋转控件" -# Source-Hash: de9566487b17a73b +# English: "Detach the joint while dragging an arrow" +# Source-Hash: c0045cef191fee69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.landing.tyqarg" -msgstr "平台" +msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" +msgstr "拖动箭头时断开连接" -# Source-Hash: d4c6c2495c233bb5 +# English: "Drag left or right to rotate selected object" +# Source-Hash: a019c8948c104a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lantern" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lantern.1h7o2xb" -msgstr "灯笼" +msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" +msgstr "左右拖动以旋转所选对象" -# Source-Hash: 9477f663422698ae +# English: "Drag selected movable object" +# Source-Hash: 7db0364deb12f06e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Leave" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.leave.1xf0sbk" -msgstr "离开" +msgid "editor.lib.contextualHelp.dragSelectedMovableObject" +msgstr "拖动所选的可移动对象" -# Source-Hash: f365c6b6dd9ebab9 +# English: "Group selection (session only)" +# Source-Hash: 0f258dd389afdc73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.left.14n06mo" -msgstr "左" +msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" +msgstr "组合所选(仅本次会话)" -# Source-Hash: 0608743fc1736d86 +# English: "Hold to rotate freely" +# Source-Hash: 85330d62893a45c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.level.i4lhtp" -msgstr "楼层" +msgid "editor.lib.contextualHelp.holdToRotateFreely" +msgstr "按住以自由旋转" -# Source-Hash: a281a83a9fef6b88 +# English: "Rotate ±45°" +# Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low profile" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.lowProfile.1h9lgzg" -msgstr "低矮型" +msgid "editor.lib.contextualHelp.rotate45" +msgstr "旋转 ±45°" -# Source-Hash: 7f7bb0e875af3260 +# English: "Rotate the selection ±45°" +# Source-Hash: 6099ee99f8c38700 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.mansard.dwzck9" -msgstr "孟莎式" +msgid "editor.lib.contextualHelp.rotateTheSelection45" +msgstr "将所选旋转 ±45°" -# Source-Hash: 387082cd5f118bed +# English: "Rotating freely (no angle step)" +# Source-Hash: 0f6b418b18ff96f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.manual.1wu7r43" -msgstr "手动" +msgid "editor.lib.contextualHelp.rotatingFreely" +msgstr "自由旋转(无角度步进)" -# Source-Hash: 321ca5101ab61db7 +# English: "Switch the rotation axis (Y → X → Z)" +# Source-Hash: 7ed174d6b03563b1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.maximumSpacing.n1fzl4" -msgstr "最大间距" +msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" +msgstr "切换旋转轴(Y → X → Z)" -# Source-Hash: 8e464a8f52254f27 +# English: "Ungroup session selection" +# Source-Hash: d6f2c5cea683de62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/contextual-help.ts #, fuzzy -msgid "editor.inspector.option.metal.1az9fgc" -msgstr "金属" +msgid "editor.lib.contextualHelp.ungroupSessionSelection" +msgstr "取消会话中的组合" -# Source-Hash: c6185ecb219a9435 +# English: "Continuous" +# Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.metalPanel.1xdz476" -msgstr "金属板" +msgid "editor.lib.continuation.continuous" +msgstr "连续" -# Source-Hash: 2fd92342f3986843 +# English: "Continuous canopy" +# Source-Hash: ebf0eb7af4636a16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Minimal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.minimal.fwmiwg" -msgstr "极简" +msgid "editor.lib.continuation.continuousCanopy" +msgstr "连续雨棚" -# Source-Hash: 5d8a5912056f83ce +# English: "Continuous run" +# Source-Hash: 1d8e87cd807e8d40 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mono" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.mono.oszvd6" -msgstr "单坡" +msgid "editor.lib.continuation.continuousRun" +msgstr "连续排布" -# Source-Hash: f2953d2931e36d0c +# English: "Curved fence" +# Source-Hash: 8990683667567c6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.none.deku7v" -msgstr "无" +msgid "editor.lib.continuation.curvedFence" +msgstr "曲线围栏" -# Source-Hash: 7b6e4a6b86ba5112 +# English: "Freehand fence" +# Source-Hash: edf1b9a2ad3ab910 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Omit" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.omit.okcs7w" -msgstr "省略" +msgid "editor.lib.continuation.freehandFence" +msgstr "手绘围栏" -# Source-Hash: 3e52f04f92e08070 +# English: "Place multiple" +# Source-Hash: 2cdb7c1c7cd7c4fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.open.n6hn1l" -msgstr "打开" +msgid "editor.lib.continuation.placeMultiple" +msgstr "连续放置多个" -# Source-Hash: e04298381837252f +# English: "Place once" +# Source-Hash: 90bf945df6f9c979 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.opening.dfet3" -msgstr "开口" +msgid "editor.lib.continuation.placeOnce" +msgstr "放置一次" -# Source-Hash: cc72b9bda6b30b5c +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open rack" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.openRack.mvt1x0" -msgstr "开放式架" +msgid "editor.lib.continuation.polygonRoom" +msgstr "多边形房间" -# Source-Hash: ed6d7b3dd9d5fc69 +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Oval" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.oval.1jiqzxx" -msgstr "椭圆" +msgid "editor.lib.continuation.rectangleRoom" +msgstr "矩形房间" -# Source-Hash: 9295ddc2805b21e2 +# English: "Single cabinet" +# Source-Hash: 7fd85d0408141157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picket" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.picket.zyeoy1" -msgstr "尖桩" +msgid "editor.lib.continuation.singleCabinet" +msgstr "单个橱柜" -# Source-Hash: 180ffb8b75c977aa +# English: "Single canopy" +# Source-Hash: dd1bdbc26165baa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pointed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.pointed.effedk" -msgstr "尖头" +msgid "editor.lib.continuation.singleCanopy" +msgstr "单个雨棚" -# Source-Hash: fca7919fb0c0669c +# English: "Single fence" +# Source-Hash: 5fb1a69316be983e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post and rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.postAndRail.lajk2u" -msgstr "立柱与横杆" +msgid "editor.lib.continuation.singleFence" +msgstr "单段围栏" -# Source-Hash: a78b28a428b27e20 +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve high edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/continuation.ts #, fuzzy -msgid "editor.inspector.option.preserveHighEdge.y6ywf0" -msgstr "保持高边" +msgid "editor.lib.continuation.walls" +msgstr "墙体" -# Source-Hash: 325c6f53f6dc7634 +# English: "Cut opening" +# Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve low edge" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preserveLowEdge.196m5pk" -msgstr "保持低边" +msgid "editor.lib.hudTitle.cutOpening" +msgstr "开洞" -# Source-Hash: 95c0b860d22a62cb +# English: "Divide room" +# Source-Hash: 60e8d235f3084a9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preserve pitch" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.preservePitch.1uq973r" -msgstr "保持坡度" +msgid "editor.lib.hudTitle.divideRoom" +msgstr "分割房间" -# Source-Hash: d4e11841abf22593 +# English: "Edit {kind}" +# Source-Hash: aa496ec33d21ab03 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Privacy" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.privacy.1th4hrb" -msgstr "私密" +msgid "editor.lib.hudTitle.editKind" +msgstr "编辑{kind}" -# Source-Hash: 1a8c46c7712a83b6 +# English: "Erase" +# Source-Hash: f3ea3efa7102844f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Purlins" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.purlins.od124c" -msgstr "檩条" +msgid "editor.lib.hudTitle.erase" +msgstr "擦除" -# Source-Hash: a9650f6f94de9247 +# English: "Level terrain" +# Source-Hash: 3f907e76526472bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pvc" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pvc.1oc4u2w" -msgstr "PVC" +msgid "editor.lib.hudTitle.levelTerrain" +msgstr "整平地形" -# Source-Hash: 47587b825e9a696b +# English: "Lower terrain" +# Source-Hash: 4e911c300d74eb47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pyramid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.pyramid.17ucsqv" -msgstr "金字塔形" +msgid "editor.lib.hudTitle.lowerTerrain" +msgstr "降低地形" -# Source-Hash: a9cb232546779779 +# English: "Mezzanine" +# Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rafters" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rafters.16tzbby" -msgstr "椽子" +msgid "editor.lib.hudTitle.mezzanine" +msgstr "夹层" -# Source-Hash: 947eacd2689daced +# English: "Move {kind}" +# Source-Hash: c48c4129c9ed249a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rail.39zqc9" -msgstr "横杆" +msgid "editor.lib.hudTitle.moveKind" +msgstr "移动{kind}" -# Source-Hash: bd50bd18e682db41 +# English: "Move room" +# Source-Hash: 89f6b7ca8904c656 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.raised.4kyx9h" -msgstr "抬高" +msgid "editor.lib.hudTitle.moveRoom" +msgstr "移动房间" -# Source-Hash: 3b6f225112035fde +# English: "Move selection" +# Source-Hash: 9c707069572fb3db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rect.tpfvaf" -msgstr "矩形" +msgid "editor.lib.hudTitle.moveSelection" +msgstr "移动所选" -# Source-Hash: 68206f70689b2340 +# English: "Paint" +# Source-Hash: cf2e870fce0f2f51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reducer" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.reducer.18byt67" -msgstr "异径管" +msgid "editor.lib.hudTitle.paint" +msgstr "涂装" -# Source-Hash: 2e0aa7113b021c67 +# English: "Paint a rectangle" +# Source-Hash: ca197314cb70aa56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.return.1klrjrz" -msgstr "回风" +msgid "editor.lib.hudTitle.paintARectangle" +msgstr "涂装矩形" -# Source-Hash: 63b39b1681f74fe4 +# English: "Paint a shape" +# Source-Hash: ea4ce729e17254f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return grille" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.returnGrille.r28ncm" -msgstr "回风格栅" +msgid "editor.lib.hudTitle.paintAShape" +msgstr "涂装形状" -# Source-Hash: b34d5ee0f218221d +# English: "Pick material" +# Source-Hash: 01359502b07284d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.right.8humpx" -msgstr "右" +msgid "editor.lib.hudTitle.pickMaterial" +msgstr "拾取材质" -# Source-Hash: ad0457800a1f0eb4 +# English: "Raise terrain" +# Source-Hash: 4066e8a3f60b1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.round.1vx0wyj" -msgstr "圆形" +msgid "editor.lib.hudTitle.raiseTerrain" +msgstr "抬升地形" -# Source-Hash: fc826da6c99303e6 +# English: "Resize" +# Source-Hash: f85bdac0a1d16603 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.rounded.199nwpm" -msgstr "圆角" +msgid "editor.lib.hudTitle.resize" +msgstr "调整大小" -# Source-Hash: dc67a1cdb2482f23 +# English: "Rotate" +# Source-Hash: 306d409f8a1cf480 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sanitary tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.sanitaryTee.oro3d4" -msgstr "卫生三通" +msgid "editor.lib.hudTitle.rotate" +msgstr "旋转" -# Source-Hash: beb3fec22c287185 +# English: "Select" +# Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scalloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scalloped.1eltba0" -msgstr "扇贝形" +msgid "editor.lib.hudTitle.select" +msgstr "选择" -# Source-Hash: e16f85e4bebfefb1 +# English: "Smart measure" +# Source-Hash: ec1f1d68af469867 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scoop" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.scoop.qy5zdv" -msgstr "勺形" +msgid "editor.lib.hudTitle.smartMeasure" +msgstr "智能测量" -# Source-Hash: f9a95d8407fafda4 +# English: "Smooth terrain" +# Source-Hash: 5ba5f22bd1a69e09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segmented panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.segmentedPanel.5r3hwl" -msgstr "分段面板" +msgid "editor.lib.hudTitle.smoothTerrain" +msgstr "平滑地形" -# Source-Hash: c8d0df27faa3feaa +# English: "Split {kind}" +# Source-Hash: cbb4687261739397 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.shed.6t994v" -msgstr "单坡" +msgid "editor.lib.hudTitle.splitKind" +msgstr "分割{kind}" -# Source-Hash: 7fbc26cd497cfd12 +# English: "Terrace" +# Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheet metal" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/hud-title.ts #, fuzzy -msgid "editor.inspector.option.sheetMetal.l164lv" -msgstr "金属板" +msgid "editor.lib.hudTitle.terrace" +msgstr "露台" -# Source-Hash: 26e2c261223cbb46 +# English: "{title} ({exterior} exterior, {interior} interior)" +# Source-Hash: 322452fdf0850c8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/tool-shortcuts.ts #, fuzzy -msgid "editor.inspector.option.shingle.pfmynh" -msgstr "瓦片" +msgid "editor.lib.toolShortcuts.wallsTooltip" +msgstr "{title}(外墙 {exterior},内墙 {interior})" -# Source-Hash: e9046e9823b400a0 +# English: "Polygon room" +# Source-Hash: 8ec6152e139b0e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingled" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.shingled.1nm9inf" -msgstr "瓦片铺设" +msgid "editor.lib.wallDrawVariant.polygonRoom" +msgstr "多边形房间" -# Source-Hash: a05a390ae205fdd8 +# English: "Rectangle room" +# Source-Hash: 83a45603ac7f41e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Simple" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.simple.12mfu73" -msgstr "简单" +msgid "editor.lib.wallDrawVariant.rectangleRoom" +msgstr "矩形房间" -# Source-Hash: 62ab2eb3c775da95 +# English: "Walls" +# Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/lib/wall-draw-variant.ts #, fuzzy -msgid "editor.inspector.option.single.1uzxurt" -msgstr "单扇" +msgid "editor.lib.wallDrawVariant.walls" +msgstr "墙体" -# Source-Hash: aee3b2bd78c87d0b +# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" +# Source-Hash: c2626e1fbf89b3c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single left" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/registry-action-contributions.tsx #, fuzzy -msgid "editor.inspector.option.singleLeft.1atdh02" -msgstr "单扇左开" +msgid "editor.mechanismAction" +msgstr "{action, select, close {关闭} open {打开} stop {停止} other {播放}}" -# Source-Hash: cc43ffbef4ac7ae0 +# English: "Curve" +# Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single right" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.singleRight.1axbu67" -msgstr "单扇右开" +msgid "editor.nodeActionMenu.curve" +msgstr "曲线" -# Source-Hash: 7672d2764445c110 +# English: "Cut out" +# Source-Hash: 5af05fb6661e4d6a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slant box" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slantBox.1ta99vy" -msgstr "斜面盒" +msgid "editor.nodeActionMenu.cutOut" +msgstr "挖空" -# Source-Hash: 405e07a8cc2c88e1 +# English: "Delete" +# Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slat" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slat.1p9njbz" -msgstr "叶片" +msgid "editor.nodeActionMenu.delete" +msgstr "删除" -# Source-Hash: 18cbc73c5e1590aa +# English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sliding.cd0jox" -msgstr "推拉门" +msgid "editor.nodeActionMenu.duplicate" +msgstr "复制" -# Source-Hash: e6d82e606b8c9301 +# English: "Edit mesh" +# Source-Hash: 94b212bd79c05141 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.slope.1bn2qw" -msgstr "坡度" +msgid "editor.nodeActionMenu.editMesh" +msgstr "编辑网格" -# Source-Hash: f6998707123fe72d +# English: "Find in catalog" +# Source-Hash: ce90cf37224f73cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sloped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.sloped.lo4sh0" -msgstr "倾斜" +msgid "editor.nodeActionMenu.findInCatalog" +msgstr "在目录中查找" -# Source-Hash: ef3e765e692faf4c +# English: "Group selection" +# Source-Hash: d5e550c345e7161c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solid.o4uy6s" -msgstr "实体" +msgid "editor.nodeActionMenu.groupSelection" +msgstr "将所选编组" -# Source-Hash: 8008b6f17089beaf +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid panel" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.solidPanel.2yaw62" -msgstr "实板" +msgid "editor.nodeActionMenu.move" +msgstr "移动" -# Source-Hash: f9a4a870cb3d3c41 +# English: "Rotate left" +# Source-Hash: 473a9d4463761107 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.spiral.91hcl4" -msgstr "螺旋" +msgid "editor.nodeActionMenu.rotateLeft" +msgstr "向左旋转" -# Source-Hash: beef1cf79388b77b +# English: "Rotate right" +# Source-Hash: b1bf4e65c8d8c8ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Splash" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.splash.1uke4cm" -msgstr "溅水板" +msgid "editor.nodeActionMenu.rotateRight" +msgstr "向右旋转" -# Source-Hash: 324677a79ab15e22 +# English: "Ungroup selection" +# Source-Hash: 6f4dbbc4a3f58fca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spread" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/node-action-menu.tsx #, fuzzy -msgid "editor.inspector.option.spread.1g1zysu" -msgstr "展开" +msgid "editor.nodeActionMenu.ungroupSelection" +msgstr "取消所选编组" -# Source-Hash: 39287346366fc206 +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Square" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.square.1xl1lk6" -msgstr "方形" +msgid "editor.nodeDisplay.ceiling" +msgstr "天花板" -# Source-Hash: 53d7084bc41c1ad6 +# English: "Column" +# Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stair.1xf86e" -msgstr "楼梯" +msgid "editor.nodeDisplay.column" +msgstr "柱" -# Source-Hash: d998c212030bc7c2 +# English: "Door" +# Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.standard.vuco" -msgstr "标准" +msgid "editor.nodeDisplay.door" +msgstr "门" -# Source-Hash: 0a26a1e61c825660 +# English: "Elevator" +# Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.start.30xvgf" -msgstr "起点" +msgid "editor.nodeDisplay.elevator" +msgstr "电梯" -# Source-Hash: ed3989f23a2d185e +# English: "Fence" +# Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.step.jtn9kf" -msgstr "台阶" +msgid "editor.nodeDisplay.fence" +msgstr "围栏" -# Source-Hash: 2c6b1cf17a973d67 +# English: "Guide image" +# Source-Hash: 4c3ff8cdc6feda97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stepped" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.stepped.naxzh0" -msgstr "阶梯式" +msgid "editor.nodeDisplay.guide" +msgstr "参考图像" -# Source-Hash: 4ee35f740730ae87 +# English: "Item" +# Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.straight.lxqbfr" -msgstr "直跑" +msgid "editor.nodeDisplay.item" +msgstr "物品" -# Source-Hash: b9b45ac5ba1daeee +# English: "Roof" +# Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Strap" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.strap.14q0ps9" -msgstr "吊带" +msgid "editor.nodeDisplay.roof" +msgstr "屋顶" -# Source-Hash: b45f382a67d6c0eb +# English: "Roof segment" +# Source-Hash: a130b4eec084c79a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supply.1oy2zwu" -msgstr "送风" +msgid "editor.nodeDisplay.roofSegment" +msgstr "屋顶分段" -# Source-Hash: 00e7a2b3b2aa36ac +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Supply register" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.supplyRegister.1lzdbbn" -msgstr "送风口" +msgid "editor.nodeDisplay.room" +msgstr "房间" -# Source-Hash: f86f1f22230596b7 +# English: "3D Scan" +# Source-Hash: b22b2f20cc7cd416 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tapered" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tapered.3y5blo" -msgstr "锥形" +msgid "editor.nodeDisplay.scan" +msgstr "扫描模型" -# Source-Hash: 5fed23c2217b83fb +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target spacing" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.targetSpacing.14uibc9" -msgstr "目标间距" +msgid "editor.nodeDisplay.selection" +msgstr "当前选择" -# Source-Hash: f3ceed7f867f090f +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tee" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tee.7d6tjp" -msgstr "三通" +msgid "editor.nodeDisplay.slab" +msgstr "楼板" -# Source-Hash: 787005b26aec58a3 +# English: "Stair" +# Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilted" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.tilted.6n0u5j" -msgstr "倾斜" +msgid "editor.nodeDisplay.stair" +msgstr "楼梯" -# Source-Hash: 27644f0497fd9849 +# English: "Stair segment" +# Source-Hash: 52de428c16bb81e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.top.2nypcs" -msgstr "顶部" +msgid "editor.nodeDisplay.stairSegment" +msgstr "楼梯分段" -# Source-Hash: e6657000554dfe53 +# English: "Wall" +# Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transition" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.transition.1rziz9e" -msgstr "过渡" +msgid "editor.nodeDisplay.wall" +msgstr "墙体" -# Source-Hash: 04bc0909a133a7e9 +# English: "Window" +# Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vent" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.vent.xda5j2" -msgstr "通风口" +msgid "editor.nodeDisplay.window" +msgstr "窗" -# Source-Hash: 7eee9aff0dd520b6 +# English: "Zone" +# Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk on" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/node-display.ts #, fuzzy -msgid "editor.inspector.option.walkOn.1n5sg9l" -msgstr "行走面" +msgid "editor.nodeDisplay.zone" +msgstr "区域" -# Source-Hash: 2210e5ee50b74dc9 +# English: "At the high side of the ground, to scale" +# Source-Hash: 59310bd629d358d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wall.blj585" -msgstr "墙体" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe" +msgstr "取地面高侧,按比例绘制" -# Source-Hash: f3578f7177ad0054 +# English: "At the high side of the ground, to scale; the dashed line is the grade" +# Source-Hash: 1d006e84523a03c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall abutment" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallAbutment.lza941" -msgstr "靠墙" +msgid "editor.panels.buildingFoundationSection.atTheHighSideOfThe2" +msgstr "取地面高侧,按比例绘制;虚线为地坪" -# Source-Hash: 8dcf182f91ba3245 +# English: "Auto" +# Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall ledger" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallLedger.5m8ana" -msgstr "墙上托梁" +msgid "editor.panels.buildingFoundationSection.auto" +msgstr "自动" -# Source-Hash: 0adda30be5f17975 +# English: "Basement slab" +# Source-Hash: ca97295ab420e54a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall shelf" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wallShelf.yiozfl" -msgstr "墙面搁板" +msgid "editor.panels.buildingFoundationSection.basementSlab" +msgstr "地下室底板" -# Source-Hash: 29d8590334492601 +# English: "Basement wall" +# Source-Hash: 8691c5ac35304434 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.waste.3xrp79" -msgstr "排污" +msgid "editor.panels.buildingFoundationSection.basementWall" +msgstr "地下室墙" -# Source-Hash: 00249876bb37079f +# English: "Poured concrete: 8 in for most basements (IRC Table R404.1.2(1))." +# Source-Hash: fecad6f58f5f6e3f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wye" -#: packages/nodes/src/i18n/definition-messages.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.inspector.option.wye.14vupc8" -msgstr "Y 形三通" +msgid "editor.panels.buildingFoundationSection.basementWallHint" +msgstr "现浇混凝土:大多数地下室为 8 in(IRC Table R404.1.2(1))。" -# Source-Hash: 179b025e25f9a091 +# English: "Basement wall (its own)" +# Source-Hash: 1f0c089c4a7cc754 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.collapse" -msgstr "折叠" +msgid "editor.panels.buildingFoundationSection.basementWallItsOwn" +msgstr "地下室墙(自有)" -# Source-Hash: dbeae4c43f49327f +# English: "Basement wall {thickness}" +# Source-Hash: c672e131662d2766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.ceiling" -msgstr "天花板({area})" +msgid "editor.panels.buildingFoundationSection.basementWallThickness" +msgstr "地下室墙 {thickness}" -# Source-Hash: d29165c9d4bfc3cb +# English: "Basement wall top over grade" +# Source-Hash: 29a11cb98f792822 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.collection" -msgstr "集合" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGrade" +msgstr "地下室墙顶高出地坪" -# Source-Hash: 8f536b859be662a0 +# English: "Measured over the highest grade along the house: the sill and the floor framing sit on it, 8 in off the ground at least (IRC R317.1). Set it higher for a daylight basement." +# Source-Hash: 4f96c4f9effefccf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.flight" -msgstr "梯段({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.basementWallTopOverGradeHint" +msgstr "从房屋沿线的最高地坪起算:地梁板和楼面框架位于其上,距地面至少 8 in(IRC R317.1)。半地下室可设得更高。" -# Source-Hash: 46dcbf4548374073 +# English: "Carpet" +# Source-Hash: e653669adc447902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing ({width}×{length}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.landing" -msgstr "平台({width}×{length}m)" +msgid "editor.panels.buildingFoundationSection.carpet" +msgstr "地毯" -# Source-Hash: 3ea44453fcd08a6e +# English: "Ceiling" +# Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Node" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.node" -msgstr "节点" +msgid "editor.panels.buildingFoundationSection.ceiling" +msgstr "天花板" -# Source-Hash: 93f3a04e15b6fb49 +# English: "Ceramic tile" +# Source-Hash: 299c16d7ad11f5e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roof" -msgstr "屋顶({count, plural, other {# 个分段}})" +msgid "editor.panels.buildingFoundationSection.ceramicTile" +msgstr "瓷砖" -# Source-Hash: a730fab01c172704 +# English: "Close the outside walls into a loop to draw the foundation under them." +# Source-Hash: 317b5b94671d871a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.roofSegment" -msgstr "{roofType, select, hip {四坡} gable {双坡} shed {单坡} gambrel {复折} dutch {歇山} mansard {孟莎} flat {平屋顶} conical {锥形} barrel {筒形} other {屋顶}}({width}×{depth}m)" +msgid "editor.panels.buildingFoundationSection.closeTheOutsideWallsIntoA" +msgstr "将外墙闭合成环,即可在其下方绘制基础。" -# Source-Hash: 54efcb0756c2f2c1 +# English: "Crawl space under joists" +# Source-Hash: 3e736505db2a19c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.room" -msgstr "房间({area})" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoists" +msgstr "搁栅下架空层" -# Source-Hash: e38a4b9986a9d21a +# English: "Measured from the underside of the joists, which bear on the sill (IRC R317.1: 18 in to the dirt). The crawl space is dug out to it." +# Source-Hash: 61f898724893d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.slab" -msgstr "楼板({area})" +msgid "editor.panels.buildingFoundationSection.crawlSpaceUnderJoistsHint" +msgstr "从搁置在地梁板上的搁栅底面起算(IRC R317.1:距土面 18 in)。架空层按此开挖。" -# Source-Hash: 57398365d4b9066f +# English: "Custom" +# Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase ({count, plural, one {# segment} other {# segments}})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.staircase" -msgstr "楼梯({count, plural, other {# 个分段}})" +msgid "editor.panels.buildingFoundationSection.custom" +msgstr "自定义" -# Source-Hash: a17f8fc29aae896e -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone ({area})" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/default-names.ts +# English: "Delete the basement and it stands on its raised floor again." +# Source-Hash: 8629f9746eac5bfa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.defaultName.zone" -msgstr "区域({area})" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementRaisedFloor" +msgstr "删除地下室后,房屋将重新立于其架空楼板上。" -# Source-Hash: 5cabcabb8bc14809 +# English: "Delete the basement and it stands on its slab again." +# Source-Hash: f99636a38ec14785 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.expand" -msgstr "展开" +msgid "editor.panels.buildingFoundationSection.deleteTheBasementSlab" +msgstr "删除地下室后,房屋将重新立于其楼板上。" -# Source-Hash: 6731e9dd94e8b268 +# English: "{depth} deep" +# Source-Hash: 09f12189bbbf8cbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{levelName} options" -#: packages/editor/src/components/ui/floating-level-selector.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.levelOptions" -msgstr "{levelName}选项" +msgid "editor.panels.buildingFoundationSection.depthDeep" +msgstr "深 {depth}" -# Source-Hash: d6952b4e54d246dd +# English: "{depth} down" +# Source-Hash: 164f8aea5d46f232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.rename" -msgstr "重命名" +msgid "editor.panels.buildingFoundationSection.depthDown" +msgstr "向下 {depth}" -# Source-Hash: 21eafa019ad62fea +# English: "{depth} I-joist" +# Source-Hash: a6010b7488316ac1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{role, select, room {Room colour} other {Zone colour}}" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx -#: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx -#: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.spaceColour" -msgstr "{role, select, room {房间颜色} other {区域颜色}}" +msgid "editor.panels.buildingFoundationSection.depthIJoist" +msgstr "{depth} 工字搁栅" -# Source-Hash: f5de042416a2c04f +# English: "Draw the foundation" +# Source-Hash: 4db57e6b9b2a22f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit colour" -#: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.layers.unitColour" -msgstr "单元颜色" +msgid "editor.panels.buildingFoundationSection.drawTheFoundation" +msgstr "绘制基础" -# Source-Hash: c0774c185060fc13 +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.cancel" -msgstr "取消" +msgid "editor.panels.buildingFoundationSection.drywall" +msgstr "石膏板" -# Source-Hash: 07c11e7e4d4681d6 +# English: "Drywall ceiling" +# Source-Hash: cea6df381e71f25f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from {levelName}." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromLevel" -msgstr "选择要从{levelName}复制的内容。" +msgid "editor.panels.buildingFoundationSection.drywallCeiling" +msgstr "石膏板天花板" -# Source-Hash: 9500d4378a319532 +# English: "Edge under grade" +# Source-Hash: 027b063ba3959c44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose what to copy from this level." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.chooseWhatToCopyFromThisLevel" -msgstr "选择要从此楼层复制的内容。" +msgid "editor.panels.buildingFoundationSection.edgeUnderGrade" +msgstr "板边埋深" -# Source-Hash: 34dc96fe77a31e51 +# English: "IRC R403.1.4: at least 12 in, or the frost line." +# Source-Hash: a114cbbf9fff17fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicate" -msgstr "复制" +msgid "editor.panels.buildingFoundationSection.edgeUnderGradeHint" +msgstr "IRC R403.1.4:至少 12 in,或深至冻土线。" -# Source-Hash: a56e3eb43e60b45c +# English: "Edge width" +# Source-Hash: ea863dda5621adfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate Level" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.duplicateLevel" -msgstr "复制楼层" +msgid "editor.panels.buildingFoundationSection.edgeWidth" +msgstr "板边宽度" -# Source-Hash: e4c6ab1ea473a082 +# English: "Edge {width} wide" +# Source-Hash: 542f041267191a8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Everything" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.everything" -msgstr "全部" +msgid "editor.panels.buildingFoundationSection.edgeWidthWide" +msgstr "板边宽 {width}" -# Source-Hash: ac3f119e560388ab +# English: "Engineered wood" +# Source-Hash: 5c0c1a6c73eb46e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, finishes, and placed items, without guide references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFinishesAndPlacedItemsWithout" -msgstr "结构、饰面和已放置的物品,不含参考底图。" +msgid "editor.panels.buildingFoundationSection.engineeredWood" +msgstr "实木复合地板" -# Source-Hash: acbdf968391d5273 +# English: "Finish" +# Source-Hash: b3907da0ad234f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + furniture" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureFurniture" -msgstr "结构 + 家具" +msgid "editor.panels.buildingFoundationSection.finish" +msgstr "饰面" -# Source-Hash: ce682864a140fe75 +# English: "Finish floor over grade" +# Source-Hash: b97e6e3652b2ea63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure + materials" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterials" -msgstr "结构 + 材质" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGrade" +msgstr "完成面高出地坪" -# Source-Hash: 5ee44d53847c5ea2 +# English: "Measured over the highest grade under the outside walls. The foundation grows down the slope from there." +# Source-Hash: 326641cd3bd5968a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure, materials, furniture, and references." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureMaterialsFurnitureAndReferences" -msgstr "结构、材质、家具和参考。" +msgid "editor.panels.buildingFoundationSection.finishFloorOverGradeHint" +msgstr "从外墙下方的最高地坪起算。基础由此沿坡向下延伸。" -# Source-Hash: 95aa1b43dbdb3f02 +# English: "Floor framing" +# Source-Hash: bcc3c6e3240bd6da # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure only" -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureOnly" -msgstr "仅结构" +msgid "editor.panels.buildingFoundationSection.floorFraming" +msgstr "楼面框架" -# Source-Hash: 2c53b46c9ca9fe2f +# English: "Floor framing and sill {depth}" +# Source-Hash: 2d7eaba6b2a7787b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure with the current material and finish assignments." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.structureWithTheCurrentMaterialAnd" -msgstr "结构及当前的材质和饰面分配。" +msgid "editor.panels.buildingFoundationSection.floorFramingAndSill" +msgstr "楼面框架和地梁板 {depth}" -# Source-Hash: 3350e9008eaba15d +# English: "Floor top down to the stem: flooring, subfloor, joists, sill." +# Source-Hash: 60bbbc62b12ee1b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls, slabs, roofs, stairs, windows, and doors without finishes." -#: packages/editor/src/components/ui/level-duplicate-dialog.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.levelDuplicateDialog.wallsSlabsRoofsStairsWindowsAnd" -msgstr "墙体、楼板、屋顶、楼梯、窗和门,不含饰面。" +msgid "editor.panels.buildingFoundationSection.floorFramingHint" +msgstr "从楼面顶向下至短墙:面层、毛地板、搁栅、地梁板。" -# Source-Hash: 395fd0821b203517 +# English: "Floor framing {depth}, sill {sill} included" +# Source-Hash: b3f975c3aa4056d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add or remove objects from the selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.addOrRemoveObjectsFromThe" -msgstr "在选择中添加或移除对象" +msgid "editor.panels.buildingFoundationSection.floorFramingSillIncluded" +msgstr "楼面框架 {depth},含地梁板 {sill}" -# Source-Hash: 8428cb0af9c6d6a0 +# English: "Flooring" +# Source-Hash: 3e6a58401ab2fc64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Adjust one side’s corner radius" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.adjustOneSideSCornerRadius" -msgstr "调整一侧的圆角半径" +msgid "editor.panels.buildingFoundationSection.flooring" +msgstr "地面面层" -# Source-Hash: d63b45c195713993 +# English: "Footing thickness" +# Source-Hash: e7ca47bb438592ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear the selection (or click outside)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clearTheSelectionOrClickOutside" -msgstr "清除选择(或点击外部)" +msgid "editor.panels.buildingFoundationSection.footingThickness" +msgstr "基脚厚度" -# Source-Hash: 40b1afbeadbd6213 +# English: "Footing under grade" +# Source-Hash: 3b481ad3c192b6a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click a handle dot to show move arrows" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickAHandleDotToShow" -msgstr "点击控制点以显示移动箭头" +msgid "editor.panels.buildingFoundationSection.footingUnderGrade" +msgstr "基脚埋深" -# Source-Hash: 0b82a35c71549580 +# English: "IRC R403.1.4: at least 12 in under the ground beside it, outside and in the dug-out crawl space; deeper to the frost line." +# Source-Hash: d1ed081aa7773257 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click or drag the selection to move it as one" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickOrDragTheSelectionTo" -msgstr "点击或拖动选择以整体移动" +msgid "editor.panels.buildingFoundationSection.footingUnderGradeHint" +msgstr "IRC R403.1.4:至少低于旁侧地面 12 in,室外及开挖后的架空层内均如此;冻土线更深时须深至冻土线。" -# Source-Hash: 73a761f393f69cb9 +# English: "Footing width" +# Source-Hash: a4bdf4be3447baec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click the handle dot to show move + rotate handles" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.clickTheHandleDotToShow" -msgstr "点击控制点以显示移动和旋转控件" +msgid "editor.panels.buildingFoundationSection.footingWidth" +msgstr "基脚宽度" -# Source-Hash: c0045cef191fee69 +# English: "Table R403.1(1), 1500 psf soil: 12, 15, 23 in for 1, 2, 3 storeys." +# Source-Hash: bfb55823e6b7d61d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detach the joint while dragging an arrow" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.detachTheJointWhileDraggingAn" -msgstr "拖动箭头时断开连接" +msgid "editor.panels.buildingFoundationSection.footingWidthHint" +msgstr "Table R403.1(1),1500 psf 地基土:1、2、3 层分别为 12、15、23 in。" -# Source-Hash: a019c8948c104a9e +# English: "Footing {width} × {thickness}" +# Source-Hash: dd973bd2c5207dd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag left or right to rotate selected object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragLeftOrRightToRotate" -msgstr "左右拖动以旋转所选对象" +msgid "editor.panels.buildingFoundationSection.footingWidthThickness" +msgstr "基脚 {width} × {thickness}" -# Source-Hash: 7db0364deb12f06e +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag selected movable object" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.dragSelectedMovableObject" -msgstr "拖动所选的可移动对象" +msgid "editor.panels.buildingFoundationSection.foundation" +msgstr "基础" -# Source-Hash: 0f258dd389afdc73 +# English: "Grade beam depth" +# Source-Hash: c4460990c10fbb11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection (session only)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.groupSelectionSessionOnly" -msgstr "组合所选(仅本次会话)" +msgid "editor.panels.buildingFoundationSection.gradeBeamDepth" +msgstr "基础梁高度" -# Source-Hash: 85330d62893a45c3 +# English: "Grade beam on piles" +# Source-Hash: 0643daff073d843d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hold to rotate freely" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.holdToRotateFreely" -msgstr "按住以自由旋转" +msgid "editor.panels.buildingFoundationSection.gradeBeamOnPiles" +msgstr "桩上的基础梁" -# Source-Hash: 8a665ecaeffa9308 +# English: "Grade beam width" +# Source-Hash: 2208ea6b7967afc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotate45" -msgstr "旋转 ±45°" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidth" +msgstr "基础梁宽度" -# Source-Hash: 6099ee99f8c38700 +# English: "Grade beam {width} × {depth}" +# Source-Hash: e70be1cfffd2567a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate the selection ±45°" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotateTheSelection45" -msgstr "将所选旋转 ±45°" +msgid "editor.panels.buildingFoundationSection.gradeBeamWidthDepth" +msgstr "基础梁 {width} × {depth}" -# Source-Hash: 0f6b418b18ff96f3 +# English: "Half the edge’s depth under the slab, 6 to 12 in" +# Source-Hash: 08e07ba612a2515a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotating freely (no angle step)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.rotatingFreely" -msgstr "自由旋转(无角度步进)" +msgid "editor.panels.buildingFoundationSection.halfTheEdgeSDepthUnder" +msgstr "取楼板下板边深度的一半,6 至 12 in" -# Source-Hash: 7ed174d6b03563b1 +# English: "Hardwood" +# Source-Hash: af3644ad05550f7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch the rotation axis (Y → X → Z)" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.switchTheRotationAxisYX" -msgstr "切换旋转轴(Y → X → Z)" +msgid "editor.panels.buildingFoundationSection.hardwood" +msgstr "实木地板" -# Source-Hash: d6f2c5cea683de62 +# English: "{height} crawl" +# Source-Hash: d0ab8d2735f2e1a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup session selection" -#: packages/editor/src/lib/contextual-help.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.contextualHelp.ungroupSessionSelection" -msgstr "取消会话中的组合" +msgid "editor.panels.buildingFoundationSection.heightCrawl" +msgstr "架空层 {height}" -# Source-Hash: 384842ae1e1c6825 +# English: "{height} FF" +# Source-Hash: 70a0363496dbb627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuous" -msgstr "连续" +msgid "editor.panels.buildingFoundationSection.heightFinishFloor" +msgstr "完成面 {height}" -# Source-Hash: ebf0eb7af4636a16 +# English: "{height} top" +# Source-Hash: 28fbe28f080c2e49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousCanopy" -msgstr "连续雨棚" +msgid "editor.panels.buildingFoundationSection.heightTop" +msgstr "顶 {height}" -# Source-Hash: 1d8e87cd807e8d40 +# English: "I-joists" +# Source-Hash: 61dc716260daf09b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous run" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.continuousRun" -msgstr "连续排布" +msgid "editor.panels.buildingFoundationSection.iJoists" +msgstr "工字搁栅" -# Source-Hash: 8990683667567c6b +# English: "Its layers make {depth} with the sill; the framing follows them once a layer is set." +# Source-Hash: dcaf89ca7f4ba36a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.curvedFence" -msgstr "曲线围栏" +msgid "editor.panels.buildingFoundationSection.itsLayersMakeWithTheSill" +msgstr "各层连同地梁板共 {depth};设定任一层后,框架将随之调整。" -# Source-Hash: edf1b9a2ad3ab910 +# English: "Its walls are poured under the house’s outside walls." +# Source-Hash: 47f3b8abf6c71c0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Freehand fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.freehandFence" -msgstr "手绘围栏" +msgid "editor.panels.buildingFoundationSection.itsWallsArePouredUnderThe" +msgstr "其墙体浇筑于房屋外墙下方。" -# Source-Hash: 2cdb7c1c7cd7c4fd +# English: "Its walls are the basement’s own, on their footing." +# Source-Hash: 2ad8c11c90166f43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place multiple" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeMultiple" -msgstr "连续放置多个" +msgid "editor.panels.buildingFoundationSection.itsWallsAreTheBasementS" +msgstr "其墙体为地下室自有墙体,位于各自的基脚上。" -# Source-Hash: 90bf945df6f9c979 +# English: "Joists" +# Source-Hash: c7aa596536ceeecb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place once" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.placeOnce" -msgstr "放置一次" +msgid "editor.panels.buildingFoundationSection.joists" +msgstr "搁栅" -# Source-Hash: 8ec6152e139b0e3f +# English: "Kind" +# Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.polygonRoom" -msgstr "多边形房间" +msgid "editor.panels.buildingFoundationSection.kind" +msgstr "类型" -# Source-Hash: 83a45603ac7f41e8 +# English: "{label} (auto)" +# Source-Hash: bc4b8b385cd50d18 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.rectangleRoom" -msgstr "矩形房间" +msgid "editor.panels.buildingFoundationSection.labelAuto" +msgstr "{label}(自动)" -# Source-Hash: 7fd85d0408141157 +# English: "Laminate" +# Source-Hash: 826e189e08abac28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single cabinet" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCabinet" -msgstr "单个橱柜" +msgid "editor.panels.buildingFoundationSection.laminate" +msgstr "强化地板" -# Source-Hash: dd1bdbc26165baa5 +# English: "Least depth under grade" +# Source-Hash: 6ace3330ae71b24d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single canopy" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleCanopy" -msgstr "单个雨棚" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGrade" +msgstr "最小埋深" -# Source-Hash: 5fb1a69316be983e +# English: "The beam goes at least this far under grade (IRC R403.1.4: 12 in, or the frost line); a deeper beam goes further." +# Source-Hash: 5e390c119e4d6da5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single fence" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.singleFence" -msgstr "单段围栏" +msgid "editor.panels.buildingFoundationSection.leastDepthUnderGradeHint" +msgstr "基础梁至少埋入地坪以下此深度(IRC R403.1.4:12 in,或冻土线);梁高更大时埋得更深。" -# Source-Hash: aac9a732d2159ccc +# English: "Least step run" +# Source-Hash: 2e918ebc24201841 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/continuation.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.continuation.walls" -msgstr "墙体" +msgid "editor.panels.buildingFoundationSection.leastStepRun" +msgstr "最小台阶长度" -# Source-Hash: 4e4f1a24e4a9d61e +# English: "IRC R403.1.5: the top of a footing is level; it steps where the ground falls." +# Source-Hash: 95c2ee3d275f29cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.cutOpening" -msgstr "开洞" +msgid "editor.panels.buildingFoundationSection.leastStepRunHint" +msgstr "IRC R403.1.5:基脚顶面保持水平;在地面下降处跌级。" -# Source-Hash: 60e8d235f3084a9e +# English: "Lumber" +# Source-Hash: 73dcfae611575500 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divide room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.divideRoom" -msgstr "分割房间" +msgid "editor.panels.buildingFoundationSection.lumber" +msgstr "规格材" -# Source-Hash: aa496ec33d21ab03 +# English: "Name the material" +# Source-Hash: 3053c459862812ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.editKind" -msgstr "编辑{kind}" +msgid "editor.panels.buildingFoundationSection.nameTheMaterial" +msgstr "为材料命名" -# Source-Hash: f3ea3efa7102844f +# English: "Off" +# Source-Hash: 50accd740fa2e736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Erase" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.erase" -msgstr "擦除" +msgid "editor.panels.buildingFoundationSection.off" +msgstr "关闭" -# Source-Hash: 3f907e76526472bf +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.levelTerrain" -msgstr "整平地形" +msgid "editor.panels.buildingFoundationSection.osb" +msgstr "定向刨花板" -# Source-Hash: 4e911c300d74eb47 +# English: "OSB subfloor" +# Source-Hash: 097c64d90d9fb848 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lower terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.lowerTerrain" -msgstr "降低地形" +msgid "editor.panels.buildingFoundationSection.osbSubfloor" +msgstr "定向刨花板毛地板" -# Source-Hash: 194a147d4618a994 +# English: "Panel" +# Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.mezzanine" -msgstr "夹层" +msgid "editor.panels.buildingFoundationSection.panel" +msgstr "板材" -# Source-Hash: c48c4129c9ed249a +# English: "Pile diameter" +# Source-Hash: 1372cffe6283ee90 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveKind" -msgstr "移动{kind}" +msgid "editor.panels.buildingFoundationSection.pileDiameter" +msgstr "桩径" -# Source-Hash: 89f6b7ca8904c656 +# English: "Piles are sized by the engineer." +# Source-Hash: 58bdc4ad2c360017 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move room" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveRoom" -msgstr "移动房间" +msgid "editor.panels.buildingFoundationSection.pileDiameterHint" +msgstr "桩的尺寸由工程师确定。" -# Source-Hash: 9c707069572fb3db +# English: "Pile {diameter} @ {spacing}, {length} deep" +# Source-Hash: 2e583e4622318f58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move selection" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.moveSelection" -msgstr "移动所选" +msgid "editor.panels.buildingFoundationSection.pileDiameterSpacingDeep" +msgstr "桩 {diameter} @ {spacing},深 {length}" -# Source-Hash: cf2e870fce0f2f51 +# English: "Pile length" +# Source-Hash: 5e99bac8b7ba3af9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paint" -msgstr "涂装" +msgid "editor.panels.buildingFoundationSection.pileLength" +msgstr "桩长" -# Source-Hash: ca197314cb70aa56 +# English: "Pile spacing" +# Source-Hash: 7dfd658f6006b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a rectangle" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintARectangle" -msgstr "涂装矩形" +msgid "editor.panels.buildingFoundationSection.pileSpacing" +msgstr "桩距" -# Source-Hash: ea4ce729e17254f8 +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint a shape" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.paintAShape" -msgstr "涂装形状" +msgid "editor.panels.buildingFoundationSection.plaster" +msgstr "灰泥" -# Source-Hash: 01359502b07284d4 +# English: "Plaster ceiling" +# Source-Hash: a1f03abd876ee26f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick material" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.pickMaterial" -msgstr "拾取材质" +msgid "editor.panels.buildingFoundationSection.plasterCeiling" +msgstr "灰泥天花板" -# Source-Hash: 4066e8a3f60b1711 +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.raiseTerrain" -msgstr "抬升地形" +msgid "editor.panels.buildingFoundationSection.plywood" +msgstr "胶合板" -# Source-Hash: f85bdac0a1d16603 +# English: "Plywood subfloor" +# Source-Hash: ded7bab2a8fe5793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Resize" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.resize" -msgstr "调整大小" +msgid "editor.panels.buildingFoundationSection.plywoodSubfloor" +msgstr "胶合板毛地板" -# Source-Hash: 306d409f8a1cf480 +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.rotate" -msgstr "旋转" +msgid "editor.panels.buildingFoundationSection.preset" +msgstr "预设" -# Source-Hash: a4395168488bb476 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.select" -msgstr "选择" +msgid "editor.panels.buildingFoundationSection.raisedFloor" +msgstr "架空楼板" -# Source-Hash: ec1f1d68af469867 +# English: "Foundation section: basement, its wall's top {top} over grade, its floor {floor} under it" +# Source-Hash: f17c359b6460adc2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smart measure" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smartMeasure" -msgstr "智能测量" +msgid "editor.panels.buildingFoundationSection.sectionBasement" +msgstr "基础剖面:地下室,墙顶高出地坪 {top},地面低于地坪 {floor}" -# Source-Hash: 5ba5f22bd1a69e09 +# English: "Foundation section: raised floor on a stem wall, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: d292ee971f5b54a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Smooth terrain" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.smoothTerrain" -msgstr "平滑地形" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloor" +msgstr "基础剖面:基础短墙承托的架空楼板,完成面高出地坪 {floor},搁栅下架空层 {crawl}" -# Source-Hash: cbb4687261739397 +# English: "Foundation section: raised floor on a stem wall on piles, finish floor {floor} over grade, crawl space {crawl} under the joists" +# Source-Hash: 786e74b8b53f3dab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split {kind}" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.splitKind" -msgstr "分割{kind}" +msgid "editor.panels.buildingFoundationSection.sectionRaisedFloorOnPiles" +msgstr "基础剖面:桩上基础短墙承托的架空楼板,完成面高出地坪 {floor},搁栅下架空层 {crawl}" -# Source-Hash: 96281876ce53ba08 +# English: "Foundation section: slab on grade, finish floor {floor} over grade" +# Source-Hash: bfb5167b838b50c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" -#: packages/editor/src/lib/hud-title.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.hudTitle.terrace" -msgstr "露台" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGrade" +msgstr "基础剖面:落地楼板,完成面高出地坪 {floor}" -# Source-Hash: 8ec6152e139b0e3f +# English: "Foundation section: slab on grade on piles, finish floor {floor} over grade" +# Source-Hash: 5a7458213c388c8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.polygonRoom" -msgstr "多边形房间" +msgid "editor.panels.buildingFoundationSection.sectionSlabOnGradeOnPiles" +msgstr "基础剖面:桩上的落地楼板,完成面高出地坪 {floor}" -# Source-Hash: 83a45603ac7f41e8 +# English: "Sill" +# Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle room" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.rectangleRoom" -msgstr "矩形房间" +msgid "editor.panels.buildingFoundationSection.sill" +msgstr "地梁板" -# Source-Hash: aac9a732d2159ccc +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" -#: packages/editor/src/lib/wall-draw-variant.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.lib.wallDrawVariant.walls" -msgstr "墙体" +msgid "editor.panels.buildingFoundationSection.size" +msgstr "尺寸" +# English: "{size} joists" +# Source-Hash: 2406f7fa46d251de # Review: required -# Source-Hash: c2626e1fbf89b3c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{action, select, close {Close} open {Open} stop {Stop} other {Play}}" -#: packages/editor/src/components/editor/registry-action-contributions.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.mechanismAction" -msgstr "{action, select, close {关闭} open {打开} stop {停止} other {播放}}" +msgid "editor.panels.buildingFoundationSection.sizeJoists" +msgstr "{size} 搁栅" +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.curve" -msgstr "曲线" +msgid "editor.panels.buildingFoundationSection.slab" +msgstr "楼板" +# English: "IRC R506.1 asks 3½ in at least." +# Source-Hash: 97296dd13f940848 # Review: required -# Source-Hash: 5af05fb6661e4d6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut out" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.cutOut" -msgstr "挖空" +msgid "editor.panels.buildingFoundationSection.slabHint" +msgstr "IRC R506.1 要求至少 3½ in。" +# English: "Slab on grade" +# Source-Hash: b8c65d3c6302c1a7 # Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.delete" -msgstr "删除" +msgid "editor.panels.buildingFoundationSection.slabOnGrade" +msgstr "落地楼板" +# English: "Slab {thickness}" +# Source-Hash: 714bbb73fd4e68b9 # Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.duplicate" -msgstr "复制" +msgid "editor.panels.buildingFoundationSection.slabThickness" +msgstr "楼板 {thickness}" +# English: "Slab top over grade" +# Source-Hash: 4e6765b0d6a9bbe5 # Review: required -# Source-Hash: 94b212bd79c05141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit mesh" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx +#, fuzzy +msgid "editor.panels.buildingFoundationSection.slabTopOverGrade" +msgstr "楼板顶高出地坪" + +# English: "The ground round the slab is graded to this on every side: filled where it falls away, cut where it rises (IRC R317.1: 8 in)." +# Source-Hash: 7a6001a64711e175 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.editMesh" -msgstr "编辑网格" +msgid "editor.panels.buildingFoundationSection.slabTopOverGradeHint" +msgstr "楼板四周地面均按此找平:低处填方,高处挖方(IRC R317.1:8 in)。" +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c # Review: required -# Source-Hash: ce90cf37224f73cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find in catalog" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.findInCatalog" -msgstr "在目录中查找" +msgid "editor.panels.buildingFoundationSection.spacing" +msgstr "间距" +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 # Review: required -# Source-Hash: d5e550c345e7161c -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.groupSelection" -msgstr "将所选编组" +msgid "editor.panels.buildingFoundationSection.spacingOnCentre" +msgstr "中距 {spacing}" +# English: "Stem top over grade" +# Source-Hash: 097812a3bcb6d256 # Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.move" -msgstr "移动" +msgid "editor.panels.buildingFoundationSection.stemTopOverGrade" +msgstr "短墙顶高出地坪" +# English: "IRC R317.1 keeps wood framing 8 in off the ground." +# Source-Hash: eabab1d380a0b860 # Review: required -# Source-Hash: 473a9d4463761107 -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate left" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateLeft" -msgstr "向左旋转" +msgid "editor.panels.buildingFoundationSection.stemTopOverGradeHint" +msgstr "IRC R317.1 要求木框架距地面 8 in。" +# English: "Stem wall" +# Source-Hash: ce9da5096b1feb02 # Review: required -# Source-Hash: b1bf4e65c8d8c8ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate right" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.rotateRight" -msgstr "向右旋转" +msgid "editor.panels.buildingFoundationSection.stemWall" +msgstr "基础短墙" +# English: "Stem wall and footing" +# Source-Hash: b0a49e954f1e5a99 # Review: required -# Source-Hash: 6f4dbbc4a3f58fca -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup selection" -#: packages/editor/src/components/editor/node-action-menu.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeActionMenu.ungroupSelection" -msgstr "取消所选编组" +msgid "editor.panels.buildingFoundationSection.stemWallAndFooting" +msgstr "基础短墙和基脚" -# Source-Hash: 4275c05172abd7fb +# English: "Table R404.1.2: 6 in for one storey, 8 in for more." +# Source-Hash: 4ea0e2b0609f6137 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.ceiling" -msgstr "天花板" +msgid "editor.panels.buildingFoundationSection.stemWallHint" +msgstr "Table R404.1.2:单层为 6 in,多层为 8 in。" -# Source-Hash: 3530ac1f4046cd22 +# English: "Stem wall {thickness}" +# Source-Hash: 965e29b358d63d58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.column" -msgstr "柱" +msgid "editor.panels.buildingFoundationSection.stemWallThickness" +msgstr "基础短墙 {thickness}" -# Source-Hash: 787e10f5db16904e +# English: "Step down the slope" +# Source-Hash: 2a131b783b8e35ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.door" -msgstr "门" +msgid "editor.panels.buildingFoundationSection.stepDownTheSlope" +msgstr "顺坡跌级" -# Source-Hash: ab5cbab7be160a1c +# English: "Step height" +# Source-Hash: 2d15d12859e8f169 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.elevator" -msgstr "电梯" +msgid "editor.panels.buildingFoundationSection.stepHeight" +msgstr "台阶高度" -# Source-Hash: 02042fde61b5aa11 +# English: "Subfloor sheathing" +# Source-Hash: 438e69e35a342a65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.fence" -msgstr "围栏" +msgid "editor.panels.buildingFoundationSection.subfloorSheathing" +msgstr "毛地板覆面板" -# Source-Hash: 4c3ff8cdc6feda97 +# English: "Tallest stem before stepping" +# Source-Hash: 1170f2affa3a3f4b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide image" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.guide" -msgstr "参考图像" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeStepping" +msgstr "跌级前短墙最大高度" -# Source-Hash: 2743a0e9d2743807 +# English: "A side whose stem would stand taller out of the ground steps down the slope: its stem drops to its top over grade, a cripple wall over it." +# Source-Hash: 9485def39ac25b45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.item" -msgstr "物品" +msgid "editor.panels.buildingFoundationSection.tallestStemBeforeSteppingHint" +msgstr "某侧短墙露出地面若将超过此高度,该侧即顺坡跌级:短墙降至设定的高出地坪顶高,其上设短柱墙。" -# Source-Hash: f5f730969df63d1d +# English: "Taper" +# Source-Hash: 6063ae2877faa35d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roof" -msgstr "屋顶" +msgid "editor.panels.buildingFoundationSection.taper" +msgstr "斜收" -# Source-Hash: a130b4eec084c79a +# English: "Taper {taper} at 45°" +# Source-Hash: 4c008bc3ff2c404c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.roofSegment" -msgstr "屋顶分段" +msgid "editor.panels.buildingFoundationSection.taperAt45" +msgstr "45° 斜收 {taper}" -# Source-Hash: 612dcd5bf06a79c4 +# English: "The edge’s inside face stands plumb from its bottom, then tapers in at 45° to the slab. Auto runs it half the edge’s depth under the slab, 6 to 12 in, so a deeper edge grows plumb, not wider." +# Source-Hash: c2d8477f6b02f2a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.room" -msgstr "房间" +msgid "editor.panels.buildingFoundationSection.taperHint" +msgstr "板边内侧面自底部竖直向上,再以 45° 向内斜收至楼板。“自动”使斜收段取楼板下板边深度的一半(6 至 12 in),因此板边加深时竖直段变长,而不会变宽。" -# Source-Hash: b22b2f20cc7cd416 +# English: "The house stands on a basement: its walls are the foundation walls, so only their footing is drawn." +# Source-Hash: 8d646e2d19be2e57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D Scan" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.scan" -msgstr "扫描模型" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnABasement" +msgstr "房屋立于地下室之上:其墙体即为基础墙,因此只绘制墙下基脚。" -# Source-Hash: 170b2049ef0f9131 +# English: "The house stands on its basement, cut into the ground: the ground round it is left as it lies." +# Source-Hash: 82f250b688c1079e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selection" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.selection" -msgstr "当前选择" +msgid "editor.panels.buildingFoundationSection.theHouseStandsOnItsBasement" +msgstr "房屋立于嵌入地下的地下室之上:四周地面保持原状。" -# Source-Hash: 8775bc8dc9593964 +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.slab" -msgstr "楼板" +msgid "editor.panels.buildingFoundationSection.thickness" +msgstr "厚度" -# Source-Hash: 53d7084bc41c1ad6 +# English: "{title} material" +# Source-Hash: 4cf16973c8dd006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stair" -msgstr "楼梯" +msgid "editor.panels.buildingFoundationSection.titleMaterial" +msgstr "{title}材料" -# Source-Hash: 52de428c16bb81e6 +# English: "Turned-down edge" +# Source-Hash: 4707e4ae34281fd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair segment" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.stairSegment" -msgstr "楼梯分段" +msgid "editor.panels.buildingFoundationSection.turnedDownEdge" +msgstr "下翻板边" -# Source-Hash: 2210e5ee50b74dc9 +# English: "Vinyl plank" +# Source-Hash: 385c128fc1026239 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.wall" -msgstr "墙体" +msgid "editor.panels.buildingFoundationSection.vinylPlank" +msgstr "乙烯基地板" -# Source-Hash: aa3c35ed33e181e5 +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.window" -msgstr "窗" +msgid "editor.panels.buildingFoundationSection.woodPlank" +msgstr "木板" -# Source-Hash: a107b4d76d6972e3 +# English: "Wood plank ceiling" +# Source-Hash: 931afc430cda5de8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" -#: packages/editor/src/components/ui/panels/node-display.ts +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/building-foundation-section.tsx #, fuzzy -msgid "editor.nodeDisplay.zone" -msgstr "区域" +msgid "editor.panels.buildingFoundationSection.woodPlankCeiling" +msgstr "木板天花板" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.cancel" msgstr "取消" +# English: "Collection name…" # Source-Hash: 6dbabf32ff8c6f37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection name…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collectionName" msgstr "集合名称…" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.collections" msgstr "集合" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.delete" msgstr "删除" +# English: "Delete \"{name}\"?" # Source-Hash: c53d71b05b8163b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete \"{name}\"?" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.deleteName" msgstr "删除“{name}”?" +# English: "New" # Source-Hash: e9f7cceb39860c56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.new" msgstr "新建" +# English: "No collections yet. Create one to group elements together." # Source-Hash: 649bc1b1799f2311 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No collections yet. Create one to group elements together." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.noCollectionsYetCreateOneTo" msgstr "暂无集合。新建一个以将元素组合在一起。" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/collections/collections-popover.tsx #, fuzzy msgid "editor.panels.collections.collectionsPopover.rename" msgstr "重命名" -# Source-Hash: 2000cf124b4e1139 +# English: "Along house" +# Source-Hash: 0a605a51555bb514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.alongHouse" +msgstr "沿房屋" + +# English: "Boards" +# Source-Hash: 19ae2360207ecf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boards" +msgstr "铺板" + +# English: "{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." +# Source-Hash: 2bc05aaf1fc289b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.boardsSummary" +msgstr "{label} 铺板 {thickness} × {width} in,板缝 {gap} in,{direction, select, parallel {沿房屋方向铺设,搁栅与房屋垂直} diagonal {与房屋成 45° 铺设} other {由房屋向外铺设,搁栅沿房屋方向,由垂直于房屋的梁承托}};搁栅中距 {spacing} in。" + +# English: "Deck boards" +# Source-Hash: 24c6255bfa1abd8f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.deckBoards" +msgstr "平台铺板" + +# English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.diagonal" +msgstr "斜向" + +# English: "Gap" +# Source-Hash: df708ece3529bcba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.gap" +msgstr "板缝" + +# English: "Out from house" +# Source-Hash: 3660fcdd12159277 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.outFromHouse" +msgstr "由房屋向外" + +# English: "Run" +# Source-Hash: 9ac7e4955593cc34 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/deck-boards-fields.tsx +#, fuzzy +msgid "editor.panels.deckBoardsFields.run" +msgstr "铺设方向" + # English: "A thicker floor grows upward: the rooms below keep their height." +# Source-Hash: 2000cf124b4e1139 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.aThickerFloorGrowsUpwardThe" msgstr "加厚的楼板向上增长:下方房间保持原有高度。" -# Source-Hash: 0d57958b99b6b352 +# English: "Its layers make {thickness}; the thickness follows them on the first change." +# Source-Hash: 767f8969959d1c48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.buildUpFollows" +msgstr "各层合计 {thickness};首次修改后,厚度将随各层调整。" + # English: "Edge finish" +# Source-Hash: 0d57958b99b6b352 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.edgeFinish" msgstr "边缘处理" -# Source-Hash: 8e458d8b21f26f09 +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floor" +msgstr "楼面" + +# English: "Floor build-up" +# Source-Hash: 097b19040c031ae4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.floorBuildUp" +msgstr "楼面构造" + # English: "Floor plate" +# Source-Hash: 8e458d8b21f26f09 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlate" msgstr "楼板" +# English: "Floor plate {thicknessLabel} thick, resting on the walls below" # Source-Hash: 2bb71e5633801afb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate {thicknessLabel} thick, resting on the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorPlateThicknesslabelThickRestingOn" msgstr "楼板厚 {thicknessLabel},支承于下方墙体" -# Source-Hash: 73614b8faa059cd6 +# English: "Floor thickness {thickness}" +# Source-Hash: 68a6aff9bf44f9c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy -msgid "editor.panels.floorFoundationPanel.floorThickness" -msgstr "楼板厚度" +msgid "editor.panels.floorFoundationPanel.floorThicknessValue" +msgstr "楼板厚度 {thickness}" +# English: "Floor top +{height}" # Source-Hash: a40a1fbacd5d2551 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top +{height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopAbove" msgstr "楼面顶 +{height}" +# English: "Floor top {height}" # Source-Hash: 5c3849e15933737f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.floorTopValue" msgstr "楼面顶 {height}" +# English: "Foundation finish" # Source-Hash: 19802dd369749ea7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationFinish" msgstr "基础饰面" +# English: "Foundation height" # Source-Hash: 3a4599be1ad687be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.foundationHeight" msgstr "基础高度" +# English: "Floor plate" # Source-Hash: 8e458d8b21f26f09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kicker" msgstr "楼板" +# English: "Floor plate · {level}" # Source-Hash: 66d52bfbb94f7bb3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerLevel" msgstr "楼板 · {level}" +# English: "Floor plate · {level} · rests on {below}" # Source-Hash: 0189e5a1987b4ec0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {level} · rests on {below}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.kickerRestsOn" msgstr "楼板 · {level} · 支承于{below}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" # Source-Hash: 648ef10535b0af73 # Review: required -# English: "{count, plural, =2 {Lower both} other {Lower all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.lowerTogether" msgstr "{count, plural, =2 {同时降低两者} other {全部 {number} 个一起降低}}" +# English: "Not to scale" # Source-Hash: c2fcc43bb7f1c5e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not to scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.notToScale" msgstr "非按比例" +# English: "On the ground" # Source-Hash: e9440fa9f770b7a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.onTheGround" msgstr "位于地面" +# English: "Raised on a foundation" # Source-Hash: 7c3692a361075ecf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised on a foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raisedOnAFoundation" msgstr "抬高于基础之上" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" # Source-Hash: 769c3ca1ca0cd4f0 # Review: required -# English: "{count, plural, =2 {Raise both} other {Raise all {number}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.raiseTogether" msgstr "{count, plural, =2 {同时抬高两者} other {全部 {number} 个一起抬高}}" +# English: "Floor plate · {thickness}" # Source-Hash: 719ae422da204ca7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor plate · {thickness}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorPlate" msgstr "楼板 · {thickness}" +# English: "Floor top" # Source-Hash: 069d64adffa05db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.floorTop" msgstr "楼面顶" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundation" msgstr "基础" +# English: "Foundation · {foundation}" # Source-Hash: 7759a59b8685c885 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation · {foundation}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.foundationKind" msgstr "基础 · {foundation}" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.ground" msgstr "地面" -# Source-Hash: 51e274b2c90e2cf3 +# English: "{layer} · {size}" +# Source-Hash: 1a0f891c672247b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.section.layer" +msgstr "{layer} · {size}" + # English: "Slab · {thickness}" +# Source-Hash: 51e274b2c90e2cf3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.slab" msgstr "楼板 · {thickness}" +# English: "Walls below" # Source-Hash: 790a0b5c6dbf0e9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.section.wallsBelow" msgstr "下方墙体" -# Source-Hash: 90e96ae5f3d3f69c +# English: "Floor plate {thickness} thick: {layers}; resting on the walls below" +# Source-Hash: 7c1a423934a360f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayers" +msgstr "楼板厚 {thickness}:{layers};支承于下方墙体" + +# English: "{layer} {size}" +# Source-Hash: 0c5b48217a0cf663 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx +#, fuzzy +msgid "editor.panels.floorFoundationPanel.sectionLayerSize" +msgstr "{layer} {size}" + # English: "Slab {thickness} thick on the ground, floor top {top} above the ground" +# Source-Hash: 90e96ae5f3d3f69c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionOnGround" msgstr "楼板厚 {thickness},位于地面上,楼面顶高出地面 {top}" +# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" # Source-Hash: 71ed2295d9932ed0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab {thickness} thick on a {foundation} foundation, floor top {top} above the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.sectionRaised" msgstr "楼板厚 {thickness},位于{foundation}基础上,楼面顶高出地面 {top}" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slab" msgstr "楼板" +# English: "Slab thickness" # Source-Hash: ca51d643691c29ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.slabThickness" msgstr "楼板厚度" +# English: "The floor's outside edge, all around the footprint." # Source-Hash: 678c39468593275b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The floor's outside edge, all around the footprint." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFloorSOutsideEdgeAll" msgstr "楼板的外缘,沿基底一周。" +# English: "The foundation stands on the ground; the slab sits on it." # Source-Hash: 6638ed5caa43bdb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The foundation stands on the ground; the slab sits on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theFoundationStandsOnTheGround" msgstr "基础立于地面上;楼板位于基础上。" +# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." # Source-Hash: 5321416b442f23b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The slab sits on the foundation (or the ground): a thicker slab raises the floor top and everything on it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.theSlabSitsOnTheFoundation" msgstr "楼板位于基础(或地面)上:加厚楼板会抬高楼面顶及其上的一切。" +# English: "Under the floor" # Source-Hash: 044ccae8bec574e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Under the floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-foundation-panel.tsx #, fuzzy msgid "editor.panels.floorFoundationPanel.underTheFloor" msgstr "楼板下方" +# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." # Source-Hash: 6aca44df3e582a81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A hatch in the mezzanine: it cuts only the mezzanine’s floor." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.aHatchInTheMezzanineIt" msgstr "夹层中的开口:只切穿夹层的楼板。" +# English: "Also cut the ceiling below" # Source-Hash: 2ce79639d42da658 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the ceiling below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheCeilingBelow" msgstr "同时切穿下方天花板" +# English: "Also cut the floor above" # Source-Hash: e360814f64792300 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Also cut the floor above" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.alsoCutTheFloorAbove" msgstr "同时切穿上方楼板" +# English: "{area} · Drag its corners to change the shape." # Source-Hash: c25cddf8b831c2b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Drag its corners to change the shape." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaDragCorners" msgstr "{area} · 拖动其角点以更改形状。" +# English: "{area} · Shape follows its owner." # Source-Hash: 32b168cf010a5daa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Shape follows its owner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.areaOwned" msgstr "{area} · 形状跟随其所属对象。" -# Source-Hash: b26de372b4b0eaac +# English: "{area} · Drag its sides or corners to resize it; it moves with the stair." +# Source-Hash: 71b5c7184cbd2e5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/floor-opening-panel.tsx +#, fuzzy +msgid "editor.panels.floorOpeningPanel.areaStairResize" +msgstr "{area} · 拖动其边或角点以调整大小;它会随楼梯移动。" + # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.delete" msgstr "删除" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.done" msgstr "完成" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kicker" msgstr "开口" +# English: "Opening · {level}" # Source-Hash: 079ad92dac7947cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerLevel" msgstr "开洞 · {level}" +# English: "Opening · in {room}" # Source-Hash: baf2c2c209637e5f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoom" msgstr "开洞 · 位于{room}" +# English: "Opening · in {room} · {level}" # Source-Hash: 1712c164335ecd86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening · in {room} · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.kickerRoomLevel" msgstr "开洞 · 位于{room} · {level}" +# English: "On {level}, as one void with this opening." # Source-Hash: 88e673123b9dbfe1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On {level}, as one void with this opening." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.oneVoidWith" msgstr "在{level},与此开洞形成一个洞口。" +# English: "Owned by Elevator" # Source-Hash: c38959fc8fd3e59d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByElevator" msgstr "归属于电梯" +# English: "Owned by Staircase" # Source-Hash: 01494805cf60d78d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Owned by Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.ownedByStaircase" msgstr "归属于楼梯" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.room" msgstr "房间" +# English: "There is no floor above this storey." # Source-Hash: 1405b914a324b153 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no floor above this storey." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoFloorAboveThis" msgstr "此层上方没有楼板。" +# English: "There is no storey below." # Source-Hash: 776170dcae88e969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "There is no storey below." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.thereIsNoStoreyBelow" msgstr "下方没有楼层。" +# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" # Source-Hash: 0fdd1877a0f2d777 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{source, select, stair {Stair opening} elevator {Elevator opening} ceiling {Ceiling opening} other {Floor opening}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/floor-opening-panel.tsx #, fuzzy msgid "editor.panels.floorOpeningPanel.title" msgstr "{source, select, stair {楼梯开洞} elevator {电梯开洞} ceiling {天花板开洞} other {楼板开洞}}" +# English: "A raised floor on a stem wall of its own." +# Source-Hash: 1839fff3aba02d40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aRaisedFloorOnAStem" +msgstr "自有基础短墙承托的架空楼板。" + +# English: "A slab of its own (a porch, a patio, a garage pad), the ground held 8 in under its top all round." +# Source-Hash: 52eead98a170fd6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.aSlabOfItsOwn" +msgstr "自有楼板(门廊、露台、车库地坪),四周地面保持低于其顶面 8 in。" + +# English: "Click its corners; click the first again, or press Enter, to close it." +# Source-Hash: 96853854f04b1514 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.clickItsCornersClickTheFirst" +msgstr "依次点击各角点;再次点击第一个点或按回车键即可闭合。" + +# English: "Close the outside walls into a loop first, or draw one" +# Source-Hash: 4655068154039021 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.closeTheOutsideWallsIntoA" +msgstr "请先将外墙闭合成环,或自行绘制轮廓" + +# English: "Delete this outline" +# Source-Hash: 8b8a23b925c30027 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.deleteThisOutline" +msgstr "删除此轮廓" + +# English: "Draw" +# Source-Hash: b5c56dd8c68de309 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.draw" +msgstr "绘制" + +# English: "Edit it myself" +# Source-Hash: e9c386def6e2dadf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.editItMyself" +msgstr "自行编辑" + +# English: "Follow the walls" +# Source-Hash: 86c8ecac2691c892 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.followTheWalls" +msgstr "跟随墙体" + +# English: "{footings, plural, one {# footing} other {# footings}} drawn: click one in the plan to size or move it." +# Source-Hash: ee78cc2aa3f77ca1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.footingsDrawn" +msgstr "已绘制 {footings, plural, other {# 个基脚}}:在平面中点击其中一个即可调整尺寸或移动。" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.foundation" +msgstr "基础" + +# English: "House foundation" +# Source-Hash: 5ec26bb3cea817e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.houseFoundation" +msgstr "房屋基础" + +# English: "How far its top sits under the house’s floor: a porch slab steps down from the door" +# Source-Hash: 0140833a30e08d70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.howFarItsTopSitsUnder" +msgstr "其顶面低于房屋楼面的距离:门廊楼板在门口处下沉一级" + +# English: "Its own: drag its corners and sides in the plan or in 3D. Moving the walls leaves it be." +# Source-Hash: 3b101db8d8839574 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.itsOwnDragItsCornersAnd" +msgstr "自有轮廓:在平面或 3D 中拖动其角点和边。移动墙体不会影响它。" + +# English: "Outline" +# Source-Hash: 3e6d52c82419517d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outline" +msgstr "轮廓" + +# English: "Outlines of slabs and raised floors (porches, patios, a garage pad), and footings under interior bearing walls and posts." +# Source-Hash: 78d4f6a686211e44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.outlinesOfSlabsAndRaisedFloors" +msgstr "楼板和架空楼板的轮廓(门廊、露台、车库地坪),以及内部承重墙和立柱下的基脚。" + +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.raisedFloor" +msgstr "架空楼板" + +# English: "Re-trace from walls" +# Source-Hash: e6eaea778b041b10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.reTraceFromWalls" +msgstr "从墙体重新描绘" + +# English: "Set the outline back to the outside faces of the walls, keeping it your own" +# Source-Hash: 5181a67d6940cccb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.setTheOutlineBackToThe" +msgstr "将轮廓重置到墙体外表面,同时保持为自有轮廓" + +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.slab" +msgstr "楼板" + +# English: "The house’s" +# Source-Hash: 523f999e5b2d497e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. What a picked foundation outline pours: the house’s own foundation, as against a slab or raised floor of its own. +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHouses" +msgstr "房屋基础" + +# English: "The house’s own foundation, poured as the house’s settings below say, in place of the walls’ outline." +# Source-Hash: 3e4d5b90c1ab8eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.theHousesOwnFoundation" +msgstr "房屋自身的基础,按下方的房屋设置浇筑,取代墙体轮廓。" + +# English: "This outline" +# Source-Hash: 51be023b5f1983f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.thisOutline" +msgstr "此轮廓" + +# English: "Top under the floor" +# Source-Hash: 85eca682e0102861 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.topUnderTheFloor" +msgstr "顶面低于楼面" + +# English: "Traced from the outside walls: it moves when they do." +# Source-Hash: 1f7c9880395d456a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/foundation-view-panel.tsx +#, fuzzy +msgid "editor.panels.foundationViewPanel.tracedFromTheOutsideWallsIt" +msgstr "沿外墙描绘:外墙移动时随之移动。" + +# English: "One of them already wraps another corner. Unwrap it first." # Source-Hash: fa35c7653e1f649e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them already wraps another corner. Unwrap it first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.alreadyJoined" msgstr "其中一扇已包住另一个转角。请先解除。" +# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" # Source-Hash: dd87eb1983876459 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.between" msgstr "{kind, select, item {它们之间有一个物品。} door {它们之间有一扇门。} window {它们之间有一扇窗。} shelf {它们之间有一个搁板。} other {它们之间有其他对象。}}" +# English: "These windows cannot be merged." # Source-Hash: 58aea0b9789ae280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "These windows cannot be merged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cannotMerge" msgstr "这些窗户无法合并。" +# English: "Their walls meet at too sharp or too flat an angle for a corner window." # Source-Hash: 15170cfd9d666c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls meet at too sharp or too flat an angle for a corner window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.cornerAngle" msgstr "它们的墙体相交角度过锐或过钝,无法做转角窗。" +# English: "The window selected first is taller than the other wall. Select the other one first." # Source-Hash: f2818a1e20ebd6ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window selected first is taller than the other wall. Select the other one first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.heightExceedsWall" msgstr "先选中的窗户比另一面墙高。请先选择另一扇。" +# English: "They are too far apart. Windows merge with at most {reach} between them." # Source-Hash: 7c97fb4af929d4db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "They are too far apart. Windows merge with at most {reach} between them." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAdjacent" msgstr "它们相距太远。窗户之间最多相隔 {reach} 才能合并。" +# English: "One of them is too far from the corner. A window joins one within {reach} of it." # Source-Hash: 57a281bdc4229b6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "One of them is too far from the corner. A window joins one within {reach} of it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notAtCorner" msgstr "其中一扇离转角太远。窗户只能与 {reach} 以内的窗合并。" +# English: "A roof or dormer window cannot join another window." # Source-Hash: e7d41ce7879ec075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A roof or dormer window cannot join another window." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.notOnWall" msgstr "屋顶窗或老虎窗不能与其他窗合并。" +# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" # Source-Hash: eaad567d5e4257dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.overlap" msgstr "{kind, select, item {将它们滑到转角会与一个物品重叠。} door {将它们滑到转角会与一扇门重叠。} window {将它们滑到转角会与一扇窗重叠。} shelf {将它们滑到转角会与一个搁板重叠。} other {将它们滑到转角会与其他对象重叠。}}" +# English: "Their walls do not meet at a corner." # Source-Hash: ecb092821ed5b5e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Their walls do not meet at a corner." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.mergeRefusal.wallsNotMeeting" msgstr "它们的墙体没有在转角相交。" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.mixed" msgstr "混合" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-panel-sheet.tsx #, fuzzy msgid "editor.panels.mobilePanelSheet.close" msgstr "关闭" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.delete" msgstr "删除" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.duplicate" msgstr "复制" +# English: "Edit properties" # Source-Hash: 7eb955d866c2668b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit properties" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editProperties" msgstr "编辑属性" +# English: "Edit {resolvedLabel}" # Source-Hash: 89f25aa13e8192bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit {resolvedLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.editResolvedlabel" msgstr "编辑{resolvedLabel}" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/mobile-selection-bar.tsx #, fuzzy msgid "editor.panels.mobileSelectionBar.move" msgstr "移动" +# English: "Currently {currentLabel}" # Source-Hash: 72be05e1796526c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {currentLabel}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.currentlyCurrentlabel" msgstr "当前为{currentLabel}" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.customHeight" msgstr "自定义高度" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.followsLevel" msgstr "跟随楼层" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.height" msgstr "高度" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-height-mode.tsx #, fuzzy msgid "editor.panels.multiHeightMode.top" msgstr "顶部" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-parametric-inspector.tsx #, fuzzy msgid "editor.panels.multiParametricInspector.labelSessionOnlyPlainClickReselects" msgstr "{label}(仅限本次会话)。普通点击会重新选中所有成员。不会随项目保存。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.delete" msgstr "删除" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.duplicate" msgstr "复制" +# English: "Group" # Source-Hash: b0e7e3b47dfb5a0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.group" msgstr "编组" +# English: "Group (Ctrl/Cmd+G)" # Source-Hash: ca0a95fe0b08b8b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Group (Ctrl/Cmd+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.groupCtrlCmdG" msgstr "编组(Ctrl/Cmd+G)" +# English: "{label} (session only). Plain click reselects all members. Not saved with the project." # Source-Hash: 70f48b0141b656f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{label} (session only). Plain click reselects all members. Not saved with the project." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects" msgstr "{label}(仅限本次会话)。普通点击会重新选中所有成员。不会随项目保存。" +# English: "{length} selected" # Source-Hash: fc98ef29c2842341 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length} selected" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.lengthSelected" msgstr "已选择 {length} 个" +# English: "Merge windows" # Source-Hash: 323aaf1ebd04ada7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindows" msgstr "合并窗户" +# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" # Source-Hash: 7855d03a6140af77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into a corner window: both take the height, sill and type of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow" msgstr "合并为转角窗:两者都采用先选中窗户的高度、窗台和类型" +# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" # Source-Hash: b79da96502d2e1c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Merge windows into one across both: it keeps the type and style of the one you selected first" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth" msgstr "合并为跨越两者的一扇窗:保留先选中窗户的类型和样式" +# English: "Select building" # Source-Hash: f409934681a046e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectBuilding" msgstr "选择建筑" +# English: "Select the whole building to move all its levels" # Source-Hash: c62a73873bd0164d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select the whole building to move all its levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove" msgstr "选择整栋建筑以移动其所有楼层" +# English: "Ungroup" # Source-Hash: 7bd5f29203a28efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroup" msgstr "取消编组" +# English: "Ungroup (Ctrl/Cmd+Shift+G)" # Source-Hash: aec9c3983a440d4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ungroup (Ctrl/Cmd+Shift+G)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG" msgstr "取消编组(Ctrl/Cmd+Shift+G)" +# English: "Unwrap the corner" # Source-Hash: 420385f27c627cee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCorner" msgstr "解除转角" +# English: "Unwrap the corner: both windows stay, each framed at the corner" # Source-Hash: 7906c9f4baebc70b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap the corner: both windows stay, each framed at the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/multi-selection-panel.tsx #, fuzzy msgid "editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay" msgstr "解除转角:两扇窗都保留,各自在转角处加框" -# Source-Hash: 170b2049ef0f9131 +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/panel-manager.tsx +#, fuzzy +msgid "editor.panels.panelManager.foundation" +msgstr "基础" + # English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-manager.tsx #, fuzzy msgid "editor.panels.panelManager.selection" msgstr "当前选择" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.back" msgstr "返回" +# English: "Close" # Source-Hash: d7cf5045c01b2eb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.close" msgstr "关闭" +# English: "Close {title}" # Source-Hash: 2dff25ca30c80559 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.closeTitle" msgstr "关闭{title}" +# English: "Collapse panel" # Source-Hash: 4f15c3e0fe6178b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collapse panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collapsePanel" msgstr "收起面板" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.collections" msgstr "集合" +# English: "Expand panel" # Source-Hash: 64916cc9ea80f43f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Expand panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.expandPanel" msgstr "展开面板" +# English: "Manage collections…" # Source-Hash: 8a9a25621f200922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manage collections…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.manageCollections" msgstr "管理集合…" +# English: "Open {title}" # Source-Hash: 2ff02ed91f54235c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open {title}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.openTitle" msgstr "打开{title}" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.reset" msgstr "重置" +# English: "“{title}” hit an error and was unloaded for this session." # Source-Hash: 04a212d6317f6787 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "“{title}” hit an error and was unloaded for this session." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/panel-wrapper.tsx #, fuzzy msgid "editor.panels.panelWrapper.titleHitAnErrorAndWas" msgstr "“{title}”发生错误,已在本次会话中卸载。" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-field-control.tsx #, fuzzy msgid "editor.panels.parametricFieldControl.mixed" msgstr "混合" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.actions" msgstr "操作" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.delete" msgstr "删除" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.deleteWithContents" msgstr "连同内容一起删除" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/parametric-inspector.tsx #, fuzzy msgid "editor.panels.parametricInspector.move" msgstr "移动" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.cancel" msgstr "取消" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.capture" msgstr "采集" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.center" msgstr "居中" +# English: "Clear Scale" # Source-Hash: a5b965dfb0bf2cd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clearScale" msgstr "清除比例" +# English: "Click both ends of a known distance on the plan, then type its real length." # Source-Hash: 4edc6e45b0c19447 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click both ends of a known distance on the plan, then type its real length." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.clickBothEndsOfAKnown" msgstr "在平面上点击已知距离的两端,然后输入其实际长度。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.delete" msgstr "删除" +# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." # Source-Hash: 59f7d81e8d36a157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a line over a known dimension on the plan, then type its real length to scale the image exactly." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.drawALineOverAKnown" msgstr "在平面上沿已知尺寸画一条线,然后输入其实际长度以精确缩放图像。" +# English: "Edit Scale" # Source-Hash: 42f6d0e4f065f114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.editScale" msgstr "编辑比例" +# English: "Guide Image" # Source-Hash: e156c84cbc90e0a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.guideImage" msgstr "参考图像" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hide" msgstr "隐藏" +# English: "Hide Scale" # Source-Hash: dd282ce13b9085ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.hideScale" msgstr "隐藏比例" +# English: "Image" # Source-Hash: fc6eaa65315f601d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Image" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.image" msgstr "图像" +# English: "Lock" # Source-Hash: 241399e11d93bd7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.lock" msgstr "锁定" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.move" msgstr "移动" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.opacity" msgstr "不透明度" +# English: "Overlay image unavailable. Replace the image to restore it." # Source-Hash: 6ca213a6b299f19f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overlay image unavailable. Replace the image to restore it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.overlayImageUnavailableReplaceTheImage" msgstr "叠加图像不可用。替换图像即可恢复。" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.position" msgstr "位置" +# English: "Quick Actions" # Source-Hash: 8b003432b36230c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Quick Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.quickActions" msgstr "快捷操作" +# English: "Reference Scale" # Source-Hash: dcd4728e9ca90181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reference Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.referenceScale" msgstr "参考比例" +# English: "Replace" # Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replace" msgstr "替换" +# English: "Replacing..." # Source-Hash: 08b45f9ba66eab3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replacing..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.replacing" msgstr "替换中..." +# English: "Reset Image Scale" # Source-Hash: 2714b5a81cedb625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Image Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetImageScale" msgstr "重置图像比例" +# English: "Reset Rotation" # Source-Hash: a05d3e429ae259ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.resetRotation" msgstr "重置旋转" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rot" msgstr "旋转" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.rotation" msgstr "旋转" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scale" msgstr "比例" +# English: "Scaled · {scale}" # Source-Hash: e7d0e7d41a775fd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaled" msgstr "已缩放 · {scale}" +# English: "Scaled (hidden) · {scale}" # Source-Hash: cd92eebfb30e708f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scaled (hidden) · {scale}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaledHidden" msgstr "已缩放(隐藏)· {scale}" +# English: "Scale & Opacity" # Source-Hash: bfd20999de7f216a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale & Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.scaleOpacity" msgstr "比例与不透明度" +# English: "Set Scale" # Source-Hash: 8d8777505acdf78e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.setScale" msgstr "设置比例" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.show" msgstr "显示" +# English: "Show Scale" # Source-Hash: 78be2337f0e81973 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.showScale" msgstr "显示比例" +# English: "Uncalibrated" # Source-Hash: ad85a193442c8f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncalibrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.uncalibrated" msgstr "未校准" +# English: "Unlock" # Source-Hash: 6c500cc253ab3fd4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unlock" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.unlock" msgstr "解锁" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/reference-panel.tsx #, fuzzy msgid "editor.panels.referencePanel.xyz" msgstr "XYZ" +# English: "+ Add mezzanine" # Source-Hash: e01549105012f8d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.addMezzanine" msgstr "+ 添加夹层" +# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." # Source-Hash: 167b0fe7fdbcd51d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A drawn slab: set its thickness and finishes here. It has no footprint height or foundation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.aDrawnSlabSetItsThickness" msgstr "绘制的楼板:在此设置其厚度和饰面。它没有基底高度或基础。" +# English: "At floor level" # Source-Hash: 6cb0969b65fb60bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At floor level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.atFloorLevel" msgstr "与楼面齐平" -# Source-Hash: e8563d5ae1ee7390 +# English: "Beam" +# Source-Hash: e14858b33b6e2623 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beam" +msgstr "梁" + +# English: "Beam size" +# Source-Hash: 4b03c226d5f88a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.beamSize" +msgstr "梁尺寸" + # English: "Built on" +# Source-Hash: e8563d5ae1ee7390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtOn" msgstr "建于" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.builtWith" msgstr "构造" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceiling" msgstr "天花板" +# English: "At {height}" # Source-Hash: 9bf177f59f971033 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "At {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.ceilingAt" msgstr "位于 {height}" +# English: "Close with walls" # Source-Hash: 48768dc0fc7b86a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close with walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.closeWithWalls" msgstr "用墙体封闭" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" # Source-Hash: 75c84b4f21aefde4 # Review: required -# English: "{ambiguous, select, yes {Floor is several drawn slabs, mostly {name}} other {Floor is a drawn slab: {name}}}{shared, plural, =0 {} one { (shared with {sharedCount} room)} other { (shared with {sharedCount} rooms)}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloor" msgstr "{ambiguous, select, yes {楼板由多块绘制楼板组成,主要是{name}} other {楼板是一块绘制楼板:{name}}}{shared, plural, =0 {} other {(与 {sharedCount} 个房间共用)}}" +# English: "This floor is a drawn slab: select it to edit or delete it." # Source-Hash: 97e0fab8ffda6d6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This floor is a drawn slab: select it to edit or delete it." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnFloorSwitchHint" msgstr "此楼板是绘制的楼板:选中它以编辑或删除。" +# English: "Drawn slab" # Source-Hash: ad5426620188242c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlab" msgstr "绘制楼板" +# English: "Drawn slab at {height}" # Source-Hash: 0da0d104eaefbbad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drawn slab at {height}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.drawnSlabAt" msgstr "绘制楼板位于 {height}" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.editShape" msgstr "编辑形状" +# English: "Flat, at wall height" # Source-Hash: fadf264be55063e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat, at wall height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.flatAtWallHeight" msgstr "平顶,位于墙高" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floor" msgstr "楼层" +# English: "{distance} down" # Source-Hash: 996e8191c069e779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorDown" msgstr "下降 {distance}" +# English: "Floor thickness" # Source-Hash: 73614b8faa059cd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorThickness" msgstr "楼板厚度" +# English: "{distance} up" # Source-Hash: 79cdde853c3e8f61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{distance} up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.floorUp" msgstr "上升 {distance}" -# Source-Hash: 77bc12b7aba0eb60 +# English: "{posts, plural, one {{postCount} post} other {{postCount} posts}} and {beams, plural, one {{beamCount} beam} other {{beamCount} beams}}, a post on each outside corner, kept with {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: b6b724f9f4db6d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutoHint" +msgstr "{posts, plural, other {{postCount} 根立柱}}和 {beams, plural, other {{beamCount} 根梁}},每个外角各设一根立柱,随{room, select, deck {平台} patio {露台} other {门廊}}自动更新。" + +# English: "Automatic" +# Source-Hash: d18036b75b2c3e4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingAutomatic" +msgstr "自动" + +# English: "Manual" +# Source-Hash: 387082cd5f118bed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManual" +msgstr "手动" + +# English: "Kept as you edited them. Automatic rebuilds them from these settings." +# Source-Hash: 287400b1b4b46abd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingManualHint" +msgstr "保持手动编辑后的状态。选择“自动”将按这些设置重建。" + +# English: "Off" +# Source-Hash: 50accd740fa2e736 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOff" +msgstr "关闭" + +# English: "None. Automatic puts a post on each outside corner and a beam on them, to carry a roof over {room, select, deck {the deck} patio {the patio} other {the porch}}." +# Source-Hash: 4dfa7082b7e30280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.framingOffHint" +msgstr "无。“自动”会在每个外角设一根立柱并在其上架梁,以承托{room, select, deck {平台} patio {露台} other {门廊}}上方的屋顶。" + # English: "Hand-drawn ceiling" +# Source-Hash: 77bc12b7aba0eb60 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.handDrawnCeiling" msgstr "手绘天花板" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.height" msgstr "高度" +# English: "Include floor" # Source-Hash: 4d35023efe090b82 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeFloor" msgstr "包含楼板" +# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" # Source-Hash: 6398ac77644eb5f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{part, select, floor {Include floor} walls {Include walls} other {Include ceiling}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includePart" msgstr "{part, select, floor {包含楼板} walls {包含墙体} other {包含天花板}}" +# English: "Include walls" # Source-Hash: 3629e64a6e796ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Include walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.includeWalls" msgstr "包含墙体" -# Source-Hash: f6d07fc7f239278b +# English: "Keep outside dimensions" +# Source-Hash: f6d07fc7f239278b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" +msgstr "保持外部尺寸" + +# English: "Lowered {lift}" +# Source-Hash: 544410e3cb0e0b07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.lowered" +msgstr "降低 {lift}" + +# English: "+ New floor" +# Source-Hash: c2daf78b740e4397 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.newFloor" +msgstr "+ 新建楼板" + +# English: "No floor" +# Source-Hash: ba9edc7910a60cea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.noFloor" +msgstr "无楼板" + +# English: "Nothing to change." +# Source-Hash: ea2675041df74f40 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.nothingToChange" +msgstr "没有需要更改的内容。" + +# English: "Not included" +# Source-Hash: ebe0d375d7938c93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.notIncluded" +msgstr "未包含" + +# English: "On the foundation" +# Source-Hash: f084b3795e3edfb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheFoundation" +msgstr "位于基础上" + +# English: "On the ground" +# Source-Hash: e9440fa9f770b7a4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheGround" +msgstr "位于地面" + +# English: "On the walls below" +# Source-Hash: a5deb36c485d91bd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.onTheWallsBelow" +msgstr "位于下方墙体上" + +# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: 2256aec0d5bdc3ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.openSides" +msgstr "{openSides, plural, other {{openSideCount} 个开放边}}" + +# English: "Partial · {coverage, number, percent} covered" +# Source-Hash: f704520c5e5b92b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialCovered" +msgstr "部分 · 覆盖 {coverage, number, percent}" + +# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# Source-Hash: b648bee0231a41cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.partialWalls" +msgstr "部分 · {walls, plural, other {{wallCount} 面墙}},{openSides, plural, other {{openSideCount} 个开放边}}" + +# English: "Posts and beam" +# Source-Hash: 4b2a51c39b382184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postsAndBeam" +msgstr "立柱与梁" + +# English: "Post size" +# Source-Hash: faa36308c1ecb2a9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.postSize" +msgstr "立柱尺寸" + +# English: "Post spacing (max)" +# Source-Hash: cc25439729a97f76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep outside dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.keepOutsideDimensions" -msgstr "保持外部尺寸" +msgid "editor.panels.roomConstructionRows.postSpacingMax" +msgstr "立柱间距(最大)" -# Source-Hash: 544410e3cb0e0b07 +# English: "Raised {lift}" +# Source-Hash: e503250e8ecb0030 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lowered {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.lowered" -msgstr "降低 {lift}" +msgid "editor.panels.roomConstructionRows.raised" +msgstr "抬高 {lift}" -# Source-Hash: c2daf78b740e4397 +# English: "Raised floor" +# Source-Hash: 44681d6010889b16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ New floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.newFloor" -msgstr "+ 新建楼板" +msgid "editor.panels.roomConstructionRows.raisedFloor" +msgstr "架空楼板" -# Source-Hash: ba9edc7910a60cea +# English: "Replace" +# Source-Hash: 0cb87327b8076151 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.noFloor" -msgstr "无楼板" +msgid "editor.panels.roomConstructionRows.replace" +msgstr "替换" -# Source-Hash: ea2675041df74f40 +# English: "Room type" +# Source-Hash: be1c56bb3ddc63c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nothing to change." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.nothingToChange" -msgstr "没有需要更改的内容。" +msgid "editor.panels.roomConstructionRows.roomType" +msgstr "房间类型" -# Source-Hash: ebe0d375d7938c93 +# English: "Deck" +# Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not included" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.notIncluded" -msgstr "未包含" +msgid "editor.panels.roomConstructionRows.roomTypeDeck" +msgstr "平台" -# Source-Hash: e9440fa9f770b7a4 +# English: "Outdoors: deck boards on a wood frame and posts, 1 in down from the house; no ceiling." +# Source-Hash: f0028986efecf7d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheGround" -msgstr "位于地面" +msgid "editor.panels.roomConstructionRows.roomTypeDeckHint" +msgstr "室外:平台铺板铺于木框架和立柱上,比房屋低 1 in;无天花板。" -# Source-Hash: a5deb36c485d91bd +# English: "Patio" +# Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "On the walls below" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.onTheWallsBelow" -msgstr "位于下方墙体上" +msgid "editor.panels.roomConstructionRows.roomTypePatio" +msgstr "露台" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: 2256aec0d5bdc3ea +# English: "Outdoors and open: a concrete slab 1½ in down from the house, no ceiling." +# Source-Hash: 5e4ea6a084f67808 # Review: required -# English: "{openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.openSides" -msgstr "{openSides, plural, other {{openSideCount} 个开放边}}" +msgid "editor.panels.roomConstructionRows.roomTypePatioHint" +msgstr "室外露天:混凝土板比房屋低 1½ in,无天花板。" -# Source-Hash: f704520c5e5b92b2 +# English: "Porch" +# Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partial · {coverage, number, percent} covered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialCovered" -msgstr "部分 · 覆盖 {coverage, number, percent}" +msgid "editor.panels.roomConstructionRows.roomTypePorch" +msgstr "门廊" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: b648bee0231a41cd +# English: "Outdoors under a ceiling: a concrete slab 1½ in down from the house, ceiling at the house’s." +# Source-Hash: af2c941e8ef874bf # Review: required -# English: "Partial · {walls, plural, one {{wallCount} wall} other {{wallCount} walls}}, {openSides, plural, one {{openSideCount} open side} other {{openSideCount} open sides}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.partialWalls" -msgstr "部分 · {walls, plural, other {{wallCount} 面墙}},{openSides, plural, other {{openSideCount} 个开放边}}" +msgid "editor.panels.roomConstructionRows.roomTypePorchHint" +msgstr "室外有顶:混凝土板比房屋低 1½ in,天花板与房屋齐平。" -# Source-Hash: e503250e8ecb0030 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raised {lift}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.raised" -msgstr "抬高 {lift}" +msgid "editor.panels.roomConstructionRows.roomTypeRoom" +msgstr "房间" -# Source-Hash: 0cb87327b8076151 +# English: "An ordinary room inside the house." +# Source-Hash: fb0a6cc444058631 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Replace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy -msgid "editor.panels.roomConstructionRows.replace" -msgstr "替换" +msgid "editor.panels.roomConstructionRows.roomTypeRoomHint" +msgstr "房屋内的普通房间。" +# English: "Select" # Source-Hash: a4395168488bb476 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.select" msgstr "选择" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.shape" msgstr "形状" +# English: "Turning walls off keeps the ones shared with {rooms}." # Source-Hash: aafd84823715d698 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turning walls off keeps the ones shared with {rooms}." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sharedWallsNote" msgstr "关闭墙体时会保留与{rooms}共用的墙体。" +# English: "Sits on: {name} · {preset}" # Source-Hash: c98df900d43f77bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sits on: {name} · {preset}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.sitsOnPreset" msgstr "支承于:{name} · {preset}" -# Source-Hash: a17a79e835fa2064 +# English: "Slab" +# Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/panels/room-construction-rows.tsx +#, fuzzy +msgid "editor.panels.roomConstructionRows.slab" +msgstr "楼板" + # English: "the next room" +# Source-Hash: a17a79e835fa2064 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.theNextRoom" msgstr "相邻房间" +# English: "Use existing" # Source-Hash: ebae699e0cb18ea6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use existing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.useExisting" msgstr "使用现有" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" # Source-Hash: 9976021d9162cd5e # Review: required -# English: "{walls, plural, one {{wallCount} wall} other {{wallCount} walls}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallCount" msgstr "{walls, plural, other {{wallCount} 面墙}}" +# English: "Walls" # Source-Hash: aac9a732d2159ccc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.walls" msgstr "墙体" +# English: "Walls thicken inward, so the building's outer size stays the same." # Source-Hash: 6c69eb46a0c40d52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walls thicken inward, so the building's outer size stays the same." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-construction-rows.tsx #, fuzzy msgid "editor.panels.roomConstructionRows.wallsThickenInwardSoTheBuilding" msgstr "墙体向内加厚,因此建筑外部尺寸保持不变。" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.cutOpening" msgstr "开洞" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.done" msgstr "完成" +# English: "Edit floor" # Source-Hash: 2c89abb1778a3f9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.editFloor" msgstr "编辑楼板" +# English: "Elevator opening" # Source-Hash: ca18a6c5398fc8ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.elevatorOpening" msgstr "电梯开洞" +# English: "No openings yet. Cut one for a stair void or a hatch." # Source-Hash: eafea43e795f3708 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No openings yet. Cut one for a stair void or a hatch." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.noOpeningsYetCutOneFor" msgstr "暂无开洞。可为楼梯洞口或检修口开一个。" +# English: "Opening {number}" # Source-Hash: 60fac94ffa614238 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openingNumber" msgstr "开洞 {number}" +# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" # Source-Hash: 7aafbf90d659dcf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, =0 {No openings} one {{number} opening} other {{number} openings}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.openings" msgstr "{count, plural, =0 {无开口} other {{number} 个开口}}" +# English: "Stair opening" # Source-Hash: c1a1794d31990f5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-openings-field.tsx #, fuzzy msgid "editor.panels.roomOpeningsField.stairOpening" msgstr "楼梯开洞" +# English: "Add stairs" # Source-Hash: f0a46329fef44d94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addStairs" msgstr "添加楼梯" +# English: "Add to a unit" # Source-Hash: 8a2ab34a61b9d181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add to a unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.addToAUnit" msgstr "添加到单元" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.area" msgstr "面积" +# English: "Browse" # Source-Hash: 88f1a727e5528c10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.browse" msgstr "浏览" +# English: "Built with" # Source-Hash: 8e5ca7510677460c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Built with" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.builtWith" msgstr "构造" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.ceiling" msgstr "天花板" +# English: "Delete room" # Source-Hash: a5e4fe4960deced9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.deleteRoom" msgstr "删除房间" +# English: "Edit shape" # Source-Hash: 5dfead58503dbc86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.editShape" msgstr "编辑形状" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.floor" msgstr "楼层" +# English: "{height} headroom" # Source-Hash: d17c3b63449c052b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{height} headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.headroom" msgstr "净空 {height}" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.height" msgstr "高度" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.hideCode" msgstr "隐藏代码" +# English: "Room · {level}" # Source-Hash: e93969f5a7cbb140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerLevel" msgstr "房间 · {level}" +# English: "Mezzanine · in {room}" # Source-Hash: f4ad56b4e0c014b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine · in {room}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerMezzanineIn" msgstr "夹层 · 位于{room}" +# English: "Room · not enclosed" # Source-Hash: de37205633316011 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room · not enclosed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.kickerNotEnclosed" msgstr "房间 · 未封闭" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.loading" msgstr "加载中…" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanine" msgstr "夹层" +# English: "Mezzanine name" # Source-Hash: 97de3df03c4a0be6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineName" msgstr "夹层名称" +# English: "Mezzanine names" # Source-Hash: 42cfb1bcb80d09e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.mezzanineNames" msgstr "夹层名称" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.newUnit" msgstr "新建单元" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.none" msgstr "无" +# English: "Not enough headroom" # Source-Hash: e92d6108b57df9e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not enough headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.notEnoughHeadroom" msgstr "净空不足" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" # Source-Hash: 81a6770092b05d48 # Review: required -# English: "{doors, plural, one {{doorCount} door} other {{doorCount} doors}} · {windows, plural, one {{windowCount} window} other {{windowCount} windows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openingCounts" msgstr "{doors, plural, other {{doorCount} 扇门}} · {windows, plural, other {{windowCount} 扇窗}}" +# English: "{area} · Close its walls to build its floor and ceiling." # Source-Hash: a8cd9507ce472a48 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{area} · Close its walls to build its floor and ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.openRoomArea" msgstr "{area} · 围合其墙体以生成楼板和天花板。" +# English: "Over the mezzanine" # Source-Hash: f8aa48ec39a15616 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Over the mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.overTheMezzanine" msgstr "位于夹层上方" +# English: "Perimeter" # Source-Hash: ac87e14dabd5a256 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.perimeter" msgstr "周长" +# English: "Pick" # Source-Hash: ed2aaf0a8d5a9b7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pick" msgstr "选择" +# English: "Picking it adds a number" # Source-Hash: 7baaebbd411cc837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picking it adds a number" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.pickingItAddsANumber" msgstr "选择后会添加编号" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.remove" msgstr "移除" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnit" msgstr "从单元中移除" +# English: "Remove from {unit}" # Source-Hash: a11d13b92b3cefdb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from {unit}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.removeFromUnitNamed" msgstr "从{unit}中移除" +# English: "Rename" # Source-Hash: d6952b4e54d246dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.rename" msgstr "重命名" +# English: "{name} · Rename" # Source-Hash: 3562f9bea0a4a8f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{name} · Rename" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.renameNamed" msgstr "{name} · 重命名" +# English: "Revert" # Source-Hash: d0d4e919bb58a977 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Revert" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.revert" msgstr "还原" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.room" msgstr "房间" +# English: "Room colour — used in the plan and labels" # Source-Hash: 15ae260fae44f42f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room colour — used in the plan and labels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomColourUsedInThePlan" msgstr "房间颜色 — 用于平面图和标签" +# English: "Room name" # Source-Hash: bf75e739dd7e5877 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomName" msgstr "房间名称" +# English: "Room names" # Source-Hash: 2ecf8c059646873b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.roomNames" msgstr "房间名称" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.scriptedFinish" msgstr "脚本饰面" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.shape" msgstr "形状" +# English: "Show mezzanine names" # Source-Hash: e51144ca1759a7bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show mezzanine names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showMezzanineNames" msgstr "显示夹层名称" +# English: "Show room names" # Source-Hash: 875b7303c47df33f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show room names" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.showRoomNames" msgstr "显示房间名称" +# English: "Stairs" # Source-Hash: f05bc67c57e4d530 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.stairs" msgstr "楼梯" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.unit" msgstr "单元" +# English: "Use" # Source-Hash: 91436d6b374e8263 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.use" msgstr "使用" +# English: "{used} on this level" # Source-Hash: 61880aff6cd698c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{used} on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.usedOnThisLevel" msgstr "此楼层已有 {used} 个" +# English: "{use} “{name}”" # Source-Hash: 611604740ed4549c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{use} “{name}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.useTyped" msgstr "{use}“{name}”" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #, fuzzy msgid "editor.panels.roomPanel.viewCode" msgstr "查看代码" +# English: "The script could not be read." # Source-Hash: ba15840e60ce672b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The script could not be read." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/room-panel.tsx #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "editor.panels.scriptUnreadable" msgstr "无法读取脚本。" +# English: "Polygon" # Source-Hash: 4e186fc057733162 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Polygon" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.polygon" msgstr "多边形" +# English: "Rectangle" # Source-Hash: 2c2c20272b0370cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rectangle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/shape-choice.tsx #, fuzzy msgid "editor.panels.shapeChoice.rectangle" msgstr "矩形" +# English: "Delete region" # Source-Hash: 5dc88f90e6aa4129 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete region" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.deleteRegion" msgstr "删除区域" +# English: "Paint regions" # Source-Hash: d81d8cbadf5d4f33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paint regions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/panels/wall-region-list.tsx #, fuzzy msgid "editor.panels.wallRegionList.paintRegions" msgstr "涂刷区域" -# Review: required -# Source-Hash: 89ee318aad485f83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Adjust design" +# Source-Hash: 89ee318aad485f83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.adjustDesign" msgstr "调整设计" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.back" msgstr "返回" -# Review: required -# Source-Hash: ba9b68175315e177 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit parts" +# Source-Hash: ba9b68175315e177 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.editParts" msgstr "编辑部件" -# Review: required -# Source-Hash: 07c62820f014ed6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid attachment" +# Source-Hash: 07c62820f014ed6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidAttachment" msgstr "附着无效" -# Review: required -# Source-Hash: f862aea8d7957c55 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid edit" +# Source-Hash: f862aea8d7957c55 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidEdit" msgstr "编辑无效" -# Review: required -# Source-Hash: 190cf30b8edd8dd1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid placement" +# Source-Hash: 190cf30b8edd8dd1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPlacement" msgstr "放置无效" -# Review: required -# Source-Hash: 45bd2ec690cc48dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid position" +# Source-Hash: 45bd2ec690cc48dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidPosition" msgstr "位置无效" -# Review: required -# Source-Hash: faccab895717c598 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Invalid reset" +# Source-Hash: faccab895717c598 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.invalidReset" msgstr "重置无效" -# Review: required -# Source-Hash: 28fe6e9deddde6e8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "pos" +# Source-Hash: 28fe6e9deddde6e8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.pos" msgstr "位置" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.position" msgstr "位置" -# Review: required -# Source-Hash: 3fce677837d5a876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reset this part" +# Source-Hash: 3fce677837d5a876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/procedural-items/inspector.tsx #, fuzzy msgid "editor.proceduralItems.inspector.resetThisPart" msgstr "重置此部件" +# English: "Reload editor" # Source-Hash: 5eab376947d29a50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reload editor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.reloadEditor" msgstr "重新加载编辑器" -# Review: required -# Source-Hash: 1063f1e315258b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close riser diagram" +# Source-Hash: 1063f1e315258b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.close" msgstr "关闭立管图" -# Review: required -# Source-Hash: c2d59ee62035974c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV plumbing isometric" +# Source-Hash: c2d59ee62035974c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvPlumbingIsometric" msgstr "排水与通气管道轴测图" -# Review: required -# Source-Hash: 3501a7c8a93d3b19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "DWV riser diagram" +# Source-Hash: 3501a7c8a93d3b19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.dwvRiserDiagram" msgstr "排水通气立管图" -# Review: required -# Source-Hash: 225e3e3dc3bb01ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No drain, waste, or vent pipes yet. Draw plumbing to see the riser diagram." +# Source-Hash: 225e3e3dc3bb01ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.noDrainWasteOrVentPipes" msgstr "尚无排水、污水或通气管道。绘制管道后即可查看立管图。" -# Review: required -# Source-Hash: bc8e172ba55160ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Riser Diagram" +# Source-Hash: bc8e172ba55160ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.riserDiagram" msgstr "立管图" -# Review: required -# Source-Hash: 6a1132961f17f320 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" P-trap" +# Source-Hash: 6a1132961f17f320 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.trap" msgstr "{diameter}\" 存水弯" -# Review: required -# Source-Hash: 04bc0909a133a7e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vent" +# Source-Hash: 04bc0909a133a7e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.vent" msgstr "通风口" -# Review: required -# Source-Hash: 02f3eef9f0fb6d9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" VTR" +# Source-Hash: 02f3eef9f0fb6d9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Vent through roof: where a plumbing vent stack exits the roof. #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.ventTermination" msgstr "{diameter}\" 出屋面通气口" -# Review: required -# Source-Hash: 29d8590334492601 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waste" +# Source-Hash: 29d8590334492601 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/riser-diagram-panel.tsx #, fuzzy msgid "editor.riserDiagramPanel.waste" msgstr "排污" +# English: "{current} · {distance} above {shared}" # Source-Hash: 0f8d128255bf4afa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} above {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.above" msgstr "{current} · 高于{shared} {distance}" +# English: "{current} · {distance} below {shared}" # Source-Hash: 8416f433385a2936 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · {distance} below {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.below" msgstr "{current} · 低于{shared} {distance}" +# English: "{current} · level with {shared}" # Source-Hash: 6dc18e9fada391f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{current} · level with {shared}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-built-on.ts #, fuzzy msgid "editor.room.builtOn.levelWith" msgstr "{current} · 与{shared}齐平" -# Source-Hash: 612dcd5bf06a79c4 +# English: "Cannot read \"{input}\"" +# Source-Hash: 782bc47cb30d5c66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.cannotReadInput" +msgstr "无法识别“{input}”" + +# English: "Dimension value" +# Source-Hash: 70ca08939d758332 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.dimensionValue" +msgstr "尺寸值" + +# English: "Floor" +# Source-Hash: d7148fa065bc709b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.floor" +msgstr "楼面" + +# English: "Type a size to move the wall" +# Source-Hash: de281c37906e7d14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/room-handles.tsx +#, fuzzy +msgid "editor.roomHandles.typeASizeToMoveThe" +msgstr "输入尺寸以移动墙体" + # English: "Room" +# Source-Hash: 612dcd5bf06a79c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.room" msgstr "房间" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/room-labels.tsx #: packages/editor/src/lib/viewer-selection.ts #, fuzzy msgid "editor.roomLabel.zone" msgstr "区域" +# English: "Attic" # Source-Hash: 7da15e7c88dab485 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attic" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.attic" msgstr "阁楼" +# English: "Balcony" # Source-Hash: 52adf6ccf54fd772 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balcony" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.balcony" msgstr "阳台" +# English: "Basement" # Source-Hash: 8caa959c0ae800c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Basement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.basement" msgstr "地下层" +# English: "Bathroom" # Source-Hash: 0655333424a8b6fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bathroom" msgstr "浴室" +# English: "Bedroom" # Source-Hash: f25239fe3891ea8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.bedroom" msgstr "卧室" +# English: "Boiler room" # Source-Hash: 77d15570e2eba719 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boiler room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.boilerRoom" msgstr "锅炉房" +# English: "Carport" # Source-Hash: 5fbe77592f8f0ca9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Carport" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.carport" msgstr "车棚" +# English: "Cellar" # Source-Hash: f31dc305ce3a803a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cellar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.cellar" msgstr "地窖" +# English: "Closet" # Source-Hash: e9af4141e3f8bdd1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.closet" msgstr "壁橱" +# English: "Corridor" # Source-Hash: 388530b7b9c6647d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corridor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.corridor" msgstr "走廊" +# English: "Courtyard" # Source-Hash: 4d4a23b87e911d2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Courtyard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.courtyard" msgstr "庭院" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.deck" msgstr "平台" +# English: "Den" # Source-Hash: 18d7f85ff03c43c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Den" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.den" msgstr "书房" +# English: "Dining room" # Source-Hash: 4424343cad1f08b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dining room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.diningRoom" msgstr "餐厅" +# English: "Dressing room" # Source-Hash: 5efd0185e89ee84e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dressing room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.dressingRoom" msgstr "更衣室" +# English: "En-suite" # Source-Hash: f45768e27780dc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "En-suite" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.enSuite" msgstr "套内卫生间" +# English: "Entrance" # Source-Hash: 51c664d19e5ea703 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Entrance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.entrance" msgstr "入口" +# English: "Family room" # Source-Hash: 4b32a81a5abf006a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Family room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.familyRoom" msgstr "家庭活动室" +# English: "Gallery" # Source-Hash: e87e6bd9a7d6d667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gallery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gallery" msgstr "回廊" +# English: "Games room" # Source-Hash: cf7cc0ba06d5eea1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Games room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gamesRoom" msgstr "游戏室" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garage" msgstr "车库" +# English: "Garden" # Source-Hash: 5073b3c2989a626d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.garden" msgstr "花园" +# English: "Garden shed" # Source-Hash: 52fe13434cdb7b27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garden shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gardenShed" msgstr "园艺棚" +# English: "Guest room" # Source-Hash: a2712156dcaec1ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guest room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.guestRoom" msgstr "客房" +# English: "Gym" # Source-Hash: a1128cf188432bb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gym" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.gym" msgstr "健身房" +# English: "Hallway" # Source-Hash: 18664ee5a1abd06d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hallway" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.hallway" msgstr "门厅" +# English: "Home cinema" # Source-Hash: 7dd957fc73d4d73a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Home cinema" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.homeCinema" msgstr "家庭影院" +# English: "Kids' room" # Source-Hash: e1579515f44233ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kids' room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kidsRoom" msgstr "儿童房" +# English: "Kitchen" # Source-Hash: 1176d2568d093bf1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchen" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchen" msgstr "厨房" +# English: "Kitchenette" # Source-Hash: 06eb4c431b9b09b6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kitchenette" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.kitchenette" msgstr "小厨房" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.landing" msgstr "平台" +# English: "Laundry" # Source-Hash: 558bcafa2ec70ded # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Laundry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.laundry" msgstr "洗衣房" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.library" msgstr "素材库" +# English: "Living room" # Source-Hash: 2204538bab0e77ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.livingRoom" msgstr "客厅" +# English: "Loft" # Source-Hash: bb14c08f0c3db4ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.loft" msgstr "阁楼" +# English: "Lounge" # Source-Hash: a6a0e8bf816cbcb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lounge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.lounge" msgstr "休息室" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mezzanine" msgstr "夹层" +# English: "Mudroom" # Source-Hash: 6b2c82e5fbccb28b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mudroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.mudroom" msgstr "玄关储物间" +# English: "Nursery" # Source-Hash: e3c3b9b917744680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nursery" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.nursery" msgstr "婴儿房" +# English: "Office" # Source-Hash: c1f1fcd9cb418340 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Office" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.office" msgstr "办公室" +# English: "Pantry" # Source-Hash: 5d98d41ec10bd05e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pantry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.pantry" msgstr "食品储藏室" +# English: "Patio" # Source-Hash: 78dbe13192c0894b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Patio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.patio" msgstr "露台" +# English: "Playroom" # Source-Hash: c3cfcccc167b1c19 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Playroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.playroom" msgstr "游戏室" +# English: "Pool area" # Source-Hash: 2e6324beaed18dd5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pool area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.poolArea" msgstr "泳池区" +# English: "Porch" # Source-Hash: 97d20e41044a3235 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Porch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.porch" msgstr "门廊" +# English: "Powder room" # Source-Hash: 04cb81f4a4ae4ff3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Powder room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.powderRoom" msgstr "客用卫生间" +# English: "Primary bedroom" # Source-Hash: 599c44d857832cd0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Primary bedroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.primaryBedroom" msgstr "主卧" +# English: "Reading nook" # Source-Hash: 9a6ac3f1cdd8de2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reading nook" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.readingNook" msgstr "阅读角" +# English: "Bathrooms" # Source-Hash: 01f8cc22989822c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bathrooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.bathrooms" msgstr "浴室" +# English: "Circulation" # Source-Hash: 9ec82561de8cb788 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Circulation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.circulation" msgstr "交通空间" +# English: "Living" # Source-Hash: f74a48b6b49644d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Living" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.living" msgstr "起居" +# English: "Mezzanine" # Source-Hash: 194a147d4618a994 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mezzanine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.mezzanine" msgstr "夹层" +# English: "Outdoor" # Source-Hash: a5a7af5e5fff1c81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outdoor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.outdoor" msgstr "室外" +# English: "Sleeping" # Source-Hash: 5df703d5b5e6ef7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sleeping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.sleeping" msgstr "睡眠" +# English: "Utility & storage" # Source-Hash: c9d15ec19f814524 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility & storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.utility" msgstr "杂物与储藏" +# English: "Work & hobbies" # Source-Hash: fbe13f7ab45e4cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Work & hobbies" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.section.work" msgstr "工作与爱好" +# English: "Shower room" # Source-Hash: 10ccb6591f6ad207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shower room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.showerRoom" msgstr "淋浴间" +# English: "Stairwell" # Source-Hash: 5254168cc5712cb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stairwell" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.stairwell" msgstr "楼梯间" +# English: "Storage" # Source-Hash: d1596647cd84ade3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Storage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.storage" msgstr "储藏" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Studio" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.studio" msgstr "影棚" +# English: "Study" # Source-Hash: ebe68e83e73021ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Study" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.study" msgstr "书房" +# English: "Sunroom" # Source-Hash: d69a0244649771ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.sunroom" msgstr "阳光房" +# English: "Terrace" # Source-Hash: 96281876ce53ba08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.terrace" msgstr "露台" +# English: "Toilet" # Source-Hash: a0cb3e6f5cf4fbec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Toilet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.toilet" msgstr "卫生间" +# English: "Utility room" # Source-Hash: 6c01526f89f3106b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Utility room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.utilityRoom" msgstr "杂物间" +# English: "Veranda" # Source-Hash: fc42d89621ae7bac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veranda" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.veranda" msgstr "游廊" +# English: "Walk-in closet" # Source-Hash: b45d7730e896fd99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walk-in closet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.walkInCloset" msgstr "步入式衣帽间" +# English: "Workshop" # Source-Hash: 399f27253ef956cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Workshop" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/lib/room-name-messages.ts #, fuzzy msgid "editor.roomName.workshop" msgstr "工作间" +# English: "Settings" # Source-Hash: 83909f82446ee9c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Settings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.settings" msgstr "设置" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/icon-rail.tsx #, fuzzy msgid "editor.sidebar.iconRail.site" msgstr "场地" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.all" msgstr "全部" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.community" msgstr "社区" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.all" msgstr "全部" +# English: "Community" # Source-Hash: fce9814c289e6b71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Community" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.community" msgstr "社区" +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.library" msgstr "素材库" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.mine" msgstr "我的" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch" msgstr "没有“{search}”的结果" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.functionTreePanel.search" msgstr "搜索..." +# English: "Library" # Source-Hash: 8d211c78417ed1b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Library" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.library" msgstr "素材库" +# English: "Mine" # Source-Hash: a0b7adfdfdec7307 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.mine" msgstr "我的" +# English: "No results for “{search}”" # Source-Hash: 6024b7778b392ec3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No results for “{search}”" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.noResultsForSearch" msgstr "没有“{search}”的结果" +# English: "Search..." # Source-Hash: 4ceb6dcb7827db7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Search..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/items-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.itemsPanel.search" msgstr "搜索..." -# Source-Hash: 3c67cde0578ac829 +# English: "Build Tools" +# Source-Hash: fb66c49eee49a3bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools" +msgstr "建造工具" + +# English: "Camera" +# Source-Hash: 14622745dbf93f4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera" +msgstr "相机" + +# English: "Direct Manipulation" +# Source-Hash: 677783232bea72a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation" +msgstr "直接操作" + +# English: "Drawing Tools" +# Source-Hash: 8e67947d43b76596 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools" +msgstr "绘图工具" + +# English: "Editor Navigation" +# Source-Hash: 8cdda30f3be2bac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation" +msgstr "编辑器导航" + +# English: "Item Placement" +# Source-Hash: 9a996e3570d615ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement" +msgstr "物品放置" + +# English: "Keyboard shortcuts" +# Source-Hash: fc18926f9bd9eebd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts" +msgstr "键盘快捷键" + +# English: "Modes & History" +# Source-Hash: cbb4c3e007d70aa0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory" +msgstr "模式与历史记录" + +# English: "Selection" +# Source-Hash: 170b2049ef0f9131 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection" +msgstr "选择" + +# English: "Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift during an active gesture to build freely." +# Source-Hash: 8a708ce860d787e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +#, fuzzy +msgid "editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints" +msgstr "快捷键随上下文而变化。引导约束默认开启;在操作进行中按住 Shift 可自由建造。" + # English: "Add level" +# Source-Hash: 3c67cde0578ac829 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addLevel" msgstr "添加楼层" +# English: "Add point" # Source-Hash: 9a56d90d461b4bca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.addPoint" msgstr "添加点" +# English: "Area:" # Source-Hash: fedccec504a9ae33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.area" msgstr "面积:" +# English: "Base elevation" # Source-Hash: 87b6880f6de7a45a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.baseElevation" msgstr "基准标高" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.building" msgstr "建筑" +# English: "Add new level" # Source-Hash: 7c90f458259d5cd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add new level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel" msgstr "添加新楼层" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.buildingTreeNode.building" msgstr "建筑" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.cameraSnapshot" msgstr "相机快照" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.capture" msgstr "采集" +# English: "Clear selection" # Source-Hash: 71f2343818c55e0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSelection" msgstr "清除选择" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.clearSnapshot" msgstr "清除快照" +# English: "Close walls around a space to make a room." # Source-Hash: def517c77fda49ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close walls around a space to make a room." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo" msgstr "用墙体围合一个空间即可创建房间。" +# English: "Collection colour" # Source-Hash: 2b9af8da4b34cbfa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collection colour" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour" msgstr "集合颜色" +# English: "Collections" # Source-Hash: 135af27a14a4cda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Collections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.collections" msgstr "集合" +# English: "Delete collection" # Source-Hash: 6f4751c26f12aef9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete collection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection" msgstr "删除集合" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.delete" msgstr "删除" +# English: "Delete level" # Source-Hash: 8e29375893d63408 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.deleteLevel" msgstr "删除楼层" +# English: "Draw a zone" # Source-Hash: 6552e45eecc0a227 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw a zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.drawAZone" msgstr "绘制区域" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicate" msgstr "复制" +# English: "Duplicate level" # Source-Hash: 933177338f5a03ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevel" msgstr "复制楼层" +# English: "Duplicate level with options" # Source-Hash: c038947f317cabba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate level with options" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateLevelWithOptions" msgstr "带选项复制楼层" +# English: "Duplicate with options..." # Source-Hash: 9614c6825a7c67bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate with options..." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.duplicateWithOptions" msgstr "带选项复制..." +# English: "Elements" # Source-Hash: 7c4196b9e791c195 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.elements" msgstr "元素" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.guide" msgstr "参考图" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.hide" msgstr "隐藏" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.level" msgstr "楼层" +# English: "New unit" # Source-Hash: 13b3e6a6afe319d1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "New unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.newUnit" msgstr "新建单元" +# English: "No buildings yet" # Source-Hash: 0192748d50f0e5f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No buildings yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noBuildingsYet" msgstr "暂无建筑" +# English: "No elements on this level" # Source-Hash: 1be310702a12e433 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No elements on this level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noElementsOnThisLevel" msgstr "此楼层上没有元素" +# English: "No levels yet" # Source-Hash: 0956af88ced36992 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No levels yet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.noLevelsYet" msgstr "暂无楼层" +# English: "{count, plural, one {# object selected} other {# objects selected}}" # Source-Hash: 45c41bfb2ae19627 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{count, plural, one {# object selected} other {# objects selected}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.objectsSelected" msgstr "{count, plural, other {已选择 # 个对象}}" +# English: "Other zones" # Source-Hash: a5434e26e5cb1b26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.otherZones" msgstr "其他区域" +# English: "Perimeter:" # Source-Hash: 2aa38d84079d4dba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Perimeter:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.perimeter" msgstr "周长:" +# English: "Property Line" # Source-Hash: 4004b9a8e4f8c9d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Property Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.propertyLine" msgstr "地界线" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rooms" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.rooms" msgstr "房间" +# English: "Select a level to view content" # Source-Hash: ca39c0581c7318e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view content" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.selectALevelToViewContent" msgstr "选择楼层以查看内容" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.show" msgstr "显示" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.site" msgstr "场地" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.takeSnapshot" msgstr "拍摄快照" +# English: "The ground level cannot be deleted" # Source-Hash: d6b381247066a7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The ground level cannot be deleted" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted" msgstr "无法删除首层楼层" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.cameraSnapshot" msgstr "相机快照" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.clearSnapshot" msgstr "清除快照" +# English: "Hide" # Source-Hash: 591087ea83a48ead # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.hide" msgstr "隐藏" +# English: "Show" # Source-Hash: 827e0c6ac9936b69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.show" msgstr "显示" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.takeSnapshot" msgstr "拍摄快照" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.updateSnapshot" msgstr "更新快照" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.treeNodeActions.viewSnapshot" msgstr "查看快照" +# English: "Units" # Source-Hash: b404dded35241274 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Units" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.units" msgstr "单位" +# English: "Delete unit" # Source-Hash: 149fec3fd1f08042 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.deleteUnit" msgstr "删除单元" +# English: "Focused" # Source-Hash: 930399d3fb247a7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focused" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.focused" msgstr "已聚焦" +# English: "Remove from unit" # Source-Hash: 41ac9dd23bb558d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.removeFromUnit" msgstr "从单元中移除" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.room" msgstr "房间" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.unitTreeNode.zone" msgstr "区域" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.updateSnapshot" msgstr "更新快照" +# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" # Source-Hash: 28a12e6806c091b0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadingProgress" msgstr "{assetType, select, guide {正在上传参考底图... {progress}%} other {正在上传扫描模型... {progress}%}}" +# English: "Upload scan/floorplan" # Source-Hash: de389a2a46d53384 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Upload scan/floorplan" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.uploadScanFloorplan" msgstr "上传扫描模型/平面图" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.viewSnapshot" msgstr "查看快照" +# English: "In {unitName}" # Source-Hash: ba9f809cb032ee57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "In {unitName}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx #, fuzzy msgid "editor.sidebar.panels.sitePanel.zoneMembershipCheckbox.inUnitname" msgstr "在{unitName}中" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.actions" msgstr "操作" +# English: "Add one" # Source-Hash: fcc43f22937d1536 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add one" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.addOne" msgstr "添加一个" +# English: "Camera snapshot" # Source-Hash: bbc65c039ae86e11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Camera snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.cameraSnapshot" msgstr "相机快照" +# English: "Clear snapshot" # Source-Hash: 67d29724469fa8d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.clearSnapshot" msgstr "清除快照" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.delete" msgstr "删除" +# English: "Delete with contents" # Source-Hash: e2411929697b7ed4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete with contents" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.deleteWithContents" msgstr "连同内容一起删除" +# English: "No zones on this level." # Source-Hash: 22ca3935e8754072 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones on this level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.noZonesOnThisLevel" msgstr "此楼层上没有区域。" +# English: "Save to catalog" # Source-Hash: 941ea30f406dcfa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save to catalog" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.saveToCatalog" msgstr "保存到目录" +# English: "Select a level to view and create zones" # Source-Hash: 98662dfb371cac13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Select a level to view and create zones" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.selectALevelToViewAnd" msgstr "选择楼层以查看和创建区域" +# English: "Take snapshot" # Source-Hash: c40163620df24a76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Take snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.takeSnapshot" msgstr "拍摄快照" +# English: "Update snapshot" # Source-Hash: faecab10b69d4863 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Update snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.updateSnapshot" msgstr "更新快照" +# English: "View snapshot" # Source-Hash: 8a1fd8827a68f236 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View snapshot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/panels/zone-panel/index.tsx #, fuzzy msgid "editor.sidebar.panels.zonePanel.viewSnapshot" msgstr "查看快照" +# English: "\"{label}\" plugin crashed" # Source-Hash: 58b05af5b5724efb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "\"{label}\" plugin crashed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.labelPluginCrashed" msgstr "“{label}”插件已崩溃" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.loading" msgstr "加载中…" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.plugins" msgstr "插件" +# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." # Source-Hash: c6312309378aaaea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This panel hit an error and was unloaded for this session. The rest of the editor is unaffected — reload to try again." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/sidebar/use-plugin-panels.tsx #, fuzzy msgid "editor.sidebar.usePluginPanels.thisPanelHitAnErrorAnd" msgstr "此面板发生错误,已在本次会话中卸载。编辑器的其余部分不受影响——重新加载即可重试。" -# Review: required -# Source-Hash: 2ab3b6983e384ecc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ceiling attachment" +# Source-Hash: 2ab3b6983e384ecc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.ceiling" msgstr "吸附到天花板" -# Review: required -# Source-Hash: 54a00bbb699e3778 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof attachment" +# Source-Hash: 54a00bbb699e3778 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.roof" msgstr "吸附到屋顶" -# Review: required -# Source-Hash: e45fee9c27f6ae69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall attachment" +# Source-Hash: e45fee9c27f6ae69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/ui/snap-target-badge.tsx #, fuzzy msgid "editor.snapTarget.wall" msgstr "吸附到墙面" +# English: "The editor scene failed to render" # Source-Hash: 834de1a7027c4da1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The editor scene failed to render" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/index.tsx #, fuzzy msgid "editor.theEditorSceneFailedToRender" msgstr "编辑器场景渲染失败" +# English: "A curtain wall takes no corner window" # Source-Hash: 151f7c7e4110016f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curtain wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i" msgstr "幕墙不能设置转角窗" +# English: "A curved wall takes no corner window" # Source-Hash: 97f5e044d46501fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A curved wall takes no corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5" msgstr "曲面墙体不能设置转角窗" +# English: "Add a corner · click the first to close" # Source-Hash: 776d8ebece7fe1bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add a corner · click the first to close" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.addACornerClickTheFirst.o4r67t" msgstr "添加转角 · 点击第一个点闭合" -# Source-Hash: 6a0745dd047b149a +# English: "Add a wall · each one starts where the last ended" +# Source-Hash: d0ddef756fb49fbc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.addAWallEachOneStarts.4zkf12" +msgstr "添加墙体 · 每面墙体从上一面的终点开始" + # English: "Another opening is in the way at the corner" +# Source-Hash: 6a0745dd047b149a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0" msgstr "转角处有另一个开口阻挡" +# English: "Auto hangers" # Source-Hash: 441124187e7da2fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangers.10554ka" msgstr "自动吊架" +# English: "Auto hangers - click or press H to toggle" # Source-Hash: 47a7f9a6a81156c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers - click or press H to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersClickOrPressH.rwa3ka" msgstr "自动吊架 - 点击或按 H 切换" +# English: "Auto hangers: Off" # Source-Hash: d7944f31cba90e26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: Off" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOff.7yx01x" msgstr "自动吊架:关" +# English: "Auto hangers: On" # Source-Hash: 295882d015d08742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto hangers: On" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.autoHangersOn.10b1umh" msgstr "自动吊架:开" +# English: "Bypass snapping" # Source-Hash: 8c039e5182b01c4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bypass snapping" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.bypassSnapping.w5lmdv" msgstr "绕过吸附" +# English: "Cancel" # Source-Hash: c0774c185060fc13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancel.ew9em3" msgstr "取消" +# English: "Cancel move" # Source-Hash: 5b33b576fd30b74e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelMove.lvp31c" msgstr "取消移动" +# English: "Cancel run / exit" # Source-Hash: 136fa29b9ae419bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel run / exit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelRunExit.zqjf3h" msgstr "取消管段 / 退出" +# English: "Cancel start point" # Source-Hash: 75b406824be91ac2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel start point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cancelStartPoint.1xbq2lh" msgstr "取消起点" -# Source-Hash: ffa5aaa1746303c4 +# English: "Close the outline" +# Source-Hash: e9b0a295122e7ad4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.closeTheOutline.f6cvzi" +msgstr "闭合轮廓" + # English: "Corner window" +# Source-Hash: ffa5aaa1746303c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cornerWindow.1avkzpo" msgstr "转角窗" +# English: "Cuts" # Source-Hash: b0afdd855a02e4a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cuts.1y0axps" msgstr "切分" +# English: "Cuts: {value}" # Source-Hash: dc9fe7601cc525f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValue.ouzhvb" msgstr "切分:{value}" +# English: "Cuts: {value}, even" # Source-Hash: a2e13bc4dc82624c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cuts: {value}, even" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cutsValueEven.1ngsz3" msgstr "切分:{value},均分" +# English: "Cycle mono / gable / butterfly" # Source-Hash: 04152b4cec726f34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle mono / gable / butterfly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleMonoGableButterfly.1poga3m" msgstr "切换单坡 / 双坡 / 蝶形" +# English: "Cycle snapping mode" # Source-Hash: 4fb6c83edb6652b5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cycle snapping mode" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.cycleSnappingMode.1ct0gn6" msgstr "切换吸附模式" +# English: "Door style — click or press L to cycle" # Source-Hash: 5d550a3ac76279de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorStyleClickOrPressL.33pe65" msgstr "门样式 — 点击或按 L 切换" +# English: "Door type — click or press O to cycle" # Source-Hash: 3272a7df5d7c6a9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.doorTypeClickOrPressO.1hhh8gt" msgstr "门类型 — 点击或按 O 切换" +# English: "Drop downspout from outlet" # Source-Hash: 366222b4435eefda # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drop downspout from outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.dropDownspoutFromOutlet.1k4h1m4" msgstr "从出水口引出落水管" +# English: "Duct diameter down / up" # Source-Hash: 03f306371f345007 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct diameter down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.ductDiameterDownUp.1wdz5vq" msgstr "风管直径减小 / 增大" +# English: "End run / cancel" # Source-Hash: 7b4137e27f8327d9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End run / cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.endRunCancel.59dh2w" msgstr "结束管段 / 取消" -# Source-Hash: 938aacb119b29dc1 +# English: "End the run" +# Source-Hash: b714e91c31108a71 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.endTheRun.1c1raig" +msgstr "结束连续绘制" + # English: "Exit" +# Source-Hash: 938aacb119b29dc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exit.engmg5" msgstr "退出" +# English: "Exit drawing" # Source-Hash: 7cb4e0688cabcb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exit drawing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.exitDrawing.9noxyb" msgstr "退出绘制" +# English: "Finish and continue" # Source-Hash: 47025a103c456125 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish and continue" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishAndContinue.1gpfsks" msgstr "完成并继续" +# English: "Finish ceiling" # Source-Hash: 000918429b74128f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishCeiling.1c1lfel" msgstr "完成天花板" +# English: "Finish grid axis" # Source-Hash: 51a80a0dba5a6f30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishGridAxis.1wvrfhr" msgstr "完成轴线" +# English: "Finish measurement" # Source-Hash: 87c7b98fc2e010e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMeasurement.1e4gv16" msgstr "完成测量" +# English: "Finish multi-point witnesses" # Source-Hash: 5e1a0af7dc506826 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish multi-point witnesses" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishMultiPointWitnesses.8wzskz" msgstr "完成多点参照" +# English: "Finish slab" # Source-Hash: 30fc0862501ba2e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finish slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.finishSlab.xmnm8e" msgstr "完成楼板" -# Source-Hash: 66ed7c7598d3aa29 +# English: "Finish the run" +# Source-Hash: 54ae97157eacbc56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.finishTheRun.ayliww" +msgstr "完成连续绘制" + # English: "Flip side" +# Source-Hash: 66ed7c7598d3aa29 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.flipSide.172xku9" msgstr "翻转方向" +# English: "Follow: trace a lineset" # Source-Hash: f5717f6a0905ffab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follow: trace a lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.followTraceALineset.mgquwe" msgstr "跟随:沿冷媒管组布线" +# English: "Force place" # Source-Hash: ed7f3d8417a3e074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Force place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.forcePlace.1hq1nr" msgstr "强制放置" -# Source-Hash: cc0c14065951444d +# English: "Free angle" +# Source-Hash: 64b4ac6d3da25183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freeAngle.1ptblho" +msgstr "自由角度" + +# English: "Free place" +# Source-Hash: 446b8aac1e5e8422 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.freePlace.1s1uusw" +msgstr "自由放置" + # English: "Free placement" +# Source-Hash: cc0c14065951444d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.freePlacement.1nztdac" msgstr "自由放置" +# English: "Go vertical ↕, click to place" # Source-Hash: f098a27096a9c1ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Go vertical ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.goVerticalClickToPlace.dg8g6m" msgstr "切换为竖向 ↕,点击放置" +# English: "Highlight outlet" # Source-Hash: fd47f1820d8799c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Highlight outlet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.highlightOutlet.65hoq8" msgstr "高亮出水口" +# English: "Join as a corner window" # Source-Hash: eb90058d0f856f67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Join as a corner window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.joinAsACornerWindow.jvdp4j" msgstr "连接为转角窗" +# English: "Alt + drag" # Source-Hash: 225b3161f2e59757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alt + drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.altDrag.ai3wn9" msgstr "Alt + 拖动" +# English: "Backspace" # Source-Hash: dd66f883b173fc72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Backspace" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.backspace.qy6qx8" msgstr "退格键" +# English: "Click" # Source-Hash: a18387bc0be54c32 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.click.m8sun" msgstr "点击" +# English: "Click again" # Source-Hash: db9e60c1d30b8499 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Click again" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.clickAgain.190ws9j" msgstr "再次点击" +# English: "Command" # Source-Hash: f400330b9c9c12e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Command" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.command.1j2mkia" msgstr "Command" +# English: "Control" # Source-Hash: 976c63285a510b09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Control" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.control.10fbl8e" msgstr "Ctrl" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.delete.oay2cq" msgstr "删除" +# English: "Double click" # Source-Hash: 88888c9d2719fa92 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.doubleClick.vlma6o" msgstr "双击" +# English: "Drag" # Source-Hash: dfb85b2addb7b9fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.drag.8pwtpf" msgstr "拖动" +# English: "Enter" # Source-Hash: ed9140d42148c99b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.enter.1ctpui5" msgstr "回车键" +# English: "Hover" # Source-Hash: 9195fa8b9c8ebdfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hover.6xw79p" msgstr "悬停" +# English: "Hover a duct end" # Source-Hash: 2d78441b741bc562 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a duct end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverADuctEnd.19axr3b" msgstr "悬停在风管端部" +# English: "Hover a pipe end" # Source-Hash: 6083405086f61c76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover a pipe end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverAPipeEnd.1jbum5f" msgstr "悬停在管道端部" +# English: "Hover gutter" # Source-Hash: a5a337eb76fdb066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hover gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.hoverGutter.1dwcsno" msgstr "悬停在檐沟上" +# English: "Left click" # Source-Hash: a58533594411e088 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.leftClick.s4gwfc" msgstr "左键单击" +# English: "Middle click" # Source-Hash: bc4ab608ec3d3b4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Middle click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.middleClick.1jbaan4" msgstr "中键单击" +# English: "Right click" # Source-Hash: 6791879c651108c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right click" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rightClick.1ovld0h" msgstr "右键单击" -# Source-Hash: c836b21b42198868 +# English: "Room" +# Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.key.room.1f3jkpm" +msgstr "房间" + # English: "R / Shift+R" +# Source-Hash: c836b21b42198868 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.rShiftR.13bnobh" msgstr "R / Shift+R" +# English: "Scroll" # Source-Hash: 9812aa06d8bb7544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scroll.ai6wr0" msgstr "滚动" +# English: "Scroll wheel" # Source-Hash: f2174536230657c8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scroll wheel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.scrollWheel.18j0ix7" msgstr "滚轮" +# English: "Space" # Source-Hash: 82dd231086d87f3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Space" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.space.1hrh82t" msgstr "空格键" +# English: "Tab" # Source-Hash: 9e68aa2f472cad56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.key.tab.1xsal0s" msgstr "Tab 键" +# English: "Mount: floor / ceiling / wall" # Source-Hash: 4b221b73f9496a49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mount: floor / ceiling / wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.mountFloorCeilingWall.32dxpv" msgstr "安装位置:地面 / 天花板 / 墙体" +# English: "No room for the matching window on the other wall" # Source-Hash: 225edfb1e45aed95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No room for the matching window on the other wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noRoomForTheMatchingWindow.1755948" msgstr "另一面墙上没有空间放置对应的窗" -# Source-Hash: a7045d6b678bcd13 +# English: "Not a rectangle: draw this roof instead" +# Source-Hash: e367d28a655f1cca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.notARectangleDrawThisRoof.100pa5g" +msgstr "非矩形:请改为绘制此屋顶" + # English: "No wall meets here" +# Source-Hash: a7045d6b678bcd13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.noWallMeetsHere.19n99yc" msgstr "此处没有相交的墙体" +# English: "Number of cuts — scroll or click to change" # Source-Hash: a639341dc21fed2c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Number of cuts — scroll or click to change" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.numberOfCutsScrollOrClick.1kqe84b" msgstr "切分数量 — 滚动或点击更改" +# English: "Pick witness point" # Source-Hash: af01bde3ae4b7e59 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pick witness point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pickWitnessPoint.k3hgh1" msgstr "拾取参照点" +# English: "Pipe size down / up" # Source-Hash: ecfd58576b28a9f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe size down / up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.pipeSizeDownUp.wp04og" msgstr "管径减小 / 增大" -# Source-Hash: 7a1eb7b64f06bad0 +# English: "Place a corner of the foundation" +# Source-Hash: e0162c885a961919 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j" +msgstr "放置基础的一个角点" + # English: "Place and continue" +# Source-Hash: 7a1eb7b64f06bad0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeAndContinue.qfwkrq" msgstr "放置并继续" +# English: "Place block" # Source-Hash: c6cda39e2e4b5d3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBlock.1aylwrx" msgstr "放置体块" +# English: "Place box vent on roof" # Source-Hash: 45a743c52a1d72de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place box vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeBoxVentOnRoof.kf88e5" msgstr "在屋顶上放置箱式通风口" +# English: "Place cabinet" # Source-Hash: 75142dd1d557efe2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCabinet.b0ouyk" msgstr "放置橱柜" +# English: "Place canopy or set the next run point" # Source-Hash: da603a713d4c9909 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place canopy or set the next run point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCanopyOrSetTheNext.12l7zf8" msgstr "放置雨棚或设置下一个连续点" +# English: "Place chimney on roof" # Source-Hash: 8b3a30c6b7a5612d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place chimney on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeChimneyOnRoof.19dbqu" msgstr "在屋顶上放置烟囱" +# English: "Place column" # Source-Hash: 710d41e4e2f259fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeColumn.1w2vq86" msgstr "放置柱" +# English: "Place cupola on roof" # Source-Hash: ee1da4e12a71d9a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place cupola on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeCupolaOnRoof.ofx3bn" msgstr "在屋顶上放置穹顶亭" +# English: "Place dimension line when needed" # Source-Hash: 19c1fe8c46d213a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dimension line when needed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDimensionLineWhenNeeded.bjw2cj" msgstr "需要时放置尺寸线" +# English: "Place door on wall" # Source-Hash: 58f3e7efb6ea120b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place door on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDoorOnWall.1jwadf" msgstr "在墙体上放置门" +# English: "Place dormer on roof" # Source-Hash: de04e9d03d937c97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place dormer on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeDormerOnRoof.1w518a6" msgstr "在屋顶上放置老虎窗" +# English: "Place elevator" # Source-Hash: c581645f2ee447c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeElevator.1f65zva" msgstr "放置电梯" +# English: "Place eyebrow vent on roof" # Source-Hash: 096b8d6482717358 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place eyebrow vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeEyebrowVentOnRoof.1h1tqxn" msgstr "在屋顶上放置眉形通风口" +# English: "Place fitting" # Source-Hash: e8d42c1c8370773f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeFitting.1pmx3vn" msgstr "放置管件" +# English: "Place gutter on roof eave" # Source-Hash: 2340a4b70cb0d9ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place gutter on roof eave" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeGutterOnRoofEave.llwzf9" msgstr "在屋檐上放置檐沟" +# English: "Place item" # Source-Hash: 706a8eafb6212833 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItem.teynf" msgstr "放置物品" +# English: "Place it (locked to 45°)" # Source-Hash: 1815c482e249932e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place it (locked to 45°)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeItLockedTo45.16e4csk" msgstr "放置(锁定为 45°)" +# English: "Place measurement point" # Source-Hash: 4a14340657532838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place measurement point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeMeasurementPoint.bsatli" msgstr "放置测量点" +# English: "Placement" # Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placement.13lcvts" msgstr "放置" +# English: "Placement: Auto" # Source-Hash: 2bb2ce6276c69b1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementAuto.1caa52d" msgstr "放置:自动" +# English: "Placement: Ground" # Source-Hash: ed78ac5354a1dcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementGround.1ruz145" msgstr "放置:地面" +# English: "Placement: Roof" # Source-Hash: a066a2c9d5e65a1a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement: Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementRoof.renkw8" msgstr "放置:屋顶" +# English: "Placement surface - click or press P to cycle" # Source-Hash: 8f74dbe5064c4c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement surface - click or press P to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementSurfaceClickOrPressP.w56h19" msgstr "放置面 - 点击或按 P 切换" +# English: "Placement type" # Source-Hash: fe119955c925feaf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementType.1mdpl60" msgstr "放置类型" +# English: "Placement type — click or press I to toggle" # Source-Hash: 33043a35500168f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Placement type — click or press I to toggle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placementTypeClickOrPressI.10cgxh2" msgstr "放置类型 — 点击或按 I 切换" +# English: "Place on a fence" # Source-Hash: 9945f412e6a9a446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place on a fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeOnAFence.1dptstr" msgstr "放置在围栏上" +# English: "Place register" # Source-Hash: 6708118775a7da7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRegister.1uebycd" msgstr "放置风口" +# English: "Place ridge vent on roof" # Source-Hash: ebd2731f5c79306e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place ridge vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeRidgeVentOnRoof.2tpd8r" msgstr "在屋顶上放置屋脊通风口" +# English: "Place shelf" # Source-Hash: 9b3c66a867c23326 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeShelf.y4l3iw" msgstr "放置置物架" +# English: "Place skylight on roof" # Source-Hash: f11cf5c1c9fe862d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place skylight on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSkylightOnRoof.e215pe" msgstr "在屋顶上放置天窗" +# English: "Place solar panel array on roof" # Source-Hash: e989a58d6709cc16 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place solar panel array on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSolarPanelArrayOnRoof.kk11fn" msgstr "在屋顶上放置光伏板阵列" +# English: "Place spawn point" # Source-Hash: b67f7036d5180cf5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeSpawnPoint.205o25" msgstr "放置出生点" +# English: "Place stairs" # Source-Hash: 6c7f32a17e5636c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place stairs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeStairs.zwxxu2" msgstr "放置楼梯" +# English: "Place trap" # Source-Hash: 641c0d3debd798a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTrap.1gxemn3" msgstr "放置存水弯" +# English: "Place turbine vent on roof" # Source-Hash: 6f0a6f6c4b7588a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place turbine vent on roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeTurbineVentOnRoof.1iqeluf" msgstr "在屋顶上放置涡轮通风器" +# English: "Place unit" # Source-Hash: e65cf5edb4f51ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeUnit.8zkx4q" msgstr "放置设备" +# English: "Place window on wall" # Source-Hash: 86ce1c730154f68b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place window on wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWindowOnWall.1k3qnfb" msgstr "在墙体上放置窗" +# English: "Place without the corner snap" # Source-Hash: 2b612180a7617901 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Place without the corner snap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.placeWithoutTheCornerSnap.1nifqew" msgstr "不使用转角吸附放置" -# Source-Hash: 0dc6d121791696f7 +# English: "Point inside a room, porch or deck" +# Source-Hash: dd2ea442152dca5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.pointInsideARoomPorchOr.58w64l" +msgstr "指向房间、门廊或平台内部" + # English: "Remove last point" +# Source-Hash: 0dc6d121791696f7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastPoint.2dmdcx" msgstr "移除最后一个点" +# English: "Remove last witness" # Source-Hash: 8b74b1d962be10fc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove last witness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.removeLastWitness.1b6vvzw" msgstr "移除最后一个参照" -# Source-Hash: 306d409f8a1cf480 +# English: "Ridge direction" +# Source-Hash: 1185bd2407dd9172 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirection.5m1369" +msgstr "屋脊方向" + +# English: "Ridge direction - click or press R to turn it 90°" +# Source-Hash: 4352028e50450055 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk" +msgstr "屋脊方向 - 点击或按 R 旋转 90°" + +# English: "Ridge: Long side" +# Source-Hash: 96c889f047db9de5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeLongSide.ew0euv" +msgstr "屋脊:长边" + +# English: "Ridge: Short side" +# Source-Hash: 58143198d3f2a265 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.ridgeShortSide.gye8pb" +msgstr "屋脊:短边" + # English: "Rotate" +# Source-Hash: 306d409f8a1cf480 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate.1thqa0a" msgstr "旋转" +# English: "Rotate ±45°" # Source-Hash: 8a665ecaeffa9308 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45.9zq68u" msgstr "旋转 ±45°" +# English: "Rotate ±45° (floor / ceiling)" # Source-Hash: be2240289aa74df5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ±45° (floor / ceiling)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotate45FloorCeiling.m9t57d" msgstr "旋转 ±45°(地面 / 天花板)" +# English: "Rotate ghost ±15°" # Source-Hash: d5fb78b07c0f8138 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate ghost ±15°" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateGhost15.rsa52c" msgstr "旋转预览 ±15°" +# English: "Rotate or flip the run side" # Source-Hash: 912d340cd16e27fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate or flip the run side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateOrFlipTheRunSide.t5ior7" msgstr "旋转或翻转排布方向" -# Source-Hash: ad1dc0b5536cceef -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate roof direction 90°" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.rotateRoofDirection90.tvbji2" -msgstr "将屋顶方向旋转 90°" - +# English: "Rotate spawn point" # Source-Hash: fa24651e9e5f595a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotate spawn point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.rotateSpawnPoint.1opcuu7" msgstr "旋转出生点" +# English: "Round / rectangular / oval" # Source-Hash: 2d693d87fe9b59e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round / rectangular / oval" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.roundRectangularOval.e4nh6x" msgstr "圆形 / 矩形 / 椭圆" -# Source-Hash: f7d2c9cdcc637c97 +# English: "Set beam start / end; the next beam starts there" +# Source-Hash: ee4ca2612f1ea085 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setBeamStartEndTheNext.di3bzj" +msgstr "设置梁的起点 / 终点;下一根梁从该处开始" + # English: "Set fence start / end" +# Source-Hash: f7d2c9cdcc637c97 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setFenceStartEnd.1phbwsq" msgstr "设置围栏起点 / 终点" -# Source-Hash: c93ae898b4d59f6f +# English: "Set footing start / end, or place a pad" +# Source-Hash: b9693a8a1f7cd6c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "editor.toolHint.setFootingStartEndOrPlace.nnv1av" +msgstr "设置基脚起点 / 终点,或放置独立基脚" + # English: "Set one corner, then the opposite" +# Source-Hash: c93ae898b4d59f6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setOneCornerThenTheOpposite.8tsl49" msgstr "设置一个角,再设置对角" +# English: "Set roof footprint" # Source-Hash: 4f55171f10c6ceeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set roof footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.setRoofFootprint.p50n0o" msgstr "设置屋顶轮廓" -# Source-Hash: 0243859b1d64c21e -# Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set wall start / end" -#: packages/nodes/src/i18n/definition-messages.ts -#, fuzzy -msgid "editor.toolHint.setWallStartEnd.1r8ubcx" -msgstr "设置墙体起点 / 终点" - +# English: "Slope / level" # Source-Hash: c2806cce9612fa50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slope / level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.slopeLevel.ftqp67" msgstr "坡度 / 水平" +# English: "Snap onto the run" # Source-Hash: 06c0ec40606db679 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap onto the run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.snapOntoTheRun.86yhmp" msgstr "吸附到管段上" +# English: "Split at the marks" # Source-Hash: e214cccbac9c4fd2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split at the marks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.splitAtTheMarks.x5tpdd" msgstr "在标记处分割" +# English: "Start grid axis" # Source-Hash: 94455c35bfeac6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start grid axis" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startGridAxis.k92yza" msgstr "开始轴线" +# English: "Start lineset" # Source-Hash: 2edb4c29bbe7c513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLineset.23q8dh" msgstr "开始冷媒管组" +# English: "Start liquid line" # Source-Hash: 9e50f1b30e325208 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start liquid line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startLiquidLine.1rlxsgd" msgstr "开始液管" +# English: "Start run" # Source-Hash: 877ddcb778c211cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start run" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startRun.r6fc5u" msgstr "开始管段" +# English: "Start segment" # Source-Hash: 8a62bee64174bbc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.startSegment.rolym6" msgstr "开始分段" +# English: "Step back or cancel" # Source-Hash: 6cc5dc51a8e66779 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step back or cancel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stepBackOrCancel.zbfdkn" msgstr "后退一步或取消" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.style.nf3vcm" msgstr "样式" +# English: "Style: Barn" # Source-Hash: 81f595b1c47c0080 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleBarn.k9n1x" msgstr "样式:谷仓门" +# English: "Style: Casement" # Source-Hash: 3a82c718fe215273 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCasement.ziklec" msgstr "样式:平开" +# English: "Style: Craftsman" # Source-Hash: 072e3331d1d105c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleCraftsman.52e6a9" msgstr "样式:工匠" +# English: "Style: Double-hung" # Source-Hash: b64d07557dec1714 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleDoubleHung.1lg8pkg" msgstr "样式:双悬" +# English: "Style: French" # Source-Hash: 55ef8761a537637f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleFrench.1ue3j64" msgstr "样式:法式" +# English: "Style: Glass" # Source-Hash: bc8a751d87536dc3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGlass.qudnb4" msgstr "样式:玻璃" +# English: "Style: Grid" # Source-Hash: 40c1eb0bc83e207d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleGrid.1vuq4po" msgstr "样式:格栅" +# English: "Style: Half-louvered" # Source-Hash: f516886bab2e7f10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHalfLouvered.13b1few" msgstr "样式:半百叶" +# English: "Style: Horizontal bands" # Source-Hash: 64e4389a695371a2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleHorizontalBands.12ethxk" msgstr "样式:横向条纹" +# English: "Style: Modern" # Source-Hash: ff6d226a425b00c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleModern.1fh8hhn" msgstr "样式:现代" +# English: "Style: Panel" # Source-Hash: c6d17ba078ff47e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePanel.922jze" msgstr "样式:镶板" +# English: "Style: Paneled glass" # Source-Hash: 5e6baad0d2ce3a62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.stylePaneledGlass.17a17en" msgstr "样式:镶板玻璃" +# English: "Style: Shaker" # Source-Hash: 6314388e6832c252 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleShaker.49z17k" msgstr "样式:震颤派" +# English: "Style: Single" # Source-Hash: a0f8e524f3ca121f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSingle.6f3cm0" msgstr "样式:单扇" +# English: "Style: Six-panel" # Source-Hash: 368279df3a472c86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSixPanel.u49laj" msgstr "样式:六镶板" +# English: "Style: Sliding" # Source-Hash: 8f3c2571d8168fee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleSliding.q81s0i" msgstr "样式:推拉" +# English: "Style: Tall grid" # Source-Hash: aa9d9eb137ca38d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTallGrid.1eotg3x" msgstr "样式:高格栅" +# English: "Style: Transom" # Source-Hash: bc164648722b63e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTransom.kb9bh6" msgstr "样式:带亮子" +# English: "Style: Triple-hung" # Source-Hash: 9a218f0c9ceb4697 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleTripleHung.pxnqi3" msgstr "样式:三悬" +# English: "Style: Wide grid" # Source-Hash: 9f6df1344bd3d694 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style: Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.styleWideGrid.12n27et" msgstr "样式:宽格栅" +# English: "Switch rotation axis (Y → X → Z)" # Source-Hash: 711d4050c0cb95d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Switch rotation axis (Y → X → Z)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.switchRotationAxisYXZ.15lohm6" msgstr "切换旋转轴(Y → X → Z)" +# English: "The walls meet at too sharp or too flat an angle" # Source-Hash: d650c71e042c2598 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The walls meet at too sharp or too flat an angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWallsMeetAtTooSharp.kh3wi3" msgstr "墙体相交的角度过尖或过平" +# English: "The window waiting there is taller than this wall" # Source-Hash: ab206ac56e65f190 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The window waiting there is taller than this wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.theWindowWaitingThereIsTaller.1tqwez4" msgstr "该处待放置的窗比这面墙体更高" +# English: "Three or more walls meet here" # Source-Hash: 9c1f47138ed5c7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Three or more walls meet here" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.threeOrMoreWallsMeetHere.rgifif" msgstr "此处有三面或更多墙体相交" +# English: "Trace ceiling outline" # Source-Hash: c3ca56fa28e15f07 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace ceiling outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceCeilingOutline.1b639wv" msgstr "描绘天花板轮廓" +# English: "Trace slab outline" # Source-Hash: bdf71499d30a29de # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trace slab outline" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.traceSlabOutline.z0j2lc" msgstr "描绘楼板轮廓" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.type.1m2zofh" msgstr "类型" +# English: "Type: Awning" # Source-Hash: b54eaaff3349dcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeAwning.1cj59al" msgstr "类型:上悬" +# English: "Type: Barn" # Source-Hash: f399572a27417c66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBarn.sbnygq" msgstr "类型:谷仓门" +# English: "Type: Bay" # Source-Hash: a1df9782200b5f1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBay.1200hc1" msgstr "类型:凸窗" +# English: "Type: Bow" # Source-Hash: ff069a390e8e3f66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeBow.14s2t9p" msgstr "类型:弧形凸窗" +# English: "Type: Cabinet" # Source-Hash: bdac5f3f2ea70fff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCabinet.1r68je5" msgstr "类型:柜门" +# English: "Type: Casement" # Source-Hash: e0754fc03997cbf9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeCasement.176uo9j" msgstr "类型:平开" +# English: "Type: Double" # Source-Hash: 47d873446d6e173b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDouble.af7w4i" msgstr "类型:双扇" +# English: "Type: Double Hung" # Source-Hash: 5942e484513aaab5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeDoubleHung.t482fu" msgstr "类型:双悬" +# English: "Type: Fixed" # Source-Hash: f9497fbfceefda27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFixed.v1cjez" msgstr "类型:固定" +# English: "Type: Folding" # Source-Hash: 0dcf07acc9cbf000 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFolding.sxxyng" msgstr "类型:折叠" +# English: "Type: French" # Source-Hash: bff7331f87f279aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeFrench.1y57z1b" msgstr "类型:法式" +# English: "Type: Hinged" # Source-Hash: 9d2c3b82079eb769 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeHinged.1xrcfju" msgstr "类型:铰链" +# English: "Type: Island" # Source-Hash: b8001b90ff522838 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Island" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeIsland.ufr3ce" msgstr "类型:岛台" +# English: "Type: Louvered" # Source-Hash: 516833c5160d412e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeLouvered.oxl1cz" msgstr "类型:百叶" +# English: "Type: Pocket" # Source-Hash: 291272bf848ad6ab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typePocket.hc65x9" msgstr "类型:内嵌推拉" +# English: "Type: Single Hung" # Source-Hash: 46d0e53d6ae07c72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSingleHung.p53pyl" msgstr "类型:单悬" +# English: "Type: Sliding" # Source-Hash: b11115b50df8faac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type: Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.typeSliding.nwmr3f" msgstr "类型:推拉" +# English: "Vertical stack ↕, click to place" # Source-Hash: bff0c1694ee0a37e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical stack ↕, click to place" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.verticalStackClickToPlace.ijcn4q" msgstr "竖向立管 ↕,点击放置" +# English: "Waste / vent" # Source-Hash: bac471ee67af6412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waste / vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.wasteVent.1hvi3vj" msgstr "污水 / 通气" +# English: "Window style — click or press L to cycle" # Source-Hash: 1c744b7e24fc786e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window style — click or press L to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowStyleClickOrPressL.ah7qm9" msgstr "窗样式 — 点击或按 L 切换" +# English: "Window type — click or press O to cycle" # Source-Hash: dd6f0ab314fea26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window type — click or press O to cycle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "editor.toolHint.windowTypeClickOrPressO.wv4x35" msgstr "窗类型 — 点击或按 O 切换" -# Review: required -# Source-Hash: a48c4b2e4d71b08d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length: {field} m" +# Source-Hash: a48c4b2e4d71b08d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.lengthField" msgstr "长度:{field} m" -# Review: required -# Source-Hash: e53695d6669245c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tab: direction" +# Source-Hash: e53695d6669245c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.tabDirection" msgstr "Tab:方向" -# Review: required -# Source-Hash: abb1d8c8bfb41acf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "type a length" +# Source-Hash: abb1d8c8bfb41acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/tools/distribution/distribution-run-tool.tsx #, fuzzy msgid "editor.tools.distribution.distributionRunTool.typeALength" msgstr "输入长度" +# English: "Plugins" # Source-Hash: 9fc7a39bc32f287a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.plugins" msgstr "插件" +# English: "2D" # Source-Hash: c54f9bac358893d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "2D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view2d" msgstr "2D" +# English: "3D" # Source-Hash: f78f2fa035f25f85 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.view3d" msgstr "3D" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.auto" msgstr "自动" +# English: "Browse plugins" # Source-Hash: 7890c110650ad1e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Browse plugins" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.browsePlugins" msgstr "浏览插件" +# English: "Close {label}" # Source-Hash: 3b11e7af681a9dcd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeLabel" msgstr "关闭{label}" +# English: "Close split (\\)" # Source-Hash: 3f00e92517e3f4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close split (\\)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.closeSplit" msgstr "关闭分屏 (\\)" +# English: "Ground" # Source-Hash: 94cc8e9f1ee16edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.ground" msgstr "地面" +# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" # Source-Hash: 0d472502a4d36e86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground solid} below {Below grade · ground solid} other {Ground solid}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundSolid" msgstr "{standing, select, partly {部分位于地坪以下 · 地面实体} below {位于地坪以下 · 地面实体} other {地面实体}}" +# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" # Source-Hash: 1b6798c307ac1b96 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{standing, select, partly {Partly below grade · ground veiled} below {Below grade · ground veiled} other {Ground veiled}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.groundVeiled" msgstr "{standing, select, partly {部分位于地坪以下 · 地面淡化} below {位于地坪以下 · 地面淡化} other {地面淡化}}" +# English: "Keep the ground solid" # Source-Hash: ebdc976a98e82626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Keep the ground solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.keepTheGroundSolid" msgstr "保持地面为实体" +# English: "More views" # Source-Hash: 5f827686a006d88c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "More views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.moreViews" msgstr "更多视图" +# English: "Pane {pane} views" # Source-Hash: f175bbcf457ab73c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane {pane} views" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.paneViews" msgstr "窗格 {pane} 视图" +# English: "Pin {label}" # Source-Hash: 33c20cc33dce9a9b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin {label}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinLabel" msgstr "固定{label}" +# English: "Pin to the view bar" # Source-Hash: 3f7231b1eb2f93f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pin to the view bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy msgid "editor.views.viewBar.pinToTheViewBar" msgstr "固定到视图栏" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.solid" +msgstr "实体" + +# English: "Split (\\)" +# Source-Hash: 98f22f3f89f17686 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split" +msgstr "分屏 (\\)" + +# English: "Split" +# Source-Hash: e4cd64b1680ac0d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.split2" +msgstr "分屏" + +# English: "Swap panes" +# Source-Hash: 15b0adc705923a27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.swapPanes" +msgstr "交换窗格" + +# English: "Unpin from the view bar" +# Source-Hash: 9781bcdb7a8549e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinFromTheViewBar" +msgstr "从视图栏取消固定" + +# English: "Unpin {label}" +# Source-Hash: f667358dec4abdea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.unpinLabel" +msgstr "取消固定{label}" + +# English: "Veil the ground over a level below grade" +# Source-Hash: e96581f0bbf9fa91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/views/view-bar.tsx +#, fuzzy +msgid "editor.views.viewBar.veilTheGroundOverALevel" +msgstr "在地坪以下的楼层上方淡化地面" + +# English: "Views" +# Source-Hash: fb82f09f8ed3059e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/editor/src/components/editor/views/view-bar.tsx #, fuzzy -msgid "editor.views.viewBar.solid" -msgstr "实体" +msgid "editor.views.viewBar.views" +msgstr "视图" + +# English: "You can retry the scene or return home without reloading the whole app shell." +# Source-Hash: fdece5c2158dfe88 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/editor/src/components/editor/index.tsx +#, fuzzy +msgid "editor.youCanRetryTheSceneOr" +msgstr "您可以重试场景,或在不重新加载整个应用的情况下返回首页。" + +# English: "A framing beam drawn end to end: a porch header, a deck beam, a girder." +# Source-Hash: bafa390e07160f3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.description" +msgstr "从一端绘制到另一端的框架梁:门廊过梁、平台梁或主梁。" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.beam.label" +msgstr "梁" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.actions" +msgstr "操作" + +# English: "Add posts" +# Source-Hash: 59f845b250e0df6f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.addPosts" +msgstr "添加立柱" + +# English: "Beam" +# Source-Hash: e14858b33b6e2623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beam" +msgstr "梁" + +# English: "Beam {size}" +# Source-Hash: 26e977da636cc882 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.beamSize" +msgstr "梁 {size}" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.delete" +msgstr "删除" + +# English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.depth" +msgstr "高度" + +# English: "Dropped" +# Source-Hash: 73694f3f45cf2ee1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.dropped" +msgstr "下挂" + +# English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.flush" +msgstr "齐平" + +# English: "Follow the ceiling plane" +# Source-Hash: 0ee3edea19fd153e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.followTheCeilingPlane" +msgstr "跟随天花板平面" + +# English: "{length} long; its underside {underside} over the floor, the ceiling plane {plane}." +# Source-Hash: 193b75ac869c251d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.lengthLongItsUnderside" +msgstr "长 {length};梁底高出楼面 {underside},天花板平面 {plane}。" + +# English: "Material" +# Source-Hash: 964b86ae1474ff98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx +#, fuzzy +msgid "node.beam.panel.material" +msgstr "材料" -# Source-Hash: 98f22f3f89f17686 +# English: "Move" +# Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split (\\)" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split" -msgstr "分屏 (\\)" +msgid "node.beam.panel.move" +msgstr "移动" -# Source-Hash: e4cd64b1680ac0d2 +# English: "1 ply" +# Source-Hash: fa876c632c284d81 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Split" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.split2" -msgstr "分屏" +msgid "node.beam.panel.n1Ply" +msgstr "1 片" -# Source-Hash: 15b0adc705923a27 +# English: "Placement" +# Source-Hash: b3be684ac62fbf53 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swap panes" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.swapPanes" -msgstr "交换窗格" +msgid "node.beam.panel.placement" +msgstr "位置" -# Source-Hash: 9781bcdb7a8549e2 +# English: "{plies} plies" +# Source-Hash: 6ac7987d650b9742 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin from the view bar" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinFromTheViewBar" -msgstr "从视图栏取消固定" +msgid "node.beam.panel.pliesPlies" +msgstr "{plies} 片" -# Source-Hash: f667358dec4abdea +# English: "Size" +# Source-Hash: 60bb3338e0ad558c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unpin {label}" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.unpinLabel" -msgstr "取消固定{label}" +msgid "node.beam.panel.size" +msgstr "尺寸" -# Source-Hash: e96581f0bbf9fa91 +# English: "Top" +# Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Veil the ground over a level below grade" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.veilTheGroundOverALevel" -msgstr "在地坪以下的楼层上方淡化地面" +msgid "node.beam.panel.top" +msgstr "顶部" -# Source-Hash: fb82f09f8ed3059e +# English: "Top elevation" +# Source-Hash: 5b4c960036b3d05b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Views" -#: packages/editor/src/components/editor/views/view-bar.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.views.viewBar.views" -msgstr "视图" +msgid "node.beam.panel.topElevation" +msgstr "顶部标高" -# Source-Hash: fdece5c2158dfe88 +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "You can retry the scene or return home without reloading the whole app shell." -#: packages/editor/src/components/editor/index.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/beam/panel.tsx #, fuzzy -msgid "editor.youCanRetryTheSceneOr" -msgstr "您可以重试场景,或在不重新加载整个应用的情况下返回首页。" +msgid "node.beam.panel.width" +msgstr "宽度" +# English: "A topology-backed solid edited directly in the canvas." # Source-Hash: a273dacdf51b26f2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A topology-backed solid edited directly in the canvas." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.description" msgstr "基于拓扑、可直接在画布中编辑的实体。" +# English: "Block" # Source-Hash: 279f456ebb2f5da0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Block" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.block.label" msgstr "体块" +# English: "Small louvered exhaust vent that sits on a roof slope." # Source-Hash: b2a371b4041b5e38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Small louvered exhaust vent that sits on a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.description" msgstr "位于屋面坡上的小型百叶排气口。" +# English: "Box Vent" # Source-Hash: f736ca24e84825c3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.boxVent.label" msgstr "箱式通风口" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: c0e9837e67038e92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Flange" +# Source-Hash: c0e9837e67038e92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseFlange" msgstr "底座法兰" -# Review: required -# Source-Hash: fa9118f6f4f020d7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Height" +# Source-Hash: fa9118f6f4f020d7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseHeight" msgstr "底座高度" -# Review: required -# Source-Hash: 0869f5a92dabb4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Inset" +# Source-Hash: 0869f5a92dabb4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.baseInset" msgstr "底座内缩" -# Review: required -# Source-Hash: e9a89a5e4ac8cd61 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box" +# Source-Hash: e9a89a5e4ac8cd61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.box" msgstr "盒形" -# Review: required -# Source-Hash: f736ca24e84825c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Vent" +# Source-Hash: f736ca24e84825c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.boxVent" msgstr "箱式通风口" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cap" msgstr "顶盖" -# Review: required -# Source-Hash: 0ce3b53766b550e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap Height" +# Source-Hash: 0ce3b53766b550e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.capHeight" msgstr "顶盖高度" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.cornerBevel" msgstr "转角倒角" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.dome" msgstr "穹顶" -# Review: required -# Source-Hash: 1c5c81aa21193d07 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome Curvature" +# Source-Hash: 1c5c81aa21193d07 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.domeCurvature" msgstr "穹顶曲率" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: 8cac057f7ae6804d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Height" +# Source-Hash: 8cac057f7ae6804d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.gapHeight" msgstr "缝隙高度" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 8a9c2ea0783a0e53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hood Overhang" +# Source-Hash: 8a9c2ea0783a0e53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.hoodOverhang" msgstr "罩檐悬挑" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.style" msgstr "样式" -# Review: required -# Source-Hash: 71d82d47e11d2564 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Taper" +# Source-Hash: 71d82d47e11d2564 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.topTaper" msgstr "顶部收分" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/box-vent/panel.tsx #, fuzzy msgid "node.boxVent.panel.width" msgstr "宽度" +# English: "A building container holding one or more levels." # Source-Hash: f49d8836ae037dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A building container holding one or more levels." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.description" msgstr "包含一个或多个楼层的建筑容器。" +# English: "Building" # Source-Hash: d42f7d29e3fc4af7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Building" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.building.label" msgstr "建筑" -# Review: required -# Source-Hash: 5a8274cbe3034558 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Baskets" +# Source-Hash: 5a8274cbe3034558 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.baskets" msgstr "拉篮" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottom" msgstr "底部" -# Review: required -# Source-Hash: 486501f027037732 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Freezer" +# Source-Hash: 486501f027037732 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bottomFreezer" msgstr "下置冷冻" -# Review: required -# Source-Hash: 375acc0016b3aa14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bowls" +# Source-Hash: 375acc0016b3aa14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.bowls" msgstr "水槽" -# Review: required -# Source-Hash: 8449b59bf054c27d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Burners on" +# Source-Hash: 8449b59bf054c27d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.burnersOn" msgstr "点火" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.chimney" msgstr "烟囱" -# Review: required -# Source-Hash: a6b76187db231ae7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Decrease {label}" +# Source-Hash: a6b76187db231ae7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.decrease" msgstr "减少{label}" -# Review: required -# Source-Hash: 787e10f5db16904e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door" +# Source-Hash: 787e10f5db16904e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.door" msgstr "门" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.double" msgstr "双扇" -# Review: required -# Source-Hash: 63fe49d73892aac8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer" +# Source-Hash: 63fe49d73892aac8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawer" msgstr "抽屉" - -# Review: required -# Source-Hash: f98242a5dacd33f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Drawers" +# Source-Hash: f98242a5dacd33f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.drawers" msgstr "抽屉" -# Review: required -# Source-Hash: 3af5347c94ae6443 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip hinge" +# Source-Hash: 3af5347c94ae6443 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipHinge" msgstr "翻转铰链" -# Review: required -# Source-Hash: c930f681e61c029c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip the door hinge to the opposite side" +# Source-Hash: c930f681e61c029c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.flipTheDoorHingeToThe" msgstr "将门铰链翻转到另一侧" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.fridge" msgstr "冰箱" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.gas" msgstr "燃气" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.glass" msgstr "玻璃" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.height" msgstr "高度" -# Review: required -# Source-Hash: 6f434b84c8b59b8c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hob" +# Source-Hash: 6f434b84c8b59b8c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.hob" msgstr "灶台" -# Review: required -# Source-Hash: d52570d41b4df776 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase {label}" +# Source-Hash: d52570d41b4df776 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.increase" msgstr "增加{label}" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.induction" msgstr "电磁" -# Review: required -# Source-Hash: 54800340a0eeab23 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Layout" +# Source-Hash: 54800340a0eeab23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.layout" msgstr "布局" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.left" msgstr "左" -# Review: required -# Source-Hash: 12ade82be3830a0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Micro" +# Source-Hash: 12ade82be3830a0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.micro" msgstr "微波炉" -# Review: required -# Source-Hash: a06a50e63f0c240e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move down" +# Source-Hash: a06a50e63f0c240e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveDown" msgstr "下移" -# Review: required -# Source-Hash: d49ea60a2312df2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move up" +# Source-Hash: d49ea60a2312df2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.moveUp" msgstr "上移" -# Review: required -# Source-Hash: 596023c8b9a8f2e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven" +# Source-Hash: 596023c8b9a8f2e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.oven" msgstr "烤箱" -# Review: required -# Source-Hash: 83b20797fb0a01a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pullout" +# Source-Hash: 83b20797fb0a01a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.pullout" msgstr "拉出式" -# Review: required -# Source-Hash: 43c251f465b647ce -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rack" +# Source-Hash: 43c251f465b647ce +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.rack" msgstr "置物架" -# Review: required -# Source-Hash: 3c2b3026d37cbcc2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove compartment" +# Source-Hash: 3c2b3026d37cbcc2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.removeCompartment" msgstr "移除隔间" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.right" msgstr "右" -# Review: required -# Source-Hash: d7145f84f8e0e3fe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelf" +# Source-Hash: d7145f84f8e0e3fe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelf" msgstr "置物架" -# Review: required -# Source-Hash: 5fbb88f1f079b6db -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves" +# Source-Hash: 5fbb88f1f079b6db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelves" msgstr "层板" -# Review: required -# Source-Hash: 47173085127029f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shelves inside" +# Source-Hash: 47173085127029f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.shelvesInside" msgstr "内部层板" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.single" msgstr "单扇" -# Review: required -# Source-Hash: 522a7535fc93fb4f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink" +# Source-Hash: 522a7535fc93fb4f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.sink" msgstr "水槽" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.style" msgstr "样式" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.surface" msgstr "表面" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.top" msgstr "顶部" -# Review: required -# Source-Hash: eaadfd2d19b27d87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Freezer" +# Source-Hash: eaadfd2d19b27d87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topFreezer" msgstr "上置冷冻" -# Review: required -# Source-Hash: 04f3a3462ef5d247 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top grate" +# Source-Hash: 04f3a3462ef5d247 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.topGrate" msgstr "顶部炉架" -# Review: required -# Source-Hash: 4a616e1d2ca55bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tray" +# Source-Hash: 4a616e1d2ca55bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.tray" msgstr "托盘" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.type" msgstr "类型" -# Review: required -# Source-Hash: 6c4de540d19316ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Washer" +# Source-Hash: 6c4de540d19316ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.washer" msgstr "洗碗机" -# Review: required -# Source-Hash: 5211a0caf3518125 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wire" +# Source-Hash: 5211a0caf3518125 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/compartment-card.tsx #, fuzzy msgid "node.cabinet.compartmentCard.wire" msgstr "金属丝" +# English: "A configurable parametric base cabinet." # Source-Hash: 693a0d8896f5cc43 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable parametric base cabinet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.description" msgstr "可配置的参数化地柜。" -# Review: required -# Source-Hash: b0568ea943addd5b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom freezer" +# Source-Hash: b0568ea943addd5b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.bottomFreezer" msgstr "下置冷冻" -# Review: required -# Source-Hash: da78819903ae0863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cooktop type" +# Source-Hash: da78819903ae0863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.cooktopType" msgstr "灶台类型" -# Review: required -# Source-Hash: c608188f58ed1cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door direction" +# Source-Hash: c608188f58ed1cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.doorDirection" msgstr "开门方向" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.double" msgstr "双扇" -# Review: required -# Source-Hash: f7fb8f6393cc4cfc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge type" +# Source-Hash: f7fb8f6393cc4cfc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.fridgeType" msgstr "冰箱类型" -# Review: required -# Source-Hash: e67f11024d683ea9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas" +# Source-Hash: e67f11024d683ea9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.gas" msgstr "燃气" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.glass" msgstr "玻璃" -# Review: required -# Source-Hash: d298f259163e868e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction" +# Source-Hash: d298f259163e868e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.induction" msgstr "电磁" -# Review: required -# Source-Hash: 76df477d42cec629 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Item options" +# Source-Hash: 76df477d42cec629 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.itemOptions" msgstr "物品选项" -# Review: required -# Source-Hash: bca1b184af1c0c4b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left hinge" +# Source-Hash: bca1b184af1c0c4b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.leftHinge" msgstr "左铰链" -# Review: required -# Source-Hash: e32da7429c85f1a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right hinge" +# Source-Hash: e32da7429c85f1a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.rightHinge" msgstr "右铰链" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.single" msgstr "单扇" -# Review: required -# Source-Hash: 4bc3042055df1e85 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink layout" +# Source-Hash: 4bc3042055df1e85 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.sinkLayout" msgstr "水槽布局" -# Review: required -# Source-Hash: 29cffe75281dbea6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top freezer" +# Source-Hash: 29cffe75281dbea6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/essential-controls.tsx #, fuzzy msgid "node.cabinet.essentialControls.topFreezer" msgstr "上置冷冻" -# Review: required -# Source-Hash: 09c2cb2ea7b1be3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose {label}" +# Source-Hash: 09c2cb2ea7b1be3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.chooseLabel" msgstr "选择{label}" -# Review: required -# Source-Hash: 1dd2cac6488ddc30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No kitchen items match. Try “fridge”, “sink”, or “cabinet”." +# Source-Hash: 1dd2cac6488ddc30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.noKitchenItemsMatchTryFridge" msgstr "没有匹配的厨房物品。试试“冰箱”“水槽”或“橱柜”。" -# Review: required -# Source-Hash: 6752675ca6280ddd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search fridge, sink, cabinets…" +# Source-Hash: 6752675ca6280ddd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchFridgeSinkCabinets" msgstr "搜索冰箱、水槽、橱柜…" -# Review: required -# Source-Hash: fe3c6d57a07c323d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Search kitchen items" +# Source-Hash: fe3c6d57a07c323d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-collection.tsx #, fuzzy msgid "node.cabinet.kitchenCollection.searchKitchenItems" msgstr "搜索厨房物品" -# Review: required -# Source-Hash: d44a46146db2e44c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliances & fixtures" +# Source-Hash: d44a46146db2e44c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.appliances" msgstr "电器与设施" -# Review: required -# Source-Hash: d4d3ad7c099b6513 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinets" +# Source-Hash: d4d3ad7c099b6513 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.kitchenGroup.cabinets" msgstr "橱柜" -# Review: required -# Source-Hash: 465db52f98c2d94f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside" +# Source-Hash: 465db52f98c2d94f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBeside" msgstr "在旁边添加" -# Review: required -# Source-Hash: 9733e2ad85deb6d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add beside selected item" +# Source-Hash: 9733e2ad85deb6d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addBesideSelectedItem" msgstr "在所选物品旁添加" -# Review: required -# Source-Hash: 4b2f676fb4c9ec63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add {label}" +# Source-Hash: 4b2f676fb4c9ec63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.addLabel" msgstr "添加{label}" -# Review: required -# Source-Hash: 5122b8e9b4ec6a00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "An upper cabinet attached to this item will be removed to make room." +# Source-Hash: 5122b8e9b4ec6a00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.anUpperCabinetAttachedToThis" msgstr "附着在此物品上的吊柜将被移除以腾出空间。" -# Review: required -# Source-Hash: 5737c4567e0f6863 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet" +# Source-Hash: 5737c4567e0f6863 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cabinet" msgstr "柜体" -# Review: required -# Source-Hash: c0774c185060fc13 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cancel" +# Source-Hash: c0774c185060fc13 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cancel" msgstr "取消" -# Review: required -# Source-Hash: f42912b116015f58 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "This {width} item cannot fit within the current run and its wall or corner limits. Move an end cabinet or free space, then try again." +# Source-Hash: f42912b116015f58 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.cannotFit" msgstr "这个 {width} 的物品无法放入当前橱柜组及其墙体或转角限制内。请移动端部橱柜或腾出空间后重试。" -# Review: required -# Source-Hash: b7eeb26a356d03c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Inserts on the left of the selected item.} other {Inserts on the right of the selected item.}}" +# Source-Hash: b7eeb26a356d03c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.insertsBeside" msgstr "{side, select, left {插入到所选物品的左侧。} other {插入到所选物品的右侧。}}" -# Review: required -# Source-Hash: f65332177316d11d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The kitchen changed. Select an item again to refresh the preview." +# Source-Hash: f65332177316d11d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenChanged" msgstr "厨房已更改。请重新选择物品以刷新预览。" -# Review: required -# Source-Hash: 5b6990b17111fd6b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run preview, highlighted item is the proposed change" +# Source-Hash: 5b6990b17111fd6b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.kitchenRunPreviewHighlightedItemIs" msgstr "厨房橱柜组预览,高亮的物品为拟议更改" -# Review: required -# Source-Hash: cb59562ba7eb1d25 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left side" +# Source-Hash: cb59562ba7eb1d25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.leftSide" msgstr "左侧" -# Review: required -# Source-Hash: 766354d53f17d78c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: moves {distance}" +# Source-Hash: 766354d53f17d78c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.moved" msgstr "{name}:移动 {distance}" -# Review: required -# Source-Hash: df1083d9ffc2c59d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {width}" +# Source-Hash: df1083d9ffc2c59d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.previewItem" msgstr "{name}:{width}" -# Review: required -# Source-Hash: ada7fbece9ca9e3c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace selected item" +# Source-Hash: ada7fbece9ca9e3c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceSelectedItem" msgstr "替换所选物品" -# Review: required -# Source-Hash: ab8b52d2704537d2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replaces only the selected item." +# Source-Hash: ab8b52d2704537d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replacesOnlyTheSelectedItem" msgstr "仅替换所选物品。" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWith" msgstr "替换为…" -# Review: required -# Source-Hash: 948fe657641f298c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with {label}" +# Source-Hash: 948fe657641f298c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.replaceWithLabel" msgstr "替换为{label}" -# Review: required -# Source-Hash: 154f9fa86b7670ba -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{name}: {before} → {after}" +# Source-Hash: 154f9fa86b7670ba +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.resized" msgstr "{name}:{before} → {after}" -# Review: required -# Source-Hash: 4ecc493400fb6aa2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right side" +# Source-Hash: 4ecc493400fb6aa2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.rightSide" msgstr "右侧" -# Review: required -# Source-Hash: f2c30e7e21cf27e6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to update this kitchen." +# Source-Hash: f2c30e7e21cf27e6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-module-actions.tsx #, fuzzy msgid "node.cabinet.kitchenModuleActions.unableToUpdateThisKitchen" msgstr "无法更新此厨房。" +# English: "Modular Cabinet" # Source-Hash: 2e6d8cda361c648d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modular Cabinet" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinet.label" msgstr "组合橱柜" -# Review: required -# Source-Hash: 740e0a4e4afa78ea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished height extends {overflow} mm above the ceiling." +# Source-Hash: 740e0a4e4afa78ea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.aboveCeiling" msgstr "完成高度超出天花板 {overflow} mm。" -# Review: required -# Source-Hash: e3b4d2ed4f6f5efb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add chimney" +# Source-Hash: e3b4d2ed4f6f5efb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addChimney" msgstr "添加烟罩" -# Review: required -# Source-Hash: 90bdc92c0a91d060 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add compartment" +# Source-Hash: 90bdc92c0a91d060 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addCompartment" msgstr "添加隔间" -# Review: required -# Source-Hash: ece05d9b6f4b616d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add wall cabinet" +# Source-Hash: ece05d9b6f4b616d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.addWallCabinet" msgstr "添加吊柜" -# Review: required -# Source-Hash: f571cb2d06ae01ae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Advanced" +# Source-Hash: f571cb2d06ae01ae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.advanced" msgstr "高级" -# Review: required -# Source-Hash: 7f4dcb90defe1ac2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance" +# Source-Hash: 7f4dcb90defe1ac2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.appliance" msgstr "电器" -# Review: required -# Source-Hash: bd5e3f533412ff6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Appliance front" +# Source-Hash: bd5e3f533412ff6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.applianceFront" msgstr "电器面板" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.auto" msgstr "自动" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.bar" msgstr "拉手条" -# Review: required -# Source-Hash: 133893b177bd05d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base Cabinet" +# Source-Hash: 133893b177bd05d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.baseCabinet" msgstr "地柜" -# Review: required -# Source-Hash: d63f16df2c754126 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet construction, compartments, and opening preview." +# Source-Hash: d63f16df2c754126 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetConstructionCompartmentsAndOpeningPreview" msgstr "橱柜构造、隔间和开启预览。" -# Review: required -# Source-Hash: d62dc35dac835228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet panel" +# Source-Hash: d62dc35dac835228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetPanel" msgstr "橱柜面板" -# Review: required -# Source-Hash: 81caf4809d58b5f5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cabinet Type" +# Source-Hash: 81caf4809d58b5f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cabinetType" msgstr "橱柜类型" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.center" msgstr "居中" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.close" msgstr "关闭" -# Review: required -# Source-Hash: cf29134333e47184 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close cabinet" +# Source-Hash: cf29134333e47184 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.closeCabinet" msgstr "关闭橱柜" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.commonWallCabinetHeightsUseThe" msgstr "常用吊柜高度。如需自定义高度,请使用下方滑块。" -# Review: required -# Source-Hash: fe9e63d5c5087386 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compartments" +# Source-Hash: fe9e63d5c5087386 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.compartments" msgstr "隔间" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.cutout" msgstr "开槽" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: 82c9ea6f403fc934 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit kitchen run" +# Source-Hash: 82c9ea6f403fc934 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.editKitchenRun" msgstr "编辑厨房橱柜组" -# Review: required -# Source-Hash: 7e9bbeb035fc73e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fill to ceiling" +# Source-Hash: 7e9bbeb035fc73e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.fillToCeiling" msgstr "填充至天花板" -# Review: required -# Source-Hash: b3907da0ad234f10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.finish" msgstr "完成" -# Review: required -# Source-Hash: c1796152935221ef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front construction" +# Source-Hash: c1796152935221ef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.frontConstruction" msgstr "面板构造" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.handles" msgstr "拉手" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.height" msgstr "高度" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.heightPreset" msgstr "预设高度" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.hole" msgstr "孔" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.inset" msgstr "内嵌" -# Review: required -# Source-Hash: 1176d2568d093bf1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen" +# Source-Hash: 1176d2568d093bf1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchen" msgstr "厨房" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.kitchenRun" msgstr "厨房橱柜组" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.knob" msgstr "旋钮拉手" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.mounting" msgstr "安装方式" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.none" msgstr "无" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.open" msgstr "打开" -# Review: required -# Source-Hash: b8ecc16c07d66f42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open Animation" +# Source-Hash: b8ecc16c07d66f42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openAnimation" msgstr "开启动画" -# Review: required -# Source-Hash: 2d4adbbd70c3ae6a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open cabinet" +# Source-Hash: 2d4adbbd70c3ae6a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.openCabinet" msgstr "打开橱柜" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.overlay" msgstr "盖门" -# Review: required -# Source-Hash: a35b7357943a5cae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel-ready" +# Source-Hash: a35b7357943a5cae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.panelReady" msgstr "可装面板" -# Review: required -# Source-Hash: 3d0d4eee59177824 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Planning checks" +# Source-Hash: 3d0d4eee59177824 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.planningChecks" msgstr "规划检查" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.play" msgstr "播放" -# Review: required -# Source-Hash: c530f8af9dfd846f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play animation" +# Source-Hash: c530f8af9dfd846f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.playAnimation" msgstr "播放动画" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.position" msgstr "位置" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.raisedArch" msgstr "凸起拱形" -# Review: required -# Source-Hash: a8639a97ab07cd59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove wall cabinet" +# Source-Hash: a8639a97ab07cd59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.removeWallCabinet" msgstr "移除吊柜" -# Review: required -# Source-Hash: 03cb6dea6e4a4c5e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Replace with…" +# Source-Hash: 03cb6dea6e4a4c5e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.replaceWith" msgstr "替换为…" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.revealGap" msgstr "缝隙" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.shaker" msgstr "夏克风" -# Review: required -# Source-Hash: c378ed346bf83375 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared countertop, plinth, and finishes" +# Source-Hash: c378ed346bf83375 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.sharedCountertopPlinthAndFinishes" msgstr "共用台面、踢脚和饰面" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.slab" msgstr "楼板" -# Review: required -# Source-Hash: 8606fe2c60764a95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid matching front between the cabinet and wall. Countertop and toe-kick continue across this filler." +# Source-Hash: 8606fe2c60764a95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.solidMatchingFrontBetweenTheCabinet" msgstr "橱柜与墙之间的同款实心面板。台面和踢脚会延续到此填充板上。" -# Review: required -# Source-Hash: 5a4d5353cc496a63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard width" +# Source-Hash: 5a4d5353cc496a63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.standardWidth" msgstr "标准宽度" -# Review: required -# Source-Hash: 4a784e3e95a08715 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop" +# Source-Hash: 4a784e3e95a08715 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stop" msgstr "停止" -# Review: required -# Source-Hash: 72c9fcd7a0eb5380 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop animation" +# Source-Hash: 72c9fcd7a0eb5380 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.stopAnimation" msgstr "停止动画" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.style" msgstr "样式" -# Review: required -# Source-Hash: bfd618690e0c2f24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Cabinet" +# Source-Hash: bfd618690e0c2f24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.tallCabinet" msgstr "高柜" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.top" msgstr "顶部" -# Review: required -# Source-Hash: d09654ebd12a538a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Cabinet" +# Source-Hash: d09654ebd12a538a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCabinet" msgstr "顶柜" -# Review: required -# Source-Hash: bc19e7805b3e3703 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top / Ceiling" +# Source-Hash: bc19e7805b3e3703 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topCeiling" msgstr "顶部 / 天花板" -# Review: required -# Source-Hash: 29ea8a68eed99925 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top depth" +# Source-Hash: 29ea8a68eed99925 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topDepth" msgstr "顶部深度" -# Review: required -# Source-Hash: 2d40f591e7dcbf5a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top height" +# Source-Hash: 2d40f591e7dcbf5a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.topHeight" msgstr "顶部高度" -# Review: required -# Source-Hash: 3b878d6015c03945 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trim / Soffit" +# Source-Hash: 3b878d6015c03945 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.trimSoffit" msgstr "饰条 / 吊顶" -# Review: required -# Source-Hash: a6e5be20001e8d26 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Uses the cabinet front style, reveal, handle, and front material settings below." +# Source-Hash: a6e5be20001e8d26 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.usesTheCabinetFrontStyleReveal" msgstr "使用下方的橱柜面板样式、缝隙、拉手和面板材质设置。" -# Review: required -# Source-Hash: cd55643590c64a42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall filler" +# Source-Hash: cd55643590c64a42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.wallFiller" msgstr "靠墙填充板" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/panel.tsx #, fuzzy msgid "node.cabinet.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: 4ef03cfd6782316b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Base cabinet" +# Source-Hash: 4ef03cfd6782316b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.baseCabinet" msgstr "地柜" -# Review: required -# Source-Hash: d353fff221a78228 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dishwasher" +# Source-Hash: d353fff221a78228 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.dishwasher" msgstr "洗碗机" -# Review: required -# Source-Hash: ecb596567b8b7179 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drawer cabinet" +# Source-Hash: ecb596567b8b7179 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.drawerCabinet" msgstr "抽屉柜" -# Review: required -# Source-Hash: 85724031c91952a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fridge" +# Source-Hash: 85724031c91952a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.fridge" msgstr "冰箱" -# Review: required -# Source-Hash: 72d587d58b645a81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gas cooktop" +# Source-Hash: 72d587d58b645a81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.gasCooktop" msgstr "燃气灶" -# Review: required -# Source-Hash: 6b02c36e6aac2cd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Induction cooktop" +# Source-Hash: 6b02c36e6aac2cd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.inductionCooktop" msgstr "电磁炉" -# Review: required -# Source-Hash: d5fcb790aef707bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Oven Tower" +# Source-Hash: d5fcb790aef707bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.ovenTower" msgstr "烤箱高柜" -# Review: required -# Source-Hash: edd4d1ab7525dcaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sink cabinet" +# Source-Hash: edd4d1ab7525dcaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.sinkCabinet" msgstr "水槽柜" -# Review: required -# Source-Hash: 9b7427a8954f618b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall Pantry" +# Source-Hash: 9b7427a8954f618b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.tallPantry" msgstr "高储物柜" -# Review: required -# Source-Hash: 3bd9629eb17cc572 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall cabinet" +# Source-Hash: 3bd9629eb17cc572 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/kitchen-catalog.ts #, fuzzy msgid "node.cabinet.preset.wallCabinet" msgstr "吊柜" -# Review: required -# Source-Hash: 7c40d26770eede82 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add left" +# Source-Hash: 7c40d26770eede82 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addLeft" msgstr "向左添加" -# Review: required -# Source-Hash: 25082cdf77e219c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add right" +# Source-Hash: 25082cdf77e219c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.addRight" msgstr "向右添加" - -# Review: required -# Source-Hash: 4b1204c1b706f618 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Align cabinets & handles" +# Source-Hash: 4b1204c1b706f618 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.alignCabinetsHandles" msgstr "对齐橱柜和拉手" -# Review: required -# Source-Hash: 8c73c7a9406db5ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Applies depth, carcass, plinth, and countertop thickness to this run." +# Source-Hash: 8c73c7a9406db5ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.appliesDepthCarcassPlinthAndCountertop" msgstr "将深度、柜体、踢脚和台面厚度应用到此橱柜组。" -# Review: required -# Source-Hash: ef576e6c8f9ebe5f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} module copy} other {Create {number} module copies}}" +# Source-Hash: ef576e6c8f9ebe5f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreate" msgstr "{count, plural, other {创建 {number} 个模块副本}}" -# Review: required -# Source-Hash: 0eec3888096306a1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {Create {number} {name} copy} other {Create {number} {name} copies}}" +# Source-Hash: 0eec3888096306a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayCreateNamed" msgstr "{count, plural, other {创建 {number} 个{name}副本}}" -# Review: required -# Source-Hash: 8cdc0b808b551d3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a copy count from 1 to 20 and spacing from 0 to 2 m" +# Source-Hash: 8cdc0b808b551d3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayInvalidOptions" msgstr "请选择 1 到 20 的副本数量和 0 到 2 m 的间距" -# Review: required -# Source-Hash: cb89fe4161e87d68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "There is not enough room in this run for the requested array" +# Source-Hash: cb89fe4161e87d68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoRoom" msgstr "此橱柜组空间不足以容纳所需阵列" -# Review: required -# Source-Hash: 075d027345689724 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a standard or appliance module as the source" +# Source-Hash: 075d027345689724 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.arrayNoSource" msgstr "请选择一个标准模块或电器模块作为源" -# Review: required -# Source-Hash: 05b65242d8c48791 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto" +# Source-Hash: 05b65242d8c48791 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.auto" msgstr "自动" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.back" msgstr "返回" -# Review: required -# Source-Hash: b2c3a03bf67a97a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Balances standard base cabinets while keeping appliance and corner-filler widths fixed." +# Source-Hash: b2c3a03bf67a97a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.balancesStandardBaseCabinetsWhileKeeping" msgstr "平衡标准地柜宽度,同时保持电器和转角填充板宽度不变。" -# Review: required -# Source-Hash: 9cf3754f15467c50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar" +# Source-Hash: 9cf3754f15467c50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.bar" msgstr "拉手条" -# Review: required -# Source-Hash: ccec5da17ca39eee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar counter" +# Source-Hash: ccec5da17ca39eee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barCounter" msgstr "吧台" -# Review: required -# Source-Hash: 97c1605fff221752 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar depth" +# Source-Hash: 97c1605fff221752 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barDepth" msgstr "吧台深度" -# Review: required -# Source-Hash: 9514a6ddebdca34e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bar height" +# Source-Hash: 9514a6ddebdca34e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.barHeight" msgstr "吧台高度" -# Review: required -# Source-Hash: 7af3b69b4b5aeb19 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Carcass height" +# Source-Hash: 7af3b69b4b5aeb19 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.carcassHeight" msgstr "柜体高度" -# Review: required -# Source-Hash: 5211341ed18f33f6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.center" msgstr "居中" -# Review: required -# Source-Hash: b1ab1ba948bb77b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Common wall-cabinet heights. Use the slider below for a custom height." +# Source-Hash: b1ab1ba948bb77b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.commonWallCabinetHeightsUseThe" msgstr "常用吊柜高度。如需自定义高度,请使用下方滑块。" -# Review: required -# Source-Hash: 88ccc87b9b45a464 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies" +# Source-Hash: 88ccc87b9b45a464 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copies" msgstr "副本" -# Review: required -# Source-Hash: d215c489b8385d9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Copies include the source cabinet structure and any attached wall cabinet. Existing modules stay fixed; the requested array must fit in the available run space." +# Source-Hash: d215c489b8385d9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.copiesIncludeTheSourceCabinetStructure" msgstr "副本包含源橱柜结构及其附着的吊柜。现有模块保持不动;所需阵列必须能放入橱柜组的可用空间。" -# Review: required -# Source-Hash: 221d1b64649b08f8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner fillers cannot be used as array sources" +# Source-Hash: 221d1b64649b08f8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cornerFillersCannotBeUsedAs" msgstr "转角填充板不能用作阵列源" -# Review: required -# Source-Hash: d8d1bc5a0f9fa0cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop depth" +# Source-Hash: d8d1bc5a0f9fa0cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopDepth" msgstr "台面深度" -# Review: required -# Source-Hash: 10bba4a11996c84b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Countertop height" +# Source-Hash: 10bba4a11996c84b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.countertopHeight" msgstr "台面高度" -# Review: required -# Source-Hash: 8abdfbc1798d9eaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Create array" +# Source-Hash: 8abdfbc1798d9eaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.createArray" msgstr "创建阵列" -# Review: required -# Source-Hash: 7984b1c42abd9c02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout" +# Source-Hash: 7984b1c42abd9c02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.cutout" msgstr "开槽" -# Review: required -# Source-Hash: 555240ffcca44233 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete {name}" +# Source-Hash: 555240ffcca44233 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.deleteModule" msgstr "删除{name}" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.depth" msgstr "深度" -# Review: required -# Source-Hash: 76cd03300faa9b2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Direction" +# Source-Hash: 76cd03300faa9b2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.direction" msgstr "方向" -# Review: required -# Source-Hash: 5ca5884d3ff7edf4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate along run" +# Source-Hash: 5ca5884d3ff7edf4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.duplicateAlongRun" msgstr "沿橱柜组复制" -# Review: required -# Source-Hash: a834f687290afc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize all resizeable standard base cabinets in this run" +# Source-Hash: a834f687290afc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAll" msgstr "均分此橱柜组中所有可调宽度的标准地柜" -# Review: required -# Source-Hash: 24da8a2de2c6e345 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The resizeable cabinet widths are already equal" +# Source-Hash: 24da8a2de2c6e345 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeAlreadyEqual" msgstr "可调宽度的橱柜已经等宽" -# Review: required -# Source-Hash: c38a4faef2097b73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "At least two standard base cabinets are required" +# Source-Hash: c38a4faef2097b73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNeedsTwo" msgstr "至少需要两个标准地柜" -# Review: required -# Source-Hash: 61f15ae24419430c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "The available run width cannot satisfy the cabinet width limits" +# Source-Hash: 61f15ae24419430c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeNoFit" msgstr "橱柜组的可用宽度无法满足橱柜宽度限制" -# Review: required -# Source-Hash: 7617bb3b455dcdda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Equalize widths" +# Source-Hash: 7617bb3b455dcdda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.equalizeWidths" msgstr "均分宽度" -# Review: required -# Source-Hash: ad8c948d883a0c9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{side, select, left {Left: {width} mm} other {Right: {width} mm}}" +# Source-Hash: ad8c948d883a0c9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fillerPlanSide" msgstr "{side, select, left {左侧:{width} mm} other {右侧:{width} mm}}" -# Review: required -# Source-Hash: 47ccd8c8b04475cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finish & align" +# Source-Hash: 47ccd8c8b04475cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishAlign" msgstr "饰面与对齐" -# Review: required -# Source-Hash: 855892e18c74153a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished back" +# Source-Hash: 855892e18c74153a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedBack" msgstr "背面饰面" -# Review: required -# Source-Hash: 7ab47aea30c73663 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finished exposed ends" +# Source-Hash: 7ab47aea30c73663 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.finishedExposedEnds" msgstr "外露端饰面" -# Review: required -# Source-Hash: c9356f25caf33eec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fits 50–150 mm gaps at run ends beside perpendicular walls." +# Source-Hash: c9356f25caf33eec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fits50150MmGapsAt" msgstr "填补橱柜组两端靠垂直墙处 50–150 mm 的缝隙。" -# Review: required -# Source-Hash: b6f541bc28e38d2f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fit wall fillers" +# Source-Hash: b6f541bc28e38d2f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fitWallFillers" msgstr "适配靠墙填充板" -# Review: required -# Source-Hash: 2af738f010ce28f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fronts" +# Source-Hash: 2af738f010ce28f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.fronts" msgstr "面板" -# Review: required -# Source-Hash: 58884f99d64e6d00 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Handles" +# Source-Hash: 58884f99d64e6d00 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.handles" msgstr "拉手" -# Review: required -# Source-Hash: d73589d7c04b1121 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height preset" +# Source-Hash: d73589d7c04b1121 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.heightPreset" msgstr "预设高度" -# Review: required -# Source-Hash: 590bfdd02dcdb857 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hole" +# Source-Hash: 590bfdd02dcdb857 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.hole" msgstr "孔" -# Review: required -# Source-Hash: 3db1dea825785a30 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inset" +# Source-Hash: 3db1dea825785a30 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.inset" msgstr "内嵌" -# Review: required -# Source-Hash: 2997c230c529590d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Island & Bar" +# Source-Hash: 2997c230c529590d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.islandBar" msgstr "岛台与吧台" -# Review: required -# Source-Hash: a74f10dadc92c8bb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Kitchen run" +# Source-Hash: a74f10dadc92c8bb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.kitchenRun" msgstr "厨房橱柜组" -# Review: required -# Source-Hash: 103e15b39c909210 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Knob" +# Source-Hash: 103e15b39c909210 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.knob" msgstr "旋钮拉手" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.left" msgstr "左" -# Review: required -# Source-Hash: 3c88a1840043e15a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match base heights, backs, reveals and handles. Align upper cabinets; preserve tall appliances and custom compartments." +# Source-Hash: 3c88a1840043e15a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.matchBaseHeightsBacksRevealsAnd" msgstr "统一地柜高度、背板、缝隙和拉手。对齐吊柜;保留高电器和自定义隔间。" -# Review: required -# Source-Hash: b112e31fe44cc94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Metric · 600 mm" +# Source-Hash: b112e31fe44cc94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.metricProfile" msgstr "公制 · 600 mm" -# Review: required -# Source-Hash: 4739f6315c5e8680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Module {number}" +# Source-Hash: 4739f6315c5e8680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.moduleNumber" msgstr "模块 {number}" -# Review: required -# Source-Hash: b8801184e07e4815 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modules" +# Source-Hash: b8801184e07e4815 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.modules" msgstr "模块" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.mounting" msgstr "安装方式" -# Review: required -# Source-Hash: a3914bd96488175c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No eligible module" +# Source-Hash: a3914bd96488175c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.noEligibleModule" msgstr "没有可用模块" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.none" msgstr "无" -# Review: required -# Source-Hash: 9c215dc512e57fa4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overlay" +# Source-Hash: 9c215dc512e57fa4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.overlay" msgstr "盖门" -# Review: required -# Source-Hash: 49fb05dfafd6001d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth height" +# Source-Hash: 49fb05dfafd6001d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.plinthHeight" msgstr "踢脚高度" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.position" msgstr "位置" -# Review: required -# Source-Hash: efca749fc4463623 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Raised Arch" +# Source-Hash: efca749fc4463623 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.raisedArch" msgstr "凸起拱形" -# Review: required -# Source-Hash: 5d6956c21fde6418 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal gap" +# Source-Hash: 5d6956c21fde6418 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.revealGap" msgstr "缝隙" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.right" msgstr "右" -# Review: required -# Source-Hash: e022da49ae8b4aef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Seating overhang" +# Source-Hash: e022da49ae8b4aef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.seatingOverhang" msgstr "座位悬挑" -# Review: required -# Source-Hash: 5b68e435b7210876 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaker" +# Source-Hash: 5b68e435b7210876 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.shaker" msgstr "夏克风" -# Review: required -# Source-Hash: cba29b18ce666b0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shared Plinth & Countertop" +# Source-Hash: cba29b18ce666b0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sharedPlinthCountertop" msgstr "共用踢脚与台面" -# Review: required -# Source-Hash: f7d577c4064c16ff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show countertop" +# Source-Hash: f7d577c4064c16ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showCountertop" msgstr "显示台面" -# Review: required -# Source-Hash: 5d98e04dfd2e0a83 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Show plinth" +# Source-Hash: 5d98e04dfd2e0a83 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.showPlinth" msgstr "显示踢脚" -# Review: required -# Source-Hash: 8775bc8dc9593964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slab" +# Source-Hash: 8775bc8dc9593964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.slab" msgstr "楼板" -# Review: required -# Source-Hash: 5bdc0a959203c52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Source module" +# Source-Hash: 5bdc0a959203c52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.sourceModule" msgstr "源模块" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.spacing" msgstr "间距" -# Review: required -# Source-Hash: 40e3105cbe68d163 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard dimensions" +# Source-Hash: 40e3105cbe68d163 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.standardDimensions" msgstr "标准尺寸" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.style" msgstr "样式" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" # Source-Hash: e9d7ad9851d476d9 # Review: required -# English: "{type, select, shelf {Shelf} drawer {Drawer} door {Door} oven {Oven} microwave {Microwave} dishwasher {Dishwasher} sink {Sink} cooktop_gas {Gas cooktop} cooktop_induction {Induction cooktop} pull_out_pantry {Pull-out pantry} fridge_single {Fridge} fridge_double {Double fridge} fridge_top_freezer {Top-freezer fridge} fridge_bottom_freezer {Bottom-freezer fridge} hood_pyramid {Pyramid hood} hood_curved_glass {Curved glass hood} other {{raw}}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartment" msgstr "{type, select, shelf {层板} drawer {抽屉} door {柜门} oven {烤箱} microwave {微波炉} dishwasher {洗碗机} sink {水槽} cooktop_gas {燃气灶} cooktop_induction {电磁炉} pull_out_pantry {拉出式储物架} fridge_single {冰箱} fridge_double {双门冰箱} fridge_top_freezer {上置冷冻冰箱} fridge_bottom_freezer {下置冷冻冰箱} hood_pyramid {金字塔形烟罩} hood_curved_glass {弧形玻璃烟罩} other {{raw}}}" -# Review: required -# Source-Hash: 98582c257f149d44 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} compartment} other {{number} compartments}}" +# Source-Hash: 98582c257f149d44 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryCompartments" msgstr "{count, plural, other {{number} 个隔间}}" -# Review: required -# Source-Hash: 65b1cbba3b92c5b2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Empty" +# Source-Hash: 65b1cbba3b92c5b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryEmpty" msgstr "空" -# Review: required -# Source-Hash: 5db8b868684cf9f3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tall cabinet" +# Source-Hash: 5db8b868684cf9f3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.summaryTall" msgstr "高柜" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.top" msgstr "顶部" -# Review: required -# Source-Hash: 88c85283afe0c0bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Unable to finish this kitchen run." +# Source-Hash: 88c85283afe0c0bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.unableToFinish" msgstr "无法完成此厨房橱柜组。" -# Review: required -# Source-Hash: 8f668fb3e1a1696a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use {name} as array source" +# Source-Hash: 8f668fb3e1a1696a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useAsArraySource" msgstr "将{name}用作阵列源" -# Review: required -# Source-Hash: 9308a109d017dcd6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Use this module as the array source" +# Source-Hash: 9308a109d017dcd6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.useThisModuleAsTheArray" msgstr "将此模块用作阵列源" -# Review: required -# Source-Hash: 1ce253a4d11bfa68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "US · 24 in" +# Source-Hash: 1ce253a4d11bfa68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.usProfile" msgstr "美制 · 24 in" -# Review: required -# Source-Hash: a2d852b2c5629796 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waterfall ends" +# Source-Hash: a2d852b2c5629796 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cabinet/run-panel.tsx #, fuzzy msgid "node.cabinet.runPanel.waterfallEnds" msgstr "瀑布式侧板" +# English: "An editable module inside a modular cabinet run." # Source-Hash: ffc8d80e438f40b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An editable module inside a modular cabinet run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.description" msgstr "组合橱柜中的可编辑模块。" +# English: "Cabinet Module" # Source-Hash: d5c19ec529e26a98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cabinet Module" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cabinetModule.label" msgstr "橱柜模块" -# Source-Hash: 4d6d5e9439f40e9b +# English: "Bottom chord" +# Source-Hash: 25be6d614ec09df9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.bottomChord" +msgstr "下弦" + +# English: "Ceiling joists" +# Source-Hash: a0c71abc0f146a2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.ceilingJoists" +msgstr "天花板搁栅" + +# English: "Decking" +# Source-Hash: 7d286c87375d417e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.decking" +msgstr "铺板" + +# English: "Finish" +# Source-Hash: b3907da0ad234f10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.finish" +msgstr "饰面" + +# English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.framing" +msgstr "框架" + +# English: "Joists" +# Source-Hash: c7aa596536ceeecb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.joists" +msgstr "搁栅" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.layers" +msgstr "构造层" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.none" +msgstr "无" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.preset" +msgstr "预设" + +# English: "The height is the finished underside; the layers rise from it." +# Source-Hash: 2bc7b44c217cb8fb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside" +msgstr "高度以完成面底面为准;各层由此向上叠加。" + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.thickness" +msgstr "厚度" + +# English: "Truss bottom chords" +# Source-Hash: 2cf52770d658d006 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/ceiling/build-up-section.tsx +#, fuzzy +msgid "node.ceiling.buildUpSection.trussBottomChords" +msgstr "桁架下弦" + # English: "A polygon-bounded ceiling surface that hosts ceiling-mounted items." +# Source-Hash: 4d6d5e9439f40e9b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.description" msgstr "由多边形界定的天花板面,可安装吸顶物品。" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ceiling.label" msgstr "天花板" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.addHole" msgstr "添加开洞" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.area" msgstr "面积" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.auto" msgstr "自动" +# English: "Ceiling" # Source-Hash: 4275c05172abd7fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ceiling" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.ceiling" msgstr "天花板" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.customHeight" msgstr "自定义高度" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.cutOpening" msgstr "开洞" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.done" msgstr "完成" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.followsLevel" msgstr "跟随楼层" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.height" msgstr "高度" +# English: "High (3.0m)" # Source-Hash: 703f56131ac45e60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (3.0m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high30m" msgstr "高(3.0m)" +# English: "High (9'0\")" # Source-Hash: 4ed40bdab397fe22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "High (9'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.high90" msgstr "高(9'0\")" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.holes" msgstr "开洞" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.info" msgstr "信息" +# English: "Limited by the level to {height} — raise the level height for a taller ceiling." # Source-Hash: 6925a008b1924062 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Limited by the level to {height} — raise the level height for a taller ceiling." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.limitedByLevel" msgstr "受楼层限制为 {height} — 提高楼层高度以获得更高的天花板。" +# English: "Low (2.4m)" # Source-Hash: c7410ff087be3a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (2.4m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low24m" msgstr "低(2.4m)" +# English: "Low (8'0\")" # Source-Hash: a59425761b7c9084 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low (8'0\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.low80" msgstr "低(8'0\")" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.move" msgstr "移动" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.noHoles" msgstr "无开洞" +# English: "Standard (2.5m)" # Source-Hash: 24f5fbd158798197 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2.5m)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard25m" msgstr "标准(2.5m)" +# English: "Standard (8'6\")" # Source-Hash: 73cf00cf1900c22b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (8'6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.standard86" msgstr "标准(8'6\")" +# English: "Taller than this level ({available} available). Raise the level height first." # Source-Hash: 065626498678bc5d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Taller than this level ({available} available). Raise the level height first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #, fuzzy msgid "node.ceiling.panel.tallerThanLevel" msgstr "高于此楼层(可用 {available})。请先提高楼层高度。" +# English: "Vertical masonry stack on a roof segment." # Source-Hash: ea12ce3c2b3588e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical masonry stack on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.description" msgstr "屋面上的竖向砌体烟囱。" +# English: "Chimney" # Source-Hash: 95169058fec6db14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.chimney.label" msgstr "烟囱" -# Review: required -# Source-Hash: dc393661f0038c2d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Above Ridge" +# Source-Hash: dc393661f0038c2d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.aboveRidge" msgstr "高出屋脊" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 7ceade8f25b58fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back" +# Source-Hash: 7ceade8f25b58fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.back" msgstr "返回" -# Review: required -# Source-Hash: 0d42509b34b05dec -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bands" +# Source-Hash: 0d42509b34b05dec +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.bands" msgstr "腰线" -# Review: required -# Source-Hash: d52b53241789ee63 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cap" +# Source-Hash: d52b53241789ee63 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cap" msgstr "顶盖" -# Review: required -# Source-Hash: 95169058fec6db14 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney" +# Source-Hash: 95169058fec6db14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimney" msgstr "烟囱" -# Review: required -# Source-Hash: b619f366d2a1dfda -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Chimney Type" +# Source-Hash: b619f366d2a1dfda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.chimneyType" msgstr "烟囱类型" -# Review: required -# Source-Hash: 6786ac75cbe9af57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corbeled" +# Source-Hash: 6786ac75cbe9af57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.corbeled" msgstr "叠涩" -# Review: required -# Source-Hash: 89eba9f077fba348 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner Bevel" +# Source-Hash: 89eba9f077fba348 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cornerBevel" msgstr "转角倒角" -# Review: required -# Source-Hash: 8a4beb9a57edc26e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Count" +# Source-Hash: 8a4beb9a57edc26e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.count" msgstr "数量" -# Review: required -# Source-Hash: f8ba7d73d3006d57 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cricket" +# Source-Hash: f8ba7d73d3006d57 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cricket" msgstr "泛水坡" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.cutoutOffset" msgstr "开洞偏移" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.diameter" msgstr "直径" -# Review: required -# Source-Hash: 3cd71355956a1821 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double" +# Source-Hash: 3cd71355956a1821 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.double" msgstr "双扇" -# Review: required -# Source-Hash: 150340cd05f4a6dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extent" +# Source-Hash: 150340cd05f4a6dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.extent" msgstr "范围" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flat" msgstr "平屋顶" -# Review: required -# Source-Hash: f817c09ef0bb6d69 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flues" +# Source-Hash: f817c09ef0bb6d69 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.flues" msgstr "烟道" -# Review: required -# Source-Hash: a3c10560fa3c1612 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Footprint" +# Source-Hash: a3c10560fa3c1612 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.footprint" msgstr "基底" -# Review: required -# Source-Hash: e360f89d3c0ec456 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Front" +# Source-Hash: e360f89d3c0ec456 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.front" msgstr "前" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 5b490bc19dddf201 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hollow Depth" +# Source-Hash: 5b490bc19dddf201 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.hollowDepth" msgstr "凹槽深度" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.length" msgstr "长度" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.none" msgstr "无" -# Review: required -# Source-Hash: 0f0e13abf9b2bb67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Offset" +# Source-Hash: 0f0e13abf9b2bb67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.offset" msgstr "偏移" -# Review: required -# Source-Hash: 88540def2ce16ac5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Overhang" +# Source-Hash: 88540def2ce16ac5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.overhang" msgstr "出挑" -# Review: required -# Source-Hash: 6ce059aee1386f72 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panels" +# Source-Hash: 6ce059aee1386f72 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.panels" msgstr "面板" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.position" msgstr "位置" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rectangular" msgstr "矩形" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.round" msgstr "圆形" -# Review: required -# Source-Hash: 8c6e3985459dacf9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shoulder" +# Source-Hash: 8c6e3985459dacf9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.shoulder" msgstr "肩部" -# Review: required -# Source-Hash: 84b9195c73dd7a67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Side Margin" +# Source-Hash: 84b9195c73dd7a67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sideMargin" msgstr "侧边距" -# Review: required -# Source-Hash: a05a390ae205fdd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple" +# Source-Hash: a05a390ae205fdd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.simple" msgstr "简单" -# Review: required -# Source-Hash: 62ab2eb3c775da95 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single" +# Source-Hash: 62ab2eb3c775da95 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.single" msgstr "单扇" -# Review: required -# Source-Hash: f6998707123fe72d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sloped" +# Source-Hash: f6998707123fe72d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.sloped" msgstr "倾斜" -# Review: required -# Source-Hash: e4698e83d02beb6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spacing" +# Source-Hash: e4698e83d02beb6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.spacing" msgstr "间距" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.square" msgstr "方形" -# Review: required -# Source-Hash: 2c6b1cf17a973d67 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped" +# Source-Hash: 2c6b1cf17a973d67 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.stepped" msgstr "阶梯式" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.style" msgstr "样式" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.tapered" msgstr "锥形" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.thickness" msgstr "厚度" -# Review: required -# Source-Hash: 14b162761ce18fe9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Offset" +# Source-Hash: 14b162761ce18fe9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.topOffset" msgstr "顶部偏移" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.wallThickness" msgstr "壁厚" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: abff67400a76e5eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brick" +# Source-Hash: abff67400a76e5eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.brick" msgstr "砖砌" -# Review: required -# Source-Hash: f6cbdaa17e989b91 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Modern" +# Source-Hash: f6cbdaa17e989b91 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.modern" msgstr "现代" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/chimney/panel.tsx #, fuzzy msgid "node.chimney.preset.round" msgstr "圆形" +# English: "A parametric column with configurable cross-section, base, and capital." # Source-Hash: 6a06e628257f3393 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A parametric column with configurable cross-section, base, and capital." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.description" msgstr "截面、柱础和柱头可配置的参数化柱。" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.column.label" msgstr "柱" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 14b21685d375581a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "A-Frame" +# Source-Hash: 14b21685d375581a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.aFrame" msgstr "A 字架" -# Review: required -# Source-Hash: 4007d88108e25c2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply preset..." +# Source-Hash: 4007d88108e25c2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyPreset" msgstr "应用预设…" -# Review: required -# Source-Hash: dbfe7c561383c964 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Apply proportion..." +# Source-Hash: dbfe7c561383c964 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.applyProportion" msgstr "应用比例…" -# Review: required -# Source-Hash: 1f3fd938f0b26c9f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Depth" +# Source-Hash: 1f3fd938f0b26c9f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomDepth" msgstr "底部深度" -# Review: required -# Source-Hash: 85576e19cd3da914 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Height" +# Source-Hash: 85576e19cd3da914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomHeight" msgstr "底部高度" -# Review: required -# Source-Hash: 4df0bbb04744a810 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Spread" +# Source-Hash: 4df0bbb04744a810 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomSpread" msgstr "底部张开" -# Review: required -# Source-Hash: 67479fecb980a441 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Step Spread" +# Source-Hash: 67479fecb980a441 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomStepSpread" msgstr "底部台阶间距" -# Review: required -# Source-Hash: 6ea977c42202ca24 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Tiers" +# Source-Hash: 6ea977c42202ca24 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomTiers" msgstr "底部层数" -# Review: required -# Source-Hash: 709ca788827f8a94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom Width" +# Source-Hash: 709ca788827f8a94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bottomWidth" msgstr "底部宽度" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.boxFrame" msgstr "箱形框架" -# Review: required -# Source-Hash: 381b1f481f747677 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Depth" +# Source-Hash: 381b1f481f747677 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceDepth" msgstr "支撑深度" -# Review: required -# Source-Hash: 8509b09b4ecdd6b6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Brace Width" +# Source-Hash: 8509b09b4ecdd6b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.braceWidth" msgstr "支撑宽度" -# Review: required -# Source-Hash: 08558dd786f45281 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulge" +# Source-Hash: 08558dd786f45281 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulge" msgstr "凸起" -# Review: required -# Source-Hash: 663b9a9e6a469b02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bulged" +# Source-Hash: 663b9a9e6a469b02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.bulged" msgstr "鼓形" -# Review: required -# Source-Hash: 3530ac1f4046cd22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Column" +# Source-Hash: 3530ac1f4046cd22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.column" msgstr "柱" -# Review: required -# Source-Hash: ca0a856f04460bd5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Connector Plates" +# Source-Hash: ca0a856f04460bd5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.connectorPlates" msgstr "连接板" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: b963a08f0d540014 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edge Softness" +# Source-Hash: b963a08f0d540014 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.edgeSoftness" msgstr "边缘柔化" -# Review: required -# Source-Hash: 82b3db356639ef92 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ends" +# Source-Hash: 82b3db356639ef92 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ends" msgstr "端部" -# Review: required -# Source-Hash: 646c3cd99df3e285 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Width" +# Source-Hash: 646c3cd99df3e285 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.endWidth" msgstr "端部宽度" -# Review: required -# Source-Hash: 02e229bca2a2b5f1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fork Spread" +# Source-Hash: 02e229bca2a2b5f1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.forkSpread" msgstr "分叉张开" -# Review: required -# Source-Hash: fdebfac05413abcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Heavy" +# Source-Hash: fdebfac05413abcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heavy" msgstr "粗重" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 957fae84ed52a2e3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height and footprint are controlled by the lean-to extension. Rotate or change the support style here; resize from the parent lean-to." +# Source-Hash: 957fae84ed52a2e3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.heightAndFootprintAreControlledBy" msgstr "高度和占地由单坡扩建控制。可在此旋转或更改支撑样式;请在所属单坡扩建中调整尺寸。" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.hourglass" msgstr "沙漏形" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.kBrace" msgstr "K 形支撑" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 6dc190abd3f22787 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Width" +# Source-Hash: 6dc190abd3f22787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.neckWidth" msgstr "颈部宽度" -# Review: required -# Source-Hash: 7dba7ed55eeba18f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Bottom" +# Source-Hash: 7dba7ed55eeba18f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noBottom" msgstr "无底座" -# Review: required -# Source-Hash: 682d71815bb20e94 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Top" +# Source-Hash: 682d71815bb20e94 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.noTop" msgstr "无柱头" -# Review: required -# Source-Hash: 4da8688437f64db8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Plinth Thickness" +# Source-Hash: 4da8688437f64db8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.plinthThickness" msgstr "柱基厚度" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.portalFrame" msgstr "门式框架" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.preset" msgstr "预设" -# Review: required -# Source-Hash: f29fef1beeb25f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rectangular" +# Source-Hash: f29fef1beeb25f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.rectangular" msgstr "矩形" -# Review: required -# Source-Hash: 23b9d290b165cd49 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Pairs" +# Source-Hash: 23b9d290b165cd49 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringPairs" msgstr "环对数" -# Review: required -# Source-Hash: 8db82d731a19651e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Spread" +# Source-Hash: 8db82d731a19651e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringSpread" msgstr "环间距" -# Review: required -# Source-Hash: bdb524a15d9e1509 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ring Thickness" +# Source-Hash: bdb524a15d9e1509 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ringThickness" msgstr "环厚度" -# Review: required -# Source-Hash: ad0457800a1f0eb4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round" +# Source-Hash: ad0457800a1f0eb4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.round" msgstr "圆形" -# Review: required -# Source-Hash: baa84a2d17095dcf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Round Band Width" +# Source-Hash: baa84a2d17095dcf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundBandWidth" msgstr "圆环带宽度" -# Review: required -# Source-Hash: 5f1f67fb0ca59a6d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Bottom" +# Source-Hash: 5f1f67fb0ca59a6d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedBottom" msgstr "圆形底座" -# Review: required -# Source-Hash: 446816980ed0da27 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rounded Top" +# Source-Hash: 446816980ed0da27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.roundedTop" msgstr "圆形柱头" -# Review: required -# Source-Hash: 9e554c26be80f0d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segment Twist" +# Source-Hash: 9e554c26be80f0d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.segmentTwist" msgstr "分段扭转" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaft" msgstr "井道" -# Review: required -# Source-Hash: 9bc3a704a9a96b3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Corner Radius" +# Source-Hash: 9bc3a704a9a96b3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftCornerRadius" msgstr "柱身圆角半径" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shaftWidth" msgstr "柱身宽度" -# Review: required -# Source-Hash: 85ea1d3c8b749680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shape" +# Source-Hash: 85ea1d3c8b749680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shape" msgstr "形状" -# Review: required -# Source-Hash: 49a2ce13016d4073 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Short / Stout" +# Source-Hash: 49a2ce13016d4073 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.shortStout" msgstr "短粗" -# Review: required -# Source-Hash: 4637a9a61638cc66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Block Bottom" +# Source-Hash: 4637a9a61638cc66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleBlockBottom" msgstr "简单块状底座" -# Review: required -# Source-Hash: 9278f9b4a48208b0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Simple Top" +# Source-Hash: 9278f9b4a48208b0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.simpleTop" msgstr "简单柱头" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.singleStrut" msgstr "单撑杆" -# Review: required -# Source-Hash: d87ce271009ce063 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slender" +# Source-Hash: d87ce271009ce063 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.slender" msgstr "纤细" -# Review: required -# Source-Hash: 39287346366fc206 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square" +# Source-Hash: 39287346366fc206 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.square" msgstr "方形" -# Review: required -# Source-Hash: 2dc84759d8df709d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Plinth Bottom" +# Source-Hash: 2dc84759d8df709d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.squarePlinthBottom" msgstr "方形柱基底座" -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.standard" msgstr "标准" -# Review: required -# Source-Hash: 01a6393135e0fb10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Bottom" +# Source-Hash: 01a6393135e0fb10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedBottom" msgstr "阶梯底座" -# Review: required -# Source-Hash: 3d7e443797d4146e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stepped Top" +# Source-Hash: 3d7e443797d4146e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.steppedTop" msgstr "阶梯柱头" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.straight" msgstr "直跑" -# Review: required -# Source-Hash: 6063ae2877faa35d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Taper" +# Source-Hash: 6063ae2877faa35d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.taper" msgstr "收分" -# Review: required -# Source-Hash: f86f1f22230596b7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered" +# Source-Hash: f86f1f22230596b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tapered" msgstr "锥形" -# Review: required -# Source-Hash: 3e7ec47626ee3f22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Depth" +# Source-Hash: 3e7ec47626ee3f22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topDepth" msgstr "顶部深度" -# Review: required -# Source-Hash: 0ca9047731784f56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Height" +# Source-Hash: 0ca9047731784f56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topHeight" msgstr "顶部高度" -# Review: required -# Source-Hash: 3d8a2be83b5b536a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Spread" +# Source-Hash: 3d8a2be83b5b536a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topSpread" msgstr "顶部张开" -# Review: required -# Source-Hash: f4ae0dc639dbc220 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Step Spread" +# Source-Hash: f4ae0dc639dbc220 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topStepSpread" msgstr "顶部台阶间距" -# Review: required -# Source-Hash: 8acc4e843934b086 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Tiers" +# Source-Hash: 8acc4e843934b086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topTiers" msgstr "顶部层数" -# Review: required -# Source-Hash: a50eb6400611a57c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Width" +# Source-Hash: a50eb6400611a57c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.topWidth" msgstr "顶部宽度" -# Review: required -# Source-Hash: f425799b82e085ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Transform" +# Source-Hash: f425799b82e085ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.transform" msgstr "变换" -# Review: required -# Source-Hash: 952fd1ef187c6646 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle" +# Source-Hash: 952fd1ef187c6646 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.trestle" msgstr "支架" -# Review: required -# Source-Hash: 7430dad59aaf98f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod" +# Source-Hash: 7430dad59aaf98f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.tripod" msgstr "三脚" -# Review: required -# Source-Hash: 0db559f22647ec73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Twist Segments" +# Source-Hash: 0db559f22647ec73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.twistSegments" msgstr "扭转分段" -# Review: required -# Source-Hash: 8038a34ffebf4932 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical" +# Source-Hash: 8038a34ffebf4932 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vertical" msgstr "垂直" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.vSupport" msgstr "V 形支撑" -# Review: required -# Source-Hash: 44dda7136b93c8ee -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Waist" +# Source-Hash: 44dda7136b93c8ee +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.waist" msgstr "板式" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.xBrace" msgstr "X 形支撑" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.yaw" msgstr "偏航" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.panel.ySupport" msgstr "Y 形支撑" +# English: "A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up." +# Source-Hash: 4d3a89de7073d38d # Review: required -# Source-Hash: bf2baf3d7b55f0df -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.aSizeDeckPostStandsNo" +msgstr "{size} 平台立柱的高度不超过 {limit}(IRC Table R507.4,取决于树种及其承载面积):请复核,或改用大一号的尺寸。" + +# English: "Custom" +# Source-Hash: 9e9715d52410c86f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.custom" +msgstr "自定义" + +# English: "Fit to the beam over it ({height})" +# Source-Hash: 31f74f6b8e7f077e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheBeamOverIt" +msgstr "贴合上方的梁({height})" + +# English: "Fit to the ceiling plane ({height})" +# Source-Hash: f09c0bf0ea50fc47 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.fitToTheCeilingPlane" +msgstr "贴合天花板平面({height})" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.height" +msgstr "高度" + +# English: "Keep its shape and style it as a column" +# Source-Hash: b1778491849d76d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.keepItsShapeAndStyleIt" +msgstr "保留其形状,并改用柱的样式" + +# English: "Make it a column" +# Source-Hash: 151f47e85cf7cc8b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.makeItAColumn" +msgstr "转为柱" + +# English: "Post" +# Source-Hash: 5cc981e79d737694 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.post" +msgstr "立柱" + +# English: "Treated" +# Source-Hash: 64ed304f3773f93d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.treated" +msgstr "防腐" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/column/post-section.tsx +#, fuzzy +msgid "node.column.postSection.untreated" +msgstr "非防腐" + # English: "A-Frame Support" +# Source-Hash: bf2baf3d7b55f0df +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.aFrameSupport" msgstr "A 字支撑" -# Review: required -# Source-Hash: 51cf77bb359cee43 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight Round" +# Source-Hash: 51cf77bb359cee43 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.basicPillar" msgstr "直圆柱" -# Review: required -# Source-Hash: 39d33008c81fc075 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Box Frame" +# Source-Hash: 39d33008c81fc075 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.boxFrameSupport" msgstr "箱形框架" -# Review: required -# Source-Hash: 4f22058e862d1f81 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Soft Bulged" +# Source-Hash: 4f22058e862d1f81 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.bulgedPillar" msgstr "微鼓形" -# Review: required -# Source-Hash: bb851e93088b73a2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hourglass" +# Source-Hash: bb851e93088b73a2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.hourglassPillar" msgstr "沙漏形" -# Review: required -# Source-Hash: 9cc75129edee59ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "K Brace" +# Source-Hash: 9cc75129edee59ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.kBraceSupport" msgstr "K 形支撑" -# Review: required -# Source-Hash: 0bf453b4882c663c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Portal Frame" +# Source-Hash: 0bf453b4882c663c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.portalFrameSupport" msgstr "门式框架" -# Review: required -# Source-Hash: 9099890734ae1238 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single Strut" +# Source-Hash: 9099890734ae1238 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.singleStrutSupport" msgstr "单撑杆" -# Review: required -# Source-Hash: 0c13af329b9b57cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Square Block" +# Source-Hash: 0c13af329b9b57cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.squarePillar" msgstr "方柱" -# Review: required -# Source-Hash: 4c294bad84924665 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tapered Round" +# Source-Hash: 4c294bad84924665 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.taperedPillar" msgstr "收分圆柱" -# Review: required -# Source-Hash: 360e405bfd74a379 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Trestle Frame" +# Source-Hash: 360e405bfd74a379 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.trestleSupport" msgstr "支架框架" -# Review: required -# Source-Hash: 4589103259b4fc4d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tripod Support" +# Source-Hash: 4589103259b4fc4d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.tripodSupport" msgstr "三脚支撑" -# Review: required -# Source-Hash: f24cc343d79acc10 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "V Support" +# Source-Hash: f24cc343d79acc10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.vFrameSupport" msgstr "V 形支撑" -# Review: required -# Source-Hash: 4e6640dfff42cea4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "X Brace" +# Source-Hash: 4e6640dfff42cea4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.xBraceSupport" msgstr "X 形支撑" -# Review: required -# Source-Hash: 86259690e332b66d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Y Support" +# Source-Hash: 86259690e332b66d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/column/panel.tsx #, fuzzy msgid "node.column.preset.yFrameSupport" msgstr "Y 形支撑" +# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." # Source-Hash: 895766ad1b766c2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Associative linear, curved, circular, angular, or coordinate plan dimension." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.description" msgstr "关联的线性、曲线、圆形、角度或坐标平面尺寸。" +# English: "Construction Dimension" # Source-Hash: 6875b60ee8a7cc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction Dimension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.constructionDimension.label" msgstr "施工尺寸" +# English: "Bottom Left" # Source-Hash: 61a6c6b9bc29c4f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomLeft" msgstr "左下" +# English: "Bottom Right" # Source-Hash: c5368accab136fa6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.bottomRight" msgstr "右下" +# English: "Top Left" # Source-Hash: c8f86af3f03af6c7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topLeft" msgstr "左上" +# English: "Top Right" # Source-Hash: 0ead7f1ffdbbd892 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.cornerRadius.topRight" msgstr "右上" +# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." # Source-Hash: a276c9693f01dec7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered roof lantern with a dome or pyramid cap and optional finial." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.description" msgstr "带百叶的屋顶亭,配圆顶或金字塔顶,可选尖顶饰。" +# English: "Cupola" # Source-Hash: e679dd19f96eba0a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.cupola.label" msgstr "穹顶亭" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: e679dd19f96eba0a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cupola" +# Source-Hash: e679dd19f96eba0a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.cupola" msgstr "穹顶亭" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: de47e23eb81efe02 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dome" +# Source-Hash: de47e23eb81efe02 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.dome" msgstr "穹顶" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: 1cfe33235b30b1d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Finial" +# Source-Hash: 1cfe33235b30b1d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.finial" msgstr "尖顶饰" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.height" msgstr "高度" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.move" msgstr "移动" -# Review: required -# Source-Hash: b5e0cb318d484d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Finial" +# Source-Hash: b5e0cb318d484d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.noFinial" msgstr "无尖顶饰" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 47587b825e9a696b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pyramid" +# Source-Hash: 47587b825e9a696b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.pyramid" msgstr "金字塔形" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.style" msgstr "样式" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/cupola/panel.tsx #, fuzzy msgid "node.cupola.panel.width" msgstr "宽度" +# English: "Auto elevator cutout" # Source-Hash: 5d4ccdf13668fab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto elevator cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoElevator" msgstr "自动电梯开洞" +# English: "Auto stair cutout" # Source-Hash: 78aa03576e02dda7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto stair cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.autoStair" msgstr "自动楼梯开洞" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.opening" msgstr "开口" +# English: "Room cutout" # Source-Hash: ce37dbd7a309940b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.cutout.room" msgstr "房间开洞" -# Source-Hash: 6123b20edb9569de +# English: "Swap hinge side" +# Source-Hash: b1757d76048706b7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapHingeSide" +msgstr "切换铰链侧" + +# English: "Swap swing side" +# Source-Hash: d8bbb9fdf69726ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/door/actions.tsx +#, fuzzy +msgid "node.door.actions.swapSwingSide" +msgstr "切换开启方向" + # English: "A door cut into a wall. Animated open/close state." +# Source-Hash: 6123b20edb9569de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.description" msgstr "开在墙体上的门,带开关动画。" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.door.label" msgstr "门" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.actions" msgstr "操作" +# English: "+ Add Segment" # Source-Hash: 8337b3213849faca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "+ Add Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.addSegment" msgstr "+ 添加分段" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.all" msgstr "全部" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.arch" msgstr "拱形" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.archHeight" msgstr "拱高" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.auto" msgstr "自动" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.autoFollowsTheWall" msgstr "自动跟随墙体" +# English: "Bar Height" # Source-Hash: 387773c8aa2820a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bar Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.barHeight" msgstr "横档高度" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.columns" msgstr "列" +# English: "Content Padding" # Source-Hash: f533584833a4a7bb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Content Padding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.contentPadding" msgstr "内容边距" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.cornerRadius" msgstr "圆角半径" +# English: "Custom: the segments match no style." # Source-Hash: aa0e8e02212091e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the segments match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.customTheSegmentsMatchNoStyle" msgstr "自定义:分段不匹配任何样式。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.delete" msgstr "删除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.depth" msgstr "深度" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.dimensions" msgstr "尺寸" +# English: "Direction" # Source-Hash: 76cd03300faa9b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Direction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.direction" msgstr "方向" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.divider" msgstr "分隔条" +# English: "Door" # Source-Hash: 787e10f5db16904e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.door" msgstr "门" +# English: "Door Closer" # Source-Hash: 4a0aa7f647e6461b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Door Closer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.doorCloser" msgstr "闭门器" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.duplicate" msgstr "复制" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.empty" msgstr "空" +# English: "Enable Handle" # Source-Hash: 6982301da2dbe186 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableHandle" msgstr "启用把手" +# English: "Enable Threshold" # Source-Hash: 2e96ebf9c6e7fe49 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.enableThreshold" msgstr "启用门槛" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.flipSide" msgstr "翻转方向" +# English: "Fold" # Source-Hash: 7e8fdd28f9d54d1f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.fold" msgstr "折叠" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frame" msgstr "框架" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.frameDepthAuto" msgstr "深度(自动)" +# English: "Garage" # Source-Hash: 53aa825a4737190a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Garage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.garage" msgstr "车库" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.glass" msgstr "玻璃" +# English: "Handle" # Source-Hash: 41483b645efc0c65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handle" msgstr "把手" +# English: "Handle Side" # Source-Hash: b4a496a904640583 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handle Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.handleSide" msgstr "把手侧" +# English: "Hardware" # Source-Hash: 8c1bc3a078056ff7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hardware" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hardware" msgstr "五金件" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.height" msgstr "高度" +# English: "Hinges Side" # Source-Hash: 7c8f079bbefe86c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinges Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.hingesSide" msgstr "铰链侧" +# English: "Horizontal" # Source-Hash: ac9c79f08ef130a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.horizontal" msgstr "水平" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.individual" msgstr "单独" +# English: "Inset" # Source-Hash: 3db1dea825785a30 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inset" msgstr "内嵌" +# English: "Inward" # Source-Hash: 1f4ad5ad3e1be222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.inward" msgstr "向内" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.left" msgstr "左" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.move" msgstr "移动" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.open" msgstr "打开" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.opening" msgstr "开口" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.openingShape" msgstr "开洞形状" +# English: "Outward" # Source-Hash: ccd8f06b8f8d27c9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outward" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.outward" msgstr "向外" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panel" msgstr "面板" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panels" msgstr "面板" +# English: "Panic Bar" # Source-Hash: ad56a8ab82ff1a77 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panic Bar" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.panicBar" msgstr "推杠锁" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.pocket" msgstr "暗藏推拉门" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.position" msgstr "位置" +# English: "Rail" # Source-Hash: 947eacd2689daced # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rail" msgstr "横杆" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rect" msgstr "矩形" +# English: "- Remove" # Source-Hash: c05172113af5a2b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "- Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.remove" msgstr "- 移除" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.revealRadius" msgstr "洞口圆角半径" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.right" msgstr "右" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.rounded" msgstr "圆角" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.segments" msgstr "分段" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.sizeIsLimitedToTheWall" msgstr "尺寸受墙体限制,包括洞口框的间隙。" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.slide" msgstr "推拉" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.style" msgstr "样式" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.swing" msgstr "平开" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.thickness" msgstr "厚度" +# English: "Threshold" # Source-Hash: f165d6c05be2b651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Threshold" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.threshold" msgstr "门槛" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.topShape" msgstr "顶部形状" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.type" msgstr "类型" +# English: "Vertical" # Source-Hash: 8038a34ffebf4932 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.vertical" msgstr "垂直" +# English: "wall" # Source-Hash: 63d6b766151965f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.wall" msgstr "墙体" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.panel.width" msgstr "宽度" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.barn" msgstr "谷仓门" +# English: "Craftsman" # Source-Hash: f747fbe71717772e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craftsman" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.craftsman" msgstr "工匠风" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.french" msgstr "法式门" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.glass" msgstr "玻璃" +# English: "Half-louvered" # Source-Hash: 6ab8b98b38eb2e65 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Half-louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.halfLouvered" msgstr "半百叶" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.modern" msgstr "现代" +# English: "Panel" # Source-Hash: 482469f5a0873f73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.panel" msgstr "面板" +# English: "Paneled glass" # Source-Hash: ac42c328610a0edd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Paneled glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.paneledGlass" msgstr "镶板玻璃" +# English: "Shaker" # Source-Hash: 5b68e435b7210876 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shaker" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.shaker" msgstr "夏克风" +# English: "Six-panel" # Source-Hash: 9ad6ac42b4152b64 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Six-panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.door.style.sixPanel" msgstr "六镶板" +# English: "Barn" # Source-Hash: 8421deea35cb5afc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.barn" msgstr "谷仓门" +# English: "Double" # Source-Hash: 3cd71355956a1821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.double" msgstr "双扇" +# English: "Folding" # Source-Hash: 29ff60e662b789fb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Folding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.folding" msgstr "折叠门" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.french" msgstr "法式门" +# English: "Roll-up" # Source-Hash: c86309fefe89699b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roll-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageRollup" msgstr "卷帘门" +# English: "Sectional" # Source-Hash: fb6a868430547544 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sectional" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageSectional" msgstr "分节门" +# English: "Tilt-up" # Source-Hash: 0b5eb64b569db837 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt-up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.garageTiltup" msgstr "翻板门" +# English: "Hinged" # Source-Hash: e744be60baad6abc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hinged" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.hinged" msgstr "平开门" +# English: "Pocket" # Source-Hash: ade6246634d93054 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pocket" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.pocket" msgstr "暗藏推拉门" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #, fuzzy msgid "node.door.type.sliding" msgstr "推拉门" +# English: "House-shaped protrusion on a roof segment." # Source-Hash: 387e57242c8d110a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "House-shaped protrusion on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.description" msgstr "屋面上的房屋形凸出部分。" +# English: "Dormer" # Source-Hash: 6b6f8068bbc4c902 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.dormer.label" msgstr "老虎窗" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: 6b6f8068bbc4c902 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dormer" +# Source-Hash: 6b6f8068bbc4c902 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dormer" msgstr "老虎窗" -# Review: required -# Source-Hash: 6949d667043bee0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dutch" +# Source-Hash: 6949d667043bee0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.dutch" msgstr "荷兰式" -# Review: required -# Source-Hash: 9cab4d4c95f63cdd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat" +# Source-Hash: 9cab4d4c95f63cdd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.flat" msgstr "平屋顶" -# Review: required -# Source-Hash: deaffa095d126dfe -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gable" +# Source-Hash: deaffa095d126dfe +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gable" msgstr "双坡" -# Review: required -# Source-Hash: 7f53d9ffca39984a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gambrel" +# Source-Hash: 7f53d9ffca39984a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.gambrel" msgstr "复折式" -# Review: required -# Source-Hash: 9eda0ea8d447066d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hip" +# Source-Hash: 9eda0ea8d447066d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.hip" msgstr "四坡" -# Review: required -# Source-Hash: 7f7bb0e875af3260 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mansard" +# Source-Hash: 7f7bb0e875af3260 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.mansard" msgstr "孟莎式" -# Review: required -# Source-Hash: 53af9efc7eca6594 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Direction" +# Source-Hash: 53af9efc7eca6594 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchDirection" msgstr "坡向" -# Review: required -# Source-Hash: 6c39ef7125494c4a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pitch Rise" +# Source-Hash: 6c39ef7125494c4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.pitchRise" msgstr "坡度高差" -# Review: required -# Source-Hash: 96336d4b0820b977 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Back" +# Source-Hash: 96336d4b0820b977 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseBack" msgstr "向后升高" -# Review: required -# Source-Hash: ee3d45f8b756d029 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rise Front" +# Source-Hash: ee3d45f8b756d029 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.riseFront" msgstr "向前升高" -# Review: required -# Source-Hash: 5e0622f3b09c9326 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Height" +# Source-Hash: 5e0622f3b09c9326 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofHeight" msgstr "屋顶高度" -# Review: required -# Source-Hash: 19ab9527ae7889f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof Type" +# Source-Hash: 19ab9527ae7889f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.roofType" msgstr "屋顶类型" -# Review: required -# Source-Hash: dba6a7b56cf7083f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Section" +# Source-Hash: dba6a7b56cf7083f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.section" msgstr "部分" -# Review: required -# Source-Hash: c8d0df27faa3feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shed" +# Source-Hash: c8d0df27faa3feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.shed" msgstr "单坡" - -# Review: required -# Source-Hash: e0f973edce3e4ca8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Wall Height" +# Source-Hash: e0f973edce3e4ca8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.wallHeight" msgstr "墙高" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: 0b1d1e9a36456a50 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows" +# Source-Hash: 0b1d1e9a36456a50 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel.tsx #, fuzzy msgid "node.dormer.panel.windows" msgstr "窗户" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.actions" msgstr "操作" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.delete" msgstr "删除" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.duplicate" msgstr "复制" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-actions-section.tsx #, fuzzy msgid "node.dormer.panelActionsSection.move" msgstr "移动" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-position-section.tsx #, fuzzy msgid "node.dormer.panelPositionSection.rotation" msgstr "旋转" -# Review: required -# Source-Hash: 7b19d21b9ad868c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Window" +# Source-Hash: 7b19d21b9ad868c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.addWindow" msgstr "添加窗户" -# Review: required -# Source-Hash: 70d7cc5a6422feaa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit {name}" +# Source-Hash: 70d7cc5a6422feaa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editNamed" msgstr "编辑{name}" -# Review: required -# Source-Hash: b6648f023e6c83dc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit window" +# Source-Hash: b6648f023e6c83dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.editWindow" msgstr "编辑窗户" -# Review: required -# Source-Hash: 4649baf9753aeb74 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Increase the dormer width to add another window." +# Source-Hash: 4649baf9753aeb74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.increaseTheDormerWidthToAdd" msgstr "增加老虎窗宽度才能再添加一扇窗。" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.move" msgstr "移动" -# Review: required -# Source-Hash: c24226954309d83e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move {name}" +# Source-Hash: c24226954309d83e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveNamed" msgstr "移动{name}" -# Review: required -# Source-Hash: f07faaf0d5e43eef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move window" +# Source-Hash: f07faaf0d5e43eef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.moveWindow" msgstr "移动窗户" -# Review: required -# Source-Hash: d2816490ea93d363 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No windows" +# Source-Hash: d2816490ea93d363 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.noWindows" msgstr "没有窗户" -# Review: required -# Source-Hash: 5cbb24f8b1ce7fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Window {number}" +# Source-Hash: 5cbb24f8b1ce7fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowNumber" msgstr "窗户 {number}" -# Review: required -# Source-Hash: 9ca7d951240907cd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Windows ({length})" +# Source-Hash: 9ca7d951240907cd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowsLength" msgstr "窗户({length})" -# Review: required -# Source-Hash: f975c4759607806a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{face, select, back {Back} left {Left} right {Right} other {Front}} · {width} × {height} m" +# Source-Hash: f975c4759607806a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/dormer/panel-windows-section.tsx #, fuzzy msgid "node.dormer.panelWindowsSection.windowSummary" msgstr "{face, select, back {背面} left {左侧} right {右侧} other {正面}} · {width} × {height} m" +# English: "Vertical drop pipe from a gutter outlet to the ground." # Source-Hash: b46392d2ea865760 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical drop pipe from a gutter outlet to the ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.description" msgstr "从檐沟出水口到地面的竖向落水管。" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/downspout/inspector-editors.tsx #, fuzzy msgid "node.downspout.inspectorEditors.position" msgstr "位置" +# English: "Downspout" # Source-Hash: 1a1ec08a311f1b35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Downspout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.downspout.label" msgstr "落水管" +# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." # Source-Hash: 29eadb8c84ae9632 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elbow, tee, reducer, or square-to-round transition connecting duct runs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.description" msgstr "连接风管的弯头、三通、变径或方圆变径。" -# Review: required -# Source-Hash: 7323a0fc9efb077f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap W/H" +# Source-Hash: 7323a0fc9efb077f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWH" msgstr "交换宽/高" -# Review: required -# Source-Hash: 064b36c4fc5deb87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swap width and height" +# Source-Hash: 064b36c4fc5deb87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/inspector-editors.tsx #, fuzzy msgid "node.ductFitting.inspectorEditors.swapWidthAndHeight" msgstr "交换宽度和高度" +# English: "Duct Fitting" # Source-Hash: 6d0c3833d87535e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductFitting.label" msgstr "风管配件" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis" msgstr "轴" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.axis2" msgstr "⌥ 轴" -# Review: required -# Source-Hash: 880b34f6fc0ae854 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hover a duct face that fits the door" +# Source-Hash: 880b34f6fc0ae854 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.hoverADuctFaceThatFits" msgstr "悬停到可容纳检修门的风管面上" -# Review: required -# Source-Hash: 706f5b43a1d4a84a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Place access door" +# Source-Hash: 706f5b43a1d4a84a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.placeAccessDoor" msgstr "放置检修门" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-fitting/tool.tsx #, fuzzy msgid "node.ductFitting.tool.rTRotate" msgstr "R/T 旋转" +# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." # Source-Hash: 09eb45a9b35d1a9f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC duct run — polyline of round, rect, or flat-oval sections." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.description" msgstr "暖通风管 — 由圆形、矩形或扁椭圆截面组成的折线。" +# English: "Duct" # Source-Hash: e971d82044e2f48e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductSegment.label" msgstr "风管" +# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." # Source-Hash: 33181d0f42421013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duct terminal — supply register, ceiling diffuser, or return grille. Duct runs end at its collar." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.description" msgstr "风口 — 送风口、散流器或回风格栅。风管在其接口处终止。" +# English: "Register" # Source-Hash: 161e5386b939e1bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Register" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ductTerminal.label" msgstr "风口" -# Review: required -# Source-Hash: 83c665eafcf43a68 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{mount, select, floor {Mount Floor} ceiling {Mount Ceiling} wall {Mount Wall} other {Mount {label}}}" +# Source-Hash: 83c665eafcf43a68 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mount" msgstr "{mount, select, floor {安装:地面} ceiling {安装:天花板} wall {安装:墙面} other {安装:{label}}}" -# Review: required -# Source-Hash: f067dd52ea051385 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "M surface" +# Source-Hash: f067dd52ea051385 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #. Key hint: pressing M switches the mounting surface. #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.mSurface" msgstr "M 表面" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.rTRotate" msgstr "R/T 旋转" -# Review: required -# Source-Hash: 98446855908ee71d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Snapped to duct" +# Source-Hash: 98446855908ee71d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/duct-terminal/tool.tsx #, fuzzy msgid "node.ductTerminal.tool.snappedToDuct" msgstr "已吸附到风管" +# English: "A multi-level elevator shaft with configurable openings per level." # Source-Hash: d0afa386e65cd3a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A multi-level elevator shaft with configurable openings per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.description" msgstr "跨多个楼层的电梯井,每层开口可配置。" +# English: "Elevator" # Source-Hash: ab5cbab7be160a1c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.elevator.label" msgstr "电梯" -# Review: required -# Source-Hash: 7e6d5f138e315bdb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Access" +# Source-Hash: 7e6d5f138e315bdb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.access" msgstr "通行" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: fdf7108a011bd94b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab" +# Source-Hash: fdf7108a011bd94b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cab" msgstr "轿厢" -# Review: required -# Source-Hash: 2d0ab44f3820f22c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cab Height" +# Source-Hash: 2d0ab44f3820f22c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.cabHeight" msgstr "轿厢高度" -# Review: required -# Source-Hash: 65f410261609060b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Center opening" +# Source-Hash: 65f410261609060b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.centerOpening" msgstr "中开" -# Review: required -# Source-Hash: c5433f4d3ea8a8a7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Default Floor" +# Source-Hash: c5433f4d3ea8a8a7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.defaultFloor" msgstr "默认楼层" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 748877ae309c313c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Destination" +# Source-Hash: 748877ae309c313c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.destination" msgstr "目标" -# Review: required -# Source-Hash: 3bff69bd74544339 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Disabled" +# Source-Hash: 3bff69bd74544339 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.disabled" msgstr "已停用" -# Review: required -# Source-Hash: d8baf402fa9c52f0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Height" +# Source-Hash: d8baf402fa9c52f0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorHeight" msgstr "门高度" -# Review: required -# Source-Hash: 20f60b98de67849b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Doors" +# Source-Hash: 20f60b98de67849b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doors" msgstr "门" -# Review: required -# Source-Hash: eb0d4023a513b16c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Time" +# Source-Hash: eb0d4023a513b16c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorTime" msgstr "开门时间" -# Review: required -# Source-Hash: b3e001acdb1f869d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Type" +# Source-Hash: b3e001acdb1f869d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorType" msgstr "门类型" -# Review: required -# Source-Hash: c6203a69fdf44065 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Door Width" +# Source-Hash: c6203a69fdf44065 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.doorWidth" msgstr "门宽度" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: eb3abe5340b26138 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dwell" +# Source-Hash: eb3abe5340b26138 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.dwell" msgstr "停靠时间" -# Review: required -# Source-Hash: ab5cbab7be160a1c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Elevator" +# Source-Hash: ab5cbab7be160a1c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.elevator" msgstr "电梯" -# Review: required -# Source-Hash: 702140636ff3977a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "From" +# Source-Hash: 702140636ff3977a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.from" msgstr "起始" -# Review: required -# Source-Hash: 8ab91094cf650204 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass" +# Source-Hash: 8ab91094cf650204 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glass" msgstr "玻璃" -# Review: required -# Source-Hash: 88810658a2b97bc6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass frame" +# Source-Hash: 88810658a2b97bc6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.glassFrame" msgstr "玻璃框" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.motion" msgstr "运动" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 825ac8855353a4e1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Style" +# Source-Hash: 825ac8855353a4e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.openingStyle" msgstr "开门方式" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: f9a95d8407fafda4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Segmented panel" +# Source-Hash: f9a95d8407fafda4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.segmentedPanel" msgstr "分段面板" -# Review: required -# Source-Hash: aa7b49b13f96057f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Service" +# Source-Hash: aa7b49b13f96057f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.service" msgstr "服务" -# Review: required -# Source-Hash: 4fc63ad21de30103 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft" +# Source-Hash: 4fc63ad21de30103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaft" msgstr "井道" -# Review: required -# Source-Hash: 6605ade39317cd42 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Depth" +# Source-Hash: 6605ade39317cd42 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftDepth" msgstr "井道深度" -# Review: required -# Source-Hash: b28db5a3a48fe52e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Style" +# Source-Hash: b28db5a3a48fe52e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftStyle" msgstr "井道样式" -# Review: required -# Source-Hash: 3dab61aeb47e504d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shaft Width" +# Source-Hash: 3dab61aeb47e504d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.shaftWidth" msgstr "井道宽度" -# Review: required -# Source-Hash: aee3b2bd78c87d0b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single left" +# Source-Hash: aee3b2bd78c87d0b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleLeft" msgstr "单扇左开" -# Review: required -# Source-Hash: cc43ffbef4ac7ae0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single right" +# Source-Hash: cc43ffbef4ac7ae0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.singleRight" msgstr "单扇右开" -# Review: required -# Source-Hash: ef3e765e692faf4c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid" +# Source-Hash: ef3e765e692faf4c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solid" msgstr "实体" -# Review: required -# Source-Hash: 8008b6f17089beaf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid panel" +# Source-Hash: 8008b6f17089beaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.solidPanel" msgstr "实板" -# Review: required -# Source-Hash: 3b006be0b1f0493f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Speed" +# Source-Hash: 3b006be0b1f0493f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.speed" msgstr "速度" -# Review: required -# Source-Hash: 6d37a1679fa3708e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Stop {stopOrder}" +# Source-Hash: 6d37a1679fa3708e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.stopStoporder" msgstr "停靠站 {stopOrder}" -# Review: required -# Source-Hash: b3214a20e45a7aed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "To" +# Source-Hash: b3214a20e45a7aed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.to" msgstr "至" -# Review: required -# Source-Hash: 1a5ccd607c400d76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Wall Thickness" +# Source-Hash: 1a5ccd607c400d76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.wallThickness" msgstr "壁厚" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: fefc9b1cfb26b5a8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yaw" +# Source-Hash: fefc9b1cfb26b5a8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/elevator/panel.tsx #, fuzzy msgid "node.elevator.panel.yaw" msgstr "偏航" +# English: "Low curved lens-shaped roof vent with a louvered front." # Source-Hash: 7b22c91cf520e161 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low curved lens-shaped roof vent with a louvered front." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.description" msgstr "低矮的弧形透镜状屋顶通风口,前部带百叶。" +# English: "Eyebrow Vent" # Source-Hash: 654b29319185ac66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.eyebrowVent.label" msgstr "眉形通风口" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: abc97377800a8333 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Back height" +# Source-Hash: abc97377800a8333 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.backHeight" msgstr "后部高度" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: 654b29319185ac66 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Eyebrow Vent" +# Source-Hash: 654b29319185ac66 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.eyebrowVent" msgstr "眉形通风口" -# Review: required -# Source-Hash: a2d107a5deaa01f4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Half-round" +# Source-Hash: a2d107a5deaa01f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.halfRound" msgstr "半圆" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 318192f27a774fb7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Louvers" +# Source-Hash: 318192f27a774fb7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.louvers" msgstr "百叶" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: e16f85e4bebfefb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Scoop" +# Source-Hash: e16f85e4bebfefb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.scoop" msgstr "勺形" -# Review: required -# Source-Hash: ec53d40877e11b62 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Slant-box" +# Source-Hash: ec53d40877e11b62 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.slantBox" msgstr "斜箱" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.style" msgstr "样式" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/eyebrow-vent/panel.tsx #, fuzzy msgid "node.eyebrowVent.panel.width" msgstr "宽度" +# English: "A straight or curved fence segment with configurable posts and infill." # Source-Hash: 20032fe9430f610d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved fence segment with configurable posts and infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.description" msgstr "立柱和填充可配置的直线或曲线围栏段。" -# Review: required -# Source-Hash: 6f84b85097bc1ffc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add point after" +# Source-Hash: 6f84b85097bc1ffc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.addPointAfter" msgstr "在后面添加点" -# Review: required -# Source-Hash: 1a15ab3101e2d166 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a slab or shaped surface on this level." +# Source-Hash: 1a15ab3101e2d166 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASlabOrShapedSurface" msgstr "请在此楼层选择一块楼板或造型表面。" -# Review: required -# Source-Hash: 78a5625401384000 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Choose a surface" +# Source-Hash: 78a5625401384000 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.chooseASurface" msgstr "选择表面" -# Review: required -# Source-Hash: 2ee121dca87bb64a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Corner" +# Source-Hash: 2ee121dca87bb64a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.corner" msgstr "转角" -# Review: required -# Source-Hash: f08fbb462e9afc0e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curve" +# Source-Hash: f08fbb462e9afc0e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curve" msgstr "曲线" -# Review: required -# Source-Hash: a028e08962c388e0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curved" +# Source-Hash: a028e08962c388e0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.curved" msgstr "弧形" -# Review: required -# Source-Hash: 81b75058b7843a0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{style, select, picket {Distribution controls pickets in each bay between posts.} horizontal {Distribution controls posts along the full fence.} other {Distribution controls infill pieces between the two end posts.}} Spacing is measured between centers. The available width limits the count so pieces do not overlap." +# Source-Hash: 81b75058b7843a0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.distributionInfo" msgstr "{style, select, picket {分布控制各柱间开间内的栅条。} horizontal {分布控制整段围栏上的立柱。} other {分布控制两端立柱之间的填充件。}}间距按中心计算。可用宽度会限制数量,避免构件重叠。" -# Review: required -# Source-Hash: fbc8f54b0e3fdeb1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Edit path" +# Source-Hash: fbc8f54b0e3fdeb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.editPath" msgstr "编辑路径" -# Review: required -# Source-Hash: 55686db44529c013 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Extend path" +# Source-Hash: 55686db44529c013 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.extendPath" msgstr "延长路径" -# Review: required -# Source-Hash: 0bf0f27e631aa98d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow highest surface" +# Source-Hash: 0bf0f27e631aa98d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followHighestSurface" msgstr "跟随最高表面" -# Review: required -# Source-Hash: e0b381fcb05a65c4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow selected surface" +# Source-Hash: e0b381fcb05a65c4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.followSelectedSurface" msgstr "跟随所选表面" -# Review: required -# Source-Hash: c418d97f803af8dd -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hold starting height" +# Source-Hash: c418d97f803af8dd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.holdStartingHeight" msgstr "保持起始高度" -# Review: required -# Source-Hash: 295ca9336dc1935b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Join" +# Source-Hash: 295ca9336dc1935b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.join" msgstr "连接" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.length" msgstr "长度" -# Review: required -# Source-Hash: 1fedc8a2f2ea6887 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{count, plural, one {{number} point} other {{number} points}} · {length} m" +# Source-Hash: 1fedc8a2f2ea6887 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.pathSummary" msgstr "{count, plural, other {{number} 个点}} · {length} m" -# Review: required -# Source-Hash: c96d499e11181429 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket rail projection" +# Source-Hash: c96d499e11181429 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.picketRailProjection" msgstr "栅条相对横杆的凸出" -# Review: required -# Source-Hash: a1880fca8372d06f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Point" +# Source-Hash: a1880fca8372d06f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.point" msgstr "点" -# Review: required -# Source-Hash: 39bc6254b5307f53 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove point" +# Source-Hash: 39bc6254b5307f53 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.removePoint" msgstr "移除点" -# Review: required -# Source-Hash: ea56af3a0d7a65e4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Set each span to straight or curved, then drag its points and curve handles in either view." +# Source-Hash: ea56af3a0d7a65e4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.setEachSpanToStraightOr" msgstr "将每段设为直线或曲线,然后在任一视图中拖动其点和曲线控制柄。" -# Review: required -# Source-Hash: 354c8bc5c8da5a6e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Smooth" +# Source-Hash: 354c8bc5c8da5a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.smooth" msgstr "平滑" -# Review: required -# Source-Hash: 3c700cc1299a150f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Span to point {number}" +# Source-Hash: 3c700cc1299a150f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.spanToPointNumber" msgstr "至点 {number} 的分段" -# Review: required -# Source-Hash: 4ee35f740730ae87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Straight" +# Source-Hash: 4ee35f740730ae87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.straight" msgstr "直跑" -# Review: required -# Source-Hash: 78a454b7ac7a3cd9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface" +# Source-Hash: 78a454b7ac7a3cd9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surface" msgstr "表面" -# Review: required -# Source-Hash: ab2de0428c093018 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Surface behavior" +# Source-Hash: ab2de0428c093018 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/inspector-editors.tsx #, fuzzy msgid "node.fence.inspectorEditors.surfaceBehavior" msgstr "表面行为" +# English: "Fence" # Source-Hash: 02042fde61b5aa11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fence" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fence.label" msgstr "围栏" -# Review: required -# Source-Hash: 3e07a9889c1bd7f9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Detach endpoint" +# Source-Hash: 3e07a9889c1bd7f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.detachEndpoint" msgstr "分离端点" -# Review: required -# Source-Hash: 7a3ce9fec6370e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Drag endpoint" +# Source-Hash: 7a3ce9fec6370e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence/move-endpoint-tool.tsx #, fuzzy msgid "node.fence.moveEndpointTool.dragEndpoint" msgstr "拖动端点" -# Review: required -# Source-Hash: d7cf5045c01b2eb9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Close" +# Source-Hash: d7cf5045c01b2eb9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.close" msgstr "关闭" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/actions.tsx #, fuzzy msgid "node.fenceFeature.actions.open" msgstr "打开" -# Review: required -# Source-Hash: fc4cf2bc034fc547 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Board width" +# Source-Hash: fc4cf2bc034fc547 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.boardWidth" msgstr "板宽" -# Review: required -# Source-Hash: f3b11e5b2dd3b25f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bracing" +# Source-Hash: f3b11e5b2dd3b25f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.bracing" msgstr "斜撑" -# Review: required -# Source-Hash: d09ec3d9565734c3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Distance from start" +# Source-Hash: d09ec3d9565734c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.distanceFromStart" msgstr "距起点距离" -# Review: required -# Source-Hash: dcd803eaf3796298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Fence unavailable." +# Source-Hash: dcd803eaf3796298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.fenceUnavailable" msgstr "围栏不可用。" -# Review: required -# Source-Hash: 3519d91b7bda18a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame width" +# Source-Hash: 3519d91b7bda18a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.frameWidth" msgstr "框架宽度" -# Review: required -# Source-Hash: cb18554b2420e700 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate height" +# Source-Hash: cb18554b2420e700 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateHeight" msgstr "门高度" -# Review: required -# Source-Hash: 54098b5ee574737a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gate style" +# Source-Hash: 54098b5ee574737a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.gateStyle" msgstr "大门样式" -# Review: required -# Source-Hash: 4fc3e60be28f60d5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ground clearance" +# Source-Hash: 4fc3e60be28f60d5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.groundClearance" msgstr "离地间隙" -# Review: required -# Source-Hash: 916f2e07bf082795 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinges and latch" +# Source-Hash: 916f2e07bf082795 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingesAndLatch" msgstr "铰链与门闩" -# Review: required -# Source-Hash: 5bd2e597fbb2a2c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hinge side" +# Source-Hash: 5bd2e597fbb2a2c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.hingeSide" msgstr "铰链侧" -# Review: required -# Source-Hash: 8fec7e969045a8e7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Horizontal boards" +# Source-Hash: 8fec7e969045a8e7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.horizontalBoards" msgstr "横向板条" -# Review: required -# Source-Hash: f4addad87cc4223e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Infill spacing" +# Source-Hash: f4addad87cc4223e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.infillSpacing" msgstr "填充间距" -# Review: required -# Source-Hash: cdec08b292742b20 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Jamb posts" +# Source-Hash: cdec08b292742b20 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.jambPosts" msgstr "门框立柱" -# Review: required -# Source-Hash: ef88410453ee7414 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Keep the opening inside the fence and clear of other gates or passages." +# Source-Hash: ef88410453ee7414 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.keepTheOpeningInsideTheFence" msgstr "请将开口保持在围栏内,并避开其他门或通道。" -# Review: required -# Source-Hash: 67cd0171b226fedb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaf thickness" +# Source-Hash: 67cd0171b226fedb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leafThickness" msgstr "门扇厚度" -# Review: required -# Source-Hash: c390ba248e629b9e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Leaves" +# Source-Hash: c390ba248e629b9e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leaves" msgstr "门扇" -# Review: required -# Source-Hash: d7bb934589475512 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left leaf proportion" +# Source-Hash: d7bb934589475512 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.leftLeafProportion" msgstr "左门扇比例" -# Review: required -# Source-Hash: aa9fe0ebace8957d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence height" +# Source-Hash: aa9fe0ebace8957d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceHeight" msgstr "与围栏同高" -# Review: required -# Source-Hash: c15c1af69ec5c1eb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Match fence style" +# Source-Hash: c15c1af69ec5c1eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.matchFenceStyle" msgstr "匹配围栏样式" -# Review: required -# Source-Hash: 51b7ab6a0a78d90a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening angle" +# Source-Hash: 51b7ab6a0a78d90a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingAngle" msgstr "开启角度" -# Review: required -# Source-Hash: d2e20300c07aa722 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening width" +# Source-Hash: d2e20300c07aa722 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openingWidth" msgstr "开口宽度" -# Review: required -# Source-Hash: 6b108fb1d0eb8f9d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open rails" +# Source-Hash: 6b108fb1d0eb8f9d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.openRails" msgstr "开放栏杆" -# Review: required -# Source-Hash: d500c4b7827d3390 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cross" +# Source-Hash: d500c4b7827d3390 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.cross" msgstr "十字" -# Review: required -# Source-Hash: 7420c0d2caee1c22 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diagonal" +# Source-Hash: 7420c0d2caee1c22 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.diagonal" msgstr "斜撑" -# Review: required -# Source-Hash: 7308869944020839 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Double gate" +# Source-Hash: 7308869944020839 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.doubleGate" msgstr "双开大门" -# Review: required -# Source-Hash: 1f4ad5ad3e1be222 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Inward" +# Source-Hash: 1f4ad5ad3e1be222 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.inward" msgstr "向内" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.left" msgstr "左" -# Review: required -# Source-Hash: f2953d2931e36d0c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.none" msgstr "无" -# Review: required -# Source-Hash: ccd8f06b8f8d27c9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Outward" +# Source-Hash: ccd8f06b8f8d27c9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.outward" msgstr "向外" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.right" msgstr "右" -# Review: required -# Source-Hash: d327097d9ac1099f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Single gate" +# Source-Hash: d327097d9ac1099f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.option.singleGate" msgstr "单开大门" -# Review: required -# Source-Hash: 9295ddc2805b21e2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Picket" +# Source-Hash: 9295ddc2805b21e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.picket" msgstr "尖桩" -# Review: required -# Source-Hash: 8dd5999b235f60ab -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Post height" +# Source-Hash: 8dd5999b235f60ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.postHeight" msgstr "立柱高度" -# Review: required -# Source-Hash: 90bcd7a8123ec343 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove gate" +# Source-Hash: 90bcd7a8123ec343 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeGate" msgstr "移除大门" -# Review: required -# Source-Hash: 7e3505710613543f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove opening" +# Source-Hash: 7e3505710613543f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.removeOpening" msgstr "移除开口" -# Review: required -# Source-Hash: af6f8885fe0fa4e9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solid privacy" +# Source-Hash: af6f8885fe0fa4e9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.solidPrivacy" msgstr "实心遮挡" -# Review: required -# Source-Hash: 1a7048255c3bdfcb -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Swing direction" +# Source-Hash: 1a7048255c3bdfcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.swingDirection" msgstr "开启方向" -# Review: required -# Source-Hash: eba3df14a2de0812 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vertical slats" +# Source-Hash: eba3df14a2de0812 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/fence-feature/inspector.tsx #, fuzzy msgid "node.fenceFeature.inspector.verticalSlats" msgstr "竖向板条" +# English: "A gate hosted by a fence." # Source-Hash: df61821ed00852d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A gate hosted by a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.description" msgstr "安装在围栏上的大门。" +# English: "Gate" # Source-Hash: 4a479aa23542d766 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceGate.label" msgstr "大门" +# English: "An intentional opening in a fence." # Source-Hash: 8f9c927d79796c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An intentional opening in a fence." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.description" msgstr "围栏上的预留开口。" +# English: "Open Passage" # Source-Hash: ab67bb47e647d594 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open Passage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.fenceOpening.label" msgstr "通道" +# English: "Floor opening" # Source-Hash: dc3adf0c2d50dba6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.floorOpening.label" msgstr "楼板开洞" -# Source-Hash: 2f4fac2b1d39fdb1 +# English: "A concrete strip or pad footing poured under the lowest floor." +# Source-Hash: 0d2952c324c93b87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.description" +msgstr "浇筑于最底层楼面之下的混凝土条形或独立基脚。" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.footing.label" +msgstr "基脚" + +# English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.actions" +msgstr "操作" + +# English: "A grade beam on piles, as the building is." +# Source-Hash: 5cbf600af0f430c1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aGradeBeamOnPilesAs" +msgstr "桩上的基础梁,与建筑一致。" + +# English: "A pier carries the floor." +# Source-Hash: 4de35a53f73e6d74 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPierCarriesTheFloor" +msgstr "由支墩承托楼面。" + +# English: "A pile cap on one pile, as the building is." +# Source-Hash: b07f0dafd314ce61 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aPileCapOnOnePile" +msgstr "单桩上的桩承台,与建筑一致。" + +# English: "A stem wall carries the floor." +# Source-Hash: 9b596c6f41bdd6dc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aStemWallCarriesTheFloor" +msgstr "由基础短墙承托楼面。" + +# English: "A thickened slab under the floor slab." +# Source-Hash: edd463abf1ba203f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.aThickenedSlabUnderTheFloor" +msgstr "楼板下方的加厚板。" + +# English: "At least" +# Source-Hash: 7d978d555a1fba4a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.atLeast" +msgstr "至少" + +# English: ", bottom" +# Source-Hash: a6b79a24d92094bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.bottom" +msgstr ",底部" + +# English: "Clear the depth" +# Source-Hash: f09c3e23c581e6e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.clearTheDepth" +msgstr "清除深度" + +# English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.delete" +msgstr "删除" + +# English: "Depth under grade" +# Source-Hash: 13c7635ae6ebd98b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGrade" +msgstr "埋深" + +# English: "Depth under grade (building's)" +# Source-Hash: fb5c116d6c004e8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.depthUnderGradeBuildingS" +msgstr "埋深(随建筑)" + +# English: "Footing" +# Source-Hash: 874e0f0ae5a23e93 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.footing" +msgstr "基脚" + +# English: "IRC R403.1 asks a footing at least 12 in wide and 6 in thick." +# Source-Hash: 742775b7550e4d10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.ircR4031AsksAFooting" +msgstr "IRC R403.1 要求基脚宽至少 12 in、厚至少 6 in。" + +# English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.length" +msgstr "长度" + +# English: "Pad" +# Source-Hash: 862a4fa9c40e14b2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.pad" +msgstr "独立" + +# English: "Strip" +# Source-Hash: 168d7c95aa1872fd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.strip" +msgstr "条形" + +# English: "the footing’s thickness under the floor reaches this far." +# Source-Hash: 2a4c3ad2bb841f1d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theFootingSThicknessUnderThe" +msgstr "楼面下的基脚厚度达到此深度。" + +# English: "the grade beam reaches this far." +# Source-Hash: b907c56408d326b6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theGradeBeamReachesThisFar" +msgstr "基础梁达到此深度。" + +# English: "the slab and the footing’s thickness reach this far." +# Source-Hash: 0ec23acc847b60c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.theSlabAndTheFootingS" +msgstr "楼板与基脚厚度合计达到此深度。" + +# English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.thickness" +msgstr "厚度" + +# English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.top" +msgstr "顶部" + +# English: "Use the building's depth" +# Source-Hash: 78d9e3ca09235067 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.useTheBuildingSDepth" +msgstr "使用建筑的深度" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/footing/panel.tsx +#, fuzzy +msgid "node.footing.panel.width" +msgstr "宽度" + +# English: "The outline the foundation is poured to: drawn, or traced from the walls and stretched." +# Source-Hash: 1e069ed68ad21990 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.description" +msgstr "基础浇筑所依据的轮廓:手动绘制,或沿墙体描绘后再拉伸调整。" + +# English: "Foundation" +# Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/i18n/definition-messages.ts +#, fuzzy +msgid "node.foundation.label" +msgstr "基础" + # English: "A measurement / reference annotation (linear, area, or arc)." +# Source-Hash: 2f4fac2b1d39fdb1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.description" msgstr "测量 / 参考标注(线性、面积或弧长)。" +# English: "Guide" # Source-Hash: 1344ea2602e3aa9e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Guide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.guide.label" msgstr "参考图" +# English: "Rain-water channel running along the eave of a roof segment." # Source-Hash: 5402d634f40b2f97 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rain-water channel running along the eave of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.description" msgstr "沿屋面檐口布置的雨水槽。" -# Review: required -# Source-Hash: fc7d4ecf973db135 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Add Downspout" +# Source-Hash: fc7d4ecf973db135 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.addDownspout" msgstr "添加落水管" -# Review: required -# Source-Hash: 9b9929bac2c39bca -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspout {number}" +# Source-Hash: 9b9929bac2c39bca +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspoutNumber" msgstr "落水管 {number}" -# Review: required -# Source-Hash: fc34d45725838141 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Downspouts" +# Source-Hash: fc34d45725838141 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.downspouts" msgstr "落水管" -# Review: required -# Source-Hash: ac329c74056f0616 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Remove downspout" +# Source-Hash: ac329c74056f0616 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/gutter/downspouts-panel.tsx #, fuzzy msgid "node.gutter.downspoutsPanel.removeDownspout" msgstr "移除落水管" +# English: "Gutter" # Source-Hash: 14ba981da3627ae2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.gutter.label" msgstr "檐沟" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Manual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.hole.manual" msgstr "手动" +# English: "Hole {number} (Editing)" # Source-Hash: fffde6fa82e65ffc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number} (Editing)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeEditing" msgstr "开洞 {number}(编辑中)" +# English: "Hole {number}" # Source-Hash: 0fe446c72451e4a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hole {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeNumber" msgstr "开洞 {number}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# Source-Hash: fd26f8df9d087c6b +# English: "{area} {unit} · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# Source-Hash: 8eeeb79ff4221939 # Review: required -# English: "{area} m² · {count, plural, one {{points} pt} other {{points} pts}} · {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.holeSummary" -msgstr "{area} m² · {count, plural, other {{points} 个点}} · {source}" +msgstr "{area} {unit} · {count, plural, other {{points} 个点}} · {source}" +# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." # Source-Hash: 50e9252d30f3fb4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Furnace, air handler, or condenser — duct runs connect to its supply/return collars." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.description" msgstr "暖风炉、空气处理机或冷凝机组 — 风管连接到其送风 / 回风接口。" +# English: "HVAC Unit" # Source-Hash: 3bf1c1a8dbb93743 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "HVAC Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.hvacEquipment.label" msgstr "暖通设备" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/hvac-equipment/tool.tsx #, fuzzy msgid "node.hvacEquipment.tool.rTRotate" msgstr "R/T 旋转" +# English: "Geometry preserved from an imported model when no native Pascal shape exists." # Source-Hash: f24a1089d9256460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry preserved from an imported model when no native Pascal shape exists." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.description" msgstr "当没有对应的 Pascal 原生形体时,保留自导入模型的几何体。" +# English: "Imported Mesh" # Source-Hash: 9f9d11f287f722d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Imported Mesh" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.importedMesh.label" msgstr "导入网格" +# English: "Couldn't rebuild with these values." # Source-Hash: 9cce89f94c673821 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Couldn't rebuild with these values." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.couldnTRebuildWithTheseValues" msgstr "无法使用这些值重建。" +# English: "Parameters" # Source-Hash: 373b929318436fe3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.parameters" msgstr "参数" +# English: "Rebuilding…" # Source-Hash: 558cb886dbe0babe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuilding" msgstr "重建中…" +# English: "Rebuilding in Blender…" # Source-Hash: a094c99dc8ad29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rebuilding in Blender…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.rebuildingInBlender" msgstr "正在 Blender 中重建…" +# English: "Reset {param}" # Source-Hash: 1778f93ee143cd95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset {param}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParam" msgstr "重置{param}" +# English: "Reset parameters" # Source-Hash: 5a28cf5f2402dd7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset parameters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetParameters" msgstr "重置参数" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/authored-params.tsx #, fuzzy msgid "node.item.authoredParams.resetToDefault" msgstr "恢复默认" +# English: "A catalog-backed item (furniture, fixtures, decorations)." # Source-Hash: 63f79b3c467177b3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A catalog-backed item (furniture, fixtures, decorations)." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.description" msgstr "来自目录的物品(家具、设施、装饰)。" +# English: "Item" # Source-Hash: 2743a0e9d2743807 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.item.label" msgstr "物品" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.actions" msgstr "操作" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.delete" msgstr "删除" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.dimensions" msgstr "尺寸" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.duplicate" msgstr "复制" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.info" msgstr "信息" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.move" msgstr "移动" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.position" msgstr "位置" +# English: "rot" # Source-Hash: 5f433581c8f404fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "rot" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rot" msgstr "旋转" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.rotation" msgstr "旋转" +# English: "Scale" # Source-Hash: 8210f376c0d29acc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale" msgstr "比例" +# English: "scale" # Source-Hash: 58fee802997c811c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.scale2" msgstr "比例" +# English: "Uniform Scale" # Source-Hash: b0d264950558d50e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uniform Scale" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.uniformScale" msgstr "等比缩放" +# English: "XYZ" # Source-Hash: 4b8d7e52013d1691 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "XYZ" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/item/panel.tsx #, fuzzy msgid "node.item.panel.xyz" msgstr "XYZ" +# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." # Source-Hash: a9e2d7b215696e2a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "An attached mono-pitch or freestanding mono, gable, or butterfly canopy with managed structure and drainage." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.description" msgstr "附墙单坡或独立式单坡、双坡、蝶形雨棚,自动处理结构与排水。" +# English: "Canopy" # Source-Hash: 8503bedfbafde9a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Canopy" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.leanToExtension.label" msgstr "雨棚" +# English: "A single floor of a building, holding walls / slabs / ceilings / items." # Source-Hash: 45673462f914d3df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single floor of a building, holding walls / slabs / ceilings / items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.description" msgstr "建筑的一个楼层,包含墙体、楼板、天花板和物品。" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.level.label" msgstr "楼层" +# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." # Source-Hash: 156bdee8fee5bec6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Refrigerant lineset — copper suction + liquid pair joining a condenser to the indoor coil." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.description" msgstr "冷媒管组 — 连接冷凝机组与室内盘管的铜质吸气管和液管。" +# English: "Lineset" # Source-Hash: 362b03eba9d8234c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lineset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.lineset.label" msgstr "冷媒管组" +# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." # Source-Hash: 2bd4a85bed158269 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standalone refrigerant liquid line — a thin bare-copper run; Follow mode traces an existing lineset." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.description" msgstr "独立冷媒液管 — 细的裸铜管;跟随模式会沿现有冷媒管组布线。" +# English: "Liquid Line" # Source-Hash: c34c9b36492c6e21 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Liquid Line" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.liquidLine.label" msgstr "液管" -# Review: required -# Source-Hash: 601aaf23ce5a9279 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Click to trace this lineset run" +# Source-Hash: 601aaf23ce5a9279 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.clickToTraceThisLinesetRun" msgstr "点击以追踪此冷媒管段" -# Review: required -# Source-Hash: 77df017af9260468 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Follow: hover a lineset" +# Source-Hash: 77df017af9260468 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/liquid-line/tool.tsx #, fuzzy msgid "node.liquidLine.tool.followHoverALineset" msgstr "跟随:悬停到冷媒管上" +# English: "A persistent distance, angle, area, perimeter, or volume annotation." # Source-Hash: ff4e3bb72267859e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A persistent distance, angle, area, perimeter, or volume annotation." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.description" msgstr "持久的距离、角度、面积、周长或体积标注。" +# English: "Measurement" # Source-Hash: ec3567fd058bab7c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.measurement.label" msgstr "测量" +# English: "{kind} {number}" # Source-Hash: d71b38e990539364 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{kind} {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.numberedName" msgstr "{kind} {number}" +# English: "Currently {value}" # Source-Hash: 09865d8781ef8699 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Currently {value}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ceiling/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -24566,1951 +28873,2321 @@ msgstr "{kind} {number}" msgid "node.panel.currently" msgstr "当前为 {value}" +# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." # Source-Hash: 7764b50334e9f71d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV joint — elbow bend, 45° wye, or sanitary tee." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.description" msgstr "排水管件 — 弯头、45° 斜三通或卫生三通。" +# English: "Pipe Fitting" # Source-Hash: c040baa6ed4c7aa5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pipe Fitting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeFitting.label" msgstr "管件" -# Review: required -# Source-Hash: dd86efcab72fc5c7 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Axis" +# Source-Hash: dd86efcab72fc5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis" msgstr "轴" -# Review: required -# Source-Hash: 5232a826549f915e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "⌥ axis" +# Source-Hash: 5232a826549f915e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.axis2" msgstr "⌥ 轴" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-fitting/tool.tsx #, fuzzy msgid "node.pipeFitting.tool.rTRotate" msgstr "R/T 旋转" +# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." # Source-Hash: a0bb4762aa66fa3e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drain / waste / vent pipe run — draw level or toggle a ¼″ per foot fall with S." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.description" msgstr "排水 / 污水 / 通气管 — 水平绘制,或按 S 切换每英尺 ¼″ 坡度。" +# English: "DWV Pipe" # Source-Hash: 457155657704f7cc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV Pipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeSegment.label" msgstr "排水管" +# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." # Source-Hash: 6d0cdd14d0675e05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "DWV P-trap — water seal on the waste line. The trap arm runs to the vent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.description" msgstr "排水 P 型存水弯 — 污水管上的水封。存水弯臂通向通气管。" +# English: "Trap" # Source-Hash: b778a315e82065a0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.pipeTrap.label" msgstr "存水弯" -# Review: required -# Source-Hash: 73078bc0f5d686fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "{diameter}\" Trap" +# Source-Hash: 73078bc0f5d686fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.diameterTrap" msgstr "{diameter}\" 存水弯" -# Review: required -# Source-Hash: 8fb180251a321cef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "R/T rotate" +# Source-Hash: 8fb180251a321cef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/pipe-trap/tool.tsx #, fuzzy msgid "node.pipeTrap.tool.rTRotate" msgstr "R/T 旋转" +# English: "Experimental editable recipe" # Source-Hash: 0efe82436b915f6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Experimental editable recipe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.description" msgstr "实验性可编辑配方" +# English: "Procedural item" # Source-Hash: c8bbcc888cce7b8a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Procedural item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.proceduralItem.label" msgstr "程序化物品" +# English: "Ventilation strip running along the ridge of a roof segment." # Source-Hash: 356aa0ed69cb8ec5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ventilation strip running along the ridge of a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.description" msgstr "沿屋面屋脊布置的通风条。" +# English: "Ridge Vent" # Source-Hash: 54b74080f33c8c4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.ridgeVent.label" msgstr "屋脊通风口" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: 18bf817325c1c537 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "End Caps" +# Source-Hash: 18bf817325c1c537 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.endCaps" msgstr "端盖" -# Review: required -# Source-Hash: 466f817a0102d4a5 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flanged" +# Source-Hash: 466f817a0102d4a5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.flanged" msgstr "带法兰" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 643687bc3adaac76 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Length" +# Source-Hash: 643687bc3adaac76 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.length" msgstr "长度" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.open" msgstr "打开" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 54b74080f33c8c4e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Ridge Vent" +# Source-Hash: 54b74080f33c8c4e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.ridgeVent" msgstr "屋脊通风口" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: e9046e9823b400a0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Shingled" +# Source-Hash: e9046e9823b400a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.shingled" msgstr "瓦片铺设" - -# Review: required -# Source-Hash: d998c212030bc7c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus + # English: "Standard" +# Source-Hash: d998c212030bc7c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.standard" msgstr "标准" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.style" msgstr "样式" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/ridge-vent/panel.tsx #, fuzzy msgid "node.ridgeVent.panel.width" msgstr "宽度" +# English: "Arch bays" # Source-Hash: c6d3976b4eb80586 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bays" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.archBays" msgstr "拱跨" +# English: "Barrel arch" # Source-Hash: 1cfdd5affedf153c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.barrelArch" msgstr "筒形拱" +# English: "Flat panes follow a circular arch. Rise is measured from the eaves." # Source-Hash: b3e8929750d18f7b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat panes follow a circular arch. Rise is measured from the eaves." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.flatPanesFollowACircularArch" msgstr "平板窗格沿圆弧排列。矢高从檐口算起。" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/barrel-panel.tsx #, fuzzy msgid "node.roof.barrelPanel.rise" msgstr "总高" +# English: "A pitched / hip / gable roof composed of one or more segments." # Source-Hash: 6afeec8de7ed618d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A pitched / hip / gable roof composed of one or more segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.description" msgstr "由一个或多个坡面组成的坡屋顶 / 四坡屋顶 / 双坡屋顶。" +# English: "Arch bay {number}" # Source-Hash: b5be04471fd3fb7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch bay {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.archBay" msgstr "拱跨 {number}" +# English: "Back slope" # Source-Hash: 93381b9024a75a99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Back slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.back" msgstr "后坡" +# English: "Left end" # Source-Hash: 08b00107b635e11d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endLeft" msgstr "左端" +# English: "Right end" # Source-Hash: 821734ea5d17a693 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right end" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.endRight" msgstr "右端" +# English: "Front slope" # Source-Hash: 40c9ef62055067e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.front" msgstr "前坡" +# English: "Shed slope" # Source-Hash: 1a88da71d5e5f0d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazing.face.slope" msgstr "单坡面" +# English: "Alignment" # Source-Hash: 3bcc542295b4c61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.alignment" msgstr "对齐" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.center" msgstr "居中" +# English: "Choose a layout to edit the grid." # Source-Hash: 512e47607a37af46 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a layout to edit the grid." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.chooseALayoutToEditThe" msgstr "选择一种布局以编辑网格。" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.empty" msgstr "空" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.end" msgstr "终点" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.fixedSpacing" msgstr "固定间距" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.glass" msgstr "玻璃" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.layout" msgstr "布局" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maximumSpacing" msgstr "最大间距" +# English: "Max spacing" # Source-Hash: d10743fce478d9f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Max spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.maxSpacing" msgstr "最大间距" +# English: "Mixed" # Source-Hash: 37d9f63758abdb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.mixed" msgstr "混合" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panelCount" msgstr "面板数量" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.panels" msgstr "面板" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.solid" msgstr "实体" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.spacing" msgstr "间距" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.start" msgstr "起点" +# English: "{title} alignment" # Source-Hash: 095237e9e2df4d9c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{title} alignment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-controls.tsx #, fuzzy msgid "node.roof.glazingControls.titleAlignment" msgstr "{title}对齐" +# English: "Changes apply to all {length} roof segments." # Source-Hash: c98a4e6b28f05e0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Changes apply to all {length} roof segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.changesApplyToAllLengthRoof" msgstr "更改将应用于全部 {length} 个屋顶分段。" +# English: "Choose one roof system to edit settings for all selected segments." # Source-Hash: c5d2ff7fda4787cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose one roof system to edit settings for all selected segments." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.chooseOneRoofSystemToEdit" msgstr "选择一种屋顶系统以编辑所有选中分段的设置。" +# English: "Close the roof ends" # Source-Hash: 80e07a119b63187c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Close the roof ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.closeTheRoofEnds" msgstr "封闭屋顶端部" +# English: "Default panel infill" # Source-Hash: e28dd27d54aaa7ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.defaultPanelInfill" msgstr "默认面板填充" +# English: "Divides each roof face into columns." # Source-Hash: 134d33e2465937e7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides each roof face into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesEachRoofFaceIntoColumns" msgstr "将每个屋面划分为列。" +# English: "Divides the length of each arch bay into columns." # Source-Hash: 896a6e4da3357c39 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divides the length of each arch bay into columns." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.dividesTheLengthOfEachArch" msgstr "将每个拱跨的长度划分为列。" +# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." # Source-Hash: be9c810d28d1feb9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End infill fills the space above the walls. Edit room walls and ceilings separately." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.endInfillFillsTheSpaceAbove" msgstr "端部填充会填满墙体上方的空间。请分别编辑房间的墙体和天花板。" +# English: "Frame color" # Source-Hash: 0b8b27dbc57cca44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameColor" msgstr "框架颜色" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameDepth" msgstr "框架深度" +# English: "Frame width" # Source-Hash: 3519d91b7bda18a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.frameWidth" msgstr "框架宽度" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.framing" msgstr "龙骨" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glass" msgstr "玻璃" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glassTint" msgstr "玻璃色调" +# English: "Glazed" # Source-Hash: dd330a94b3f7a65a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazed" msgstr "玻璃" +# English: "Glazing is available for gable, straight shed, and barrel roofs." # Source-Hash: e6be52df9197243a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing is available for gable, straight shed, and barrel roofs." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.glazingIsAvailableForGableStraight" msgstr "双坡、直单坡和筒形屋顶可使用玻璃。" +# English: "Grid across the roof" # Source-Hash: a4e7ef2e31d7220b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid across the roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAcrossTheRoof" msgstr "沿屋顶横向网格" +# English: "Grid along the length" # Source-Hash: 2f28a3b33fccf56b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheLength" msgstr "沿长度方向网格" +# English: "Grid along the slope" # Source-Hash: 1f701893dbf9d72b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid along the slope" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridAlongTheSlope" msgstr "沿坡度方向网格" +# English: "Grid within each arch bay" # Source-Hash: 20c8dfbadda20e3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid within each arch bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.gridWithinEachArchBay" msgstr "每个拱跨内的网格" +# English: "Infill" # Source-Hash: 7c339a5fae55681d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.infill" msgstr "填充" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.opacity" msgstr "不透明度" +# English: "Pane thickness" # Source-Hash: 4d412746658f2faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pane thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.paneThickness" msgstr "窗格厚度" +# English: "Roof system" # Source-Hash: 570136e723cb7d99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roofSystem" msgstr "屋顶系统" +# English: "Roughness" # Source-Hash: a6a7a36d982b4911 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.roughness" msgstr "粗糙度" +# English: "Spacing is measured on the slope, not its horizontal projection." # Source-Hash: 2fa6fbeda5d45949 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing is measured on the slope, not its horizontal projection." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.spacingIsMeasuredOnTheSlope" msgstr "间距沿坡面测量,而非其水平投影。" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.standard" msgstr "标准" +# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." # Source-Hash: efbe1c4facf561f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Subdivides each flat arch bay. Arch bays controls the divisions around the curve." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel.tsx #, fuzzy msgid "node.roof.glazingPanel.subdividesEachFlatArchBayArch" msgstr "细分每个平板拱跨。拱跨数控制曲线上的分格。" +# English: "Choose a panel to override its default infill." # Source-Hash: 1497eac9e4f63016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a panel to override its default infill." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.chooseAPanelToOverrideIts" msgstr "选择一个面板以覆盖其默认填充。" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.column2" msgstr "柱" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.custom" msgstr "自定义" +# English: "Default" # Source-Hash: 4ca0e0e73229540c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.default" msgstr "默认" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" # Source-Hash: 9ef92ad8ae49ed72 # Review: required -# English: "{columns, plural, one {{columnCount} column} other {{columnCount} columns}} · {rows, plural, one {{rowCount} row} other {{rowCount} rows}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.grid" msgstr "{columns, plural, other {{columnCount} 列}} · {rows, plural, other {{rowCount} 行}}" +# English: "Individual panels" # Source-Hash: 11624abbcfdd5506 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.individualPanels" msgstr "单个面板" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.panelInfill" msgstr "面板填充" +# English: "Reset to default" # Source-Hash: 6eb8025c60d6d3ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset to default" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.resetToDefault" msgstr "恢复默认" +# English: "Roof face" # Source-Hash: 6fba44f2bfd8af8e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.roofFace" msgstr "屋面" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.row2" msgstr "行" +# English: "Selected panel infill" # Source-Hash: 6c2cc2d9cd778998 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/glazing-panel-editor.tsx #, fuzzy msgid "node.roof.glazingPanelEditor.selectedPanelInfill" msgstr "选中面板的填充" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roof.label" msgstr "屋顶" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.actions" msgstr "操作" +# English: "Add Chimney" # Source-Hash: d60471ce3cd45348 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addChimney" msgstr "添加烟囱" +# English: "Add Cupola" # Source-Hash: 1ca6a1cf9cc6e3ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Cupola" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addCupola" msgstr "添加穹顶" +# English: "Add Dormer" # Source-Hash: 94dea147eeaac52b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addDormer" msgstr "添加老虎窗" +# English: "Add Eyebrow Vent" # Source-Hash: 5679acdd64484726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Eyebrow Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addEyebrowVent" msgstr "添加眉形通风口" +# English: "Add Gutter" # Source-Hash: 25a141dc0d7c4b44 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Gutter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addGutter" msgstr "添加檐沟" +# English: "Add Skylight" # Source-Hash: 7230bdc8a2252626 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSkylight" msgstr "添加天窗" +# English: "Add Solar Panel" # Source-Hash: 0f220e2602b7ff24 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addSolarPanel" msgstr "添加太阳能板" +# English: "Add Vent" # Source-Hash: 4d83a2e5a4d9e6b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.addVent" msgstr "添加通风口" +# English: "Box" # Source-Hash: e9a89a5e4ac8cd61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Box" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.box" msgstr "盒形" +# English: "box vent" # Source-Hash: 3bf078fe2560daca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "box vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.boxVent" msgstr "箱式通风口" +# English: "chimney" # Source-Hash: 4986740251121471 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "chimney" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.chimney" msgstr "烟囱" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.custom" msgstr "自定义" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.delete" msgstr "删除" +# English: "dormer" # Source-Hash: 5169968a4b0c1840 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "dormer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.dormer" msgstr "老虎窗" +# English: "Draw Segment" # Source-Hash: 72e51c6e5e9c6c26 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Draw Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.drawSegment" msgstr "绘制分段" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.duplicate" msgstr "复制" -# Source-Hash: 7c4196b9e791c195 +# English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaveOverhang" +msgstr "檐口出挑" + +# English: "Eaves" +# Source-Hash: 34756c5ea718ccb5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.eaves" +msgstr "檐口" + # English: "Elements" +# Source-Hash: 7c4196b9e791c195 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.elements" msgstr "元素" +# English: "Follows walls" # Source-Hash: 6b8ba49aa355fe87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows walls" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.followsWalls" msgstr "跟随墙体" -# Source-Hash: 23b434461c990711 +# English: "Gable overhang" +# Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof/panel.tsx +#, fuzzy +msgid "node.roof.panel.gableOverhang" +msgstr "山墙出挑" + # English: "gutter" +# Source-Hash: 23b434461c990711 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.gutter" msgstr "檐沟" +# English: "Level" # Source-Hash: 0608743fc1736d86 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.level" msgstr "楼层" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.move" msgstr "移动" +# English: "Move to {level}" # Source-Hash: 39661f4827b0f435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to {level}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToLevel" msgstr "移到{level}" +# English: "Move to the roof level" # Source-Hash: c534093ff560a9f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move to the roof level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.moveToTheRoofLevel" msgstr "移到屋顶楼层" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" # Source-Hash: 0939fad1cb1831f0 # Review: required -# English: "On {storey} with its walls, so it hides that floor's plan in solo and exploded views.{roofs, plural, =0 {} =1 {} other { The floor's {roofCount} roofs move together.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.onStoreyWalls" msgstr "与其墙体同在{storey},因此在单独和分解视图中会遮挡该层平面。{roofs, plural, =0 {} =1 {} other { 该层的 {roofCount} 个屋顶会一起移动。}}" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.position" msgstr "位置" +# English: "Ridge" # Source-Hash: e6ddc8a624465793 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ridge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridge" msgstr "屋脊" +# English: "ridge vent" # Source-Hash: 83112cbc205e9972 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.ridgeVent" msgstr "屋脊通风口" +# English: "Roof" # Source-Hash: f5f730969df63d1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.roof" msgstr "屋顶" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.rotation" msgstr "旋转" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segments" msgstr "分段" +# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" # Source-Hash: 0c084c517dbc153b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.segmentType" msgstr "{roofType, select, hip {四坡} gable {双坡} shed {单坡} gambrel {复折} dutch {歇山} mansard {孟莎} flat {平屋顶} conical {锥形} barrel {筒形} other {屋顶}}" +# English: "skylight" # Source-Hash: 0c4c82caccb4775c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.skylight" msgstr "天窗" +# English: "solar panel" # Source-Hash: b9f931508dd6ec70 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "solar panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.solarPanel" msgstr "太阳能板" +# English: "Turbine" # Source-Hash: d2428b22f1757a4e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbine" msgstr "涡轮" +# English: "turbine vent" # Source-Hash: 86d02a3d3a3a7fa2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "turbine vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof/panel.tsx #, fuzzy msgid "node.roof.panel.turbineVent" msgstr "涡轮通风器" -# Source-Hash: 99afd827dbe5d647 +# English: "Apply to all planes of this roof" +# Source-Hash: 6df330bcfcaac604 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.applyToAllPlanesOfThis" +msgstr "应用到此屋顶的所有坡面" + +# English: "Architectural shingle" +# Source-Hash: 7254a3eb47471b8a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.architecturalShingle" +msgstr "叠层沥青瓦" + +# English: "Asphalt 3-tab" +# Source-Hash: afd23a6d653689c6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.asphalt3Tab" +msgstr "三片式沥青瓦" + +# English: "Clay tile" +# Source-Hash: 2650e7c436297ef2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.clayTile" +msgstr "黏土瓦" + +# English: "Concrete tile" +# Source-Hash: 92af091c56892f25 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.concreteTile" +msgstr "混凝土瓦" + +# English: "Covering" +# Source-Hash: 75168c15633cbad3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.covering" +msgstr "覆盖层" + +# English: "Deck" +# Source-Hash: f3aa561772702108 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.deck" +msgstr "屋面板" + +# English: "Edit layers" +# Source-Hash: 14187d972ce9e1de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.editLayers" +msgstr "编辑构造层" + +# English: "Finish name" +# Source-Hash: 5aff5f630e4b18c3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.finishName" +msgstr "饰面名称" + +# English: "Ice and water shield" +# Source-Hash: b9ed3f21665672eb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.iceAndWaterShield" +msgstr "防冰防水卷材" + +# English: "Insulation name" +# Source-Hash: 6d79431c0092abac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.insulationName" +msgstr "保温层名称" + +# English: "Interior finish" +# Source-Hash: e4e9305964238fda +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.interiorFinish" +msgstr "内饰面" + +# English: "Layers" +# Source-Hash: 4ccd2fc1a471fca5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.layers" +msgstr "构造层" + +# English: "#15 felt" +# Source-Hash: 8f343a87f121347e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n15Felt" +msgstr "#15 油毡" + +# English: "#30 felt" +# Source-Hash: faa54521399dc5b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.n30Felt" +msgstr "#30 油毡" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.none" +msgstr "无" + +# English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.preset" +msgstr "预设" + +# English: "Rafters" +# Source-Hash: a9cb232546779779 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.rafters" +msgstr "椽子" + +# English: "Roofing" +# Source-Hash: 5c0d95afa820e706 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofing" +msgstr "屋面材料" + +# English: "Roofing name" +# Source-Hash: 75a1bff32810894e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.roofingName" +msgstr "屋面材料名称" + +# English: "Sheathing" +# Source-Hash: 36a588e44085108c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathing" +msgstr "覆面板" + +# English: "Sheathing name" +# Source-Hash: abdad84e524e56e1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.sheathingName" +msgstr "覆面板名称" + +# English: "Slate" +# Source-Hash: a7b5921026935493 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.slate" +msgstr "石板瓦" + +# English: "Standing-seam metal" +# Source-Hash: c156b8bd7920b129 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.standingSeamMetal" +msgstr "直立锁边金属板" + +# English: "Synthetic" +# Source-Hash: f5defb7d8a17154e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.synthetic" +msgstr "合成卷材" + +# English: "Top chord" +# Source-Hash: 472f395a8b6319a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.topChord" +msgstr "上弦" + +# English: "Trusses" +# Source-Hash: 4dd558e7eeec7acf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.trusses" +msgstr "桁架" + +# English: "Underlayment" +# Source-Hash: 3909955f8a920be1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlayment" +msgstr "防水垫层" + +# English: "Underlayment name" +# Source-Hash: 0167cfd2ec25cf5d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.underlaymentName" +msgstr "防水垫层名称" + +# English: "Wood shake" +# Source-Hash: 7095fa6e1c927c5e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/build-up-section.tsx +#, fuzzy +msgid "node.roofSegment.buildUpSection.woodShake" +msgstr "木瓦" + # English: "A single pitched plane of a parent roof." +# Source-Hash: 99afd827dbe5d647 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.description" msgstr "所属屋顶的单个坡面。" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.roofSegment.label" msgstr "屋面" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.actions" msgstr "操作" +# English: "Angle" # Source-Hash: c69865ec5d9d1ac9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.angle" msgstr "角度" +# English: "Arc" # Source-Hash: eb8f344a3c137280 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arc" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.arc" msgstr "弧形" +# English: "Auto gutters" # Source-Hash: 1eb45f83eeaabfad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto gutters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoGutters" msgstr "自动檐沟" +# English: "Auto ridge vent" # Source-Hash: 0986c4933d2b5808 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto ridge vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.autoRidgeVent" msgstr "自动屋脊通风" +# English: "Barrel" # Source-Hash: 344fe9d407a233a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Barrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.barrel" msgstr "筒形" +# English: "Clipped version" # Source-Hash: 6ca418a2ecab5984 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clipped version" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.clippedVersion" msgstr "截角版本" +# English: "Conical" # Source-Hash: 3241ecabee8221a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conical" msgstr "圆锥" +# English: "Conical Shape" # Source-Hash: a37cfbc4db93cfd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Conical Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.conicalShape" msgstr "锥形" +# English: "Deck Thick." # Source-Hash: a10f29fcde60eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.deckThick" msgstr "平台厚" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.delete" msgstr "删除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.depth" msgstr "深度" +# English: "Diameter" # Source-Hash: 016d8e70618916d4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.diameter" msgstr "直径" +# English: "Done editing" # Source-Hash: e132bf88b9e4d75e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.doneEditing" msgstr "完成编辑" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.drainage" msgstr "排水" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.duplicate" msgstr "复制" +# English: "Dutch" # Source-Hash: 6949d667043bee0b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dutch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.dutch" msgstr "荷兰式" -# Source-Hash: 5c99a67253bfc02a +# English: "Eave cut" +# Source-Hash: 4c539865731d5d66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.eaveCut" +msgstr "檐口切法" + # English: "Eave overhang" +# Source-Hash: 5c99a67253bfc02a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.eaveOverhang" msgstr "檐口出挑" +# English: "Edit footprint" # Source-Hash: dc90862aafc9d287 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.editFootprint" msgstr "编辑基底" +# English: "End overhang" # Source-Hash: 9532d5e73585ea62 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.endOverhang" msgstr "端部出挑" +# English: "Fascia" # Source-Hash: 74f4143cd02de4a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fascia" msgstr "封檐板" +# English: "Fascia height" # Source-Hash: 45baed9dc8b8047a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fascia height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.fasciaHeight" msgstr "封檐板高度" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.flat" msgstr "平屋顶" +# English: "Footprint" # Source-Hash: a3c10560fa3c1612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.footprint" msgstr "基底" +# English: "Gable" # Source-Hash: deaffa095d126dfe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gable" msgstr "双坡" +# English: "Gable overhang" # Source-Hash: dbd062efc7f4fd7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gable overhang" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gableOverhang" msgstr "山墙出挑" +# English: "Gambrel" # Source-Hash: 7f53d9ffca39984a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gambrel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.gambrel" msgstr "复折式" +# English: "Hip" # Source-Hash: 9eda0ea8d447066d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hip" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.hip" msgstr "四坡" +# English: "Kink Depth" # Source-Hash: 7ae4179d0abd80f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkDepth" msgstr "折点深度" +# English: "Kink Height" # Source-Hash: e19b2bca70030855 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kink Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.kinkHeight" msgstr "折点高度" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.length" msgstr "长度" +# English: "Mansard" # Source-Hash: 7f7bb0e875af3260 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Mansard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.mansard" msgstr "孟莎式" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.move" msgstr "移动" +# English: "Pitch" # Source-Hash: 75e3449fc1776d09 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Pitch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.pitch" msgstr "坡度" -# Source-Hash: 9da0cbfad144d737 +# English: "Plumb" +# Source-Hash: 6ae4075a23bb4ec4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumb" +msgstr "垂直切" + +# English: "Plumb + level" +# Source-Hash: 16d8ab5937d74bdb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.plumbLevel" +msgstr "垂直切 + 水平切" + # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.position" msgstr "位置" +# English: "Reset" # Source-Hash: bf91b71a9aa76e94 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.reset" msgstr "重置" +# English: "Roof Segment" # Source-Hash: e72a64b3e00aad98 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofSegment" msgstr "屋面" +# English: "Roof Type" # Source-Hash: 19ab9527ae7889f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Roof Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.roofType" msgstr "屋顶类型" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.rotation" msgstr "旋转" +# English: "Shape" # Source-Hash: 85ea1d3c8b749680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shape" msgstr "形状" +# English: "Shed" # Source-Hash: c8d0df27faa3feaa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shed" msgstr "单坡" +# English: "Shingle Thick." # Source-Hash: 4d944a2393bbddb2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shingle Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.shingleThick" msgstr "瓦片厚" +# English: "Span" # Source-Hash: 93b34a5b546664c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Span" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.span" msgstr "跨度" -# Source-Hash: e90aceedf4a6b183 +# English: "Square" +# Source-Hash: 39287346366fc206 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/roof-segment/panel.tsx +#, fuzzy +msgid "node.roofSegment.panel.square" +msgstr "直角切" + # English: "Start Angle" +# Source-Hash: e90aceedf4a6b183 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.startAngle" msgstr "起始角度" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.structure" msgstr "结构" +# English: "Top Rake Length" # Source-Hash: a2db62c74fe2bcee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeLength" msgstr "顶部檐口长度" +# English: "Top Rake Thick." # Source-Hash: 913d9552030ac86a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Rake Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.topRakeThick" msgstr "顶部檐口厚" +# English: "Trim" # Source-Hash: 3beb5eb35f0c5c17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Trim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.trim" msgstr "装饰线" +# English: "Waist Height" # Source-Hash: e6c5009c0d31fca1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistHeight" msgstr "腰部高度" +# English: "Waist Length" # Source-Hash: 4a8caffd24b6ee02 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistLength" msgstr "腰部长度" +# English: "Waist Width" # Source-Hash: 71045af5c81a11e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.waistWidth" msgstr "腰部宽度" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wall" msgstr "墙体" +# English: "Wall Height" # Source-Hash: e0f973edce3e4ca8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallHeight" msgstr "墙高" +# English: "Wall Thick." # Source-Hash: d0db6dee5fe2edbd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall Thick." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.wallThick" msgstr "墙厚" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/roof-segment/panel.tsx #, fuzzy msgid "node.roofSegment.panel.width" msgstr "宽度" +# English: "A captured session with optional mesh, motion, media, and sensor data." # Source-Hash: 6600a850000b98ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A captured session with optional mesh, motion, media, and sensor data." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.description" msgstr "包含可选网格、运动、媒体和传感器数据的采集会话。" +# English: "Capture" # Source-Hash: 3a0701ebcb9f26cb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Capture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.scan.label" msgstr "采集" +# English: "Segment {number}" # Source-Hash: 95b5307eefb187c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segment {number}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/door/panel.tsx #: packages/nodes/src/roof/panel.tsx #: packages/nodes/src/stair/panel.tsx @@ -26518,7007 +31195,7752 @@ msgstr "采集" msgid "node.segmentNumber" msgstr "分段 {number}" +# English: "Separator" # Source-Hash: acb8cb92b3674b50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separator" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.separator.label" msgstr "分隔线" -# Source-Hash: e7512c891d8da7d8 +# English: "Batt" +# Source-Hash: e34eb5b5bfbfce7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.batt" +msgstr "保温棉毡" + +# English: "Blown" +# Source-Hash: 430cef5fa9bd2787 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.blown" +msgstr "喷吹保温" + +# English: "Drywall" +# Source-Hash: 4da800c75b9a6b23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywall" +msgstr "石膏板" + +# English: "Drywall thickness" +# Source-Hash: 4e8f119349fa2dd4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.drywallThickness" +msgstr "石膏板厚度" + +# English: "Insulation" +# Source-Hash: 7d6ed24c02ab0c5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulation" +msgstr "保温层" + +# English: "Insulation thickness" +# Source-Hash: 4251fc1e9051a7db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.insulationThickness" +msgstr "保温层厚度" + +# English: "{label} thickness" +# Source-Hash: f567133879529366 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.labelThickness" +msgstr "{label}厚度" + +# English: "None" +# Source-Hash: f2953d2931e36d0c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.none" +msgstr "无" + +# English: "OSB" +# Source-Hash: 4f7f8af75bf8e914 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.osb" +msgstr "定向刨花板" + +# English: "Plaster" +# Source-Hash: a6fd2cbf5c2154e2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plaster" +msgstr "灰泥" + +# English: "Plywood" +# Source-Hash: e669c2c08bd80e23 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.plywood" +msgstr "胶合板" + +# English: "Rigid board" +# Source-Hash: 3d153fa2037159f5 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.rigidBoard" +msgstr "硬质保温板" + +# English: "Spacing" +# Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacing" +msgstr "间距" + +# English: "{spacing} o.c." +# Source-Hash: bb2a1cccfd0bd194 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. A framing spacing on centre (centre of one member to the next), e.g. 16" o.c. +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.spacingOC" +msgstr "中距 {spacing}" + +# English: "Wood plank" +# Source-Hash: ab62afff8ba3f37f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/build-up-controls.tsx +#, fuzzy +msgid "node.shared.buildUpControls.woodPlank" +msgstr "木板" + # English: "Turn light off" +# Source-Hash: e7512c891d8da7d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOff" msgstr "关灯" +# English: "Turn light on" # Source-Hash: d47ec95e14909fe5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turn light on" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/item-interaction-actions.tsx #, fuzzy msgid "node.shared.itemInteractionActions.turnLightOn" msgstr "开灯" +# English: "Casing" +# Source-Hash: 18bf48fbbe121ef0 # Review: required -# Source-Hash: ee1c8bc3e27fb550 -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.casing" +msgstr "门窗套" + +# English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/shared/opening-casing-section.tsx +#, fuzzy +msgid "node.shared.openingCasingSection.width" +msgstr "宽度" + # English: "Automatic support" +# Source-Hash: ee1c8bc3e27fb550 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.automaticSupport" msgstr "自动支撑" -# Review: required -# Source-Hash: 1353e40320c72a99 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} · segment {segment}" +# Source-Hash: 1353e40320c72a99 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.hangerOnSegment" msgstr "吊架 {number} · 第 {segment} 段" -# Review: required -# Source-Hash: 737ced90d0454197 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Individual hangers" +# Source-Hash: 737ced90d0454197 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.individualHangers" msgstr "单个吊架" -# Review: required -# Source-Hash: e6b17b09ed91c8c2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Missing support" +# Source-Hash: e6b17b09ed91c8c2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.missingSupport" msgstr "缺少支撑" -# Review: required -# Source-Hash: c895c3c603382ec9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No support within reach. Move the hanger or choose another host." +# Source-Hash: c895c3c603382ec9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.noSupportWithinReachMoveThe" msgstr "附近没有可用支撑。请移动吊架或选择其他承托构件。" -# Review: required -# Source-Hash: 6ce526cb7444be36 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} position in metres" +# Source-Hash: 6ce526cb7444be36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionLabel" msgstr "吊架 {number} 的位置(米)" -# Review: required -# Source-Hash: cbd36c4b82a55539 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position (m)" +# Source-Hash: cbd36c4b82a55539 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionM" msgstr "位置(m)" -# Review: required -# Source-Hash: b629c4f8388fd36f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Positions are measured along each path segment. Changing spacing regenerates the numbered slots." +# Source-Hash: b629c4f8388fd36f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.positionsAreMeasuredAlongEachPath" msgstr "位置沿各路径段测量。更改间距会重新生成编号位置。" -# Review: required -# Source-Hash: c21cfbdb64e53388 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Reveal support" +# Source-Hash: c21cfbdb64e53388 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.revealSupport" msgstr "显示支撑" -# Review: required -# Source-Hash: 9d5e4440db18e477 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skip" +# Source-Hash: 9d5e4440db18e477 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.skip" msgstr "跳过" -# Review: required -# Source-Hash: 1b184f78bf989931 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Hanger {number} support" +# Source-Hash: 1b184f78bf989931 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/run-hanger-inspector.tsx #, fuzzy msgid "node.shared.runHangerInspector.supportLabel" msgstr "吊架 {number} 的支撑" +# English: "About these checks" # Source-Hash: b2d703d283baf64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "About these checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.aboutTheseChecks" msgstr "关于这些检查" +# English: "All findings" # Source-Hash: 461cc26706956993 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allFindings" msgstr "所有问题" +# English: "All systems" # Source-Hash: 7beeb4c4997f5edb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All systems" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.allSystems" msgstr "所有系统" +# English: "Connections" # Source-Hash: d613df280aa13b42 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.connections" msgstr "连接" +# English: "Drainage" # Source-Hash: 00a4416e0a33d856 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drainage" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.drainage" msgstr "排水" +# English: "Error" # Source-Hash: fc4ac19649f0ee8f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Error" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.error" msgstr "错误" +# English: "Errors" # Source-Hash: b92720ff88a1bcfd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Errors" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.errors" msgstr "错误" +# English: "Filter system checks" # Source-Hash: d0e23bdbe1b380ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Filter system checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.filterSystemChecks" msgstr "筛选系统检查" +# English: "Hangers" # Source-Hash: 30d8dba84c42040e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hangers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.hangers" msgstr "吊架" +# English: "Intersections" # Source-Hash: 8cd416ca57d969f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Intersections" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.intersections" msgstr "交叉" +# English: "Locate {name} ({id})" # Source-Hash: 18d652275a9a525c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Locate {name} ({id})" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.locate" msgstr "定位{name}({id})" +# English: "Missing item" # Source-Hash: 88546aa3a5987966 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Missing item" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.missingItem" msgstr "缺失的物品" +# English: "No findings" # Source-Hash: 2452d2922a7ecf4a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindings" msgstr "没有问题" +# English: "No findings in this category" # Source-Hash: 995442fbfc786768 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No findings in this category" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.noFindingsInThisCategory" msgstr "此类别中没有问题" +# English: "Open ends" # Source-Hash: b5c58159b3eef89c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEnds" msgstr "开放端" +# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." # Source-Hash: 3003dacc858d36ff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open ends and separate branches may be intentional. Intersection checks use bounding boxes; inspect openings and clearances." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.openEndsAndSeparateBranchesMay" msgstr "开放端和独立分支可能是有意为之。交叉检查使用包围盒;请检查开口和间隙。" +# English: "Selected connected system" # Source-Hash: c40c64562b6a1a91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Selected connected system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedConnectedSystem" msgstr "选中的连接系统" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" # Source-Hash: 68355a98439229e2 # Review: required -# English: "Selected: {count, plural, one {{number} run} other {{number} runs}} · {length} m" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.selectedRuns" msgstr "已选:{count, plural, other {{number} 段管线}} · {length} m" +# English: "Separate branches" # Source-Hash: e7a352627f8fcd45 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate branches" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.separateBranches" msgstr "独立分支" +# English: "Show all findings" # Source-Hash: 910a5ee73f2ef8a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show all findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.showAllFindings" msgstr "显示所有问题" +# English: "System checks" # Source-Hash: dda8887833d07390 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System checks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemChecks" msgstr "系统检查" +# English: "System finding" # Source-Hash: 4dad0ed68e63d124 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System finding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFinding" msgstr "系统问题" +# English: "System findings" # Source-Hash: e37307a519466b38 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "System findings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.systemFindings" msgstr "系统问题" +# English: "Warning" # Source-Hash: 5128de8b5a618339 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warning" msgstr "警告" +# English: "Warnings" # Source-Hash: 7e1647f9ce51509e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Warnings" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/system-check-panel.tsx #, fuzzy msgid "node.shared.systemCheckPanel.warnings" msgstr "警告" +# English: "A configurable shelving unit. Items host on each row." # Source-Hash: 26aff27b7284fc84 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A configurable shelving unit. Items host on each row." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.description" msgstr "可配置的置物架,每层都可放置物品。" +# English: "Shelf" # Source-Hash: d7145f84f8e0e3fe # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shelf" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.shelf.label" msgstr "置物架" +# English: "The top-level container holding buildings, zones, and the property boundary." # Source-Hash: c52492c73312a922 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The top-level container holding buildings, zones, and the property boundary." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.description" msgstr "包含建筑、区域和用地红线的顶层容器。" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.site.label" msgstr "场地" +# English: "Address" # Source-Hash: 3b5a53b6d058a02a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.address" msgstr "地址" +# English: "APN" # Source-Hash: ea03c49930137a0f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "APN" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.apn" msgstr "地块编号" +# English: "Auto — most north-facing edge" # Source-Hash: 8519c9ddf5e6b371 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto — most north-facing edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.autoMostNorthFacingEdge" msgstr "自动 — 最朝北的边" +# English: "Contour interval (site plan and 3D)" # Source-Hash: bc493d7655b3e3ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Contour interval (site plan and 3D)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.contourIntervalSitePlanAnd3d" msgstr "等高线间距(总平面与 3D)" +# English: "County" # Source-Hash: 99208d226da1315f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "County" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.county" msgstr "县" +# English: "e.g. RS-60" # Source-Hash: 0830cf0f95ad429d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. RS-60" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGRs60" msgstr "例如 RS-60" +# English: "e.g. Tampa LDC §27-156" # Source-Hash: b3e7e118266b5074 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "e.g. Tampa LDC §27-156" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.eGTampaLdc27156" msgstr "例如 Tampa LDC §27-156" +# English: "every 12 in" # Source-Hash: 187e11889d08a2dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 12 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every12In" msgstr "每 12 in" +# English: "every 24 in" # Source-Hash: 17ee041f655085f8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 24 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every24In" msgstr "每 24 in" +# English: "every 6 in" # Source-Hash: 40d7f9ddf5efcb7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "every 6 in" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.every6In" msgstr "每 6 in" +# English: "Finding…" # Source-Hash: 442a3ce78ae6b462 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Finding…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.finding" msgstr "查找中…" +# English: "Find parcel" # Source-Hash: 734c1eac014adc69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Find parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.findParcel" msgstr "查找地块" +# English: "Front (street) edge" # Source-Hash: ccb70faaf91dbcb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front (street) edge" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.frontStreetEdge" msgstr "前侧(临街)边" +# English: "Looking up the parcel and the streets around it…" # Source-Hash: 4232218226616327 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Looking up the parcel and the streets around it…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lookingUpTheParcel" msgstr "正在查找地块及其周边街道…" +# English: "Lot area" # Source-Hash: c39eb5933468a637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lot area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.lotArea" msgstr "地块面积" +# English: "1200 W Cass St, Tampa, FL" # Source-Hash: c8a874b339ff9696 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "1200 W Cass St, Tampa, FL" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.n1200WCassStTampaFl" msgstr "1200 W Cass St, Tampa, FL" +# English: "none" # Source-Hash: 30a0924a3360be68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "none" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.none" msgstr "无" +# English: "North rotation" # Source-Hash: dd4e835cb90c5736 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "North rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.northRotation" msgstr "北向旋转" +# English: "No surveyed lines — contours are drawn from the sculpted ground." # Source-Hash: a4bfb2891c820e2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No surveyed lines — contours are drawn from the sculpted ground." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.noSurveyedLinesContoursAreDrawn" msgstr "无测量线 — 等高线根据塑形的地面绘制。" +# English: "Orientation" # Source-Hash: 1f0d024c0adbf0e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Orientation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.orientation" msgstr "朝向" +# English: "Parcel" # Source-Hash: 9a3961409e34bf66 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcel" msgstr "地块" +# English: "Parcel lookup failed." # Source-Hash: 3aecc563cb9a2c68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Parcel lookup failed." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.parcelLookupFailed" msgstr "地块查找失败。" +# English: "Project look" # Source-Hash: 36696c4a8bede421 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.projectLook" msgstr "项目外观" +# English: "Save current look" # Source-Hash: 5b50fc78ccdaf7aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save current look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveCurrentLook" msgstr "保存当前外观" +# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." # Source-Hash: f06b316f421963d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Save the current lighting and display as this project's default. Personal display changes stay in this browser." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.saveTheCurrentLightingAndDisplay" msgstr "将当前照明和显示保存为此项目的默认设置。个人显示更改仅保留在此浏览器中。" +# English: "Setbacks" # Source-Hash: e87df48b61eda8ce # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Setbacks" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.setbacks" msgstr "退界" +# English: "Show ground" # Source-Hash: 9ede31abea7ad545 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show ground" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showGround" msgstr "显示地面" +# English: "Show the contour lines in 3D" # Source-Hash: 1e16a47e5e10c83e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Show the contour lines in 3D" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.showTheContourLinesIn3d" msgstr "在 3D 中显示等高线" +# English: "side" # Source-Hash: 464241679edcbdb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.side" msgstr "侧边" +# English: "Site" # Source-Hash: 91f90818d42a984e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Site" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.site" msgstr "场地" +# English: "Source" # Source-Hash: 6165ee130f0082e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.source" msgstr "来源" +# English: "Source / citation" # Source-Hash: 5f3110d075c6a1a7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Source / citation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.sourceCitation" msgstr "来源 / 引用" +# English: "Street address" # Source-Hash: 041cfc610fef830b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Street address" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.streetAddress" msgstr "街道地址" +# English: "survey" # Source-Hash: bdca51c5e6437075 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "survey" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.survey" msgstr "测量" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" # Source-Hash: 2177f21713b01a24 # Review: required -# English: "{count, plural, one {{lineCount} surveyed line} other {{lineCount} surveyed lines}} at {interval} ft ({datum}) — {source}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.surveyedContours" msgstr "{count, plural, other {{lineCount} 条测量线}},间距 {interval} ft({datum})— {source}" +# English: "Terrain" # Source-Hash: d457508edd5bb8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.terrain" msgstr "地形" +# English: "Type an address first." # Source-Hash: 4c8601a0b7aeed3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type an address first." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.typeAnAddressFirst" msgstr "请先输入地址。" +# English: "Use project look" # Source-Hash: dfe4cd35b3e0d5f5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use project look" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.useProjectLook" msgstr "使用项目外观" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.panel.zone" msgstr "区域" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.front" msgstr "前" +# English: "Left (override)" # Source-Hash: ab1354f1aa520a54 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.leftOverride" msgstr "左(覆盖)" +# English: "Rear" # Source-Hash: 1a7e8e8f8e9232d7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rear" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rear" msgstr "后" +# English: "Right (override)" # Source-Hash: 8370146b38c30102 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right (override)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.rightOverride" msgstr "右(覆盖)" +# English: "Side" # Source-Hash: feefe9c7cb273e79 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/site/panel.tsx #, fuzzy msgid "node.site.setback.side" msgstr "侧" +# English: "Framed glass opening on a roof segment." # Source-Hash: 67d903d7fe0c69c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framed glass opening on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.description" msgstr "屋面上带框的玻璃开口。" +# English: "Skylight" # Source-Hash: 358826fbdeaf72b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skylight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.skylight.label" msgstr "天窗" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: f1113261ad2797de -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along X" +# Source-Hash: f1113261ad2797de +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongX" msgstr "沿 X" -# Review: required -# Source-Hash: 5b84b1c644493370 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Along Z" +# Source-Hash: 5b84b1c644493370 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.alongZ" msgstr "沿 Z" -# Review: required -# Source-Hash: b3a163b453896bef -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Bottom" +# Source-Hash: b3a163b453896bef +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.bottom" msgstr "底部" -# Review: required -# Source-Hash: b8ff053666ee326b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Curb" +# Source-Hash: b8ff053666ee326b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.curb" msgstr "路缘" -# Review: required -# Source-Hash: 14144df9e1364eea -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cutout Offset" +# Source-Hash: 14144df9e1364eea +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.cutoutOffset" msgstr "开洞偏移" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 99ac4d03c3061f3a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Depth" +# Source-Hash: 99ac4d03c3061f3a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.depth" msgstr "深度" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: ce3a6c03765dbbd8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame" +# Source-Hash: ce3a6c03765dbbd8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.frame" msgstr "框架" -# Review: required -# Source-Hash: 911ea36eb835f3a6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Glass Thickness" +# Source-Hash: 911ea36eb835f3a6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.glassThickness" msgstr "玻璃厚度" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.height" msgstr "高度" -# Review: required -# Source-Hash: d56303913961984f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Lantern Height" +# Source-Hash: d56303913961984f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.lanternHeight" msgstr "采光亭高度" -# Review: required -# Source-Hash: f365c6b6dd9ebab9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Left" +# Source-Hash: f365c6b6dd9ebab9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.left" msgstr "左" -# Review: required -# Source-Hash: f3e9604e3b85510f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor" +# Source-Hash: f3e9604e3b85510f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motor" msgstr "电机" -# Review: required -# Source-Hash: a025444e0f500725 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motor Housing" +# Source-Hash: a025444e0f500725 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.motorHousing" msgstr "电机外壳" -# Review: required -# Source-Hash: b2cbc4e2e5545a6c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No" +# Source-Hash: b2cbc4e2e5545a6c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.no" msgstr "否" -# Review: required -# Source-Hash: a8e0b9fe5cfb4e3e -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "No Motor" +# Source-Hash: a8e0b9fe5cfb4e3e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.noMotor" msgstr "无电机" -# Review: required -# Source-Hash: 3e52f04f92e08070 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.open" msgstr "打开" -# Review: required -# Source-Hash: 9bfc55f879926fe6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening Angle" +# Source-Hash: 9bfc55f879926fe6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.openingAngle" msgstr "开启角度" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.position" msgstr "位置" -# Review: required -# Source-Hash: b34d5ee0f218221d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Right" +# Source-Hash: b34d5ee0f218221d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.right" msgstr "右" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: 358826fbdeaf72b9 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Skylight" +# Source-Hash: 358826fbdeaf72b9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.skylight" msgstr "天窗" -# Review: required -# Source-Hash: fdbb503f0941bd56 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.thickness" msgstr "厚度" -# Review: required -# Source-Hash: 27644f0497fd9849 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.top" msgstr "顶部" -# Review: required -# Source-Hash: 75991d106eb93dae -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Top Scale" +# Source-Hash: 75991d106eb93dae +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.topScale" msgstr "顶部缩放" -# Review: required -# Source-Hash: 3272a61417949895 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Track Width" +# Source-Hash: 3272a61417949895 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.trackWidth" msgstr "轨道宽度" -# Review: required -# Source-Hash: c44f7ab8bbd24858 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Type" +# Source-Hash: c44f7ab8bbd24858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.type" msgstr "类型" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: 184fda5c980b9d9c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Yes" +# Source-Hash: 184fda5c980b9d9c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.panel.yes" msgstr "是" -# Review: required -# Source-Hash: 445aacf307de133d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flat roof skylight" +# Source-Hash: 445aacf307de133d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.flat" msgstr "平屋顶天窗" -# Review: required -# Source-Hash: 446176b7c5d8b284 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Roof lantern" +# Source-Hash: 446176b7c5d8b284 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.lantern" msgstr "屋顶采光亭" -# Review: required -# Source-Hash: a6559387f108518f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Opening skylight" +# Source-Hash: a6559387f108518f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.opening" msgstr "可开启天窗" -# Review: required -# Source-Hash: 379436bb5a74867a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Sliding skylight" +# Source-Hash: 379436bb5a74867a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.sliding" msgstr "推拉天窗" -# Review: required -# Source-Hash: d26ac61f593fe1bf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Walk-on rooflight" +# Source-Hash: d26ac61f593fe1bf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/skylight/panel.tsx #, fuzzy msgid "node.skylight.type.walkOn" msgstr "可上人天窗" +# English: "A polygon-bounded floor surface that hosts items on top." # Source-Hash: ad8f8036d34f6cea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygon-bounded floor surface that hosts items on top." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.description" msgstr "由多边形界定的地面,可在其上放置物品。" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.slab.label" msgstr "楼板" +# English: "Add Hole" # Source-Hash: 659e6b799ad7c27d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add Hole" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.addHole" msgstr "添加开洞" +# English: "Area" # Source-Hash: 8332ab41e22958f1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.area" msgstr "面积" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.auto" msgstr "自动" +# English: "Base" # Source-Hash: fadada809e27477c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Base" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.base" msgstr "底座" +# English: "Cut hatch" # Source-Hash: 12195951bba19804 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut hatch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutHatch" msgstr "开设检修口" +# English: "Cut opening" # Source-Hash: 4e4f1a24e4a9d61e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cut opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.cutOpening" msgstr "开洞" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.depth" msgstr "深度" +# English: "Done" # Source-Hash: 16552c2580bbe8b8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Done" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.done" msgstr "完成" +# English: "Elevation" # Source-Hash: 8a3bc5111b9e97f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Elevation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.elevation" msgstr "标高" +# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." # Source-Hash: 201b720b76d79762 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends the perimeter down to terrain. The flat surface, base, and thickness stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.extendsThePerimeterDownToTerrain" msgstr "将周边向下延伸至地形。平面、底面和厚度保持不变。" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.fixed" msgstr "固定" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.floor" msgstr "楼层" +# English: "Follows terrain" # Source-Hash: 89b2cfd72a0a7e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.followsTerrain" msgstr "跟随地形" +# English: "Foundation" # Source-Hash: 48c832c1fc389290 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Foundation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.foundation" msgstr "基础" +# English: "Holes" # Source-Hash: 34504c6ca6445e76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.holes" msgstr "开洞" +# English: "Info" # Source-Hash: 6e68b62d2aa611a4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Info" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.info" msgstr "信息" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.move" msgstr "移动" +# English: "No holes" # Source-Hash: 1d2dafb0d8f26ed5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No holes" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.noHoles" msgstr "无开洞" +# English: "Rim" # Source-Hash: c6a29e75bf215488 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rim" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.rim" msgstr "边沿" +# English: "Slab" # Source-Hash: 8775bc8dc9593964 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slab" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.slab" msgstr "楼板" +# English: "Standard (2\")" # Source-Hash: fb23c960abf5de23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (2\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard2" msgstr "标准(2\")" +# English: "Standard (5cm)" # Source-Hash: 5f865b052c9b40eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard (5cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.standard5cm" msgstr "标准(5cm)" +# English: "Sunken (15cm)" # Source-Hash: cff0e2e72cfab157 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken15cm" msgstr "下沉(15cm)" +# English: "Sunken (6\")" # Source-Hash: 8abc2aec4be98fe9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sunken (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.sunken6" msgstr "下沉(6\")" +# English: "Surface" # Source-Hash: 78a454b7ac7a3cd9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.surface" msgstr "表面" +# English: "Thick (15cm)" # Source-Hash: 516636d85be919f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (15cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick15cm" msgstr "厚(15cm)" +# English: "Thick (6\")" # Source-Hash: c84078f3560da233 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thick (6\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thick6" msgstr "厚(6\")" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thickness" msgstr "厚度" +# English: "Thin (1\")" # Source-Hash: 61c191f6df40285b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (1\")" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin1" msgstr "薄(1\")" +# English: "Thin (2cm)" # Source-Hash: d345b1f707506fe7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thin (2cm)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/slab/panel.tsx #, fuzzy msgid "node.slab.panel.thin2cm" msgstr "薄(2cm)" +# English: "Grid of photovoltaic panels mounted on a roof segment." # Source-Hash: 065ffaaf64ab479d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid of photovoltaic panels mounted on a roof segment." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.description" msgstr "安装在屋面上的光伏板阵列。" +# English: "Solar Panel" # Source-Hash: 4d18446ff8eaa680 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solar Panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.solarPanel.label" msgstr "光伏板" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: 2184867946897298 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Array" +# Source-Hash: 2184867946897298 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.array" msgstr "阵列" -# Review: required -# Source-Hash: 6eaa0e2a78d44d59 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Auto-fit to roof" +# Source-Hash: 6eaa0e2a78d44d59 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.autoFitToRoof" msgstr "自动适配屋顶" -# Review: required -# Source-Hash: 30edd1c180aac9b4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Columns" +# Source-Hash: 30edd1c180aac9b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.columns" msgstr "列" -# Review: required -# Source-Hash: d1c6dd6335337cc1 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Custom — dimensions don't match any preset" +# Source-Hash: d1c6dd6335337cc1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.customDimensionsDonTMatchAny" msgstr "自定义——尺寸与任何预设都不匹配" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: fe9356b1d0e0396b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flip orientation" +# Source-Hash: fe9356b1d0e0396b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flipOrientation" msgstr "翻转方向" -# Review: required -# Source-Hash: eaaa20f3b603140f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Flush" +# Source-Hash: eaaa20f3b603140f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.flush" msgstr "齐平" -# Review: required -# Source-Hash: fd413477f1a9e5ed -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame depth" +# Source-Hash: fd413477f1a9e5ed +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameDepth" msgstr "框架深度" -# Review: required -# Source-Hash: 6aa2318fa815e5f2 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frame thickness" +# Source-Hash: 6aa2318fa815e5f2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.frameThickness" msgstr "框架厚度" -# Review: required -# Source-Hash: c5657f721eae3dff -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap X" +# Source-Hash: c5657f721eae3dff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapX" msgstr "X 间距" -# Review: required -# Source-Hash: e6e3b393aa7c0d98 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Gap Y" +# Source-Hash: e6e3b393aa7c0d98 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.gapY" msgstr "Y 间距" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.height" msgstr "高度" -# Review: required -# Source-Hash: 81d365ccef73068c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Mounting" +# Source-Hash: 81d365ccef73068c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.mounting" msgstr "安装方式" -# Review: required -# Source-Hash: 482469f5a0873f73 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Panel" +# Source-Hash: 482469f5a0873f73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.panel" msgstr "面板" -# Review: required -# Source-Hash: 441f7f652ea6a0d8 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Preset" +# Source-Hash: 441f7f652ea6a0d8 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.preset" msgstr "预设" -# Review: required -# Source-Hash: c913b494c3901a17 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rows" +# Source-Hash: c913b494c3901a17 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.rows" msgstr "行" -# Review: required -# Source-Hash: a4dba2b2675dbba0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Setbacks too large to fit a panel." +# Source-Hash: a4dba2b2675dbba0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.setbacksTooLarge" msgstr "退界过大,无法放下面板。" -# Review: required -# Source-Hash: 4d18446ff8eaa680 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Solar Panel" +# Source-Hash: 4d18446ff8eaa680 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.solarPanel" msgstr "光伏板" -# Review: required -# Source-Hash: 0af6194a8b8d8be6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Standoff" +# Source-Hash: 0af6194a8b8d8be6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.standoff" msgstr "支脚" -# Review: required -# Source-Hash: 58cb60c14826f1d0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilt angle" +# Source-Hash: 58cb60c14826f1d0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tiltAngle" msgstr "倾斜角度" -# Review: required -# Source-Hash: 787005b26aec58a3 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Tilted" +# Source-Hash: 787005b26aec58a3 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.tilted" msgstr "倾斜" -# Review: required -# Source-Hash: 3a0ccd6c46877a87 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Width" +# Source-Hash: 3a0ccd6c46877a87 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.panel.width" msgstr "宽度" -# Review: required -# Source-Hash: cd53f16290b67f7f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Compact" +# Source-Hash: cd53f16290b67f7f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.compact" msgstr "紧凑型" -# Review: required -# Source-Hash: 317ebe2aa996af70 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Frameless" +# Source-Hash: 317ebe2aa996af70 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.frameless" msgstr "无边框" -# Review: required -# Source-Hash: 46660ad432e19312 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential" +# Source-Hash: 46660ad432e19312 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residential" msgstr "住宅型" -# Review: required -# Source-Hash: b33386b8fe3725fa -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Residential Large" +# Source-Hash: b33386b8fe3725fa +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/solar-panel/panel.tsx #, fuzzy msgid "node.solarPanel.preset.residentialLarge" msgstr "大尺寸住宅型" +# English: "Player or camera origin within a level. One per level." # Source-Hash: 638f17c80f424dd3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Player or camera origin within a level. One per level." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.description" msgstr "楼层内的玩家或相机起点,每层一个。" +# English: "Spawn Point" # Source-Hash: 3e2f6b4274f592c0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spawn Point" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.spawn.label" msgstr "出生点" +# English: "Body" # Source-Hash: faaa4f6b40001667 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Body" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.body" msgstr "主体" +# English: "Center stringer" # Source-Hash: 29b9fdb127a54435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center stringer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.centerStringer" msgstr "中间梯梁" +# English: "Closed risers" # Source-Hash: b2b2383577835a61 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Closed risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.closedRisers" msgstr "封闭踢面" -# Source-Hash: 6f6b188bff3c0223 +# English: "From structure" +# Source-Hash: 49a25acbaf33ee3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/construction-controls.tsx +#, fuzzy +msgid "node.stair.constructionControls.fromStructure" +msgstr "随结构" + # English: "Inherit" +# Source-Hash: 6f6b188bff3c0223 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.inherit" msgstr "继承" +# English: "Nosing" # Source-Hash: ce25dc4a23f3ae10 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Nosing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.nosing" msgstr "踏步前缘" +# English: "Open" # Source-Hash: 3e52f04f92e08070 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Open" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.open" msgstr "打开" +# English: "Original" # Source-Hash: 7717e0464ad1e7df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.original" msgstr "原始" +# English: "Riser thickness" # Source-Hash: 8a6f6c6b0cca7460 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.riserThickness" msgstr "踢面厚度" +# English: "Side stringers" # Source-Hash: 2fe058966023b542 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side stringers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.sideStringers" msgstr "侧梯梁" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.solid" msgstr "实体" +# English: "Stringer depth" # Source-Hash: 4c1489839e306865 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerDepth" msgstr "梯梁高度" +# English: "Stringer width" # Source-Hash: 17e1a09c422d3660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stringer width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.stringerWidth" msgstr "梯梁宽度" +# English: "Tread finish" # Source-Hash: 146f8e61ff310dc5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadFinish" msgstr "踏板饰面" +# English: "Tread thickness" # Source-Hash: 9089f91f3fbbf4cd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tread thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.treadThickness" msgstr "踏板厚度" +# English: "Waist" # Source-Hash: 44dda7136b93c8ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waist" msgstr "板式" +# English: "Waist thickness" # Source-Hash: dfd6d2ff7a94f22e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Waist thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/construction-controls.tsx #, fuzzy msgid "node.stair.constructionControls.waistThickness" msgstr "梯板厚度" +# English: "A stair composed of one or more flights with configurable treads, risers, railings." # Source-Hash: 8535b75637105412 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A stair composed of one or more flights with configurable treads, risers, railings." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.description" msgstr "由一个或多个梯段组成的楼梯,踏板、踢板和栏杆可配置。" +# English: "The flight chain does not reach the declared destination." # Source-Hash: c24b098693e1006d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The flight chain does not reach the declared destination." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.arrivalMismatch" msgstr "梯段链未到达指定的目标。" +# English: "Two risers plus going is outside the 0.60–0.65 m design range." # Source-Hash: 124ad1cd6bb00013 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Two risers plus going is outside the 0.60–0.65 m design range." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.comfortTarget" msgstr "两倍踢面高加踏面宽不在 0.60–0.65 m 的设计范围内。" +# English: "Going is below the design target." # Source-Hash: 7452fcd8370bef34 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Going is below the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.goingTarget" msgstr "踏面宽低于设计目标。" +# English: "Overhead surfaces leave less than the target headroom." # Source-Hash: 449683eb727091d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Overhead surfaces leave less than the target headroom." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomTarget" msgstr "上方表面留出的净空低于目标值。" +# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." # Source-Hash: 9e9357408f6a0d08 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Detailed headroom could not be evaluated: check riser counts and the surface query budget." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.headroomUnresolved" msgstr "无法评估详细净空:请检查踢面数量和表面查询预算。" +# English: "Flight dimensions and integer riser count must be positive." # Source-Hash: 40fd019d72ff3835 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight dimensions and integer riser count must be positive." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidFlight" msgstr "梯段尺寸和整数踢面数必须为正。" +# English: "The destination must be above the stair base." # Source-Hash: 3d4b6f859bd8de50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The destination must be above the stair base." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.invalidRise" msgstr "目标必须高于楼梯底部。" +# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." # Source-Hash: 5f196b22e2c8296a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing depth exceeds a full circle at the walking line; the visible landing is limited to one circle." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.landingOverlap" msgstr "平台深度在行走线上超过一整圈;可见平台限制为一圈。" +# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." # Source-Hash: 0122ec92b951a114 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "The narrow end of a winder tread is below 0.10 m; check the intended stair use." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.narrowWinderEnd" msgstr "扇形踏步窄端小于 0.10 m;请检查楼梯的预期用途。" +# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." # Source-Hash: a664dc7f3b014af8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight riser heights differ. Use uniform-riser repair only if this is a proposed design." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.nonuniformRisers" msgstr "梯段踢面高度不一致。仅在此为方案设计时使用均匀踢面修复。" +# English: "A landing has a stored rise; inspect its transition before repair." # Source-Hash: cd140b519d37b01e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A landing has a stored rise; inspect its transition before repair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.raisedLanding" msgstr "平台带有存储的高度;修复前请检查其过渡。" +# English: "Riser height exceeds the design target." # Source-Hash: a94cfa7b2a21cf14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Riser height exceeds the design target." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.diagnostic.riserTarget" msgstr "踢面高度超过设计目标。" +# English: "Stair" # Source-Hash: 53d7084bc41c1ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stair.label" msgstr "楼梯" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.straight" msgstr "直跑" +# English: "{layout} turn" # Source-Hash: 1cda16cb49e6fb93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{layout} turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.layout.turn" msgstr "{layout} 转折" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.actions" msgstr "操作" +# English: "Add flight" # Source-Hash: 15eaf25f306541f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addFlight" msgstr "添加梯段" +# English: "Add landing" # Source-Hash: 1fa37c346bffb8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Add landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.addLanding" msgstr "添加平台" +# English: "Apply {label} layout" # Source-Hash: 9ccbd6c1785f1e73 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply {label} layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.applyLabelLayout" msgstr "应用{label}布局" +# English: "Auto Cutout" # Source-Hash: 7eb42ea0fc442b8d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto Cutout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.autoCutout" msgstr "自动开洞" +# English: "Balusters" # Source-Hash: 3eda29328f91485d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Balusters" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.balusters" msgstr "栏杆柱" +# English: "Boards" # Source-Hash: 19ae2360207ecf5a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Boards" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.boards" msgstr "木板" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.both" msgstr "两侧" +# English: "Cable" # Source-Hash: 5e2429ced8144ab4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.cable" msgstr "钢索" +# English: "Center Column" # Source-Hash: 11d6eec5d485488d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.centerColumn" msgstr "中柱" +# English: "Clockwise" # Source-Hash: 5fbbf4ad24befa7f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.clockwise" msgstr "顺时针" +# English: "Connected to {surface}. Move the stair to connect to another edge." # Source-Hash: 31028ca51b49018b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to {surface}. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedTo" msgstr "已连接到{surface}。移动楼梯可连接到其他边。" +# English: "Connected to a surface. Move the stair to connect to another edge." # Source-Hash: 33133921e270509f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Connected to a surface. Move the stair to connect to another edge." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.connectedToSurface" msgstr "已连接到表面。移动楼梯可连接到其他边。" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.construction" msgstr "构造" +# English: "Counterclockwise" # Source-Hash: 5e3e3ab7d91c2c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Counterclockwise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.counterclockwise" msgstr "逆时针" +# English: "Curved" # Source-Hash: a028e08962c388e0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curved" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.curved" msgstr "弧形" +# English: "Custom rise" # Source-Hash: 0bd4232a56d25366 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.customRise" msgstr "自定义踏步高" +# English: "Deck" # Source-Hash: f3aa561772702108 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.deck" msgstr "平台" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.delete" msgstr "删除" +# English: "Design targets" # Source-Hash: 1c34e5cc37add163 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Design targets" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.designTargets" msgstr "设计目标" +# English: "Destination" # Source-Hash: 748877ae309c313c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Destination" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.destination" msgstr "目标" +# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." # Source-Hash: 1fa48e82752d1505 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drag near a deck, patio, concrete slab, or landing edge to fit the stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.dragNearADeckPatioConcrete" msgstr "拖到平台、露台、混凝土板或楼梯平台边缘附近以贴合楼梯。" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.duplicate" msgstr "复制" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalAngle" msgstr "等角" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.equalGoing" msgstr "等踏步宽" +# English: "Fit risers and going" # Source-Hash: 6ff728d5058c7e31 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit risers and going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitRisersAndGoing" msgstr "适配踢面和踏面" +# English: "Fit To Floor" # Source-Hash: d60a4c7995bd2184 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit To Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitToFloor" msgstr "贴合楼面" +# English: "Fit uniform risers" # Source-Hash: 0638614580661eb0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fit uniform risers" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fitUniformRisers" msgstr "适配均匀踢面" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" # Source-Hash: 4d365dbc1fb92b90 # Review: required -# English: "Flight {number}: {count, plural, one {{riserCount} riser} other {{riserCount} risers}} · R {riser} · T {going} · {slope}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.flightMeasurements" msgstr "梯段 {number}:{count, plural, other {{riserCount} 级踢面}} · 踢面高 {riser} · 踏面宽 {going} · {slope}" +# English: "Floors, ceilings and stairs only." # Source-Hash: 6e946bd1acd94637 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floors, ceilings and stairs only." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.floorsCeilingsAndStairsOnly" msgstr "仅限楼板、天花板和楼梯。" +# English: "Follows deck" # Source-Hash: fa10ea42944107a8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows deck" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsDeck" msgstr "跟随平台" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.followsLevel" msgstr "跟随楼层" +# English: "From Level" # Source-Hash: b7fc627a965bc016 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "From Level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.fromLevel" msgstr "起始楼层" +# English: "Geometry" # Source-Hash: 523b9912e283b644 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Geometry" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.geometry" msgstr "几何" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.glass" msgstr "玻璃" +# English: "Headroom:" # Source-Hash: 55adba80c39500f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Headroom:" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.headroom" msgstr "净空:" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.height" msgstr "高度" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerGap" msgstr "内间隙" +# English: "Inner Radius" # Source-Hash: 8b54356df7ae40bf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.innerRadius" msgstr "内半径" +# English: "Integrated" # Source-Hash: 88d5b9ecb242c650 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Integrated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.integrated" msgstr "一体式" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.landing" msgstr "平台" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.layout" msgstr "布局" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.left" msgstr "左" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.leftTurn" msgstr "左转" +# English: "Measurements" # Source-Hash: c6aa74075f37313a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Measurements" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.measurements" msgstr "测量" +# English: "Metal" # Source-Hash: 8e464a8f52254f27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Metal" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.metal" msgstr "金属" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.move" msgstr "移动" +# English: "Moving this stair keeps its current rise, steps, and run." # Source-Hash: a4f1cc34aae4db27 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Moving this stair keeps its current rise, steps, and run." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.movingThisStairKeepsItsCurrent" msgstr "移动此楼梯会保持其当前的总高、级数和水平长度。" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.none" msgstr "无" +# English: "No overhead surface" # Source-Hash: 47c858b3022b3ae5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No overhead surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.noOverheadSurface" msgstr "上方无表面" +# English: "Not evaluated" # Source-Hash: 17f1933d93cf655c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not evaluated" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.notEvaluated" msgstr "未评估" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.opening" msgstr "开口" +# English: "Opening Offset" # Source-Hash: 599220556fc4bbfb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.openingOffset" msgstr "开洞偏移" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.position" msgstr "位置" +# English: "Post & rail" # Source-Hash: f522323372a7a59e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post & rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.postRail" msgstr "立柱与横杆" +# English: "Railing" # Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Railing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.railing" msgstr "栏杆" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.right" msgstr "右" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rightTurn" msgstr "右转" +# English: "Rise" # Source-Hash: fe8c5b735495eb00 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rise" msgstr "总高" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.rotation" msgstr "旋转" +# English: "Segments" # Source-Hash: 505d8419cf4cc5e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Segments" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segments" msgstr "分段" +# English: "{type, select, landing {Landing} other {Stair}}" # Source-Hash: 0b9a0bef102b75bc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{type, select, landing {Landing} other {Stair}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.segmentType" msgstr "{type, select, landing {平台} other {梯段}}" +# English: "Sizing" # Source-Hash: 87b926bbf34a78ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sizing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sizing" msgstr "尺寸设定" +# English: "Snap to nearby surfaces" # Source-Hash: 8a0f4562198c266f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Snap to nearby surfaces" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.snapToNearbySurfaces" msgstr "吸附到附近表面" +# English: "Spiral" # Source-Hash: f9a4a870cb3d3c41 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spiral" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.spiral" msgstr "螺旋" +# English: "Staircase" # Source-Hash: f8d4f66145e1eba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Staircase" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.staircase" msgstr "楼梯" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.steps" msgstr "踏步" +# English: "Step Supports" # Source-Hash: 8846dc96332c5181 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step Supports" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.stepSupports" msgstr "踏步支撑" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.straight" msgstr "直跑" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.style" msgstr "样式" +# English: "Surface connection" # Source-Hash: cde3878287069df2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface connection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.surfaceConnection" msgstr "表面连接" +# English: "Sweep" # Source-Hash: 4e0f8b363503efff # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sweep" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.sweep" msgstr "扫掠" +# English: "Target going" # Source-Hash: 4c294d88d712855a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetGoing" msgstr "目标踏面宽" +# English: "Target headroom" # Source-Hash: 74cfba22cf2e6eb8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target headroom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetHeadroom" msgstr "目标净空" +# English: "Target max riser" # Source-Hash: 9d05958a265a6726 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target max riser" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMaxRiser" msgstr "目标最大踢面高" +# English: "Target minimum going" # Source-Hash: d10322b16178ce37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Target minimum going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.targetMinimumGoing" msgstr "目标最小踏面宽" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.thickness" msgstr "厚度" +# English: "To" # Source-Hash: b3214a20e45a7aed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "To" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.to" msgstr "至" +# English: "Top Landing" # Source-Hash: 846fc58043454194 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.topLanding" msgstr "顶部平台" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.type" msgstr "类型" +# English: "Unavailable" # Source-Hash: b4c2f503b19443c6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.unavailable" msgstr "不可用" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.walkingLineOffset" msgstr "行走线偏移" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.width" msgstr "宽度" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/panel.tsx #, fuzzy msgid "node.stair.panel.winder" msgstr "扇形踏步" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.both" msgstr "两侧" +# English: "Bottom sloped extension" # Source-Hash: fe633d20f198d7e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom sloped extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.bottomExtension" msgstr "底部斜向延伸" +# English: "Continuous" # Source-Hash: 384842ae1e1c6825 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.continuous" msgstr "连续" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.floor" msgstr "楼层" +# English: "Handrail diameter" # Source-Hash: 644d2d9e9c3dd31f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail diameter" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailDiameter" msgstr "扶手直径" +# English: "Handrail height" # Source-Hash: 5f3f2be1e572a066 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailHeight" msgstr "扶手高度" +# English: "Handrail inset" # Source-Hash: cb3239847f068a1b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Handrail inset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.handrailInset" msgstr "扶手内缩" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.left" msgstr "左" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.none" msgstr "无" +# English: "No return" # Source-Hash: e6ac8d7c9639c351 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No return" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.noReturn" msgstr "无回弯" +# English: "Original layout" # Source-Hash: 5688b2003002d03a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Original layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.originalLayout" msgstr "原始布局" +# English: "Post-facing" # Source-Hash: d0d904fbd052d7b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Post-facing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.postFacing" msgstr "朝向立柱" +# English: "Return length" # Source-Hash: fc4d8c6f156b0430 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Return length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.returnLength" msgstr "回弯长度" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.right" msgstr "右" +# English: "Separate handrail" # Source-Hash: e1f8354a95b5060a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate handrail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.separateHandrail" msgstr "独立扶手" +# English: "Set the length to reach the wall or post." # Source-Hash: 005f0197ec3bc95a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Set the length to reach the wall or post." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy msgid "node.stair.railingControls.setTheLengthToReachThe" msgstr "设置长度以到达墙体或立柱。" +# English: "Top horizontal extension" # Source-Hash: bef713f3ec96f758 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top horizontal extension" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/railing-controls.tsx +#, fuzzy +msgid "node.stair.railingControls.topExtension" +msgstr "顶部水平延伸" + +# English: "Wall-facing" +# Source-Hash: bdad79929de6b142 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair/railing-controls.tsx #, fuzzy -msgid "node.stair.railingControls.topExtension" -msgstr "顶部水平延伸" +msgid "node.stair.railingControls.wallFacing" +msgstr "朝向墙体" + +# English: "A solid poured flight on a footing; risers closed, no stringers." +# Source-Hash: 1709ce71eba750ff +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.aSolidPouredFlightOnA" +msgstr "现浇实心梯段,位于基脚上;踢面封闭,无梯梁。" + +# English: "Auto ({autoCount})" +# Source-Hash: 56be143b76f65392 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoAutocount" +msgstr "自动({autoCount})" + +# English: "Auto (closed)" +# Source-Hash: d7820aaac08e4fcb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoClosed" +msgstr "自动(封闭)" + +# English: "Auto (open)" +# Source-Hash: c7cbd493df719e71 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoOpen" +msgstr "自动(开放)" + +# English: "Auto (pressure-treated)" +# Source-Hash: be21258d52930d5c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoPressureTreated" +msgstr "自动(加压防腐)" + +# English: "Auto ({treads})" +# Source-Hash: 1828540d95a5b5c7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoTreads" +msgstr "自动({treads})" + +# English: "Auto (untreated)" +# Source-Hash: 43c1949fa53685b4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.autoUntreated" +msgstr "自动(非防腐)" + +# English: "Closed (1x8 boards)" +# Source-Hash: d0a3ca3ce29ae833 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.closed1x8Boards" +msgstr "封闭(1x8 板)" + +# English: "Concrete" +# Source-Hash: 824471a3691b51db +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.concrete" +msgstr "混凝土" + +# English: "{count, plural, one {# steel channel} other {# steel channels}}" +# Source-Hash: dcb2dd54c3301068 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannels" +msgstr "{count, plural, other {# 根槽钢}}" + +# English: "{count, plural, one {# steel channel at {spacing} o.c.} other {# steel channels at {spacing} o.c.}}" +# Source-Hash: fbf12088b828a103 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countSteelChannelsAtSpacing" +msgstr "{count, plural, other {# 根槽钢,中距 {spacing}}}" + +# English: "{count, plural, one {# stringer} other {# stringers}}" +# Source-Hash: 6f0f06f0531c7e96 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringers" +msgstr "{count, plural, other {# 根梯梁}}" + +# English: "{count, plural, one {# stringer at {spacing} o.c.} other {# stringers at {spacing} o.c.}}" +# Source-Hash: cfdb3f3fc95ea940 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.countStringersAtSpacing" +msgstr "{count, plural, other {# 根梯梁,中距 {spacing}}}" + +# English: "Cut" +# Source-Hash: fa4b97d3aa600bcd +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.cut" +msgstr "锯齿形" + +# English: "Flight {number}: {layout}" +# Source-Hash: ba4f3c1346b3e7af +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.flightLayout" +msgstr "梯段 {number}:{layout}" + +# English: "Housed sides" +# Source-Hash: 4cbb34fdf4c1e107 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.housedSides" +msgstr "开槽侧梁" + +# English: "{inches} in o.c." +# Source-Hash: e3ddff241f642295 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.inchesInOC" +msgstr "中距 {inches} in" + +# English: "{layout} (auto)" +# Source-Hash: 542ca0bee99a1533 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. The stringer count was chosen automatically, not set by hand. +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.layoutAuto" +msgstr "{layout}(自动)" + +# English: "Lumber" +# Source-Hash: 73dcfae611575500 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.lumber" +msgstr "木材" + +# English: "Max spacing" +# Source-Hash: d10743fce478d9f4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.maxSpacing" +msgstr "最大间距" + +# English: "Nosing" +# Source-Hash: ce25dc4a23f3ae10 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.nosing" +msgstr "踏步前缘" + +# English: "One {stock}" +# Source-Hash: ba534b07476bc06d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.oneStock" +msgstr "一块 {stock}" + +# English: "Open" +# Source-Hash: 3e52f04f92e08070 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.open" +msgstr "开放" + +# English: "Original body" +# Source-Hash: 20f023962f848e14 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.originalBody" +msgstr "原始主体" + +# English: "Plywood + finish" +# Source-Hash: ac658d76a942652f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.plywoodFinish" +msgstr "胶合板 + 饰面" + +# English: "Pressure-treated" +# Source-Hash: 86fda85cdc89fd27 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.pressureTreated" +msgstr "加压防腐" + +# English: "Risers" +# Source-Hash: d903bba423ddb755 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.risers" +msgstr "踢面" + +# English: "Steel stringers" +# Source-Hash: ff0400a550f24adf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.steelStringers" +msgstr "钢梯梁" + +# English: "Stringer count" +# Source-Hash: 50d4cac7f3002cd2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerCount" +msgstr "梯梁数量" + +# English: "Stringers" +# Source-Hash: 71733e5b479af2a0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringers" +msgstr "梯梁" + +# English: "Stringer type" +# Source-Hash: 191196b94d1c86f9 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.stringerType" +msgstr "梯梁类型" + +# English: "Structure" +# Source-Hash: a20c89205a4448d2 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.structure" +msgstr "结构" + +# English: "Treads" +# Source-Hash: 869e3785611a0858 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.treads" +msgstr "踏板" + +# English: "Two {stock}" +# Source-Hash: e1e6555b827b4620 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.twoStock" +msgstr "两块 {stock}" + +# English: "Untreated" +# Source-Hash: 5c362de43a2cbac1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx +#, fuzzy +msgid "node.stair.structureControls.untreated" +msgstr "非防腐" -# Source-Hash: bdad79929de6b142 +# English: "Wood stringers" +# Source-Hash: 401bae5ffee978fa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall-facing" -#: packages/nodes/src/stair/railing-controls.tsx +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/stair/structure-controls.tsx #, fuzzy -msgid "node.stair.railingControls.wallFacing" -msgstr "朝向墙体" +msgid "node.stair.structureControls.woodStringers" +msgstr "木梯梁" +# English: "A single flight of a parent stair." # Source-Hash: ce0b6361eb77e93c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A single flight of a parent stair." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.description" msgstr "所属楼梯的单个梯段。" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.stairSegment.label" msgstr "梯段" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.actions" msgstr "操作" +# English: "Attachment" # Source-Hash: 551b04c03b07595e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Attachment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.attachment" msgstr "连接" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.delete" msgstr "删除" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.dimensions" msgstr "尺寸" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.duplicate" msgstr "复制" +# English: "Editing switches the stair to Custom rise" # Source-Hash: ad063fb27f370c28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Editing switches the stair to Custom rise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.editingSwitchesTheStairToCustom" msgstr "编辑后楼梯将切换为自定义踏步高" +# English: "Equal angle" # Source-Hash: 776903eb3756eea9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal angle" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalAngle" msgstr "等角" +# English: "Equal going" # Source-Hash: 8956a359d7cec0a6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Equal going" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.equalGoing" msgstr "等踏步宽" +# English: "Fill to floor" # Source-Hash: 66f1018b50c2e426 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to floor" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.fillToFloor" msgstr "填充至地面" +# English: "Flight" # Source-Hash: deff4254bb163b2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flight" msgstr "梯段" +# English: "Flight shape" # Source-Hash: d6da828cd0c5e8cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flight shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.flightShape" msgstr "梯段形状" +# English: "Front" # Source-Hash: e360f89d3c0ec456 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Front" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.front" msgstr "前" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.height" msgstr "高度" +# English: "Inner gap" # Source-Hash: 9bcdccd29e64c4d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inner gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.innerGap" msgstr "内间隙" +# English: "Landing" # Source-Hash: de9566487b17a73b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Landing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.landing" msgstr "平台" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.left" msgstr "左" +# English: "Left turn" # Source-Hash: 7d4f8532c0d115e9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.leftTurn" msgstr "左转" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.length" msgstr "长度" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.move" msgstr "移动" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.position" msgstr "位置" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.right" msgstr "右" +# English: "Right turn" # Source-Hash: ae876c3e9a5913eb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right turn" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rightTurn" msgstr "右转" +# English: "Rotation" # Source-Hash: 01989595ee7c1dc0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rotation" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.rotation" msgstr "旋转" +# English: "Stair Segment" # Source-Hash: 2159ec5769e1c59a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stair Segment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.stairSegment" msgstr "梯段" +# English: "Steps" # Source-Hash: be8122fbf16dec33 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.steps" msgstr "踏步" +# English: "Straight" # Source-Hash: 4ee35f740730ae87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Straight" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.straight" msgstr "直跑" +# English: "Structure" # Source-Hash: a20c89205a4448d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structure" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.structure" msgstr "结构" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.thickness" msgstr "厚度" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.type" msgstr "类型" +# English: "Walking line offset" # Source-Hash: 546fa6dd4e62e82d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Walking line offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.walkingLineOffset" msgstr "行走线偏移" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.width" msgstr "宽度" +# English: "Winder" # Source-Hash: 7b65cc10483fb191 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Winder" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/stair-segment/panel.tsx #, fuzzy msgid "node.stairSegment.panel.winder" msgstr "扇形踏步" +# English: "Persistent construction grid axis with identification bubbles." # Source-Hash: 5434a3189441c913 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Persistent construction grid axis with identification bubbles." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.description" msgstr "带编号圆圈的持久结构轴线。" +# English: "Structural Grid" # Source-Hash: 0cfe636f7fdd9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.structuralGrid.label" msgstr "结构轴网" +# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." # Source-Hash: 36b714210f25c284 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wind-driven spinning whirlybird exhaust vent for a roof slope." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.description" msgstr "用于屋面坡的风力旋转排气扇。" +# English: "Turbine Vent" # Source-Hash: 4a855abb962f5e7a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Turbine Vent" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.turbineVent.label" msgstr "涡轮通风器" -# Review: required -# Source-Hash: aa6d82bdb3cc1d9a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Actions" +# Source-Hash: aa6d82bdb3cc1d9a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.actions" msgstr "操作" -# Review: required -# Source-Hash: c65f02bcdc3231d6 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Cylinder" +# Source-Hash: c65f02bcdc3231d6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.cylinder" msgstr "圆柱" -# Review: required -# Source-Hash: b26de372b4b0eaac -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Delete" +# Source-Hash: b26de372b4b0eaac +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.delete" msgstr "删除" -# Review: required -# Source-Hash: 016d8e70618916d4 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Diameter" +# Source-Hash: 016d8e70618916d4 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.diameter" msgstr "直径" -# Review: required -# Source-Hash: 12df69f42beb0643 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Dimensions" +# Source-Hash: 12df69f42beb0643 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.dimensions" msgstr "尺寸" -# Review: required -# Source-Hash: 34dc96fe77a31e51 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Duplicate" +# Source-Hash: 34dc96fe77a31e51 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.duplicate" msgstr "复制" -# Review: required -# Source-Hash: 2d4223f80dc3945c -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Globe" +# Source-Hash: 2d4223f80dc3945c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.globe" msgstr "球形" -# Review: required -# Source-Hash: 646ef0fbcbc58edf -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.height" msgstr "高度" -# Review: required -# Source-Hash: c0ff16d77ebc9593 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Motion" +# Source-Hash: c0ff16d77ebc9593 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.motion" msgstr "运动" -# Review: required -# Source-Hash: bd02c614454fbd2b -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Move" +# Source-Hash: bd02c614454fbd2b +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.move" msgstr "移动" -# Review: required -# Source-Hash: 6faf9532dcbfe4bc -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Neck Height" +# Source-Hash: 6faf9532dcbfe4bc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.neckHeight" msgstr "颈部高度" -# Review: required -# Source-Hash: 1550d72740e66f77 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Pause" +# Source-Hash: 1550d72740e66f77 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.pause" msgstr "暂停" -# Review: required -# Source-Hash: c7b10382e01914ad -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Play" +# Source-Hash: c7b10382e01914ad +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.play" msgstr "播放" -# Review: required -# Source-Hash: 9da0cbfad144d737 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Position" +# Source-Hash: 9da0cbfad144d737 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.position" msgstr "位置" -# Review: required -# Source-Hash: 01989595ee7c1dc0 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Rotation" +# Source-Hash: 01989595ee7c1dc0 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.rotation" msgstr "旋转" -# Review: required -# Source-Hash: bf553d40ce68834d -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Spin Speed" +# Source-Hash: bf553d40ce68834d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.spinSpeed" msgstr "转速" -# Review: required -# Source-Hash: 1a79a485d91f3f28 -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Style" +# Source-Hash: 1a79a485d91f3f28 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.style" msgstr "样式" -# Review: required -# Source-Hash: 4a855abb962f5e7a -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Turbine Vent" +# Source-Hash: 4a855abb962f5e7a +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.turbineVent" msgstr "涡轮通风器" -# Review: required -# Source-Hash: 80cef0dce71e121f -# AI-Provider: cursor-agent -# AI-Model: claude-opus # English: "Vanes" +# Source-Hash: 80cef0dce71e121f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/turbine-vent/panel.tsx #, fuzzy msgid "node.turbineVent.panel.vanes" msgstr "叶片" +# English: "A named group of zones forming one apartment, hotel room or commercial lot." # Source-Hash: e3d2aaaf5c72bc3b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A named group of zones forming one apartment, hotel room or commercial lot." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.description" msgstr "组成一套公寓、酒店客房或商业单元的命名区域组。" +# English: "Apartment" # Source-Hash: 40e5a51d0d205fcf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apartment" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.apartment" msgstr "公寓" +# English: "Commercial" # Source-Hash: ad041d9634dd55ea # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Commercial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.commercial" msgstr "商业" +# English: "Common" # Source-Hash: 862f30cb2d49f812 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Common" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.common" msgstr "公共" +# English: "Hotel room" # Source-Hash: cd7e1bc42d47a105 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hotel room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.kind.hotelRoom" msgstr "酒店客房" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.unit.label" msgstr "单元" +# English: "Room" # Source-Hash: 612dcd5bf06a79c4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberRoom" msgstr "房间" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.memberZone" msgstr "区域" +# English: "Members sit on levels that are not adjacent." # Source-Hash: e25d5b5630b9d8df # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members sit on levels that are not adjacent." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.nonAdjacentLevels" msgstr "成员位于不相邻的楼层上。" +# English: "No zones assigned yet." # Source-Hash: 5ebeb440e0e64d01 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No zones assigned yet." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.noZonesAssigned" msgstr "尚未分配区域。" +# English: "Remove {name} from unit" # Source-Hash: 07731fa851c75d8c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove {name} from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeMember" msgstr "从单元中移除{name}" +# English: "Remove zone from unit" # Source-Hash: 389839734c8350b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove zone from unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.panel.removeZone" msgstr "从单元中移除区域" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.actions" msgstr "操作" +# English: "Color" # Source-Hash: fe18958cedd627d2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Color" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.color" msgstr "颜色" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.delete" msgstr "删除" +# English: "Focus" # Source-Hash: b80dc93401f953a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Focus" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.focus" msgstr "聚焦" +# English: "Gross area" # Source-Hash: 1218205b3bb34f1d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gross area" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.grossArea" msgstr "总面积" +# English: "Kind" # Source-Hash: aa9d6710f4cac041 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Kind" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.kind" msgstr "类型" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Levels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.levels" msgstr "楼层" +# English: "Members" # Source-Hash: 6bbc51529519435c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Members" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.members" msgstr "成员" +# English: "Name" # Source-Hash: 6cc5df35f0338e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Name" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.name" msgstr "名称" +# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." # Source-Hash: 54b25f70c7b63446 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No rooms yet. Pick this unit from a room's panel, or draw zones while it is focused." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.noRoomsYetPickThisUnit" msgstr "暂无房间。可在房间面板中选择此单元,或在其聚焦时绘制区域。" +# English: "Report" # Source-Hash: 85d640066187108d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Report" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.report" msgstr "报告" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/unit/unit-panel.tsx #, fuzzy msgid "node.unit.unitPanel.unit" msgstr "单元" -# Source-Hash: d53242481ff46157 +# English: "Dimensions to the exterior faces (outside size). Click for both" +# Source-Hash: e04a35319a83ad7c +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheExteriorFacesOutside" +msgstr "标注至外表面(外部尺寸)。点击切换为两者" + +# English: "Dimensions to the interior and exterior faces. Click for interior only" +# Source-Hash: 41c6c329ecb15086 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorAndExterior" +msgstr "标注至内、外表面。点击切换为仅内侧" + +# English: "Dimensions to the interior faces (room size). Click for exterior only" +# Source-Hash: 57524fee08ddde73 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.dimensionsToTheInteriorFacesRoom" +msgstr "标注至内表面(房间尺寸)。点击切换为仅外侧" + +# English: "ID" +# Source-Hash: 81ef3a25ccb6eaf7 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: interior dimension, measured to the interior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.id" +msgstr "内" + +# English: "ID+OD" +# Source-Hash: e846b1fcc616fb6d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: ID is the interior dimension, OD the outside dimension. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.idOd" +msgstr "内+外" + # English: "Merge walls" +# Source-Hash: d53242481ff46157 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.mergeWalls" msgstr "合并墙体" -# Source-Hash: d7ead4708d50965f +# English: "OD" +# Source-Hash: fdaa3b3faa1844fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#. Short button text: outside dimension, measured to the exterior faces. +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.od" +msgstr "外" + # English: "Split wall" +# Source-Hash: d7ead4708d50965f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/actions.tsx #, fuzzy msgid "node.wall.actions.splitWall" msgstr "拆分墙体" -# Source-Hash: 96a6f084d095ea36 +# English: "These walls cannot be merged." +# Source-Hash: 2f8c3189a0b3972b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/actions.tsx +#, fuzzy +msgid "node.wall.actions.theseWallsCannotBeMerged" +msgstr "这些墙体无法合并。" + # English: "Column {column}, row {row}: {type, select, glass {glass} solid {solid} other {empty}}" +# Source-Hash: 96a6f084d095ea36 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.cell" msgstr "第 {column} 列,第 {row} 行:{type, select, glass {玻璃} solid {实板} other {空}}" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" # Source-Hash: 904e9b269b68c670 # Review: required -# English: "{count, plural, one {{number} column} other {{number} columns}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.columnSpacing" msgstr "{count, plural, other {{number} 列}} · 网格间距 {spacing}" +# English: "No panels" # Source-Hash: 8b2b2f11fca9f077 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "No panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.noPanels" msgstr "无面板" -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" # Source-Hash: 9f5d9775377b666e # Review: required -# English: "{count, plural, one {{number} row} other {{number} rows}} · {spacing} grid spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtain.rowSpacing" msgstr "{count, plural, other {{number} 行}} · 网格间距 {spacing}" +# English: "Apply tint" # Source-Hash: 90638d5a19a4f0a9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Apply tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.applyTint" msgstr "应用色调" +# English: "Border / opening frame" # Source-Hash: a7d624ec0f879829 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Border / opening frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.borderOpeningFrame" msgstr "边框 / 洞口框" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottom" msgstr "底部" +# English: "Bottom row" # Source-Hash: 5503fb89c40cefeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.bottomRow" msgstr "底行" +# English: "Cancel tint" # Source-Hash: 1928d9e7a16360d6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cancel tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.cancelTint" msgstr "取消色调" +# English: "Center" # Source-Hash: 5211341ed18f33f6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Center" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.center" msgstr "居中" +# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." # Source-Hash: 9f4c033fa80119db # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Choose a cell or enter its column and row. Use arrow keys to move between cells. Columns run from the wall start; rows count upward." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.chooseACellOrEnterIts" msgstr "选择一个单元格,或输入其列和行。使用方向键在单元格间移动。列从墙体起点开始计数;行自下而上计数。" +# English: "Clear hidden overrides" # Source-Hash: bbeadcebc59c923f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Clear hidden overrides" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.clearHiddenOverrides" msgstr "清除隐藏的覆盖" +# English: "Column" # Source-Hash: 3530ac1f4046cd22 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.column" msgstr "柱" +# English: "Column layout" # Source-Hash: 711a6426e336877a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnLayout" msgstr "列布局" +# English: "Column {column}, row {row}" # Source-Hash: 59b2ab5fdf54c2e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Column {column}, row {row}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.columnRow" msgstr "第 {column} 列,第 {row} 行" +# English: "Construction" # Source-Hash: ce1429556f57d7ee # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Construction" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.construction" msgstr "构造" +# English: "Continuous mullions and transoms assembled along the wall." # Source-Hash: 59d2bec8b9ae1025 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Continuous mullions and transoms assembled along the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.continuousMullionsAndTransomsAssembledAlong" msgstr "沿墙体组装的连续竖梃和横档。" +# English: "Curtain wall panel selection" # Source-Hash: bbc5e5f3f3b99232 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall panel selection" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.curtainWallPanelSelection" msgstr "幕墙面板选择" +# English: "Custom infill" # Source-Hash: 9fd79cf417eab9ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.customInfill" msgstr "自定义填充" +# English: "Default panel" # Source-Hash: 097ed751fa47e5fd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Default panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.defaultPanel" msgstr "默认面板" +# English: "Edit individual panels" # Source-Hash: 0a0c98ae42f519ef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Edit individual panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.editIndividualPanels" msgstr "编辑单个面板" +# English: "Effective thickness: {thickness}, limited by frame depth." # Source-Hash: 8690f82f64806e4d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Effective thickness: {thickness}, limited by frame depth." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.effectiveThicknessLimited" msgstr "有效厚度:{thickness},受框架深度限制。" +# English: "Empty" # Source-Hash: 65b1cbba3b92c5b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Empty" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.empty" msgstr "空" +# English: "Enable panel editing" # Source-Hash: c6e7792839d09c69 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable panel editing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.enablePanelEditing" msgstr "启用面板编辑" +# English: "End" # Source-Hash: 135874d189176d13 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "End" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.end" msgstr "终点" +# English: "Fixed spacing" # Source-Hash: 4fb65101d0b1a222 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fixedSpacing" msgstr "固定间距" +# English: "Following wall defaults" # Source-Hash: 1c851bde836b3b57 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Following wall defaults" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.followingWallDefaults" msgstr "沿用墙体默认值" +# English: "Frame dimensions" # Source-Hash: 49be990b89741501 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.frameDimensions" msgstr "框架尺寸" +# English: "Framing" # Source-Hash: c7e72beff9fe4b37 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Framing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.framing" msgstr "龙骨" +# English: "Fully capped" # Source-Hash: 221f418784836f11 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fully capped" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.fullyCapped" msgstr "完全封顶" +# English: "Glass" # Source-Hash: 8ab91094cf650204 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glass" msgstr "玻璃" +# English: "Glass appearance" # Source-Hash: a17c63662aa4404d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearance" msgstr "玻璃外观" +# English: "Glass appearance is controlled by the paint tool." # Source-Hash: f59c4938a2116a2e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass appearance is controlled by the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassAppearanceIsControlledByThe" msgstr "玻璃外观由涂刷工具控制。" +# English: "Glass tint" # Source-Hash: 9c38875c310547ae # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glass tint" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glassTint" msgstr "玻璃色调" +# English: "Glazing gap" # Source-Hash: 7660a6aa7e7cd365 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Glazing gap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.glazingGap" msgstr "玻璃间隙" +# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." # Source-Hash: 6e739c8a698efaab # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid limited to 32 panels per axis. Spacing expands to fit this wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.gridLimitedTo32PanelsPer" msgstr "网格每个方向最多 32 个面板。间距会扩大以适应此墙体。" +# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" # Source-Hash: 1c63255b675bbcd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{length, plural, one {{number} custom panel is outside this grid. It returns if the grid expands.} other {{number} custom panels are outside this grid. They return if the grid expands.}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.hiddenCustomPanelCount" msgstr "{length, plural, other {有 {number} 个自定义面板位于此网格之外。网格扩大后会恢复。}}" +# English: "Horizontal caps" # Source-Hash: 84af317e818c10cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalCaps" msgstr "水平盖板" +# English: "Horizontal frame width" # Source-Hash: 7a71f6b25c5dc718 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.horizontalFrameWidth" msgstr "水平框架宽度" +# English: "Layout" # Source-Hash: 54800340a0eeab23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.layout" msgstr "布局" +# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." # Source-Hash: fc37833ef19c8feb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Low roughness is smooth; high roughness gives a matte surface in rendered shading." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.lowRoughnessIsSmoothHighRoughness" msgstr "粗糙度低则光滑;粗糙度高则在渲染中呈哑光表面。" +# English: "Maximum spacing" # Source-Hash: 321ca5101ab61db7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Maximum spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.maximumSpacing" msgstr "最大间距" +# English: "Module joint width" # Source-Hash: 1452f51ec5dfc6ba # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Module joint width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.moduleJointWidth" msgstr "模块接缝宽度" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.none" msgstr "无" +# English: "Opacity" # Source-Hash: 1c0b8a5503c5daeb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opacity" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opacity" msgstr "不透明度" +# English: "Opaque band" # Source-Hash: 0f9ba67daa6b4625 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque band" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBand" msgstr "不透明带" +# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." # Source-Hash: fdf533d6592eff60 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opaque bands replace a full row with solid panels. Apply their finish with the paint tool." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.opaqueBandsReplaceAFullRow" msgstr "不透明带用实板替换整行。使用涂刷工具应用其饰面。" +# English: "Panel count" # Source-Hash: b91302bf741c9fd6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel count" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelCount" msgstr "面板数量" +# English: "Panel infill" # Source-Hash: 3a019ce58d021431 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel infill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelInfill" msgstr "面板填充" +# English: "Panels" # Source-Hash: 6ce059aee1386f72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panels" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panels" msgstr "面板" +# English: "Panel thickness" # Source-Hash: d24fe036670f54a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Panel thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.panelThickness" msgstr "面板厚度" +# English: "Reset this panel" # Source-Hash: a780f13a732bab88 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reset this panel" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.resetThisPanel" msgstr "重置此面板" +# English: "Row" # Source-Hash: 03a22f6df69c47d3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.row2" msgstr "行" +# English: "Row layout" # Source-Hash: 0e18ec19d0e4ea93 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row layout" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.rowLayout" msgstr "行布局" +# English: "Separate framed modules with joints between units." # Source-Hash: dfb58bbd8932811d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Separate framed modules with joints between units." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.separateFramedModulesWithJointsBetween" msgstr "分离的带框模块,单元之间有接缝。" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Solid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.solid" msgstr "实体" +# English: "Spacing" # Source-Hash: e4698e83d02beb6c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Spacing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.spacing" msgstr "间距" +# English: "Start" # Source-Hash: 0a26a1e61c825660 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Start" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.start" msgstr "起点" +# English: "Stick-built" # Source-Hash: d3d738ccbc7103ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stick-built" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.stickBuilt" msgstr "现场搭建" +# English: "Structural glazing" # Source-Hash: fbdb9260ee302d3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Structural glazing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.structuralGlazing" msgstr "结构玻璃" +# English: "Surface roughness" # Source-Hash: 393d5c5ec4e7014c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Surface roughness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.surfaceRoughness" msgstr "表面粗糙度" +# English: "Top" # Source-Hash: 27644f0497fd9849 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.top" msgstr "顶部" +# English: "Top row" # Source-Hash: 51aa1c35a1761178 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top row" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.topRow" msgstr "顶行" +# English: "Uncapped members sit behind the glazing." # Source-Hash: 1181faa315f26c83 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Uncapped members sit behind the glazing." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.uncappedMembersSitBehindTheGlazing" msgstr "无盖板的构件位于玻璃后方。" +# English: "Unitized" # Source-Hash: e68eeb4033268842 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unitized" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.unitized" msgstr "单元式" +# English: "Use glass settings instead" # Source-Hash: 355b469b8a9c42f9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use glass settings instead" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useGlassSettingsInstead" msgstr "改用玻璃设置" +# English: "Use the Door or Window tool to add an opening through the wall." # Source-Hash: 9d75fad413e547e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Use the Door or Window tool to add an opening through the wall." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.useTheDoorOrWindowTool" msgstr "使用门或窗工具在墙上添加开口。" +# English: "Vertical caps" # Source-Hash: c1fa8c24203a5dde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical caps" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalCaps" msgstr "竖向盖板" +# English: "Vertical frame width" # Source-Hash: 0560516882b93f0d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Vertical frame width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.verticalFrameWidth" msgstr "竖向框架宽度" +# English: "Wall system" # Source-Hash: 4b903041c38cdd72 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall system" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/curtain-wall-panel.tsx #, fuzzy msgid "node.wall.curtainWallPanel.wallSystem" msgstr "墙体系统" +# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." # Source-Hash: 6c753f6234766116 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A straight or curved wall segment. Hosts doors, windows, lean-to extensions, and wall-mounted items." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.description" msgstr "直线或曲线墙体段,可承载门、窗、单坡扩建和壁挂物品。" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.wall.label" msgstr "墙体" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.actions" msgstr "操作" +# English: "Assembly" # Source-Hash: 67d2ec01cdce2bdc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.assembly" msgstr "构造" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Auto" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.auto" msgstr "自动" +# English: "Both" # Source-Hash: 69578b7dd7723fa1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Both" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.both" msgstr "两侧" +# English: "Bottom" # Source-Hash: b3a163b453896bef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bottom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.bottom" msgstr "底部" +# English: "Brick veneer" # Source-Hash: 75d323d0159b0df8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Brick veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.brickVeneer" msgstr "砖饰面" +# English: "Chair rail" # Source-Hash: 32d0bfee628c42a5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Chair rail" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.chairRail" msgstr "护墙板条" +# English: "CMU" # Source-Hash: a85f39db8f92f8e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "CMU" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cmu" msgstr "混凝土砌块" +# English: "Colonial" # Source-Hash: f76e125a47ed0612 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Colonial" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.colonial" msgstr "殖民式" +# English: "Cove" # Source-Hash: a4962c0f39d629ac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Cove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.cove" msgstr "凹弧" +# English: "Craft" # Source-Hash: e5d425095ade6f06 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Craft" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.craft" msgstr "工艺风格" +# English: "Crown molding" # Source-Hash: fa8a6b7f2f4eee63 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Crown molding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.crownMolding" msgstr "顶角线" +# English: "Curtain wall" # Source-Hash: 9736e0a78294e68d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curtain wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curtainWall" msgstr "幕墙" +# English: "Curve" # Source-Hash: f08fbb462e9afc0e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Curve" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.curve" msgstr "曲线" +# English: "Custom" # Source-Hash: 9e9715d52410c86f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.custom" msgstr "自定义" +# English: "Custom height" # Source-Hash: b2a1b3bc9676fe05 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.customHeight" msgstr "自定义高度" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.depth" msgstr "深度" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.dimensions" msgstr "尺寸" +# English: "Drywall" # Source-Hash: 4da800c75b9a6b23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Drywall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.drywall" msgstr "石膏板" +# English: "Extends downward to meet the terrain. Height and top stay unchanged." # Source-Hash: d6ba089e1ca40cdf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Extends downward to meet the terrain. Height and top stay unchanged." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.extendsDownwardToMeetTheTerrain" msgstr "向下延伸至地形。高度和顶面保持不变。" +# English: "Exterior" # Source-Hash: 0ead5e5711b98bf6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Exterior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.exterior" msgstr "外部" +# English: "Fiber cement" # Source-Hash: fd89c31f16639f50 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fiber cement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fiberCement" msgstr "纤维水泥" +# English: "Fill to terrain" # Source-Hash: a6be391d580de5cf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fill to terrain" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.fillToTerrain" msgstr "填充至地形" +# English: "Flat" # Source-Hash: 9cab4d4c95f63cdd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flat" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.flat" msgstr "平屋顶" +# English: "Follows level" # Source-Hash: a5c2353769b603d5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Follows level" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.followsLevel" msgstr "跟随楼层" +# English: "Frame depth" # Source-Hash: fd413477f1a9e5ed # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.frameDepth" msgstr "框架深度" -# Source-Hash: c7e72beff9fe4b37 +# English: "Framer's default" +# Source-Hash: cb22c8e4b0d92234 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.framersDefault" +msgstr "框架工默认" + # English: "Framing" +# Source-Hash: c7e72beff9fe4b37 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.framing" msgstr "龙骨" +# English: "Gypsum" # Source-Hash: eda78e8a76f068d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Gypsum" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.gypsum" msgstr "石膏" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.height" msgstr "高度" +# English: "Hide code" # Source-Hash: 2d89c1638aa8dd7e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hide code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.hideCode" msgstr "隐藏代码" +# English: "ICF" # Source-Hash: 0c6dcbbbda6b4dbb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "ICF" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.icf" msgstr "保温模板" +# English: "Interior" # Source-Hash: ff005196861cef68 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Interior" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.interior" msgstr "内部" +# English: "Lap siding" # Source-Hash: 9a4325140858cfec # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Lap siding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.lapSiding" msgstr "搭接壁板" +# English: "Layered" # Source-Hash: 297b21e90e8c25f0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layered" msgstr "分层" +# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." # Source-Hash: bf279d0f8f1e3b78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Layer thicknesses follow the 2021 IRC assembly data. Drafting aid, not engineering — verify with the authority having jurisdiction." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.layerThicknessesFollowThe2021Irc" msgstr "各层厚度依据 2021 IRC 构造数据。仅作制图辅助,并非工程计算——请向主管部门核实。" +# English: "Length" # Source-Hash: 643687bc3adaac76 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Length" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.length" msgstr "长度" +# English: "Loading…" # Source-Hash: 9e84d6d7b9885c4f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Loading…" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.loading" msgstr "加载中…" +# English: "Modern" # Source-Hash: f6cbdaa17e989b91 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Modern" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.modern" msgstr "现代" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.none" msgstr "无" +# English: "None (single layer)" # Source-Hash: c7300ca33851fba7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None (single layer)" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.noneSingleLayer" msgstr "无(单层)" +# English: "Offset" # Source-Hash: 0f0e13abf9b2bb67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Offset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.offset" msgstr "偏移" +# English: "Ogee" # Source-Hash: 04b3006bd4e6e207 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Ogee" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.ogee" msgstr "S 形" +# English: "OSB" # Source-Hash: 4f7f8af75bf8e914 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "OSB" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.osb" msgstr "定向刨花板" +# English: "Partition: the interior finish is applied to both faces." # Source-Hash: 1243161deca7d470 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Partition: the interior finish is applied to both faces." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.partitionTheInteriorFinishIsApplied" msgstr "隔墙:内饰面应用于两面。" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.picture" msgstr "画框" +# English: "Plaster" # Source-Hash: a6fd2cbf5c2154e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plaster" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plaster" msgstr "灰泥" +# English: "Plywood" # Source-Hash: e669c2c08bd80e23 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Plywood" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.plywood" msgstr "胶合板" +# English: "Preset" # Source-Hash: 441f7f652ea6a0d8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Preset" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.preset" msgstr "预设" -# Source-Hash: 62bf2677b9ec30fc +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.railing" +msgstr "栏杆" + # English: "Reference" +# Source-Hash: 62bf2677b9ec30fc +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.reference" msgstr "参照" +# English: "Remove" # Source-Hash: 9469a102f04ea196 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Remove" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.remove" msgstr "移除" +# English: "Round" # Source-Hash: ad0457800a1f0eb4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Round" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.round" msgstr "圆形" +# English: "Scripted finish" # Source-Hash: e9a0a3688faba18b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Scripted finish" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.scriptedFinish" msgstr "脚本饰面" +# English: "Sheathing" # Source-Hash: 36a588e44085108c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sheathing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sheathing" msgstr "覆面板" +# English: "Shoe" # Source-Hash: 6ae9aa7061d8c617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Shoe" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.shoe" msgstr "踢脚" +# English: "Side A" # Source-Hash: 136762ac1bc96e6f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side A" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideA" msgstr "A 面" +# English: "Side B" # Source-Hash: 8b9cb16850549c35 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side B" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideB" msgstr "B 面" +# English: "Side {face}" # Source-Hash: e6a27f6f8cd489b9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Side {face}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.sideFace" msgstr "{face} 面" +# English: "Skirting" # Source-Hash: d9d55c5883a53789 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Skirting" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.skirting" msgstr "踢脚线" +# English: "Standard" # Source-Hash: d998c212030bc7c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Standard" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.standard" msgstr "标准" +# English: "Steel studs" # Source-Hash: da3e3dd077163ba0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Steel studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.steelStuds" msgstr "钢龙骨" +# English: "Step" # Source-Hash: ed3989f23a2d185e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Step" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.step" msgstr "台阶" +# English: "Stone veneer" # Source-Hash: 9450fcb4a14674af # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stone veneer" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stoneVeneer" msgstr "石材饰面" +# English: "Stucco" # Source-Hash: 687cf7bc64b0224e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Stucco" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.stucco" msgstr "灰泥饰面" -# Source-Hash: fdbb503f0941bd56 +# English: "Studs" +# Source-Hash: 4fce02ee3f69bf99 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.studs" +msgstr "龙骨" + # English: "Thickness" +# Source-Hash: fdbb503f0941bd56 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thickness" msgstr "厚度" +# English: "{thickness} · from assembly" # Source-Hash: 00cb7b58f3a10140 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{thickness} · from assembly" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.thicknessFromAssembly" msgstr "{thickness} · 来自构造" -# Source-Hash: 27644f0497fd9849 +# English: "This project" +# Source-Hash: 9eef4c34dfbc970b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/panel.tsx +#, fuzzy +msgid "node.wall.panel.thisProject" +msgstr "本项目" + # English: "Top" +# Source-Hash: 27644f0497fd9849 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.top" msgstr "顶部" +# English: "Total thickness" # Source-Hash: 3ee03b0e9ae1be4c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Total thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.totalThickness" msgstr "总厚度" +# English: "Unverified: {presetNote}" # Source-Hash: ea7e8bbd87ce4152 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unverified: {presetNote}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.unverifiedPresetnote" msgstr "未核实:{presetNote}" +# English: "View code" # Source-Hash: 21242af83a2a6d17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "View code" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.viewCode" msgstr "查看代码" +# English: "Wall" # Source-Hash: 2210e5ee50b74dc9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wall" msgstr "墙体" +# English: "Wall type" # Source-Hash: eab795728c4a3f5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.wallType" msgstr "墙体类型" +# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." # Source-Hash: e97a456d667465f7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Which face is outside is undetermined (no room detected on either side) — the exterior layers are drawn on side B." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.whichFaceIsOutsideIsUndetermined" msgstr "无法确定哪一面朝外(两侧都未检测到房间)— 外层绘制在 B 面。" +# English: "Wood studs" # Source-Hash: 31e404a4a4870faf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wood studs" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.panel.woodStuds" msgstr "木龙骨" -# Source-Hash: 5211341ed18f33f6 +# English: "A guard's openings must not pass a 4 in sphere (IRC R312.1.3)." +# Source-Hash: ece946252401f4ab +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.aGuardSOpeningsMustNot" +msgstr "护栏开口不得让 4 in 球体通过(IRC R312.1.3)。" + +# English: "Height" +# Source-Hash: 646ef0fbcbc58edf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.height" +msgstr "高度" + +# English: "Infill" +# Source-Hash: 7c339a5fae55681d +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.infill" +msgstr "填充" + +# English: "2x6 cap" +# Source-Hash: d814a47c4eccee3f +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n2x6Cap" +msgstr "2x6 顶盖" + +# English: "36 in" +# Source-Hash: 137b45940b723a6e +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n36In" +msgstr "36 in" + +# English: "42 in" +# Source-Hash: ebf854ac4dd4daaf +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.n42In" +msgstr "42 in" + +# English: "Posts" +# Source-Hash: ede386e1d510ebfb +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.posts" +msgstr "立柱" + +# English: "Railing" +# Source-Hash: 240ed9c77627b9a1 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.railing" +msgstr "栏杆" + +# English: "Style" +# Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 +#: packages/nodes/src/wall/railing-wall-panel.tsx +#, fuzzy +msgid "node.wall.railingWallPanel.style" +msgstr "样式" + # English: "Center" +# Source-Hash: 5211341ed18f33f6 +# Review: required +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.center" msgstr "居中" +# English: "Inside face" # Source-Hash: 09576259041494f3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Inside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.inside" msgstr "内侧面" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.left" msgstr "左" +# English: "Outside face" # Source-Hash: 697001376b872e74 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Outside face" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.outside" msgstr "外侧面" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.reference.right" msgstr "右" +# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" # Source-Hash: eac63d14f275db3d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Hide skirting} crown {Hide crown molding} other {Hide chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.hide" msgstr "{trim, select, skirting {隐藏踢脚线} crown {隐藏顶角线} other {隐藏护墙板条}}" +# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" # Source-Hash: 8db070a4247db221 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "{trim, select, skirting {Show skirting} crown {Show crown molding} other {Show chair rail}}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/wall/panel.tsx #, fuzzy msgid "node.wall.trim.show" msgstr "{trim, select, skirting {显示踢脚线} crown {显示顶角线} other {显示护墙板条}}" +# English: "A window cut into a wall. Animated open/close for opening windows." # Source-Hash: ea6a51e66eb0c0dc # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A window cut into a wall. Animated open/close for opening windows." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.description" msgstr "开在墙体上的窗,可开启的窗带开关动画。" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.window.label" msgstr "窗" +# English: "Raise" # Source-Hash: 011e4de244050a78 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Raise" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.raise" msgstr "上提" +# English: "Slats" # Source-Hash: 19ca2434d4f7eb87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slats" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slats" msgstr "叶片" +# English: "Slide" # Source-Hash: c00de0e2c9b0ddcb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Slide" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.slide" msgstr "推拉" +# English: "Swing" # Source-Hash: e48604b5f7b2a5f4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Swing" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.swing" msgstr "平开" +# English: "Tilt" # Source-Hash: 1615637fe68e188a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tilt" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.operation.tilt" msgstr "倾斜" +# English: "Actions" # Source-Hash: aa6d82bdb3cc1d9a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Actions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.actions" msgstr "操作" +# English: "All" # Source-Hash: 663175a9c461a96d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "All" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.all" msgstr "全部" +# English: "Arch" # Source-Hash: 710261f5ffb32617 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.arch" msgstr "拱形" +# English: "Arch Height" # Source-Hash: 3715cd54e4775087 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Arch Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.archHeight" msgstr "拱高" +# English: "Auto" # Source-Hash: 05b65242d8c48791 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.auto" msgstr "自动" +# English: "Auto follows the wall" # Source-Hash: 8a04486ba122ea7d # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Auto follows the wall" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.autoFollowsTheWall" msgstr "自动跟随墙体" +# English: "Columns" # Source-Hash: 30edd1c180aac9b4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Columns" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.columns" msgstr "列" +# English: "Col Widths" # Source-Hash: 2f417371c399ad67 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Col Widths" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.colWidths" msgstr "列宽" +# English: "Corner" # Source-Hash: 2ee121dca87bb64a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.corner" msgstr "转角" +# English: "Corner post" # Source-Hash: 04e3425c5ea29359 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner post" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerPost" msgstr "角柱" +# English: "Corner Radius" # Source-Hash: 7a90a42b578f2435 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Corner Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.cornerRadius" msgstr "圆角半径" +# English: "Custom: the panes match no style." # Source-Hash: 67d10b97eff316c1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Custom: the panes match no style." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.customThePanesMatchNoStyle" msgstr "自定义:窗格不匹配任何样式。" +# English: "Delete" # Source-Hash: b26de372b4b0eaac # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Delete" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.delete" msgstr "删除" +# English: "Depth" # Source-Hash: 99ac4d03c3061f3a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Depth" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.depth" msgstr "深度" +# English: "Dimensions" # Source-Hash: 12df69f42beb0643 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.dimensions" msgstr "尺寸" +# English: "Divider" # Source-Hash: c53c3307e2a2a32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Divider" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.divider" msgstr "分隔条" +# English: "Down" # Source-Hash: 72c65752a1d1bbb1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Down" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.down" msgstr "向下" +# English: "Duplicate" # Source-Hash: 34dc96fe77a31e51 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Duplicate" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.duplicate" msgstr "复制" +# English: "Enable Sill" # Source-Hash: 8497c4373d5d18ad # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enable Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.enableSill" msgstr "启用窗台" +# English: "Flip Side" # Source-Hash: 61f9413d3bbabde7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Flip Side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.flipSide" msgstr "翻转方向" +# English: "Frame" # Source-Hash: ce3a6c03765dbbd8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Frame" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frame" msgstr "框架" +# English: "Depth (auto)" # Source-Hash: 0cce8ae784f78478 # Review: required # AI-Provider: claude-code -# AI-Model: claude-fable-5-1 -# English: "Depth (auto)" +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.frameDepthAuto" msgstr "深度(自动)" +# English: "French" # Source-Hash: 092033938ca2debb # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "French" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.french" msgstr "法式门" +# English: "Fused glass" # Source-Hash: a6362b937fffe999 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fused glass" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.fusedGlass" msgstr "熔融玻璃" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.grid" msgstr "网格" +# English: "Height" # Source-Hash: 646ef0fbcbc58edf # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Height" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.height" msgstr "高度" +# English: "Individual" # Source-Hash: 42824e50de26bc14 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Individual" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.individual" msgstr "单独" +# English: "Left" # Source-Hash: f365c6b6dd9ebab9 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Left" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.left" msgstr "左" +# English: "Move" # Source-Hash: bd02c614454fbd2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Move" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.move" msgstr "移动" +# English: "Opening" # Source-Hash: e04298381837252f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.opening" msgstr "开口" +# English: "Opening Shape" # Source-Hash: ecb81bd3eefa6a47 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Opening Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.openingShape" msgstr "开洞形状" +# English: "Other side" # Source-Hash: cf40395b71bedcca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Other side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.otherSide" msgstr "另一侧" +# English: "pos" # Source-Hash: 28fe6e9deddde6e8 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "pos" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.pos" msgstr "位置" +# English: "Position" # Source-Hash: 9da0cbfad144d737 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Position" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.position" msgstr "位置" +# English: "Rect" # Source-Hash: 3b6f225112035fde # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rect" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rect" msgstr "矩形" +# English: "Reveal Radius" # Source-Hash: 59236bd016c6b904 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Reveal Radius" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.revealRadius" msgstr "洞口圆角半径" +# English: "Right" # Source-Hash: b34d5ee0f218221d # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Right" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.right" msgstr "右" +# English: "Rounded" # Source-Hash: fc826da6c99303e6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rounded" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rounded" msgstr "圆角" +# English: "Row Heights" # Source-Hash: fb811056b862b6d0 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Row Heights" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rowHeights" msgstr "行高" +# English: "Rows" # Source-Hash: c913b494c3901a17 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Rows" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.rows" msgstr "行" +# English: "Sill" # Source-Hash: 62ded63b01564969 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sill" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sill" msgstr "窗台" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.single" msgstr "单扇" +# English: "Size is limited to the wall, including clearance for the opening frame." # Source-Hash: a8096eb4beaadeef # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Size is limited to the wall, including clearance for the opening frame." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.sizeIsLimitedToTheWall" msgstr "尺寸受墙体限制,包括洞口框的间隙。" +# English: "Style" # Source-Hash: 1a79a485d91f3f28 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Style" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.style" msgstr "样式" +# English: "Thickness" # Source-Hash: fdbb503f0941bd56 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Thickness" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thickness" msgstr "厚度" +# English: "This side" # Source-Hash: 79ff1ae7250c1711 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "This side" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.thisSide" msgstr "此侧" +# English: "Top Shape" # Source-Hash: 7e2505dd1d5e3513 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top Shape" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.topShape" msgstr "顶部形状" +# English: "Type" # Source-Hash: c44f7ab8bbd24858 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.type" msgstr "类型" +# English: "Unwrap" # Source-Hash: c29767f86bd9df1e # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unwrap" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.unwrap" msgstr "解除" +# English: "Up" # Source-Hash: 1d732503a54a532f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Up" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.up" msgstr "向上" +# English: "Width" # Source-Hash: 3a0ccd6c46877a87 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Width" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.width" msgstr "宽度" +# English: "Window" # Source-Hash: aa3c35ed33e181e5 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.window" msgstr "窗" +# English: "Window Type" # Source-Hash: f59cd625e6082651 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Window Type" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.windowType" msgstr "窗户类型" +# English: "Wraps the corner with {partner}" # Source-Hash: 7bf8c4e2b90b3021 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with {partner}" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithNamed" msgstr "与{partner}包住转角" +# English: "Wraps the corner with its partner" # Source-Hash: 8f1f8eff5fbe90e1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wraps the corner with its partner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapsTheCornerWithPartner" msgstr "与其配对窗户包住转角" +# English: "Wrap the corner" # Source-Hash: cccf75b8c8dc5ad6 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wrap the corner" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/window/panel.tsx #, fuzzy msgid "node.window.panel.wrapTheCorner" msgstr "包住转角" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.casement" msgstr "平开" +# English: "Double-hung" # Source-Hash: e06fc5a432b74a5b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.doubleHung" msgstr "双悬" +# English: "Grid" # Source-Hash: 7e4a0a38f694be6b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.grid" msgstr "网格" +# English: "Horizontal bands" # Source-Hash: fbb293fe8aee84b2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Horizontal bands" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.horizontalBands" msgstr "水平条带" +# English: "Picture" # Source-Hash: 3ee270463954f502 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Picture" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.picture" msgstr "画框" +# English: "Single" # Source-Hash: 62ab2eb3c775da95 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.single" msgstr "单扇" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.sliding" msgstr "推拉门" +# English: "Tall grid" # Source-Hash: 0c88aea2bd130193 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Tall grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tallGrid" msgstr "竖长网格" +# English: "Transom" # Source-Hash: 9628d0cc155e29a3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Transom" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.transom" msgstr "气窗" +# English: "Triple-hung" # Source-Hash: 305f3aba543b1962 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Triple-hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.tripleHung" msgstr "三悬" +# English: "Wide grid" # Source-Hash: 8af77effb76bc6e2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wide grid" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.style.wideGrid" msgstr "横宽网格" +# English: "Awning" # Source-Hash: 50393830365aaf2b # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Awning" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.awning" msgstr "上悬窗" +# English: "Bay" # Source-Hash: 8447ee52258e16e4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bay" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bay" msgstr "凸窗" +# English: "Bow" # Source-Hash: e828c89bd0ba1ea4 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Bow" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.bow" msgstr "弓形窗" +# English: "Casement" # Source-Hash: 9d63109edd8b22c2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Casement" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.casement" msgstr "平开窗" +# English: "Double Hung" # Source-Hash: b0b737fc5cb1d32a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Double Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.doubleHung" msgstr "双悬窗" +# English: "Fixed" # Source-Hash: 387ce45ca2eb045f # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Fixed" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.fixed" msgstr "固定" +# English: "Hopper" # Source-Hash: 04a40f27911c3192 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Hopper" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.hopper" msgstr "下悬窗" +# English: "Louvered" # Source-Hash: 5746be19ba2a53be # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Louvered" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.louvered" msgstr "百叶窗" +# English: "Single Hung" # Source-Hash: de6ffb1cf676f270 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Single Hung" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.singleHung" msgstr "单悬窗" +# English: "Sliding" # Source-Hash: 18cbc73c5e1590aa # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Sliding" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/shared/opening-labels.ts #, fuzzy msgid "node.window.type.sliding" msgstr "推拉门" +# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." # Source-Hash: 4a4d97a4101793b7 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "A polygonal site zone (lawn, water, paving) with a TSL gradient material." +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.description" msgstr "使用 TSL 渐变材质的多边形场地区域(草坪、水面、铺装)。" +# English: "Zone" # Source-Hash: a107b4d76d6972e3 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/i18n/definition-messages.ts #, fuzzy msgid "node.zone.label" msgstr "区域" +# English: "Enclosed room" # Source-Hash: e9a6d2e71b97e757 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Enclosed room" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.enclosedRoom" msgstr "封闭房间" +# English: "Floor surface" # Source-Hash: 3660922f0a8f1d3c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Floor surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.floorSurface" msgstr "楼面面积" +# English: "Footprint only" # Source-Hash: 88c8703c4ff21ee1 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Footprint only" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.footprintOnly" msgstr "仅基底" +# English: "None" # Source-Hash: f2953d2931e36d0c # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "None" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.none" msgstr "无" +# English: "Not proven" # Source-Hash: a9756a3da570c3dd # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Not proven" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.notProven" msgstr "未验证" +# English: "Room quantities" # Source-Hash: ff97d005a536cf58 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Room quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.roomQuantities" msgstr "房间工程量" +# English: "Top view with zone edge dimensions" # Source-Hash: f520e6a06fbeb803 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Top view with zone edge dimensions" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.topViewWithZoneEdgeDimensions" msgstr "带区域边长尺寸的俯视图" +# English: "Unit" # Source-Hash: 062cc0f1afef0c52 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Unit" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.unit" msgstr "单元" +# English: "Volume" # Source-Hash: 429bd43d726de417 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Volume" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.volume" msgstr "体积" +# English: "Wall surface" # Source-Hash: a7941c60a3b6998a # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Wall surface" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.wallSurface" msgstr "墙面面积" +# English: "Zone boundary unavailable" # Source-Hash: b8b0dbdff53a83ca # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone boundary unavailable" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneBoundaryUnavailable" msgstr "区域边界不可用" +# English: "Zone quantities" # Source-Hash: 6bfb4c6f4d7b0ff2 # Review: required -# AI-Provider: cursor-agent -# AI-Model: claude-opus -# English: "Zone quantities" +# AI-Provider: claude-code +# AI-Model: claude-opus-5-5 #: packages/nodes/src/zone/quantities-panel.tsx #, fuzzy msgid "node.zone.quantitiesPanel.zoneQuantities" msgstr "区域工程量" +# English: "Orbit left" # Source-Hash: 8bf25afa3cb8baea # Review: human -# English: "Orbit left" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitLeft" msgstr "向左旋转" +# English: "Orbit right" # Source-Hash: b9cc64c1d1bb0d46 # Review: human -# English: "Orbit right" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.orbitRight" msgstr "向右旋转" +# English: "Top view" # Source-Hash: 1ab1eb3a14f4b0e0 # Review: human -# English: "Top view" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.camera.topView" msgstr "俯视图" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.off" msgstr "关闭" +# English: "On" # Source-Hash: 9cc01faad79c28da # Review: human -# English: "On" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.common.on" msgstr "开启" +# English: "Camera" # Source-Hash: 14622745dbf93f4c # Review: human -# English: "Camera" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.camera" msgstr "相机" +# English: "Colored" # Source-Hash: d3d478fa6c6d7ddc # Review: human -# English: "Colored" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colored" msgstr "彩色" +# English: "Colors" # Source-Hash: 82b74c4f5ef43336 # Review: human -# English: "Colors" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.colors" msgstr "颜色" +# English: "Off" # Source-Hash: 50accd740fa2e736 # Review: human -# English: "Off" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.off" msgstr "关闭" +# English: "No edge lines" # Source-Hash: 4ff58913a4492199 # Review: human -# English: "No edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.offDetail" msgstr "不显示边线" +# English: "Soft" # Source-Hash: d266d83ec490df4e # Review: human -# English: "Soft" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.soft" msgstr "柔和" +# English: "Faint outline of major creases" # Source-Hash: 0c583f1d4201293b # Review: human -# English: "Faint outline of major creases" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.softDetail" msgstr "淡化显示主要折线" +# English: "Strong" # Source-Hash: f82927a01d4c5e45 # Review: human -# English: "Strong" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strong" msgstr "清晰" +# English: "Crisp, opaque edge lines" # Source-Hash: 2416a48df28d6205 # Review: human -# English: "Crisp, opaque edge lines" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.strongDetail" msgstr "清晰、不透明的边线" +# English: "Edges" # Source-Hash: 027bbc8b7287c06a # Review: human -# English: "Edges" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.edges.title" msgstr "边线" +# English: "Monochrome" # Source-Hash: ecde2f7c26914be3 # Review: human -# English: "Monochrome" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.monochrome" msgstr "单色" +# English: "Orthographic" # Source-Hash: ed59dd8ce7797a66 # Review: human -# English: "Orthographic" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.orthographic" msgstr "正交" +# English: "Perspective" # Source-Hash: 316e25993ff72b88 # Review: human -# English: "Perspective" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.perspective" msgstr "透视" +# English: "Rendered" # Source-Hash: f363702a10aedaeb # Review: human -# English: "Rendered" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.rendered" msgstr "渲染效果" +# English: "Full ambient occlusion" # Source-Hash: 8bb9e54fc786ca77 # Review: human -# English: "Full ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.renderedDetail" msgstr "完整环境光遮蔽" +# English: "Solid" # Source-Hash: ef3e765e692faf4c # Review: human -# English: "Solid" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solid" msgstr "实体" +# English: "Flat and fast — no ambient occlusion" # Source-Hash: 8827df1cdeb02ed9 # Review: human -# English: "Flat and fast — no ambient occlusion" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.solidDetail" msgstr "平整快速,不启用环境光遮蔽" +# English: "Render" # Source-Hash: 5e58af99d6d4fa6a # Review: human -# English: "Render" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.render.title" msgstr "渲染" +# English: "Shadows" # Source-Hash: 4c6bcb30b40047fc # Review: human -# English: "Shadows" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.shadows" msgstr "阴影" +# English: "Display settings" # Source-Hash: d0db46f206a88e2e # Review: human -# English: "Display settings" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.display.title" msgstr "显示设置" +# English: "Align view to north" # Source-Hash: 2bdf83801d4a40af # Review: human -# English: "Align view to north" #: packages/editor/src/components/viewer/floorplan-compass-button.tsx msgid "viewer.floorplan.alignNorth" msgstr "视图对齐北向" +# English: "No floor plans are available for this scene." # Source-Hash: 286a757d8b52bb02 # Review: human -# English: "No floor plans are available for this scene." #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.empty" msgstr "此场景没有可用的平面图。" +# English: "Fit floor plan" # Source-Hash: bf63c679ab94dbcd # Review: human -# English: "Fit floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.fit" msgstr "适合窗口" +# English: "Floor" # Source-Hash: d7148fa065bc709b # Review: human -# English: "Floor" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floor" msgstr "楼层" +# English: "Floor plan" # Source-Hash: fee6203bb9046929 # Review: human -# English: "Floor plan" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.floorPlan" msgstr "平面图" +# English: "{floor} 2D view" # Source-Hash: 28b255b7bf5f1084 # Review: human -# English: "{floor} 2D view" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.viewLabel" msgstr "{floor}二维视图" +# English: "Zoom in" # Source-Hash: d17804412f8dd081 # Review: human -# English: "Zoom in" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomIn" msgstr "放大" +# English: "Zoom out" # Source-Hash: 23b012a283b2c14a # Review: human -# English: "Zoom out" #: packages/editor/src/components/viewer/floorplan-preview.tsx msgid "viewer.floorplan.zoomOut" msgstr "缩小" +# English: "Back" # Source-Hash: 7ceade8f25b58fe9 # Review: human -# English: "Back" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.back" msgstr "返回" +# English: "Levels" # Source-Hash: cc3d601d59e2e26c # Review: human -# English: "Levels" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.levels" msgstr "楼层" +# English: "Rooms" # Source-Hash: 3e7408ed44676176 # Review: human -# English: "Rooms" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.rooms" msgstr "房间" +# English: "Selection" # Source-Hash: 170b2049ef0f9131 # Review: human -# English: "Selection" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.selection" msgstr "当前选择" +# English: "Units" # Source-Hash: b404dded35241274 # Review: human -# English: "Units" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.units" msgstr "单元" +# English: "Untitled" # Source-Hash: 6547a4c0c368042a # Review: human -# English: "Untitled" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.untitled" msgstr "未命名" +# English: "Zones" # Source-Hash: 665db49a26f81425 # Review: human -# English: "Zones" #: packages/editor/src/components/viewer/viewer-scene-header.tsx msgid "viewer.header.zones" msgstr "分区" +# English: "Exploded" # Source-Hash: be12f8788f1f0a05 # Review: human -# English: "Exploded" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.exploded" msgstr "分解" +# English: "Levels: {mode}" # Source-Hash: 4bf4a6861d7d633e # Review: human -# English: "Levels: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.label" msgstr "楼层:{mode}" +# English: "Manual" # Source-Hash: 387082cd5f118bed # Review: human -# English: "Manual" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.manual" msgstr "手动" +# English: "Solo" # Source-Hash: e9f59b513f2f45ac # Review: human -# English: "Solo" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.solo" msgstr "单层" +# English: "Stacked" # Source-Hash: c203c84eee18c0f6 # Review: human -# English: "Stacked" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.levels.stacked" msgstr "叠层" +# English: "Viewer layout" # Source-Hash: 9dd21ca47cffa3b8 # Review: human -# English: "Viewer layout" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.layout" msgstr "视图布局" +# English: "Split" # Source-Hash: e4cd64b1680ac0d2 # Review: human -# English: "Split" #: packages/editor/src/components/viewer/viewer-stage-switcher.tsx msgid "viewer.stage.split" msgstr "分屏" +# English: "Blueprint" # Source-Hash: f14b89d713d2528a # Review: human -# English: "Blueprint" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.blueprint" msgstr "蓝图" +# English: "Mediterranean" # Source-Hash: 142bcb2841f423c5 # Review: human -# English: "Mediterranean" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.mediterranean" msgstr "地中海" +# English: "Night" # Source-Hash: b399942ec5d7e1ba # Review: human -# English: "Night" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.night" msgstr "夜景" +# English: "Overcast" # Source-Hash: f512d1b947b8ae5a # Review: human -# English: "Overcast" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.overcast" msgstr "阴天" +# English: "Paper" # Source-Hash: 583542b3bc837192 # Review: human -# English: "Paper" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.paper" msgstr "纸面" +# English: "Studio" # Source-Hash: 7abc2dc6e39b1747 # Review: human -# English: "Studio" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.studio" msgstr "影棚" +# English: "Sunset" # Source-Hash: efe94d3ec8d8c44e # Review: human -# English: "Sunset" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.sunset" msgstr "日落" +# English: "Scene theme" # Source-Hash: 82720864e5034398 # Review: human -# English: "Scene theme" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.theme.title" msgstr "场景主题" +# English: "Twilight" # Source-Hash: 4c10ea0aa0e2ea41 # Review: human -# English: "Twilight" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.twilight" msgstr "暮色" +# English: "Verdant" # Source-Hash: f9592fb0672740c0 # Review: human -# English: "Verdant" #: packages/editor/src/i18n/scene-theme-name.ts msgid "viewer.theme.verdant" msgstr "绿意" +# English: "Guides" # Source-Hash: 892d5dd34372028b # Review: human -# English: "Guides" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.guides" msgstr "参考图" +# English: "Scans" # Source-Hash: 3260bfa69cca50e5 # Review: human -# English: "Scans" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.scans" msgstr "扫描模型" +# English: "Visibility" # Source-Hash: 2cefbb554eadbb6e # Review: human -# English: "Visibility" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.visibility.title" msgstr "可见性" +# English: "Walkthrough: {state}" # Source-Hash: 31afd9dee47ba943 # Review: human -# English: "Walkthrough: {state}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walkthrough.label" msgstr "漫游:{state}" +# English: "Cutaway" # Source-Hash: 4da8b3678ed77b45 # Review: human -# English: "Cutaway" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.cutaway" msgstr "剖切墙" +# English: "Full height" # Source-Hash: 131bac2467f4fdf6 # Review: human -# English: "Full height" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.fullHeight" msgstr "全高墙" +# English: "Walls: {mode}" # Source-Hash: 32f3d058cf2526d2 # Review: human -# English: "Walls: {mode}" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.label" msgstr "墙体:{mode}" +# English: "Low" # Source-Hash: 803532407fb5962b # Review: human -# English: "Low" #: packages/editor/src/components/viewer/viewer-controls-bar.tsx msgid "viewer.walls.low" msgstr "矮墙" diff --git a/packages/editor/src/i18n/locales/zh-CN/messages.ts b/packages/editor/src/i18n/locales/zh-CN/messages.ts index 311c4550a6..1918fb8198 100644 --- a/packages/editor/src/i18n/locales/zh-CN/messages.ts +++ b/packages/editor/src/i18n/locales/zh-CN/messages.ts @@ -1 +1 @@ -/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"地下层\"],\"core.level.basementNumber\":[\"地下 \",[\"number\"],\" 层\"],\"core.level.floorNumber\":[[\"number\"],\" 层\"],\"core.level.groundFloor\":[\"首层\"],\"editor.buildPanel.advanced\":[\"高级\"],\"editor.buildPanel.rooms.hint\":[\"墙体 · 楼板 · 天花板\"],\"editor.buildPanel.rooms.idle\":[\"选择一种形状开始绘制。每个房间都会同时创建墙体、楼板和天花板。\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\"然后再次点击起点闭合。适用于 L 形和斜角房间。\"],\"editor.buildPanel.rooms.polygon.label\":[\"多边形\"],\"editor.buildPanel.rooms.polygon.lead\":[\"依次点击各个角点,\"],\"editor.buildPanel.rooms.polygon.title\":[\"多边形房间\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"在网格上\",[\"lead\"],\"。墙体、楼板和天花板将一并创建。\"],\"editor.buildPanel.rooms.rectangle.label\":[\"矩形\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"点击两个对角点\"],\"editor.buildPanel.rooms.rectangle.title\":[\"矩形房间\"],\"editor.buildPanel.rooms.title\":[\"房间\"],\"editor.buildPanel.rooms.walls.caption\":[[\"lead\"],\"闭合轮廓后会形成房间;保持开口可绘制隔墙。\"],\"editor.buildPanel.rooms.walls.label\":[\"墙体\"],\"editor.buildPanel.rooms.walls.lead\":[\"逐面绘制墙体。\"],\"editor.buildPanel.rooms.walls.title\":[\"墙体\"],\"viewer.camera.orbitLeft\":[\"向左旋转\"],\"viewer.camera.orbitRight\":[\"向右旋转\"],\"viewer.camera.topView\":[\"俯视图\"],\"viewer.common.off\":[\"关闭\"],\"viewer.common.on\":[\"开启\"],\"viewer.display.camera\":[\"相机\"],\"viewer.display.colored\":[\"彩色\"],\"viewer.display.colors\":[\"颜色\"],\"viewer.display.edges.off\":[\"关闭\"],\"viewer.display.edges.offDetail\":[\"不显示边线\"],\"viewer.display.edges.soft\":[\"柔和\"],\"viewer.display.edges.softDetail\":[\"淡化显示主要折线\"],\"viewer.display.edges.strong\":[\"清晰\"],\"viewer.display.edges.strongDetail\":[\"清晰、不透明的边线\"],\"viewer.display.edges.title\":[\"边线\"],\"viewer.display.monochrome\":[\"单色\"],\"viewer.display.orthographic\":[\"正交\"],\"viewer.display.perspective\":[\"透视\"],\"viewer.display.render.rendered\":[\"渲染效果\"],\"viewer.display.render.renderedDetail\":[\"完整环境光遮蔽\"],\"viewer.display.render.solid\":[\"实体\"],\"viewer.display.render.solidDetail\":[\"平整快速,不启用环境光遮蔽\"],\"viewer.display.render.title\":[\"渲染\"],\"viewer.display.shadows\":[\"阴影\"],\"viewer.display.title\":[\"显示设置\"],\"viewer.floorplan.alignNorth\":[\"视图对齐北向\"],\"viewer.floorplan.empty\":[\"此场景没有可用的平面图。\"],\"viewer.floorplan.fit\":[\"适合窗口\"],\"viewer.floorplan.floor\":[\"楼层\"],\"viewer.floorplan.floorPlan\":[\"平面图\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\"二维视图\"],\"viewer.floorplan.zoomIn\":[\"放大\"],\"viewer.floorplan.zoomOut\":[\"缩小\"],\"viewer.header.back\":[\"返回\"],\"viewer.header.levels\":[\"楼层\"],\"viewer.header.rooms\":[\"房间\"],\"viewer.header.selection\":[\"当前选择\"],\"viewer.header.units\":[\"单元\"],\"viewer.header.untitled\":[\"未命名\"],\"viewer.header.zones\":[\"分区\"],\"viewer.levels.exploded\":[\"分解\"],\"viewer.levels.label\":[\"楼层:\",[\"mode\"]],\"viewer.levels.manual\":[\"手动\"],\"viewer.levels.solo\":[\"单层\"],\"viewer.levels.stacked\":[\"叠层\"],\"viewer.stage.layout\":[\"视图布局\"],\"viewer.stage.split\":[\"分屏\"],\"viewer.theme.blueprint\":[\"蓝图\"],\"viewer.theme.mediterranean\":[\"地中海\"],\"viewer.theme.night\":[\"夜景\"],\"viewer.theme.overcast\":[\"阴天\"],\"viewer.theme.paper\":[\"纸面\"],\"viewer.theme.studio\":[\"影棚\"],\"viewer.theme.sunset\":[\"日落\"],\"viewer.theme.title\":[\"场景主题\"],\"viewer.theme.twilight\":[\"暮色\"],\"viewer.theme.verdant\":[\"绿意\"],\"viewer.visibility.guides\":[\"参考图\"],\"viewer.visibility.scans\":[\"扫描模型\"],\"viewer.visibility.title\":[\"可见性\"],\"viewer.walkthrough.label\":[\"漫游:\",[\"state\"]],\"viewer.walls.cutaway\":[\"剖切墙\"],\"viewer.walls.fullHeight\":[\"全高墙\"],\"viewer.walls.label\":[\"墙体:\",[\"mode\"]],\"viewer.walls.low\":[\"矮墙\"]}")as Messages; \ No newline at end of file +/*eslint-disable*/import type{Messages}from"@lingui/core";export const messages=JSON.parse("{\"core.level.basement\":[\"地下层\"],\"core.level.basementNumber\":[\"地下 \",[\"number\"],\" 层\"],\"core.level.floorNumber\":[[\"number\"],\" 层\"],\"core.level.groundFloor\":[\"首层\"],\"editor.buildPanel.advanced\":[\"高级\"],\"editor.buildPanel.rooms.hint\":[\"墙体 · 楼板 · 天花板\"],\"editor.buildPanel.rooms.idle\":[\"选择一种形状开始绘制。每个房间都会同时创建墙体、楼板和天花板。\"],\"editor.buildPanel.rooms.polygon.caption\":[[\"lead\"],\"然后再次点击起点闭合。适用于 L 形和斜角房间。\"],\"editor.buildPanel.rooms.polygon.label\":[\"多边形\"],\"editor.buildPanel.rooms.polygon.lead\":[\"依次点击各个角点,\"],\"editor.buildPanel.rooms.polygon.title\":[\"多边形房间\"],\"editor.buildPanel.rooms.rectangle.caption\":[\"在网格上\",[\"lead\"],\"。墙体、楼板和天花板将一并创建。\"],\"editor.buildPanel.rooms.rectangle.label\":[\"矩形\"],\"editor.buildPanel.rooms.rectangle.lead\":[\"点击两个对角点\"],\"editor.buildPanel.rooms.rectangle.title\":[\"矩形房间\"],\"editor.buildPanel.rooms.title\":[\"房间\"],\"editor.buildPanel.rooms.walls.label\":[\"墙体\"],\"editor.buildPanel.rooms.walls.title\":[\"墙体\"],\"viewer.camera.orbitLeft\":[\"向左旋转\"],\"viewer.camera.orbitRight\":[\"向右旋转\"],\"viewer.camera.topView\":[\"俯视图\"],\"viewer.common.off\":[\"关闭\"],\"viewer.common.on\":[\"开启\"],\"viewer.display.camera\":[\"相机\"],\"viewer.display.colored\":[\"彩色\"],\"viewer.display.colors\":[\"颜色\"],\"viewer.display.edges.off\":[\"关闭\"],\"viewer.display.edges.offDetail\":[\"不显示边线\"],\"viewer.display.edges.soft\":[\"柔和\"],\"viewer.display.edges.softDetail\":[\"淡化显示主要折线\"],\"viewer.display.edges.strong\":[\"清晰\"],\"viewer.display.edges.strongDetail\":[\"清晰、不透明的边线\"],\"viewer.display.edges.title\":[\"边线\"],\"viewer.display.monochrome\":[\"单色\"],\"viewer.display.orthographic\":[\"正交\"],\"viewer.display.perspective\":[\"透视\"],\"viewer.display.render.rendered\":[\"渲染效果\"],\"viewer.display.render.renderedDetail\":[\"完整环境光遮蔽\"],\"viewer.display.render.solid\":[\"实体\"],\"viewer.display.render.solidDetail\":[\"平整快速,不启用环境光遮蔽\"],\"viewer.display.render.title\":[\"渲染\"],\"viewer.display.shadows\":[\"阴影\"],\"viewer.display.title\":[\"显示设置\"],\"viewer.floorplan.alignNorth\":[\"视图对齐北向\"],\"viewer.floorplan.empty\":[\"此场景没有可用的平面图。\"],\"viewer.floorplan.fit\":[\"适合窗口\"],\"viewer.floorplan.floor\":[\"楼层\"],\"viewer.floorplan.floorPlan\":[\"平面图\"],\"viewer.floorplan.viewLabel\":[[\"floor\"],\"二维视图\"],\"viewer.floorplan.zoomIn\":[\"放大\"],\"viewer.floorplan.zoomOut\":[\"缩小\"],\"viewer.header.back\":[\"返回\"],\"viewer.header.levels\":[\"楼层\"],\"viewer.header.rooms\":[\"房间\"],\"viewer.header.selection\":[\"当前选择\"],\"viewer.header.units\":[\"单元\"],\"viewer.header.untitled\":[\"未命名\"],\"viewer.header.zones\":[\"分区\"],\"viewer.levels.exploded\":[\"分解\"],\"viewer.levels.label\":[\"楼层:\",[\"mode\"]],\"viewer.levels.manual\":[\"手动\"],\"viewer.levels.solo\":[\"单层\"],\"viewer.levels.stacked\":[\"叠层\"],\"viewer.stage.layout\":[\"视图布局\"],\"viewer.stage.split\":[\"分屏\"],\"viewer.theme.blueprint\":[\"蓝图\"],\"viewer.theme.mediterranean\":[\"地中海\"],\"viewer.theme.night\":[\"夜景\"],\"viewer.theme.overcast\":[\"阴天\"],\"viewer.theme.paper\":[\"纸面\"],\"viewer.theme.studio\":[\"影棚\"],\"viewer.theme.sunset\":[\"日落\"],\"viewer.theme.title\":[\"场景主题\"],\"viewer.theme.twilight\":[\"暮色\"],\"viewer.theme.verdant\":[\"绿意\"],\"viewer.visibility.guides\":[\"参考图\"],\"viewer.visibility.scans\":[\"扫描模型\"],\"viewer.visibility.title\":[\"可见性\"],\"viewer.walkthrough.label\":[\"漫游:\",[\"state\"]],\"viewer.walls.cutaway\":[\"剖切墙\"],\"viewer.walls.fullHeight\":[\"全高墙\"],\"viewer.walls.label\":[\"墙体:\",[\"mode\"]],\"viewer.walls.low\":[\"矮墙\"]}")as Messages; \ No newline at end of file diff --git a/packages/editor/src/lib/building-foundation.test.ts b/packages/editor/src/lib/building-foundation.test.ts index 6fbbb779a7..8952d31433 100644 --- a/packages/editor/src/lib/building-foundation.test.ts +++ b/packages/editor/src/lib/building-foundation.test.ts @@ -4,6 +4,7 @@ import { type AnyNodeId, type BuildingFoundationRecord, BuildingNode, + createI18n, createTerrainField, createZone, type DesignedFoundationReport, @@ -38,6 +39,7 @@ import { applyRoomPlan } from './room-structure-commands' const IN = 0.0254 const inches = (meters: number) => `${Math.round((meters / IN) * 100) / 100} in` +const english = createI18n() // A raised floor's least finish floor over grade: the stem's top 8 in over it, the default framing // on that (¾ in hardwood, ⅝ in subfloor, 2x12 joists, 1½ in sill: 14⅛ in). const RAISED_FLOOR_IN = 8 + 14.125 @@ -74,27 +76,27 @@ function report( const keys = (advice: ReturnType) => advice.map((item) => item.key) test('a foundation to code on flat ground has nothing to warn of', () => { - expect(keys(foundationAdvice(report({ type: 'slab' }), inches))).toEqual([]) + expect(keys(foundationAdvice(report({ type: 'slab' }), inches, english))).toEqual([]) }) test('a footing or slab edge under 12 in of grade is warned of, with the fix', () => { - const raised = foundationAdvice(report({ type: 'raised', footingDepthIn: 6 }), inches) + const raised = foundationAdvice(report({ type: 'raised', footingDepthIn: 6 }), inches, english) const depth = raised.find((item) => item.key === 'depth') expect(depth?.text).toContain('6 in under grade') expect(depth?.fix?.patch).toEqual({ footingDepthIn: 12 }) - const slab = foundationAdvice(report({ type: 'slab', edgeDepthIn: 0 }), inches) + const slab = foundationAdvice(report({ type: 'slab', edgeDepthIn: 0 }), inches, english) expect(slab.find((item) => item.key === 'depth')?.fix?.patch).toEqual({ edgeDepthIn: 12 }) // Piles are engineered: no depth asked of the grade beam here. const piles = report({ type: 'raised', support: 'piles', footingDepthIn: 6 }) - expect(keys(foundationAdvice(piles, inches))).not.toContain('depth') + expect(keys(foundationAdvice(piles, inches, english))).not.toContain('depth') }) test('a footing narrower than Table R403.1(1) asks for the storeys is warned of', () => { - const two = foundationAdvice(report({ type: 'raised', footingWidthIn: 12 }, 2), inches) + const two = foundationAdvice(report({ type: 'raised', footingWidthIn: 12 }, 2), inches, english) const width = two.find((item) => item.key === 'footing-width') expect(width?.text).toContain('2 storeys ask 15 in') expect(width?.fix?.patch).toEqual({ footingWidthIn: 15 }) - expect(keys(foundationAdvice(report({ type: 'raised' }, 2), inches))).not.toContain( + expect(keys(foundationAdvice(report({ type: 'raised' }, 2), inches, english))).not.toContain( 'footing-width', ) }) @@ -103,18 +105,20 @@ test('the step note counts one side and names the tallest step', () => { const even = foundationAdvice( report({ type: 'raised' }, 1, { steps: 2, tallestStep: 8 * IN, lowestGradeY: -0.5 }), inches, + english, ).find((item) => item.key === 'steps') expect(even?.text).toContain('steps 2 times along its steepest side, 8 in a step') const merged = foundationAdvice( report({ type: 'raised' }, 1, { steps: 1, tallestStep: 16 * IN, lowestGradeY: -0.5 }), inches, + english, ).find((item) => item.key === 'steps') expect(merged?.text).toContain('up to 16 in a step') }) test('a floor the ground has risen over reads as under it', () => { const sunk = report({ type: 'slab' }, 1, { highestGradeY: 1, lowestGradeY: 1 }) - const seat = foundationAdvice(sunk, inches).find((item) => item.key === 'seat') + const seat = foundationAdvice(sunk, inches, english).find((item) => item.key === 'seat') expect(seat?.text).toContain('stands 31.37 in under the high side') }) @@ -161,7 +165,7 @@ test('a house on a basement with walls says so; one standing at grade does not', test('the ground round a slab is filled and cut to hold it 8 in under the top; deep fill asks for a stem wall', () => { const said = (seated: DesignedFoundationReport, key: string) => - foundationAdvice(seated, inches).find((item) => item.key === key)?.text + foundationAdvice(seated, inches, english).find((item) => item.key === key)?.text expect(said(report({ type: 'slab', ffAboveGradeIn: 30 }), 'grading')).toBe( 'The ground is held 8 in under the slab’s top on every side: filled 22 in.', ) @@ -185,7 +189,7 @@ test('the ground round a slab is filled and cut to hold it 8 in under the top; d test('a raised floor notes its crawl space dug out, the ground cut round it, and its cripple walls', () => { const said = (seated: DesignedFoundationReport, key: string) => - foundationAdvice(seated, inches).find((item) => item.key === key)?.text + foundationAdvice(seated, inches, english).find((item) => item.key === key)?.text // The stem's top 8 in over flat ground: the joists 9.5 in over it, the crawl dug 8.5 in. const raised = report({ type: 'raised' }) expect(said(raised, 'crawl')).toBe( @@ -205,7 +209,9 @@ test('a raised floor notes its crawl space dug out, the ground cut round it, and test('over a basement the panel speaks of its walls’ seat, its dig and a downhill wall out of the ground', () => { const said = (seated: DesignedFoundationReport) => - Object.fromEntries(foundationAdvice(seated, inches).map((item) => [item.key, item.text])) + Object.fromEntries( + foundationAdvice(seated, inches, english).map((item) => [item.key, item.text]), + ) // Flat ground at 0, the slab's underside 2.8 m under it. const basement = (wallTopY: number, extra: Partial = {}) => report({}, 1, { @@ -228,7 +234,7 @@ test('over a basement the panel speaks of its walls’ seat, its dig and a downh 'Cut 110.24 in into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.', }) // Added under a house at grade: the framing over it in the ground until the house is seated. - const sunk = foundationAdvice(basement(-10 * IN), inches) + const sunk = foundationAdvice(basement(-10 * IN), inches, english) expect(sunk.map((item) => item.key)).toEqual(['seat', 'excavation']) expect(sunk[0]!.text).toBe( 'The basement wall’s top stands 10 in under the high side of the ground; the foundation is set for 8 in over it.', @@ -240,23 +246,25 @@ test('over a basement the panel speaks of its walls’ seat, its dig and a downh const low = basement(4 * IN, { foundation: resolveBuildingFoundation({ enabled: true, basementTopAboveGradeIn: 4 }, 1), }) - expect(foundationAdvice(low, inches).map((item) => item.key)).toEqual([ + expect(foundationAdvice(low, inches, english).map((item) => item.key)).toEqual([ 'basement-top', 'excavation', ]) }) test('the ground line says which rule its click seats the house by', () => { - expect(groundSeatAction('basement', 8)).toBe( + expect(groundSeatAction('basement', 8, english)).toBe( 'Seat the basement wall’s top 8 in over the high side of the ground', ) - expect(groundSeatAction('raised', 22.125)).toBe( + expect(groundSeatAction('raised', 22.125, english)).toBe( 'Seat the raised floor’s top 22.13 in over the high side of the ground', ) - expect(groundSeatAction('slab', 8)).toBe( + expect(groundSeatAction('slab', 8, english)).toBe( 'Seat the slab’s top 8 in over the high side of the ground', ) - expect(groundSeatAction('construction', 7.99)).toBe('Seat the ground floor 7.99 in over grade') + expect(groundSeatAction('construction', 7.99, english)).toBe( + 'Seat the ground floor 7.99 in over grade', + ) }) test('a floor top under the ground prints one minus, not "+-"', () => { @@ -415,7 +423,7 @@ describe('switching the foundation on from the floor panel', () => { applyBuildingFoundation('building_f', { enabled: true }) expectGroundFloor(true) expect(over()).toBeCloseTo(8 * IN, 6) - expect(keys(foundationAdvice(seat(), inches))).not.toContain('seat') + expect(keys(foundationAdvice(seat(), inches, english))).not.toContain('seat') applyBuildingFoundation('building_f', { type: 'raised' }) expect(over()).toBeCloseTo(RAISED_FLOOR_IN * IN, 6) }) diff --git a/packages/editor/src/lib/building-foundation.ts b/packages/editor/src/lib/building-foundation.ts index 2721e7cb2c..05a50b194d 100644 --- a/packages/editor/src/lib/building-foundation.ts +++ b/packages/editor/src/lib/building-foundation.ts @@ -11,6 +11,7 @@ import { getLevelElevations, groundLevelOf, levelGradeStanding, + type PascalI18n, type SlabNode, upperFloorHeightControl, useScene, @@ -87,19 +88,39 @@ export function standsOnBasement(nodes: Record, buildingId: str * seats it (`groundSeat`): the construction's floor height over grade, or the designed * foundation's own over the high side of the ground. `askedIn` in inches. */ -export function groundSeatAction(rule: GroundSeatRule, askedIn: number): string { - const asked = `${Math.round(askedIn * 100) / 100} in` +export function groundSeatAction(rule: GroundSeatRule, askedIn: number, i18n: PascalI18n): string { + const values = { asked: `${Math.round(askedIn * 100) / 100} in` } switch (rule) { case 'basement': - return `Seat the basement wall’s top ${asked} over the high side of the ground` + return i18n._({ + id: 'editor.lib.buildingFoundation.seatBasementWallTop', + message: 'Seat the basement wall’s top {asked} over the high side of the ground', + values, + }) case 'raised': - return `Seat the raised floor’s top ${asked} over the high side of the ground` + return i18n._({ + id: 'editor.lib.buildingFoundation.seatRaisedFloorTop', + message: 'Seat the raised floor’s top {asked} over the high side of the ground', + values, + }) case 'piles': - return `Seat the floor’s top ${asked} over the high side of the ground` + return i18n._({ + id: 'editor.lib.buildingFoundation.seatFloorTop', + message: 'Seat the floor’s top {asked} over the high side of the ground', + values, + }) case 'slab': - return `Seat the slab’s top ${asked} over the high side of the ground` + return i18n._({ + id: 'editor.lib.buildingFoundation.seatSlabTop', + message: 'Seat the slab’s top {asked} over the high side of the ground', + values, + }) default: - return `Seat the ground floor ${asked} over grade` + return i18n._({ + id: 'editor.lib.buildingFoundation.seatGroundFloor', + message: 'Seat the ground floor {asked} over grade', + values, + }) } } @@ -121,19 +142,42 @@ export type FoundationAdvice = { export function foundationAdvice( report: DesignedFoundationReport, length: (meters: number) => string, + i18n: PascalI18n, ): FoundationAdvice[] { - if (report.basement) return basementAdvice(report, report.basement, length) + if (report.basement) return basementAdvice(report, report.basement, length, i18n) const { foundation } = report const advice: FoundationAdvice[] = [] const floorOver = report.floorTopY - report.highestGradeY const asked = foundation.ffAboveGradeIn * IN - if (Math.abs(floorOver - asked) > 0.5 * IN) + if (Math.abs(floorOver - asked) > 0.5 * IN) { + const values = { distance: length(Math.abs(floorOver)), asked: length(asked) } advice.push({ key: 'seat', tone: 'warning', - text: `The floor stands ${length(Math.abs(floorOver))} ${floorOver < 0 ? 'under' : 'over'} the high side of the ground; the foundation is set for ${length(asked)} over it.`, - fix: { label: `Seat it at ${length(asked)}`, patch: {} }, + text: + floorOver < 0 + ? i18n._({ + id: 'editor.lib.buildingFoundation.floorUnderTheGround', + message: + 'The floor stands {distance} under the high side of the ground; the foundation is set for {asked} over it.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.floorOverTheGround', + message: + 'The floor stands {distance} over the high side of the ground; the foundation is set for {asked} over it.', + values, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.seatItAt', + message: 'Seat it at {asked}', + values, + }), + patch: {}, + }, }) + } const least = foundation.topAboveGradeIn * IN // The ground is cut down round the stem wall's top (a slab's, filled and cut, below). const cutOver = report.stemTopY === null ? 0 : report.highestGradeY - (report.stemTopY - least) @@ -141,29 +185,74 @@ export function foundationAdvice( advice.push({ key: 'top', tone: 'note', - text: `The ground is cut ${length(cutOver)} on the high side to hold the stem wall’s top ${length(least)} over it (IRC R317.1). Raising the floor cuts less.`, - fix: { label: 'Raise the floor', patch: { ffAboveGradeIn: foundation.ffMinIn } }, + text: i18n._({ + id: 'editor.lib.buildingFoundation.groundCutRoundTheStem', + message: + 'The ground is cut {cut} on the high side to hold the stem wall’s top {least} over it (IRC R317.1). Raising the floor cuts less.', + values: { cut: length(cutOver), least: length(least) }, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.raiseTheFloor', + message: 'Raise the floor', + }), + patch: { ffAboveGradeIn: foundation.ffMinIn }, + }, }) const footingWidthIn = buildingFoundationDefaults(foundation.storeys).footingWidthIn const piles = foundation.support === 'piles' const raised = foundation.type === 'raised' const depth = raised ? foundation.footingDepthIn : foundation.edgeDepthIn - if (!piles && depth < 12) + if (!piles && depth < 12) { + const values = { depth: length(depth * IN), least: length(12 * IN) } advice.push({ key: 'depth', tone: 'warning', - text: `The ${raised ? 'footing' : 'slab edge'}’s bottom is ${length(depth * IN)} under grade: keep it ${length(12 * IN)} at least, or the frost line (IRC R403.1.4).`, + text: raised + ? i18n._({ + id: 'editor.lib.buildingFoundation.footingTooShallow', + message: + 'The footing’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4).', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.slabEdgeTooShallow', + message: + 'The slab edge’s bottom is {depth} under grade: keep it {least} at least, or the frost line (IRC R403.1.4).', + values, + }), fix: { - label: `Set it ${length(12 * IN)} deep`, + label: i18n._({ + id: 'editor.lib.buildingFoundation.setItDeep', + message: 'Set it {depth} deep', + values: { depth: length(12 * IN) }, + }), patch: raised ? { footingDepthIn: 12 } : { edgeDepthIn: 12 }, }, }) + } if (raised && !piles && foundation.footingWidthIn < footingWidthIn) advice.push({ key: 'footing-width', tone: 'warning', - text: `The footing is ${length(foundation.footingWidthIn * IN)} wide: ${foundation.storeys === 1 ? 'one storey asks' : `${foundation.storeys} storeys ask`} ${length(footingWidthIn * IN)} on 1500 psf soil (IRC Table R403.1(1)).`, - fix: { label: `Widen it to ${length(footingWidthIn * IN)}`, patch: { footingWidthIn } }, + text: i18n._({ + id: 'editor.lib.buildingFoundation.footingTooNarrow', + message: + 'The footing is {width} wide: {storeys, plural, one {one storey asks} other {# storeys ask}} {asked} on 1500 psf soil (IRC Table R403.1(1)).', + values: { + width: length(foundation.footingWidthIn * IN), + storeys: foundation.storeys, + asked: length(footingWidthIn * IN), + }, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.widenItTo', + message: 'Widen it to {width}', + values: { width: length(footingWidthIn * IN) }, + }), + patch: { footingWidthIn }, + }, }) if (report.stemTopY !== null && report.crawlY !== null) { // The crawl space is dug out to its clearance under the joists, where the ground stands higher. @@ -173,25 +262,69 @@ export function foundationAdvice( advice.push({ key: 'crawl', tone: 'note', - text: `Crawl space: dug out up to ${length(dug)} for ${length(clearance)} under the joists (IRC R317.1).`, + text: i18n._({ + id: 'editor.lib.buildingFoundation.crawlSpaceDugOut', + message: + 'Crawl space: dug out up to {dug} for {clearance} under the joists (IRC R317.1).', + values: { dug: length(dug), clearance: length(clearance) }, + }), }) } if (report.cripples > 0) advice.push({ key: 'cripple', tone: 'note', - text: `Past ${length(foundation.maxStemIn * IN)} of stem out of the ground the stem drops to ${length(least)} over grade: ${report.cripples} ${report.cripples === 1 ? 'cripple wall' : 'cripple walls'} over it, up to ${length(report.tallestCripple)} tall.`, + text: i18n._({ + id: 'editor.lib.buildingFoundation.crippleWalls', + message: + 'Past {stem} of stem out of the ground the stem drops to {least} over grade: {cripples, plural, one {# cripple wall} other {# cripple walls}} over it, up to {tallest} tall.', + values: { + stem: length(foundation.maxStemIn * IN), + least: length(least), + cripples: report.cripples, + tallest: length(report.tallestCripple), + }, + }), }) if (report.steps > 0) { const step = foundation.stepHeightIn * IN - const riser = - report.tallestStep > step + 0.25 * IN - ? `up to ${length(report.tallestStep)} a step` - : `${length(step)} a step` + const upTo = report.tallestStep > step + 0.25 * IN + const values = { + fall: length(report.highestGradeY - report.lowestGradeY), + steps: report.steps, + riser: length(upTo ? report.tallestStep : step), + } advice.push({ key: 'steps', tone: 'note', - text: `The ground falls ${length(report.highestGradeY - report.lowestGradeY)} across the house: the ${report.stemTopY === null ? 'slab edge' : 'footing'} steps ${report.steps} ${report.steps === 1 ? 'time' : 'times'} along its steepest side, ${riser}.`, + text: + report.stemTopY === null + ? upTo + ? i18n._({ + id: 'editor.lib.buildingFoundation.slabEdgeStepsUpTo', + message: + 'The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.slabEdgeSteps', + message: + 'The ground falls {fall} across the house: the slab edge steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step.', + values, + }) + : upTo + ? i18n._({ + id: 'editor.lib.buildingFoundation.footingStepsUpTo', + message: + 'The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, up to {riser} a step.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.footingSteps', + message: + 'The ground falls {fall} across the house: the footing steps {steps, plural, one {# time} other {# times}} along its steepest side, {riser} a step.', + values, + }), }) } if (report.stemTopY === null) { @@ -200,28 +333,100 @@ export function foundationAdvice( const fill = pad - report.lowestGradeY const cut = report.highestGradeY - pad const sloped = report.highestGradeY - report.lowestGradeY > 0.5 * IN - const works = [ - fill > 0.5 * IN && - (sloped ? `filled up to ${length(fill)} on the low side` : `filled ${length(fill)}`), - cut > 0.5 * IN && (sloped ? `cut ${length(cut)} on the high side` : `cut ${length(cut)}`), - ].filter(Boolean) - if (works.length > 0) + const grading = slabGrading(fill > 0.5 * IN, cut > 0.5 * IN, sloped, i18n, { + least: length(least), + fill: length(fill), + cut: length(cut), + }) + if (grading) advice.push({ key: 'grading', tone: 'note', - text: `The ground is held ${length(least)} under the slab’s top on every side: ${works.join(', ')}.`, + text: grading, }) if (fill > 24 * IN) advice.push({ key: 'exposed', tone: 'warning', - text: `That is ${length(fill)} of fill${sloped ? ' on the low side' : ''}. A raised floor on a stem wall suits ${sloped ? 'this slope' : 'a floor this high'}.`, - fix: { label: 'Raise it on a stem wall', patch: { type: 'raised' } }, + text: sloped + ? i18n._({ + id: 'editor.lib.buildingFoundation.deepFillOnASlope', + message: + 'That is {fill} of fill on the low side. A raised floor on a stem wall suits this slope.', + values: { fill: length(fill) }, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.deepFill', + message: + 'That is {fill} of fill. A raised floor on a stem wall suits a floor this high.', + values: { fill: length(fill) }, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.raiseItOnAStemWall', + message: 'Raise it on a stem wall', + }), + patch: { type: 'raised' }, + }, }) } return advice } +/** + * How the ground round a slab is graded to hold it `least` under its top: filled on the low side, + * cut on the high side, or both (on flat ground, by how much only). `null` when it is neither. + */ +function slabGrading( + filled: boolean, + cut: boolean, + sloped: boolean, + i18n: PascalI18n, + values: { least: string; fill: string; cut: string }, +): string | null { + if (filled && cut) + return sloped + ? i18n._({ + id: 'editor.lib.buildingFoundation.gradingFilledAndCutOnASlope', + message: + 'The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side, cut {cut} on the high side.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.gradingFilledAndCut', + message: + 'The ground is held {least} under the slab’s top on every side: filled {fill}, cut {cut}.', + values, + }) + if (filled) + return sloped + ? i18n._({ + id: 'editor.lib.buildingFoundation.gradingFilledOnASlope', + message: + 'The ground is held {least} under the slab’s top on every side: filled up to {fill} on the low side.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.gradingFilled', + message: 'The ground is held {least} under the slab’s top on every side: filled {fill}.', + values, + }) + if (cut) + return sloped + ? i18n._({ + id: 'editor.lib.buildingFoundation.gradingCutOnASlope', + message: + 'The ground is held {least} under the slab’s top on every side: cut {cut} on the high side.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.gradingCut', + message: 'The ground is held {least} under the slab’s top on every side: cut {cut}.', + values, + }) + return null +} + /** * What the panel says about a house over a basement: its walls' top off the height the record * seats it at (the house not moved since the basement went in, or the ground since), a top under @@ -232,29 +437,69 @@ function basementAdvice( report: DesignedFoundationReport, basement: BasementReport, length: (meters: number) => string, + i18n: PascalI18n, ): FoundationAdvice[] { const { foundation } = report const advice: FoundationAdvice[] = [] const over = basement.wallTopY - report.highestGradeY const asked = foundation.basementTopAboveGradeIn * IN - if (Math.abs(over - asked) > 0.5 * IN) + if (Math.abs(over - asked) > 0.5 * IN) { + const values = { distance: length(Math.abs(over)), asked: length(asked) } advice.push({ key: 'seat', tone: 'warning', - text: `The basement wall’s top stands ${length(Math.abs(over))} ${over < 0 ? 'under' : 'over'} the high side of the ground; the foundation is set for ${length(asked)} over it.`, - fix: { label: `Seat it at ${length(asked)}`, patch: {} }, + text: + over < 0 + ? i18n._({ + id: 'editor.lib.buildingFoundation.basementTopUnderTheGround', + message: + 'The basement wall’s top stands {distance} under the high side of the ground; the foundation is set for {asked} over it.', + values, + }) + : i18n._({ + id: 'editor.lib.buildingFoundation.basementTopOverTheGround', + message: + 'The basement wall’s top stands {distance} over the high side of the ground; the foundation is set for {asked} over it.', + values, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.seatItAt', + message: 'Seat it at {asked}', + values, + }), + patch: {}, + }, }) + } if (foundation.basementTopAboveGradeIn < 8) advice.push({ key: 'basement-top', tone: 'warning', - text: `The sill and the floor framing sit ${length(asked)} over the ground: keep them ${length(8 * IN)} off it at least (IRC R317.1).`, - fix: { label: `Set it ${length(8 * IN)}`, patch: { basementTopAboveGradeIn: 8 } }, + text: i18n._({ + id: 'editor.lib.buildingFoundation.sillTooNearTheGround', + message: + 'The sill and the floor framing sit {height} over the ground: keep them {least} off it at least (IRC R317.1).', + values: { height: length(asked), least: length(8 * IN) }, + }), + fix: { + label: i18n._({ + id: 'editor.lib.buildingFoundation.setIt', + message: 'Set it {height}', + values: { height: length(8 * IN) }, + }), + patch: { basementTopAboveGradeIn: 8 }, + }, }) advice.push({ key: 'excavation', tone: 'note', - text: `Cut ${length(report.highestGradeY - basement.excavationY)} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.`, + text: i18n._({ + id: 'editor.lib.buildingFoundation.basementExcavation', + message: + 'Cut {depth} into the ground on the high side, dug out inside the walls’ outside faces; the ground round the house is left as it lies.', + values: { depth: length(report.highestGradeY - basement.excavationY) }, + }), }) const fall = report.highestGradeY - report.lowestGradeY const exposed = basement.wallTopY - report.lowestGradeY @@ -262,7 +507,12 @@ function basementAdvice( advice.push({ key: 'daylight', tone: 'note', - text: `The ground falls ${length(fall)} across the house: the downhill walls stand up to ${length(exposed)} out of it.`, + text: i18n._({ + id: 'editor.lib.buildingFoundation.daylightWalls', + message: + 'The ground falls {fall} across the house: the downhill walls stand up to {exposed} out of it.', + values: { fall: length(fall), exposed: length(exposed) }, + }), }) return advice } diff --git a/packages/nodes/src/beam/panel.tsx b/packages/nodes/src/beam/panel.tsx index 8907fc00b7..7b4dfc758e 100644 --- a/packages/nodes/src/beam/panel.tsx +++ b/packages/nodes/src/beam/panel.tsx @@ -13,6 +13,7 @@ import { beamTopElevation, beamTopReferencePatch, DEFAULT_FRAMING_POST_SIZE, + usePascalI18n, useScene, } from '@pascal-app/core' import { @@ -85,6 +86,7 @@ export function beamPliesPatch(beam: Pick, plies: n * and posts under it at each end and every 8 ft. */ export default function BeamPanel() { + const i18n = usePascalI18n() const selectedId = useViewer((state) => state.selection.selectedIds[0]) const setSelection = useViewer((state) => state.setSelection) const unit = useViewer((state) => state.unit) @@ -104,12 +106,18 @@ export default function BeamPanel() { setSelection({ selectedIds: [] })} - title={beam.name ?? 'Beam'} + title={beam.name ?? i18n._({ id: 'node.beam.panel.beam', message: 'Beam' })} width={300} > - + { if (id !== CUSTOM) update(beamSizePresetPatch(beam, id)) }} @@ -117,41 +125,51 @@ export default function BeamPanel() { value={size} /> update({ material: material as BeamMaterial })} options={BEAM_MATERIAL_OPTIONS} value={beam.material} /> update({ width, size: undefined })} value={beam.width} /> update({ depth, size: undefined })} value={beam.depth} /> update(beamPliesPatch(beam, Number(plies)))} options={['1', '2', '3', '4'].map((plies) => ({ - label: plies === '1' ? '1 ply' : `${plies} plies`, + label: + plies === '1' + ? i18n._({ id: 'node.beam.panel.n1Ply', message: '1 ply' }) + : i18n._({ + id: 'node.beam.panel.pliesPlies', + message: '{plies} plies', + values: { plies }, + }), value: plies, }))} value={String(beam.plies)} /> - + update({ placement })} options={[ - { label: 'Dropped', value: 'dropped' }, - { label: 'Flush', value: 'flush' }, + { + label: i18n._({ id: 'node.beam.panel.dropped', message: 'Dropped' }), + value: 'dropped', + }, + { label: i18n._({ id: 'node.beam.panel.flush', message: 'Flush' }), value: 'flush' }, ]} value={beam.placement} /> { if (reference !== CUSTOM) update(beamTopReferencePatch(nodes, beam, reference as BeamTopReference)) @@ -160,7 +178,7 @@ export default function BeamPanel() { value={top} /> update({ topElevation: value })} precision={3} step={0.01} @@ -168,8 +186,16 @@ export default function BeamPanel() { value={topY} />

- {length(beamPlan(beam).length)} long; its underside {length(topY - beam.depth)} over the - floor, the ceiling plane {length(plane)}. + {i18n._({ + id: 'node.beam.panel.lengthLongItsUnderside', + message: + '{length} long; its underside {underside} over the floor, the ceiling plane {plane}.', + values: { + length: length(beamPlan(beam).length), + underside: length(topY - beam.depth), + plane: length(plane), + }, + })}

{beam.topElevation !== undefined && ( )}
- + } - label="Add posts" + label={i18n._({ id: 'node.beam.panel.addPosts', message: 'Add posts' })} onClick={() => { const posts = beam.parentId ? framingPostsUnderBeam({ @@ -210,7 +239,7 @@ export default function BeamPanel() { /> } - label="Move" + label={i18n._({ id: 'node.beam.panel.move', message: 'Move' })} onClick={() => { triggerSFX('sfx:item-pick') useEditor.getState().setMovingNode(beam) @@ -218,7 +247,7 @@ export default function BeamPanel() { /> } - label="Delete" + label={i18n._({ id: 'node.beam.panel.delete', message: 'Delete' })} onClick={() => { useScene.getState().deleteNode(beam.id) setSelection({ selectedIds: [] }) diff --git a/packages/nodes/src/ceiling/build-up-section.tsx b/packages/nodes/src/ceiling/build-up-section.tsx index 179198c140..67bb89d810 100644 --- a/packages/nodes/src/ceiling/build-up-section.tsx +++ b/packages/nodes/src/ceiling/build-up-section.tsx @@ -6,6 +6,8 @@ import { type CeilingNode, ceilingBuildUpThickness, LUMBER_DEPTH, + type MessageDescriptor, + usePascalI18n, } from '@pascal-app/core' import { PanelSection, SelectControl } from '@pascal-app/editor' import { @@ -26,10 +28,25 @@ import { const INCH = 0.0254 -const FRAMING_OPTIONS: { label: string; value: CeilingFraming['system'] | typeof NONE }[] = [ - { label: 'None', value: NONE }, - { label: 'Ceiling joists', value: 'joists' }, - { label: 'Truss bottom chords', value: 'truss-chords' }, +const FRAMING_OPTIONS: { + label: MessageDescriptor + value: CeilingFraming['system'] | typeof NONE +}[] = [ + { label: /* i18n */ { id: 'node.ceiling.buildUpSection.none', message: 'None' }, value: NONE }, + { + label: /* i18n */ { + id: 'node.ceiling.buildUpSection.ceilingJoists', + message: 'Ceiling joists', + }, + value: 'joists', + }, + { + label: /* i18n */ { + id: 'node.ceiling.buildUpSection.trussBottomChords', + message: 'Truss bottom chords', + }, + value: 'truss-chords', + }, ] const NEW_FRAMING: Record = { @@ -46,6 +63,7 @@ export function CeilingBuildUpSection({ onUpdate: (patch: Partial) => void unit: 'metric' | 'imperial' }) { + const i18n = usePascalI18n() const buildUp = node.buildUp const edit = (change: Partial) => { if (buildUp) onUpdate({ buildUp: editedBuildUp(buildUp, change) }) @@ -53,9 +71,9 @@ export function CeilingBuildUpSection({ const framing = buildUp?.framing return ( - + { const patch = ceilingBuildUpPresetPatch(id) if (patch) onUpdate(patch) @@ -66,13 +84,13 @@ export function CeilingBuildUpSection({ {buildUp && ( <> edit({ finish })} unit={unit} /> edit({ framing: @@ -83,20 +101,27 @@ export function CeilingBuildUpSection({ : NEW_FRAMING[system], }) } - options={FRAMING_OPTIONS} + options={FRAMING_OPTIONS.map((option) => ({ ...option, label: i18n._(option.label) }))} value={framing?.system ?? NONE} /> {framing && ( edit({ framing: { ...framing, ...change } })} - sizeLabel={framing.system === 'joists' ? 'Joists' : 'Bottom chord'} + sizeLabel={ + framing.system === 'joists' + ? i18n._({ id: 'node.ceiling.buildUpSection.joists', message: 'Joists' }) + : i18n._({ + id: 'node.ceiling.buildUpSection.bottomChord', + message: 'Bottom chord', + }) + } unit={unit} /> )} edit({ decking })} optional @@ -109,11 +134,14 @@ export function CeilingBuildUpSection({ unit={unit} />
- The height is the finished underside; the layers rise from it. + {i18n._({ + id: 'node.ceiling.buildUpSection.theHeightIsTheFinishedUnderside', + message: 'The height is the finished underside; the layers rise from it.', + })}
)} diff --git a/packages/nodes/src/column/post-section.tsx b/packages/nodes/src/column/post-section.tsx index f8bf0f630d..ec34f3c0ab 100644 --- a/packages/nodes/src/column/post-section.tsx +++ b/packages/nodes/src/column/post-section.tsx @@ -13,6 +13,7 @@ import { framingPostHeightLimit, framingPostSize, framingPostTopAt, + usePascalI18n, useScene, } from '@pascal-app/core' import { @@ -69,6 +70,7 @@ export function FramingPostSection({ node: ColumnNode onUpdate: (updates: Partial) => void }) { + const i18n = usePascalI18n() const unit = useViewer((state) => state.unit) const metricNotation = useViewer((state) => state.metricNotation) const nodes = useScene((state) => state.nodes) @@ -83,7 +85,7 @@ export function FramingPostSection({ const fitBeam = fit?.beamId ? nodes[fit.beamId as AnyNodeId] : undefined return ( - + { if (next !== CUSTOM) onUpdate(framingPostSizePatch(node, next as FramingPostSize)) @@ -93,7 +95,14 @@ export function FramingPostSection({ label: value, value, })), - ...(size === CUSTOM ? [{ label: 'Custom', value: CUSTOM }] : []), + ...(size === CUSTOM + ? [ + { + label: i18n._({ id: 'node.column.postSection.custom', message: 'Custom' }), + value: CUSTOM, + }, + ] + : []), ]} value={size} /> @@ -115,13 +124,19 @@ export function FramingPostSection({ }) } options={[ - { label: 'Treated', value: 'treated' }, - { label: 'Untreated', value: 'untreated' }, + { + label: i18n._({ id: 'node.column.postSection.treated', message: 'Treated' }), + value: 'treated', + }, + { + label: i18n._({ id: 'node.column.postSection.untreated', message: 'Untreated' }), + value: 'untreated', + }, ]} value={treated ? 'treated' : 'untreated'} /> onUpdate({ height })} precision={3} @@ -137,8 +152,16 @@ export function FramingPostSection({ type="button" > {fitBeam - ? `Fit to the beam over it (${length(fit.height)})` - : `Fit to the ceiling plane (${length(fit.height)})`} + ? i18n._({ + id: 'node.column.postSection.fitToTheBeamOverIt', + message: 'Fit to the beam over it ({height})', + values: { height: length(fit.height) }, + }) + : i18n._({ + id: 'node.column.postSection.fitToTheCeilingPlane', + message: 'Fit to the ceiling plane ({height})', + values: { height: length(fit.height) }, + })} )} {limit && ( @@ -146,17 +169,24 @@ export function FramingPostSection({ className="rounded-lg bg-accent/30 px-2.5 py-2 text-muted-foreground text-xs" role="status" > - A {limit.size} deck post stands no taller than {length(limit.limit)} (IRC Table R507.4, by - species and the area it carries): check it, or use the next size up. + {i18n._({ + id: 'node.column.postSection.aSizeDeckPostStandsNo', + message: + 'A {size} deck post stands no taller than {limit} (IRC Table R507.4, by species and the area it carries): check it, or use the next size up.', + values: { size: limit.size, limit: length(limit.limit) }, + })}

)}
) diff --git a/packages/nodes/src/door/actions.tsx b/packages/nodes/src/door/actions.tsx index 1e2cb13dad..5f3519cd4f 100644 --- a/packages/nodes/src/door/actions.tsx +++ b/packages/nodes/src/door/actions.tsx @@ -1,5 +1,5 @@ 'use client' -import { type AnyNodeId, type DoorNode, useScene } from '@pascal-app/core' +import { type AnyNodeId, type DoorNode, usePascalI18n, useScene } from '@pascal-app/core' import { ActionMenuButton, triggerSFX } from '@pascal-app/editor' import { useViewer } from '@pascal-app/viewer' import { ArrowLeftRight, ArrowUpDown } from 'lucide-react' @@ -18,6 +18,7 @@ function applyDoorPatch(door: DoorNode, patch: Partial) { * side without opening the panel's Swing section. One undo step each. */ export default function DoorActions() { + const i18n = usePascalI18n() const selected = useViewer((s) => s.selection.selectedIds) const door = useScene((s) => { const node = selected.length === 1 ? s.nodes[selected[0] as AnyNodeId] : undefined @@ -29,7 +30,7 @@ export default function DoorActions() { <> {canSwapDoorHinge(door) ? ( { event.stopPropagation() applyDoorPatch(door, swapDoorHingePatch(door)) @@ -39,7 +40,7 @@ export default function DoorActions() { ) : null} { event.stopPropagation() applyDoorPatch(door, swapDoorSwingPatch(door)) diff --git a/packages/nodes/src/footing/panel.tsx b/packages/nodes/src/footing/panel.tsx index 79900f7c5d..65e9de1a1c 100644 --- a/packages/nodes/src/footing/panel.tsx +++ b/packages/nodes/src/footing/panel.tsx @@ -9,6 +9,7 @@ import { type FootingShape, footingDesign, footingPlan, + usePascalI18n, useScene, } from '@pascal-app/core' import { @@ -124,6 +125,7 @@ export function footingDepthPatch( } export default function FootingPanel() { + const i18n = usePascalI18n() const selectedId = useViewer((state) => state.selection.selectedIds[0]) const setSelection = useViewer((state) => state.setSelection) const unit = useViewer((state) => state.unit) @@ -150,36 +152,43 @@ export default function FootingPanel() { setSelection({ selectedIds: [] })} - title={footing.name ?? 'Footing'} + title={footing.name ?? i18n._({ id: 'node.footing.panel.footing', message: 'Footing' })} width={300} > - + update(reshaped(footing, shape))} options={[ - { label: 'Strip', value: 'strip' }, - { label: 'Pad', value: 'pad' }, + { label: i18n._({ id: 'node.footing.panel.strip', message: 'Strip' }), value: 'strip' }, + { label: i18n._({ id: 'node.footing.panel.pad', message: 'Pad' }), value: 'pad' }, ]} value={footing.shape} /> - update({ width })} value={footing.width} /> + update({ width })} + value={footing.width} + /> {footing.shape === 'pad' && ( update({ length })} value={footing.length ?? footing.width} /> )} update({ thickness })} value={footing.thickness} /> update(footingDepthPatch(depth, reach, underSlab))} @@ -187,12 +196,21 @@ export default function FootingPanel() { /> {reach > 0.01 && (

- At least {length(reach)}:{' '} + {i18n._({ id: 'node.footing.panel.atLeast', message: 'At least' })} {length(reach)}:{' '} {design?.foundation.support === 'piles' - ? 'the grade beam reaches this far.' + ? i18n._({ + id: 'node.footing.panel.theGradeBeamReachesThisFar', + message: 'the grade beam reaches this far.', + }) : design?.foundation.type === 'raised' - ? 'the footing’s thickness under the floor reaches this far.' - : 'the slab and the footing’s thickness reach this far.'} + ? i18n._({ + id: 'node.footing.panel.theFootingSThicknessUnderThe', + message: 'the footing’s thickness under the floor reaches this far.', + }) + : i18n._({ + id: 'node.footing.panel.theSlabAndTheFootingS', + message: 'the slab and the footing’s thickness reach this far.', + })}

)} {footing.depth !== undefined && ( @@ -201,21 +219,43 @@ export default function FootingPanel() { onClick={() => update({ depth: undefined })} type="button" > - {underSlab ? 'Clear the depth' : "Use the building's depth"} + {underSlab + ? i18n._({ id: 'node.footing.panel.clearTheDepth', message: 'Clear the depth' }) + : i18n._({ + id: 'node.footing.panel.useTheBuildingSDepth', + message: "Use the building's depth", + })} )} {design && (

- Top {against(design.topY)}, bottom {against(design.bottomY)}.{' '} + {i18n._({ id: 'node.footing.panel.top', message: 'Top' })} {against(design.topY)} + {i18n._({ id: 'node.footing.panel.bottom', message: ', bottom' })}{' '} + {against(design.bottomY)}.{' '} {design.foundation.support === 'piles' ? footing.shape === 'strip' - ? 'A grade beam on piles, as the building is.' - : 'A pile cap on one pile, as the building is.' + ? i18n._({ + id: 'node.footing.panel.aGradeBeamOnPilesAs', + message: 'A grade beam on piles, as the building is.', + }) + : i18n._({ + id: 'node.footing.panel.aPileCapOnOnePile', + message: 'A pile cap on one pile, as the building is.', + }) : design.foundation.type === 'slab' - ? 'A thickened slab under the floor slab.' + ? i18n._({ + id: 'node.footing.panel.aThickenedSlabUnderTheFloor', + message: 'A thickened slab under the floor slab.', + }) : footing.shape === 'strip' - ? 'A stem wall carries the floor.' - : 'A pier carries the floor.'} + ? i18n._({ + id: 'node.footing.panel.aStemWallCarriesTheFloor', + message: 'A stem wall carries the floor.', + }) + : i18n._({ + id: 'node.footing.panel.aPierCarriesTheFloor', + message: 'A pier carries the floor.', + })}

)} {small && ( @@ -223,15 +263,18 @@ export default function FootingPanel() { className="rounded-lg bg-accent/30 px-2.5 py-2 text-muted-foreground text-xs" role="status" > - IRC R403.1 asks a footing at least 12 in wide and 6 in thick. + {i18n._({ + id: 'node.footing.panel.ircR4031AsksAFooting', + message: 'IRC R403.1 asks a footing at least 12 in wide and 6 in thick.', + })}

)}
- + } - label="Delete" + label={i18n._({ id: 'node.footing.panel.delete', message: 'Delete' })} onClick={() => { useScene.getState().deleteNode(footing.id) setSelection({ selectedIds: [] }) diff --git a/packages/nodes/src/i18n/definition-messages.ts b/packages/nodes/src/i18n/definition-messages.ts index c162a8c316..5a5011275c 100644 --- a/packages/nodes/src/i18n/definition-messages.ts +++ b/packages/nodes/src/i18n/definition-messages.ts @@ -473,6 +473,7 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.aCurtainWallTakesNoCorner.1qxjv5i", message: "A curtain wall takes no corner window" }, /* i18n */ { id: "editor.toolHint.aCurvedWallTakesNoCorner.1vdnaw5", message: "A curved wall takes no corner window" }, /* i18n */ { id: "editor.toolHint.addACornerClickTheFirst.o4r67t", message: "Add a corner · click the first to close" }, + /* i18n */ { id: "editor.toolHint.addAWallEachOneStarts.4zkf12", message: "Add a wall · each one starts where the last ended" }, /* i18n */ { id: "editor.toolHint.anotherOpeningIsInTheWay.11l9dd0", message: "Another opening is in the way at the corner" }, /* i18n */ { id: "editor.toolHint.autoHangers.10554ka", message: "Auto hangers" }, /* i18n */ { id: "editor.toolHint.autoHangersClickOrPressH.rwa3ka", message: "Auto hangers - click or press H to toggle" }, @@ -483,6 +484,7 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.cancelMove.lvp31c", message: "Cancel move" }, /* i18n */ { id: "editor.toolHint.cancelRunExit.zqjf3h", message: "Cancel run / exit" }, /* i18n */ { id: "editor.toolHint.cancelStartPoint.1xbq2lh", message: "Cancel start point" }, + /* i18n */ { id: "editor.toolHint.closeTheOutline.f6cvzi", message: "Close the outline" }, /* i18n */ { id: "editor.toolHint.cornerWindow.1avkzpo", message: "Corner window" }, /* i18n */ { id: "editor.toolHint.cuts.1y0axps", message: "Cuts" }, /* i18n */ { id: "editor.toolHint.cutsValue.ouzhvb", message: "Cuts: {value}" }, @@ -494,6 +496,7 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.dropDownspoutFromOutlet.1k4h1m4", message: "Drop downspout from outlet" }, /* i18n */ { id: "editor.toolHint.ductDiameterDownUp.1wdz5vq", message: "Duct diameter down / up" }, /* i18n */ { id: "editor.toolHint.endRunCancel.59dh2w", message: "End run / cancel" }, + /* i18n */ { id: "editor.toolHint.endTheRun.1c1raig", message: "End the run" }, /* i18n */ { id: "editor.toolHint.exit.engmg5", message: "Exit" }, /* i18n */ { id: "editor.toolHint.exitDrawing.9noxyb", message: "Exit drawing" }, /* i18n */ { id: "editor.toolHint.finishAndContinue.1gpfsks", message: "Finish and continue" }, @@ -502,9 +505,12 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.finishMeasurement.1e4gv16", message: "Finish measurement" }, /* i18n */ { id: "editor.toolHint.finishMultiPointWitnesses.8wzskz", message: "Finish multi-point witnesses" }, /* i18n */ { id: "editor.toolHint.finishSlab.xmnm8e", message: "Finish slab" }, + /* i18n */ { id: "editor.toolHint.finishTheRun.ayliww", message: "Finish the run" }, /* i18n */ { id: "editor.toolHint.flipSide.172xku9", message: "Flip side" }, /* i18n */ { id: "editor.toolHint.followTraceALineset.mgquwe", message: "Follow: trace a lineset" }, /* i18n */ { id: "editor.toolHint.forcePlace.1hq1nr", message: "Force place" }, + /* i18n */ { id: "editor.toolHint.freeAngle.1ptblho", message: "Free angle" }, + /* i18n */ { id: "editor.toolHint.freePlace.1s1uusw", message: "Free place" }, /* i18n */ { id: "editor.toolHint.freePlacement.1nztdac", message: "Free placement" }, /* i18n */ { id: "editor.toolHint.goVerticalClickToPlace.dg8g6m", message: "Go vertical ↕, click to place" }, /* i18n */ { id: "editor.toolHint.highlightOutlet.65hoq8", message: "Highlight outlet" }, @@ -526,6 +532,7 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.key.leftClick.s4gwfc", message: "Left click" }, /* i18n */ { id: "editor.toolHint.key.middleClick.1jbaan4", message: "Middle click" }, /* i18n */ { id: "editor.toolHint.key.rightClick.1ovld0h", message: "Right click" }, + /* i18n */ { id: "editor.toolHint.key.room.1f3jkpm", message: "Room" }, /* i18n */ { id: "editor.toolHint.key.rShiftR.13bnobh", message: "R / Shift+R" }, /* i18n */ { id: "editor.toolHint.key.scroll.ai6wr0", message: "Scroll" }, /* i18n */ { id: "editor.toolHint.key.scrollWheel.18j0ix7", message: "Scroll wheel" }, @@ -533,10 +540,12 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.key.tab.1xsal0s", message: "Tab" }, /* i18n */ { id: "editor.toolHint.mountFloorCeilingWall.32dxpv", message: "Mount: floor / ceiling / wall" }, /* i18n */ { id: "editor.toolHint.noRoomForTheMatchingWindow.1755948", message: "No room for the matching window on the other wall" }, + /* i18n */ { id: "editor.toolHint.notARectangleDrawThisRoof.100pa5g", message: "Not a rectangle: draw this roof instead" }, /* i18n */ { id: "editor.toolHint.noWallMeetsHere.19n99yc", message: "No wall meets here" }, /* i18n */ { id: "editor.toolHint.numberOfCutsScrollOrClick.1kqe84b", message: "Number of cuts — scroll or click to change" }, /* i18n */ { id: "editor.toolHint.pickWitnessPoint.k3hgh1", message: "Pick witness point" }, /* i18n */ { id: "editor.toolHint.pipeSizeDownUp.wp04og", message: "Pipe size down / up" }, + /* i18n */ { id: "editor.toolHint.placeACornerOfTheFoundation.1o1ze9j", message: "Place a corner of the foundation" }, /* i18n */ { id: "editor.toolHint.placeAndContinue.qfwkrq", message: "Place and continue" }, /* i18n */ { id: "editor.toolHint.placeBlock.1aylwrx", message: "Place block" }, /* i18n */ { id: "editor.toolHint.placeBoxVentOnRoof.kf88e5", message: "Place box vent on roof" }, @@ -575,20 +584,25 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.placeUnit.8zkx4q", message: "Place unit" }, /* i18n */ { id: "editor.toolHint.placeWindowOnWall.1k3qnfb", message: "Place window on wall" }, /* i18n */ { id: "editor.toolHint.placeWithoutTheCornerSnap.1nifqew", message: "Place without the corner snap" }, + /* i18n */ { id: "editor.toolHint.pointInsideARoomPorchOr.58w64l", message: "Point inside a room, porch or deck" }, /* i18n */ { id: "editor.toolHint.removeLastPoint.2dmdcx", message: "Remove last point" }, /* i18n */ { id: "editor.toolHint.removeLastWitness.1b6vvzw", message: "Remove last witness" }, + /* i18n */ { id: "editor.toolHint.ridgeDirection.5m1369", message: "Ridge direction" }, + /* i18n */ { id: "editor.toolHint.ridgeDirectionClickOrPressR.1t14dyk", message: "Ridge direction - click or press R to turn it 90°" }, + /* i18n */ { id: "editor.toolHint.ridgeLongSide.ew0euv", message: "Ridge: Long side" }, + /* i18n */ { id: "editor.toolHint.ridgeShortSide.gye8pb", message: "Ridge: Short side" }, /* i18n */ { id: "editor.toolHint.rotate.1thqa0a", message: "Rotate" }, /* i18n */ { id: "editor.toolHint.rotate45.9zq68u", message: "Rotate ±45°" }, /* i18n */ { id: "editor.toolHint.rotate45FloorCeiling.m9t57d", message: "Rotate ±45° (floor / ceiling)" }, /* i18n */ { id: "editor.toolHint.rotateGhost15.rsa52c", message: "Rotate ghost ±15°" }, /* i18n */ { id: "editor.toolHint.rotateOrFlipTheRunSide.t5ior7", message: "Rotate or flip the run side" }, - /* i18n */ { id: "editor.toolHint.rotateRoofDirection90.tvbji2", message: "Rotate roof direction 90°" }, /* i18n */ { id: "editor.toolHint.rotateSpawnPoint.1opcuu7", message: "Rotate spawn point" }, /* i18n */ { id: "editor.toolHint.roundRectangularOval.e4nh6x", message: "Round / rectangular / oval" }, + /* i18n */ { id: "editor.toolHint.setBeamStartEndTheNext.di3bzj", message: "Set beam start / end; the next beam starts there" }, /* i18n */ { id: "editor.toolHint.setFenceStartEnd.1phbwsq", message: "Set fence start / end" }, + /* i18n */ { id: "editor.toolHint.setFootingStartEndOrPlace.nnv1av", message: "Set footing start / end, or place a pad" }, /* i18n */ { id: "editor.toolHint.setOneCornerThenTheOpposite.8tsl49", message: "Set one corner, then the opposite" }, /* i18n */ { id: "editor.toolHint.setRoofFootprint.p50n0o", message: "Set roof footprint" }, - /* i18n */ { id: "editor.toolHint.setWallStartEnd.1r8ubcx", message: "Set wall start / end" }, /* i18n */ { id: "editor.toolHint.slopeLevel.ftqp67", message: "Slope / level" }, /* i18n */ { id: "editor.toolHint.snapOntoTheRun.86yhmp", message: "Snap onto the run" }, /* i18n */ { id: "editor.toolHint.splitAtTheMarks.x5tpdd", message: "Split at the marks" }, @@ -647,6 +661,8 @@ export const definitionMessages = [ /* i18n */ { id: "editor.toolHint.wasteVent.1hvi3vj", message: "Waste / vent" }, /* i18n */ { id: "editor.toolHint.windowStyleClickOrPressL.ah7qm9", message: "Window style — click or press L to cycle" }, /* i18n */ { id: "editor.toolHint.windowTypeClickOrPressO.wv4x35", message: "Window type — click or press O to cycle" }, + /* i18n */ { id: "node.beam.description", message: "A framing beam drawn end to end: a porch header, a deck beam, a girder." }, + /* i18n */ { id: "node.beam.label", message: "Beam" }, /* i18n */ { id: "node.block.description", message: "A topology-backed solid edited directly in the canvas." }, /* i18n */ { id: "node.block.label", message: "Block" }, /* i18n */ { id: "node.boxVent.description", message: "Small louvered exhaust vent that sits on a roof slope." }, @@ -690,6 +706,10 @@ export const definitionMessages = [ /* i18n */ { id: "node.fenceOpening.description", message: "An intentional opening in a fence." }, /* i18n */ { id: "node.fenceOpening.label", message: "Open Passage" }, /* i18n */ { id: "node.floorOpening.label", message: "Floor opening" }, + /* i18n */ { id: "node.footing.description", message: "A concrete strip or pad footing poured under the lowest floor." }, + /* i18n */ { id: "node.footing.label", message: "Footing" }, + /* i18n */ { id: "node.foundation.description", message: "The outline the foundation is poured to: drawn, or traced from the walls and stretched." }, + /* i18n */ { id: "node.foundation.label", message: "Foundation" }, /* i18n */ { id: "node.guide.description", message: "A measurement / reference annotation (linear, area, or arc)." }, /* i18n */ { id: "node.guide.label", message: "Guide" }, /* i18n */ { id: "node.gutter.description", message: "Rain-water channel running along the eave of a roof segment." }, diff --git a/packages/nodes/src/roof-segment/build-up-section.tsx b/packages/nodes/src/roof-segment/build-up-section.tsx index 62e69e4834..f46db20b77 100644 --- a/packages/nodes/src/roof-segment/build-up-section.tsx +++ b/packages/nodes/src/roof-segment/build-up-section.tsx @@ -2,6 +2,8 @@ import { LUMBER_DEPTH, + type MessageDescriptor, + type PascalI18n, ROOFING_THICKNESS, type RoofBuildUp, type RoofingKind, @@ -10,6 +12,7 @@ import { roofBuildUpPatch, UNDERLAYMENT_THICKNESS, type UnderlaymentKind, + usePascalI18n, } from '@pascal-app/core' import { ActionButton, PanelSection, SegmentedControl, SelectControl } from '@pascal-app/editor' import { useState } from 'react' @@ -20,6 +23,7 @@ import { FramingSizeFields, formatSpacing, InsulationFields, + LAYER_KIND_LABELS, NONE, PanelFields, } from '../shared/build-up-controls' @@ -33,37 +37,87 @@ import { roofBuildUpPresetValue, } from './panel-model' -const ROOFING_OPTIONS: { label: string; value: RoofingKind }[] = [ - { label: 'Asphalt 3-tab', value: 'asphalt-3-tab' }, - { label: 'Architectural shingle', value: 'architectural-shingle' }, - { label: 'Standing-seam metal', value: 'standing-seam-metal' }, - { label: 'Concrete tile', value: 'concrete-tile' }, - { label: 'Clay tile', value: 'clay-tile' }, - { label: 'Wood shake', value: 'wood-shake' }, - { label: 'Slate', value: 'slate' }, +const ROOFING_OPTIONS: { label: MessageDescriptor; value: RoofingKind }[] = [ + { + label: /* i18n */ { + id: 'node.roofSegment.buildUpSection.asphalt3Tab', + message: 'Asphalt 3-tab', + }, + value: 'asphalt-3-tab', + }, + { + label: /* i18n */ { + id: 'node.roofSegment.buildUpSection.architecturalShingle', + message: 'Architectural shingle', + }, + value: 'architectural-shingle', + }, + { + label: /* i18n */ { + id: 'node.roofSegment.buildUpSection.standingSeamMetal', + message: 'Standing-seam metal', + }, + value: 'standing-seam-metal', + }, + { + label: /* i18n */ { + id: 'node.roofSegment.buildUpSection.concreteTile', + message: 'Concrete tile', + }, + value: 'concrete-tile', + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.clayTile', message: 'Clay tile' }, + value: 'clay-tile', + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.woodShake', message: 'Wood shake' }, + value: 'wood-shake', + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.slate', message: 'Slate' }, + value: 'slate', + }, ] -const UNDERLAYMENT_OPTIONS: { label: string; value: UnderlaymentKind | typeof NONE }[] = [ - { label: 'None', value: NONE }, - { label: '#15 felt', value: 'felt-15' }, - { label: '#30 felt', value: 'felt-30' }, - { label: 'Synthetic', value: 'synthetic' }, - { label: 'Ice and water shield', value: 'ice-and-water' }, -] +const UNDERLAYMENT_OPTIONS: { label: MessageDescriptor; value: UnderlaymentKind | typeof NONE }[] = + [ + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.none', message: 'None' }, + value: NONE, + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.n15Felt', message: '#15 felt' }, + value: 'felt-15', + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.n30Felt', message: '#30 felt' }, + value: 'felt-30', + }, + { + label: /* i18n */ { id: 'node.roofSegment.buildUpSection.synthetic', message: 'Synthetic' }, + value: 'synthetic', + }, + { + label: /* i18n */ { + id: 'node.roofSegment.buildUpSection.iceAndWaterShield', + message: 'Ice and water shield', + }, + value: 'ice-and-water', + }, + ] /** What each kind is called where the layer has no name of its own. */ -const KIND_LABELS: Record = { +const KIND_LABELS: Record = { ...Object.fromEntries( [...ROOFING_OPTIONS, ...UNDERLAYMENT_OPTIONS].map((o) => [o.value, o.label]), ), - osb: 'OSB', - plywood: 'Plywood', - batt: 'Batt', - blown: 'Blown', - rigid: 'Rigid board', - drywall: 'Drywall', - plaster: 'Plaster', - 'wood-plank': 'Wood plank', + ...LAYER_KIND_LABELS, +} + +const kindLabel = (kind: string, i18n: PascalI18n) => { + const label = KIND_LABELS[kind] + return label ? i18n._(label) : kind } type NamedLayerKey = 'roofing' | 'underlayment' | 'sheathing' | 'insulation' | 'finish' @@ -81,6 +135,7 @@ function LayerNameField({ layer: { kind: string; name?: string } onCommit: (name: string) => void }) { + const i18n = usePascalI18n() const [draft, setDraft] = useState(null) return (